text: /*
   * 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"
  
  const char *tests_file_system_mount_dir = TESTS_DEFAULT_FILE_SYSTEM_MOUNT_DIR;
  
  const 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] */
  const 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);
  }
  
  static 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 */
  static 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;
  	const char *source, *target, *filesystemtype;
  	char cwd[4096], *data;
  	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;
  	const char *source;
  	const char *target;
  	const 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);
  }
  