summaryrefslogtreecommitdiff
path: root/tests/fs-tests/lib/tests.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/fs-tests/lib/tests.c')
-rw-r--r--tests/fs-tests/lib/tests.c1063
1 files changed, 1063 insertions, 0 deletions
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);
+}