blob: b47cace08d679e964f58fc543d8658baba5a1933 [file] [log] [blame] [edit]
/*
* Copyright 2019 The Emscripten Authors. All rights reserved.
* Emscripten is available under two separate licenses, the MIT license and the
* University of Illinois/NCSA Open Source License. Both these licenses can be
* found in the LICENSE file.
*/
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#define FNAME_RO "data_ro.dat"
#define FNAME_RW "data_rw.dat"
/*
* Each test will return 0 as success (no error) and error code in case of failure:
* - > 0 it is errno value
* - < 0 test specific failure (not related to any syscall)
*/
#define ASSERT(cond, msg) do { \
if (!(cond)) { \
printf("%s:%03d FAILED '%s' - %s errno: %d\n", \
__func__, __LINE__, #cond, msg, errno); \
return (errno != 0 ? errno : -1); \
} \
} while (0)
#define TEST_START() printf("%s - START\n", __func__)
#define TEST_PASS() do { \
printf("%s - PASSED\n", __func__); \
return 0; \
} while (0)
const char file_data[] =
"Copyright 2019 The Emscripten Authors. All rights reserved.\n"
"Emscripten is available under two separate licenses, the MIT license and the\n"
"University of Illinois/NCSA Open Source License. Both these licenses can be\n"
"found in the LICENSE file.\n";
FILE *f_ro = NULL;
FILE *f_rw = NULL;
// Length of the file data excluding trailing '\0'. Equivalent to strlen(file_data) .
size_t file_len() {
return sizeof(file_data) - 1;
}
int test_mmap_read() {
TEST_START();
errno = 0;
char *m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f_ro), 0);
ASSERT(m != MAP_FAILED, "Failed to mmap file");
for (size_t i = 0; i < file_len(); ++i) {
ASSERT(m[i] == file_data[i], "Wrong file data mmaped");
}
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
TEST_PASS();
}
int test_mmap_write() {
TEST_START();
errno = 0;
char *m = (char *)mmap(NULL, file_len(), PROT_READ | PROT_WRITE, MAP_SHARED, fileno(f_rw), 0);
ASSERT(m != MAP_FAILED, "Failed to mmap file");
// Reverse the data in the mapped file in memory, which should be written
// out.
for (size_t i = 0; i < file_len(); ++i) {
size_t k = file_len() - i - 1;
m[k] = file_data[i];
}
// Write to a byte past the end of the file. mmap will allocate a multiple
// of the page size, which is bigger than our small file, and it will zero
// that out. So it is ok for us to write there, but those changes should
// not be saved anywhere.
ASSERT(m[file_len()] == 0, "No zero past file contents");
m[file_len()] = 42;
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
// mmap it again, where we should see the reversed data that was written.
m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f_rw), 0);
ASSERT(m != MAP_FAILED, "Failed to mmap file");
for (size_t i = 0; i < file_len(); ++i) {
size_t k = file_len() - i - 1;
ASSERT(m[k] == file_data[i], "Wrong file data written or mapped");
}
ASSERT(m[file_len()] == 0, "No zero past file contents");
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
TEST_PASS();
}
int test_mmap_write_private() {
TEST_START();
errno = 0;
/*
* POSIX spec (http://pubs.opengroup.org/onlinepubs/9699919799/) for mmap
* says that it should be possible to set PROT_WRITE flag for file opened
* in RO mode if we use MAP_PRIVATE flag.
*/
char *m = (char *)mmap(NULL, file_len(), PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(f_ro), 0);
ASSERT(m != MAP_FAILED, "Failed to mmap file");
for (size_t i = 0; i < file_len(); ++i) {
size_t k = file_len() - i;
m[k] = file_data[i];
}
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
/*
* It is undefined however, if subsequent maps in the same process will or won't
* see data written by such mmap.
*/
m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f_ro), 0);
ASSERT(m != MAP_FAILED, "Failed to mmap file");
for (size_t i = 0; i < file_len(); ++i) {
size_t k = file_len() - i;
ASSERT(m[k] == file_data[i] || m[i] == file_data[i],
"Wrong file data written or mapped");
}
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
TEST_PASS();
}
int test_mmap_write_to_ro_file() {
TEST_START();
errno = 0;
char *m = (char *)mmap(NULL, file_len(), PROT_READ | PROT_WRITE, MAP_SHARED, fileno(f_ro), 0);
ASSERT(m == MAP_FAILED && errno == EACCES,
"Expected EACCES when requesting writing to file opened in read-only mode");
TEST_PASS();
}
int test_mmap_anon() {
TEST_START();
errno = 0;
void *m = (char *)mmap(NULL, file_len(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT(m != MAP_FAILED, "Failed to mmap pages");
ASSERT(munmap(m, file_len()) == 0, "Failed to unmmap allocated pages");
TEST_PASS();
}
int test_mmap_fixed() {
TEST_START();
errno = 0;
size_t page_size = sysconf(_SC_PAGE_SIZE);
char *m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f_ro), 0);
ASSERT(m != MAP_FAILED, "Failed to mmap file");
char *invalid_addr = m;
if (((uintptr_t)m) % page_size == 0) {
invalid_addr++;
}
char *m2 = (char *)mmap(invalid_addr, file_len(), PROT_READ, MAP_FIXED, fileno(f_ro), 0);
ASSERT(m2 == MAP_FAILED && errno == EINVAL, "Expected EINVAL for invalid addr");
size_t invalid_offset = 1;
char *m3 = (char *)mmap(m, file_len(), PROT_READ, MAP_FIXED, fileno(f_ro), invalid_offset);
ASSERT(m3 == MAP_FAILED && errno == EINVAL, "Expected EINVAL for invalid offset");
ASSERT(munmap(m, file_len()) == 0, "Failed to unmmap allocated pages");
TEST_PASS();
}
int test_mmap_wrong_fd() {
TEST_START();
errno = 0;
char *m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, -1, 0);
ASSERT(m == MAP_FAILED && errno == EBADF, "Expected EBADF error");
TEST_PASS();
}
int test_unmap_wrong_addr() {
TEST_START();
errno = 0;
ASSERT(munmap(MAP_FAILED, file_len()) == -1 && errno == EINVAL,
"Expected EINVAL, as munmap should fail for wrong addr argument");
TEST_PASS();
}
int test_unmap_zero_len() {
TEST_START();
errno = 0;
char *m = (char *)mmap(NULL, file_len(), PROT_READ, MAP_PRIVATE, fileno(f_ro), 0);
ASSERT(m != MAP_FAILED, "Failed to mmap file");
ASSERT(munmap(m, 0) == -1 && errno == EINVAL,
"Expected EINVAL, as munmap should fail when len argument is 0");
ASSERT(munmap(m, file_len()) == 0, "Failed to unmap allocated pages");
TEST_PASS();
}
typedef int (*test_fn)();
int set_up() {
FILE *f = fopen(FNAME_RO, "w");
ASSERT(f != NULL, "Failed to open file " FNAME_RO " for writing");
fprintf(f, "%s", file_data);
fclose(f);
f = fopen(FNAME_RW, "w");
ASSERT(f != NULL, "Failed to open file " FNAME_RW " for writing");
fprintf(f, "%s", file_data);
fclose(f);
f_ro = fopen(FNAME_RO, "r");
ASSERT(f_ro != NULL, "Failed to open file " FNAME_RO " for reading");
f_rw = fopen(FNAME_RW, "r+");
ASSERT(f_rw != NULL, "Failed to open file " FNAME_RW " for reading and writing");
return 0;
}
void tear_down() {
if (f_ro != NULL) {
fclose(f_ro);
f_ro = NULL;
}
if (f_rw != NULL) {
fclose(f_rw);
f_rw = NULL;
}
}
int main() {
int failures = 0;
test_fn tests[] = {
test_mmap_read,
test_mmap_write,
test_mmap_write_private,
test_mmap_write_to_ro_file,
test_mmap_anon,
test_mmap_fixed,
test_mmap_wrong_fd,
test_unmap_wrong_addr,
test_unmap_zero_len,
NULL
};
int tests_run = 0;
while (tests[tests_run] != NULL) {
if (set_up() != 0) {
printf("Failed to set_up environment for TC\n");
++failures;
} else {
failures += (tests[tests_run]() != 0 ? 1 : 0);
}
tear_down();
++tests_run;
}
printf("tests_run: %d failures: %d\n", tests_run, failures);
#ifdef REPORT_RESULT
REPORT_RESULT(failures);
#endif
return 0;
}