/* * Copyright (C) 2007 Nokia Corporation. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Author: Adrian Hunter */ #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <stdint.h> #include <fcntl.h> #include <errno.h> #include <libgen.h> #include <dirent.h> #include <ctype.h> #include <limits.h> #include <mntent.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/vfs.h> #include <sys/mount.h> #include <sys/statvfs.h> #include <linux/fs.h> #include <linux/jffs2.h> #include "tests.h" char *tests_file_system_mount_dir = TESTS_DEFAULT_FILE_SYSTEM_MOUNT_DIR; char *tests_file_system_type = TESTS_DEFAULT_FILE_SYSTEM_TYPE; int tests_ok_to_sync = 0; /* Whether to use fsync */ /* General purpose test parameter to specify some aspect of test size. May be used by different tests in different ways or not at all. Set by the -z or --size option. */ int64_t tests_size_parameter = 0; /* General purpose test parameter to specify some aspect of test repetition. May be used by different tests in different ways or not at all. Set by the -n, --repeat options. */ int64_t tests_repeat_parameter = 0; /* General purpose test parameter to specify some aspect of test sleeping. May be used by different tests in different ways or not at all. Set by the -p, --sleep options. */ int64_t tests_sleep_parameter = 0; /* Program name from argv[0] */ char *program_name = "unknown"; /* General purpose test parameter to specify a file should be unlinked. May be used by different tests in different ways or not at all. */ int tests_unlink_flag = 0; /* General purpose test parameter to specify a file should be closed. May be used by different tests in different ways or not at all. */ int tests_close_flag = 0; /* General purpose test parameter to specify a file should be deleted. May be used by different tests in different ways or not at all. */ int tests_delete_flag = 0; /* General purpose test parameter to specify a file have a hole. May be used by different tests in different ways or not at all. */ int tests_hole_flag = 0; /* Whether it is ok to test on the root file system */ static int rootok = 0; /* Maximum file name length of test file system (from statfs) */ long tests_max_fname_len = 255; /* Function invoked by the CHECK macro */ void tests_test(int test,const char *msg,const char *file,unsigned line) { int eno; time_t t; if (test) return; eno = errno; time(&t); fprintf(stderr, "Test failed: %s on %s" "Test failed: %s in %s at line %u\n", program_name, ctime(&t), msg, file, line); if (eno) { fprintf(stderr,"errno = %d\n",eno); fprintf(stderr,"strerror = %s\n",strerror(eno)); } exit(1); } static int is_zero(const char *p) { for (;*p;++p) if (*p != '0') return 0; return 1; } static void fold(const char *text, int width) { int pos, bpos = 0; const char *p; char line[1024]; if (width > 1023) { printf("%s\n", text); return; } p = text; pos = 0; while (p[pos]) { while (!isspace(p[pos])) { line[pos] = p[pos]; if (!p[pos]) break; ++pos; if (pos == width) { line[pos] = '\0'; printf("%s\n", line); p += pos; pos = 0; } } while (pos < width) { line[pos] = p[pos]; if (!p[pos]) { bpos = pos; break; } if (isspace(p[pos])) bpos = pos; ++pos; } line[bpos] = '\0'; printf("%s\n", line); p += bpos; pos = 0; while (p[pos] && isspace(p[pos])) ++p; } } /* Handle common program options */ int tests_get_args(int argc, char *argv[], const char *title, const char *desc, const char *opts) { int run_test = 0; int display_help = 0; int display_title = 0; int display_description = 0; int i; char *s; program_name = argv[0]; s = getenv("TEST_FILE_SYSTEM_MOUNT_DIR"); if (s) tests_file_system_mount_dir = strdup(s); s = getenv("TEST_FILE_SYSTEM_TYPE"); if (s) tests_file_system_type = strdup(s); run_test = 1; rootok = 1; for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) display_help = 1; else if (strcmp(argv[i], "--title") == 0 || strcmp(argv[i], "-t") == 0) display_title = 1; else if (strcmp(argv[i], "--description") == 0 || strcmp(argv[i], "-d") == 0) display_description = 1; else if (strcmp(argv[i], "--sync") == 0 || strcmp(argv[i], "-s") == 0) tests_ok_to_sync = 1; else if (strncmp(argv[i], "--size", 6) == 0 || strncmp(argv[i], "-z", 2) == 0) { int64_t n; char *p; if (i+1 < argc && !isdigit(argv[i][strlen(argv[i])-1])) ++i; p = argv[i]; while (*p && !isdigit(*p)) ++p; n = atoll(p); if (n) tests_size_parameter = n; else { int all_zero = 1; for (; all_zero && *p; ++p) if (*p != '0') all_zero = 0; if (all_zero) tests_size_parameter = 0; else display_help = 1; } } else if (strncmp(argv[i], "--repeat", 8) == 0 || strncmp(argv[i], "-n", 2) == 0) { int64_t n; char *p; if (i+1 < argc && !isdigit(argv[i][strlen(argv[i])-1])) ++i; p = argv[i]; while (*p && !isdigit(*p)) ++p; n = atoll(p); if (n || is_zero(p)) tests_repeat_parameter = n; else display_help = 1; } else if (strncmp(argv[i], "--sleep", 7) == 0 || strncmp(argv[i], "-p", 2) == 0) { int64_t n; char *p; if (i+1 < argc && !isdigit(argv[i][strlen(argv[i])-1])) ++i; p = argv[i]; while (*p && !isdigit(*p)) ++p; n = atoll(p); if (n || is_zero(p)) tests_sleep_parameter = n; else display_help = 1; } else if (strcmp(argv[i], "--unlink") == 0 || strcmp(argv[i], "-u") == 0) tests_unlink_flag = 1; else if (strcmp(argv[i], "--hole") == 0 || strcmp(argv[i], "-o") == 0) tests_hole_flag = 1; else if (strcmp(argv[i], "--close") == 0 || strcmp(argv[i], "-c") == 0) tests_close_flag = 1; else if (strcmp(argv[i], "--delete") == 0 || strcmp(argv[i], "-e") == 0) tests_delete_flag = 1; else display_help = 1; } if (display_help) { run_test = 0; display_title = 0; display_description = 0; if (!opts) opts = ""; printf("File System Test Program\n\n"); printf("Test Title: %s\n\n", title); printf("Usage is: %s [ options ]\n",argv[0]); printf(" Options are:\n"); printf(" -h, --help "); printf("Display this help\n"); printf(" -t, --title "); printf("Display the test title\n"); printf(" -d, --description "); printf("Display the test description\n"); if (strchr(opts, 's')) { printf(" -s, --sync "); printf("Make use of fsync\n"); } if (strchr(opts, 'z')) { printf(" -z, --size "); printf("Set size parameter\n"); } if (strchr(opts, 'n')) { printf(" -n, --repeat "); printf("Set repeat parameter\n"); } if (strchr(opts, 'p')) { printf(" -p, --sleep "); printf("Set sleep parameter\n"); } if (strchr(opts, 'u')) { printf(" -u, --unlink "); printf("Unlink file\n"); } if (strchr(opts, 'o')) { printf(" -o, --hole "); printf("Create a hole in a file\n"); } if (strchr(opts, 'c')) { printf(" -c, --close "); printf("Close file\n"); } if (strchr(opts, 'e')) { printf(" -e, --delete "); printf("Delete file\n"); } printf("\nBy default, testing is done in directory "); printf("/mnt/test_file_system. To change this\nuse "); printf("environmental variable "); printf("TEST_FILE_SYSTEM_MOUNT_DIR. By default, "); printf("the file\nsystem tested is jffs2. To change this "); printf("set TEST_FILE_SYSTEM_TYPE.\n\n"); printf("Test Description:\n"); fold(desc, 80); } else { if (display_title) printf("%s\n", title); if (display_description) printf("%s\n", desc); if (display_title || display_description) if (argc == 2 || (argc == 3 && display_title && display_description)) run_test = 0; } return run_test; } /* Return the number of files (or directories) in the given directory */ unsigned tests_count_files_in_dir(const char *dir_name) { DIR *dir; struct dirent *entry; unsigned count = 0; dir = opendir(dir_name); CHECK(dir != NULL); for (;;) { errno = 0; entry = readdir(dir); if (entry) { if (strcmp(".",entry->d_name) != 0 && strcmp("..",entry->d_name) != 0) ++count; } else { CHECK(errno == 0); break; } } CHECK(closedir(dir) != -1); return count; } /* Change to the file system mount directory, check that it is empty, matches the file system type, and is not the root file system */ void tests_check_test_file_system(void) { struct statfs fs_info; struct stat f_info; struct stat root_f_info; if (chdir(tests_file_system_mount_dir) == -1 || statfs(tests_file_system_mount_dir, &fs_info) == -1) { fprintf(stderr, "Invalid test file system mount directory:" " %s\n", tests_file_system_mount_dir); fprintf(stderr, "Use environment variable " "TEST_FILE_SYSTEM_MOUNT_DIR\n"); CHECK(0); } tests_max_fname_len = fs_info.f_namelen; if (strcmp(tests_file_system_type, "jffs2") == 0 && fs_info.f_type != JFFS2_SUPER_MAGIC) { fprintf(stderr, "File system type is not jffs2\n"); CHECK(0); } /* Check that the test file system is not the root file system */ if (!rootok) { CHECK(stat(tests_file_system_mount_dir, &f_info) != -1); CHECK(stat("/", &root_f_info) != -1); CHECK(f_info.st_dev != root_f_info.st_dev); } } /* Get the free space for the file system of the current directory */ uint64_t tests_get_free_space(void) { struct statvfs fs_info; CHECK(statvfs(tests_file_system_mount_dir, &fs_info) != -1); return (uint64_t) fs_info.f_bavail * (uint64_t) fs_info.f_frsize; } /* Get the total space for the file system of the current directory */ uint64_t tests_get_total_space(void) { struct statvfs fs_info; CHECK(statvfs(tests_file_system_mount_dir, &fs_info) != -1); return (uint64_t) fs_info.f_blocks * (uint64_t) fs_info.f_frsize; } #define WRITE_BUFFER_SIZE 32768 static char write_buffer[WRITE_BUFFER_SIZE]; static void init_write_buffer() { static int init = 0; if (!init) { int i, d; uint64_t u; u = RAND_MAX; u += 1; u /= 256; d = (int) u; srand(1); for (i = 0; i < WRITE_BUFFER_SIZE; ++i) write_buffer[i] = rand() / d; init = 1; } } /* Write size random bytes into file descriptor fd at the current position, returning the number of bytes actually written */ uint64_t tests_fill_file(int fd, uint64_t size) { ssize_t written; size_t sz; unsigned start = 0, length; uint64_t remains; uint64_t actual_size = 0; init_write_buffer(); remains = size; while (remains > 0) { length = WRITE_BUFFER_SIZE - start; if (remains > length) sz = length; else sz = (size_t) remains; written = write(fd, write_buffer + start, sz); if (written <= 0) { CHECK(errno == ENOSPC); /* File system full */ errno = 0; break; } remains -= written; actual_size += written; if (written == sz) start = 0; else start += written; } tests_maybe_sync(fd); return actual_size; } /* Write size random bytes into file descriptor fd at offset, returning the number of bytes actually written */ uint64_t tests_write_filled_file(int fd, off_t offset, uint64_t size) { ssize_t written; size_t sz; unsigned start = 0, length; uint64_t remains; uint64_t actual_size = 0; CHECK(lseek(fd, offset, SEEK_SET) == offset); init_write_buffer(); remains = size; start = offset % WRITE_BUFFER_SIZE; while (remains > 0) { length = WRITE_BUFFER_SIZE - start; if (remains > length) sz = length; else sz = (size_t) remains; written = write(fd, write_buffer + start, sz); if (written <= 0) { CHECK(errno == ENOSPC); /* File system full */ errno = 0; break; } remains -= written; actual_size += written; if (written == sz) start = 0; else start += written; } tests_maybe_sync(fd); return actual_size; } /* Check that a file written using tests_fill_file() and/or tests_write_filled_file() and/or tests_create_file() contains the expected random data */ void tests_check_filled_file_fd(int fd) { ssize_t sz; char buf[WRITE_BUFFER_SIZE]; CHECK(lseek(fd, 0, SEEK_SET) == 0); do { sz = read(fd, buf, WRITE_BUFFER_SIZE); CHECK(sz >= 0); CHECK(memcmp(buf, write_buffer, sz) == 0); } while (sz); } /* Check that a file written using tests_fill_file() and/or tests_write_filled_file() and/or tests_create_file() contains the expected random data */ void tests_check_filled_file(const char *file_name) { int fd; fd = open(file_name, O_RDONLY); CHECK(fd != -1); tests_check_filled_file_fd(fd); CHECK(close(fd) != -1); } void tests_sync_directory(const char *file_name) { char *path; char *dir; int fd; if (!tests_ok_to_sync) return; path = strdup(file_name); dir = dirname(path); fd = open(dir,O_RDONLY | tests_maybe_sync_flag()); CHECK(fd != -1); CHECK(fsync(fd) != -1); CHECK(close(fd) != -1); free(path); } /* Delete a file */ void tests_delete_file(const char *file_name) { CHECK(unlink(file_name) != -1); tests_sync_directory(file_name); } /* Create a file of size file_size */ uint64_t tests_create_file(const char *file_name, uint64_t file_size) { int fd; int flags; mode_t mode; uint64_t actual_size; /* Less than size if the file system is full */ flags = O_CREAT | O_TRUNC | O_WRONLY | tests_maybe_sync_flag(); mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; fd = open(file_name, flags, mode); if (fd == -1 && errno == ENOSPC) { errno = 0; return 0; /* File system full */ } CHECK(fd != -1); actual_size = tests_fill_file(fd, file_size); CHECK(close(fd) != -1); if (file_size != 0 && actual_size == 0) tests_delete_file(file_name); else tests_sync_directory(file_name); return actual_size; } /* Calculate: free_space * numerator / denominator */ uint64_t tests_get_big_file_size(unsigned numerator, unsigned denominator) { if (denominator == 0) denominator = 1; if (numerator > denominator) numerator = denominator; return numerator * (tests_get_free_space() / denominator); } /* Create file "fragment_n" where n is the file_number, and unlink it */ int tests_create_orphan(unsigned file_number) { int fd; int flags; mode_t mode; char file_name[256]; sprintf(file_name, "fragment_%u", file_number); flags = O_CREAT | O_TRUNC | O_RDWR | tests_maybe_sync_flag(); mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; fd = open(file_name, flags, mode); if (fd == -1 && (errno == ENOSPC || errno == EMFILE)) return fd; /* File system full or too many open files */ CHECK(fd != -1); tests_sync_directory(file_name); CHECK(unlink(file_name) != -1); return fd; } /* Write size bytes at offset to the file "fragment_n" where n is the file_number and file_number also determines the random data written i.e. seed for random numbers */ unsigned tests_write_fragment_file(unsigned file_number, int fd, off_t offset, unsigned size) { int i, d; uint64_t u; ssize_t written; off_t pos; char buf[WRITE_BUFFER_SIZE]; if (size > WRITE_BUFFER_SIZE) size = WRITE_BUFFER_SIZE; pos = lseek(fd, 0, SEEK_END); CHECK(pos != (off_t) -1); if (offset > pos) offset = pos; pos = lseek(fd, offset, SEEK_SET); CHECK(pos != (off_t) -1); CHECK(pos == offset); srand(file_number); while (offset--) rand(); u = RAND_MAX; u += 1; u /= 256; d = (int) u; for (i = 0; i < size; ++i) buf[i] = rand() / d; written = write(fd, buf, size); if (written <= 0) { CHECK(errno == ENOSPC); /* File system full */ errno = 0; written = 0; } tests_maybe_sync(fd); return (unsigned) written; } /* Write size bytes to the end of file descriptor fd using file_number to determine the random data written i.e. seed for random numbers */ unsigned tests_fill_fragment_file(unsigned file_number, int fd, unsigned size) { off_t offset; offset = lseek(fd, 0, SEEK_END); CHECK(offset != (off_t) -1); return tests_write_fragment_file(file_number, fd, offset, size); } /* Write size bytes to the end of file "fragment_n" where n is the file_number and file_number also determines the random data written i.e. seed for random numbers */ unsigned tests_append_to_fragment_file(unsigned file_number, unsigned size, int create) { int fd; int flags; mode_t mode; unsigned actual_growth; char file_name[256]; sprintf(file_name, "fragment_%u", file_number); if (create) flags = O_CREAT | O_EXCL | O_WRONLY | tests_maybe_sync_flag(); else flags = O_WRONLY | tests_maybe_sync_flag(); mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; fd = open(file_name, flags, mode); if (fd == -1 && errno == ENOSPC) { errno = 0; return 0; /* File system full */ } CHECK(fd != -1); actual_growth = tests_fill_fragment_file(file_number, fd, size); CHECK(close(fd) != -1); if (create && !actual_growth) tests_delete_fragment_file(file_number); return actual_growth; } /* Write size bytes at offset to the file "fragment_n" where n is the file_number and file_number also determines the random data written i.e. seed for random numbers */ unsigned tests_overwite_fragment_file( unsigned file_number, off_t offset, unsigned size) { int fd; unsigned actual_size; char file_name[256]; sprintf(file_name, "fragment_%u", file_number); fd = open(file_name, O_RDWR | tests_maybe_sync_flag()); if (fd == -1 && errno == ENOSPC) { errno = 0; return 0; /* File system full */ } CHECK(fd != -1); actual_size = tests_write_fragment_file(file_number, fd, offset, size); CHECK(close(fd) != -1); return actual_size; } /* Delete file "fragment_n" where n is the file_number */ void tests_delete_fragment_file(unsigned file_number) { char file_name[256]; sprintf(file_name, "fragment_%u", file_number); tests_delete_file(file_name); } /* Check the random data in file "fragment_n" is what is expected */ void tests_check_fragment_file_fd(unsigned file_number, int fd) { ssize_t sz, i; int d; uint64_t u; char buf[8192]; CHECK(lseek(fd, 0, SEEK_SET) == 0); srand(file_number); u = RAND_MAX; u += 1; u /= 256; d = (int) u; for (;;) { sz = read(fd, buf, 8192); if (sz == 0) break; CHECK(sz >= 0); for (i = 0; i < sz; ++i) CHECK(buf[i] == (char) (rand() / d)); } } /* Check the random data in file "fragment_n" is what is expected */ void tests_check_fragment_file(unsigned file_number) { int fd; ssize_t sz, i; int d; uint64_t u; char file_name[256]; char buf[8192]; sprintf(file_name, "fragment_%u", file_number); fd = open(file_name, O_RDONLY); CHECK(fd != -1); srand(file_number); u = RAND_MAX; u += 1; u /= 256; d = (int) u; for (;;) { sz = read(fd, buf, 8192); if (sz == 0) break; CHECK(sz >= 0); for (i = 0; i < sz; ++i) CHECK(buf[i] == (char) (rand() / d)); } CHECK(close(fd) != -1); } /* Central point to decide whether to use fsync */ void tests_maybe_sync(int fd) { if (tests_ok_to_sync) CHECK(fsync(fd) != -1); } /* Return O_SYNC if ok to sync otherwise return 0 */ int tests_maybe_sync_flag(void) { if (tests_ok_to_sync) return O_SYNC; return 0; } /* Return random number from 0 to n - 1 */ size_t tests_random_no(size_t n) { uint64_t a, b; if (!n) return 0; if (n - 1 <= RAND_MAX) { a = rand(); b = RAND_MAX; b += 1; } else { const uint64_t u = 1 + (uint64_t) RAND_MAX; a = rand(); a *= u; a += rand(); b = u * u; CHECK(n <= b); } if (RAND_MAX <= UINT32_MAX && n <= UINT32_MAX) return a * n / b; else /*if (RAND_MAX <= UINT64_MAX && n <= UINT64_MAX)*/ { uint64_t x, y; if (a < n) { x = a; y = n; } else { x = n; y = a; } return (x * (y / b)) + ((x * (y % b)) / b); } } /* Make a directory empty */ void tests_clear_dir(const char *dir_name) { DIR *dir; struct dirent *entry; char buf[4096]; dir = opendir(dir_name); CHECK(dir != NULL); CHECK(getcwd(buf, 4096) != NULL); CHECK(chdir(dir_name) != -1); for (;;) { errno = 0; entry = readdir(dir); if (entry) { if (strcmp(".",entry->d_name) != 0 && strcmp("..",entry->d_name) != 0) { if (entry->d_type == DT_DIR) { tests_clear_dir(entry->d_name); CHECK(rmdir(entry->d_name) != -1); } else CHECK(unlink(entry->d_name) != -1); } } else { CHECK(errno == 0); break; } } CHECK(chdir(buf) != -1); CHECK(closedir(dir) != -1); } /* Create an empty sub-directory or small file in the current directory */ int64_t tests_create_entry(char *return_name) { int fd; char name[256]; for (;;) { sprintf(name, "%u", (unsigned) tests_random_no(10000000)); fd = open(name, O_RDONLY); if (fd == -1) break; close(fd); } if (return_name) strcpy(return_name, name); if (tests_random_no(2)) { return tests_create_file(name, tests_random_no(4096)); } else { if (mkdir(name, 0777) == -1) { CHECK(errno == ENOSPC); errno = 0; return 0; } return TESTS_EMPTY_DIR_SIZE; } } /* Remove a random file of empty sub-directory from the current directory */ int64_t tests_remove_entry(void) { DIR *dir; struct dirent *entry; unsigned count = 0, pos; int64_t result = 0; dir = opendir("."); CHECK(dir != NULL); for (;;) { errno = 0; entry = readdir(dir); if (entry) { if (strcmp(".",entry->d_name) != 0 && strcmp("..",entry->d_name) != 0) ++count; } else { CHECK(errno == 0); break; } } pos = tests_random_no(count); count = 0; rewinddir(dir); for (;;) { errno = 0; entry = readdir(dir); if (!entry) { CHECK(errno == 0); break; } if (strcmp(".",entry->d_name) != 0 && strcmp("..",entry->d_name) != 0) { if (count == pos) { if (entry->d_type == DT_DIR) { tests_clear_dir(entry->d_name); CHECK(rmdir(entry->d_name) != -1); result = TESTS_EMPTY_DIR_SIZE; } else { struct stat st; CHECK(stat(entry->d_name, &st) != -1); result = st.st_size; CHECK(unlink(entry->d_name) != -1); } } ++count; } } CHECK(closedir(dir) != -1); return result; } /* Read mount information from /proc/mounts or /etc/mtab */ int tests_get_mount_info(struct mntent *info) { FILE *f; struct mntent *entry; int found = 0; f = fopen("/proc/mounts", "rb"); if (!f) f = fopen("/etc/mtab", "rb"); CHECK(f != NULL); while (!found) { entry = getmntent(f); if (entry) { if (strcmp(entry->mnt_dir, tests_file_system_mount_dir) == 0) { found = 1; *info = *entry; } } else break; } CHECK(fclose(f) == 0); return found; } /* * This funcion parses file-system options string, extracts standard mount * options from there, and saves them in the @flags variable. The non-standard * (fs-specific) mount options are left in @mnt_opts string, while the standard * ones will be removed from it. * * The reason for this perverted function is that we want to preserve mount * options when unmounting the file-system and mounting it again. But we cannot * pass standard* mount optins (like sync, ro, etc) as a string to the * 'mount()' function, because it fails. It accepts standard mount options only * as flags. And only the FS-specific mount options are accepted in form of a * string. */ static int process_mount_options(char **mnt_opts, unsigned long *flags) { char *tmp, *opts, *p; const char *opt; /* * We are going to use 'strtok()' which modifies the original string, * so duplicate it. */ tmp = strdup(*mnt_opts); if (!tmp) goto out_mem; p = opts = calloc(1, strlen(*mnt_opts) + 1); if (!opts) { free(tmp); goto out_mem; } *flags = 0; opt = strtok(tmp, ","); while (opt) { if (!strcmp(opt, "rw")) ; else if (!strcmp(opt, "ro")) *flags |= MS_RDONLY; else if (!strcmp(opt, "dirsync")) *flags |= MS_DIRSYNC; else if (!strcmp(opt, "noatime")) *flags |= MS_NOATIME; else if (!strcmp(opt, "nodiratime")) *flags |= MS_NODIRATIME; else if (!strcmp(opt, "noexec")) *flags |= MS_NOEXEC; else if (!strcmp(opt, "nosuid")) *flags |= MS_NOSUID; else if (!strcmp(opt, "relatime")) *flags |= MS_RELATIME; else if (!strcmp(opt, "sync")) *flags |= MS_SYNCHRONOUS; else { int len = strlen(opt); if (p != opts) *p++ = ','; memcpy(p, opt, len); p += len; *p = '\0'; } opt = strtok(NULL, ","); } free(tmp); *mnt_opts = opts; return 0; out_mem: fprintf(stderr, "cannot allocate memory\n"); return 1; } /* * Re-mount test file system. Randomly choose how to do this: re-mount R/O then * re-mount R/W, or unmount, then mount R/W, or unmount then mount R/O then * re-mount R/W, etc. This should improve test coverage. */ void tests_remount(void) { int err; struct mntent mount_info; char *source, *target, *filesystemtype, *data; char cwd[4096]; unsigned long mountflags, flags; unsigned int rorw1, um, um_ro, um_rorw, rorw2; CHECK(tests_get_mount_info(&mount_info)); if (strcmp(mount_info.mnt_dir,"/") == 0) return; /* Save current working directory */ CHECK(getcwd(cwd, 4096) != NULL); /* Temporarily change working directory to '/' */ CHECK(chdir("/") != -1); /* Choose what to do */ rorw1 = tests_random_no(2); um = tests_random_no(2); um_ro = tests_random_no(2); um_rorw = tests_random_no(2); rorw2 = tests_random_no(2); if (rorw1 + um + rorw2 == 0) um = 1; source = mount_info.mnt_fsname; target = tests_file_system_mount_dir; filesystemtype = tests_file_system_type; data = mount_info.mnt_opts; process_mount_options(&data, &mountflags); if (rorw1) { /* Re-mount R/O and then re-mount R/W */ flags = mountflags | MS_RDONLY | MS_REMOUNT; err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); flags = mountflags | MS_REMOUNT; flags &= ~((unsigned long)MS_RDONLY); err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); } if (um) { /* Unmount and mount */ if (um_ro) { /* But re-mount R/O before unmounting */ flags = mountflags | MS_RDONLY | MS_REMOUNT; err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); } CHECK(umount(target) != -1); if (!um_rorw) { /* Mount R/W straight away */ err = mount(source, target, filesystemtype, mountflags, data); CHECK(err != -1); } else { /* Mount R/O and then re-mount R/W */ err = mount(source, target, filesystemtype, mountflags | MS_RDONLY, data); CHECK(err != -1); flags = mountflags | MS_REMOUNT; flags &= ~((unsigned long)MS_RDONLY); err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); } } if (rorw2) { /* Re-mount R/O and then re-mount R/W */ flags = mountflags | MS_RDONLY | MS_REMOUNT; err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); flags = mountflags | MS_REMOUNT; flags &= ~((unsigned long)MS_RDONLY); err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); } /* Restore the previous working directory */ CHECK(chdir(cwd) != -1); } /* Un-mount or re-mount test file system */ static void tests_mnt(int mnt) { static struct mntent mount_info; char *source; char *target; char *filesystemtype; unsigned long mountflags; char *data; static char cwd[4096]; if (mnt == 0) { CHECK(tests_get_mount_info(&mount_info)); if (strcmp(mount_info.mnt_dir,"/") == 0) return; CHECK(getcwd(cwd, 4096) != NULL); CHECK(chdir("/") != -1); CHECK(umount(tests_file_system_mount_dir) != -1); } else { source = mount_info.mnt_fsname; target = tests_file_system_mount_dir; filesystemtype = tests_file_system_type; data = mount_info.mnt_opts; process_mount_options(&data, &mountflags); CHECK(mount(source, target, filesystemtype, mountflags, data) != -1); CHECK(chdir(cwd) != -1); } } /* Unmount test file system */ void tests_unmount(void) { tests_mnt(0); } /* Mount test file system */ void tests_mount(void) { tests_mnt(1); } /* Check whether the test file system is also the root file system */ int tests_fs_is_rootfs(void) { struct stat f_info; struct stat root_f_info; CHECK(stat(tests_file_system_mount_dir, &f_info) != -1); CHECK(stat("/", &root_f_info) != -1); if (f_info.st_dev == root_f_info.st_dev) return 1; else return 0; } /* Try to make a directory empty */ void tests_try_to_clear_dir(const char *dir_name) { DIR *dir; struct dirent *entry; char buf[4096]; dir = opendir(dir_name); if (dir == NULL) return; if (getcwd(buf, 4096) == NULL || chdir(dir_name) == -1) { closedir(dir); return; } for (;;) { errno = 0; entry = readdir(dir); if (entry) { if (strcmp(".",entry->d_name) != 0 && strcmp("..",entry->d_name) != 0) { if (entry->d_type == DT_DIR) { tests_try_to_clear_dir(entry->d_name); rmdir(entry->d_name); } else unlink(entry->d_name); } } else { CHECK(errno == 0); break; } } if (chdir(buf) < 0) perror("chdir"); closedir(dir); } /* Check whether the test file system is also the current file system */ int tests_fs_is_currfs(void) { struct stat f_info; struct stat curr_f_info; CHECK(stat(tests_file_system_mount_dir, &f_info) != -1); CHECK(stat(".", &curr_f_info) != -1); if (f_info.st_dev == curr_f_info.st_dev) return 1; else return 0; } #define PID_BUF_SIZE 64 /* Concatenate a pid to a string in a signal safe way */ void tests_cat_pid(char *buf, const char *name, pid_t pid) { char *p; unsigned x; const char digits[] = "0123456789"; char pid_buf[PID_BUF_SIZE]; x = (unsigned) pid; p = pid_buf + PID_BUF_SIZE; *--p = '\0'; if (x) while (x) { *--p = digits[x % 10]; x /= 10; } else *--p = '0'; buf[0] = '\0'; strcat(buf, name); strcat(buf, p); }