diff options
author | Adrian Hunter <ext-adrian.hunter@nokia.com> | 2007-03-15 08:45:19 -0500 |
---|---|---|
committer | Josh Boyer <jwboyer@gmail.com> | 2007-03-15 08:45:19 -0500 |
commit | 726ac243f051f0daee6149db66ac21ba621fb454 (patch) | |
tree | 257483dc155e539e5d7f08fb8009c57181919962 /tests/fs-tests/lib | |
parent | 876476b7bbf158c64868d379460a7b6bce7e95e0 (diff) |
Add fs-tests from Adrian Hunter
Signed-off-by: Adrian Hunter <ext-adrian.hunter@nokia.com>
Signed-off-by: Josh Boyer <jwboyer@gmail.com>
Diffstat (limited to 'tests/fs-tests/lib')
-rw-r--r-- | tests/fs-tests/lib/Makefile | 18 | ||||
-rw-r--r-- | tests/fs-tests/lib/tests.c | 1063 | ||||
-rw-r--r-- | tests/fs-tests/lib/tests.h | 185 |
3 files changed, 1266 insertions, 0 deletions
diff --git a/tests/fs-tests/lib/Makefile b/tests/fs-tests/lib/Makefile new file mode 100644 index 0000000..8d57824 --- /dev/null +++ b/tests/fs-tests/lib/Makefile @@ -0,0 +1,18 @@ + +ifeq ($(origin CC),default) +CC = gcc +endif + +CFLAGS := $(CFLAGS) -Wall -g -O2 + +LDFLAGS := $(LDFLAGS) + +all: tests.o + +tests.o: tests.h + +clean: + rm -f *.o + +tests: + echo diff --git a/tests/fs-tests/lib/tests.c b/tests/fs-tests/lib/tests.c new file mode 100644 index 0000000..2f513d7 --- /dev/null +++ b/tests/fs-tests/lib/tests.c @@ -0,0 +1,1063 @@ +/* + * 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 <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/vfs.h> +#include <sys/statvfs.h> +#include <linux/jffs2.h> +#if 0 +#include <linux/jffs3.h> +#endif +#include <libgen.h> +#include <dirent.h> +#include <ctype.h> +#include <limits.h> +#include <sys/mount.h> +#include <mntent.h> +#include <time.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; + +/* 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); + } + if (strcmp(tests_file_system_type, "jffs2") != 0 && + strcmp(tests_file_system_type, "jffs3") != 0) { + fprintf(stderr, "Invalid test file system type:" + " %s\n", tests_file_system_type); + fprintf(stderr, "Must be jffs2 or jffs3\n"); + CHECK(0); + } + 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); + } +#ifdef JFFS3_SUPER_MAGIC + if (strcmp(tests_file_system_type, "jffs3") == 0 && + fs_info.f_type != JFFS3_SUPER_MAGIC) { + fprintf(stderr, "File system type is not jffs3\n"); + CHECK(0); + } +#endif + /* 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]; + + 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); +} + +/* 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(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; +} + +/* Un-mount and re-mount test file system */ +void tests_remount(void) +{ + struct mntent mount_info; + char *source; + char *target; + char *filesystemtype; + unsigned long mountflags; + void *data; + char cwd[4096]; + + 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); + + source = mount_info.mnt_fsname; + target = tests_file_system_mount_dir; + filesystemtype = tests_file_system_type; + mountflags = 0; + data = NULL; + + CHECK(mount(source, target, filesystemtype, mountflags, data) != -1); + + CHECK(chdir(cwd) != -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; + } + } + chdir(buf); + 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); +} diff --git a/tests/fs-tests/lib/tests.h b/tests/fs-tests/lib/tests.h new file mode 100644 index 0000000..36ca310 --- /dev/null +++ b/tests/fs-tests/lib/tests.h @@ -0,0 +1,185 @@ +/* + * 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 + */ + +#ifndef included_tests_tests_h__ +#define included_tests_tests_h__ + +#include <stdint.h> + +/* Main macro for testing */ +#define CHECK(x) tests_test((x),__func__,__FILE__,__LINE__) + +/* The default directory in which tests are conducted */ +#define TESTS_DEFAULT_FILE_SYSTEM_MOUNT_DIR "/mnt/test_file_system" + +/* The default file system type to test */ +#define TESTS_DEFAULT_FILE_SYSTEM_TYPE "jffs2" + +/* Estimated size of an empty directory */ +#define TESTS_EMPTY_DIR_SIZE 128 + +/* Function invoked by the CHECK macro */ +void tests_test(int test,const char *msg,const char *file,unsigned line); + +/* Handle common program options */ +int tests_get_args(int argc, + char *argv[], + const char *title, + const char *desc, + const char *opts); + +/* Return the number of files (or directories) in the given directory */ +unsigned tests_count_files_in_dir(const char *dir_name); + +/* 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); + +/* Get the free space for the file system of the current directory */ +uint64_t tests_get_free_space(void); + +/* Get the total space for the file system of the current directory */ +uint64_t tests_get_total_space(void); + +/* 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); + +/* 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); + +/* 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); + +/* 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); + +/* Delete a file */ +void tests_delete_file(const char *file_name); + +/* Create a file of size file_size */ +uint64_t tests_create_file(const char *file_name, uint64_t file_size); + +/* Calculate: free_space * numerator / denominator */ +uint64_t tests_get_big_file_size(unsigned numerator, unsigned denominator); + +/* 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); + +/* 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); + +/* 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); + +/* Delete file "fragment_n" where n is the file_number */ +void tests_delete_fragment_file(unsigned file_number); + +/* Check the random data in file "fragment_n" is what is expected */ +void tests_check_fragment_file(unsigned file_number); + +/* Central point to decide whether to use fsync */ +void tests_maybe_sync(int fd); + +/* Return O_SYNC if ok to sync otherwise return 0 */ +int tests_maybe_sync_flag(void); + +/* Return random number from 0 to n - 1 */ +size_t tests_random_no(size_t n); + +/* Make a directory empty */ +void tests_clear_dir(const char *dir_name); + +/* Create an empty sub-directory or small file in the current directory */ +int64_t tests_create_entry(char *return_name); + +/* Remove a random file of empty sub-directory from the current directory */ +int64_t tests_remove_entry(void); + +/* Un-mount and re-mount test file system */ +void tests_remount(void); + +/* Check whether the test file system is also the root file system */ +int tests_fs_is_rootfs(void); + +/* Try to make a directory empty */ +void tests_try_to_clear_dir(const char *dir_name); + +/* Check whether the test file system is also the current file system */ +int tests_fs_is_currfs(void); + +/* Concatenate a pid to a string in a signal safe way */ +void tests_cat_pid(char *buf, const char *name, pid_t pid); + +extern char *tests_file_system_mount_dir; + +extern char *tests_file_system_type; + +/* General purpose test parameter to specify some aspect of test size. + May be used by different tests in different ways. + Set by the -z, --size options. */ +extern int64_t tests_size_parameter; + +/* General purpose test parameter to specify some aspect of test repetition. + May be used by different tests in different ways. + Set by the -n, --repeat options. */ +extern int64_t tests_repeat_parameter; + +/* General purpose test parameter to specify some aspect of test sleeping. + May be used by different tests in different ways. + Set by the -p, --sleep options. */ +extern int64_t tests_sleep_parameter; + +/* General purpose test parameter to specify a file should be unlinked. + May be used by different tests in different ways or not at all. */ +extern int tests_unlink_flag; + +/* General purpose test parameter to specify a file should be closed. + May be used by different tests in different ways or not at all. */ +extern int tests_close_flag; + +/* General purpose test parameter to specify a file should be deleted. + May be used by different tests in different ways or not at all. */ +extern int tests_delete_flag; + +/* General purpose test parameter to specify a file have a hole. + May be used by different tests in different ways or not at all. */ +extern int tests_hole_flag; + +/* Program name from argv[0] */ +extern char *program_name; + +#endif |