1282 lines
30 KiB
C
1282 lines
30 KiB
C
/*
|
|
* 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);
|
|
}
|