3385 lines
77 KiB
C
3385 lines
77 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 <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <dirent.h>
|
|
#include <getopt.h>
|
|
#include <assert.h>
|
|
#include <mntent.h>
|
|
#include <execinfo.h>
|
|
#include <bits/stdio_lim.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/vfs.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/statvfs.h>
|
|
|
|
#define PROGRAM_VERSION "1.1"
|
|
#define PROGRAM_NAME "integck"
|
|
#include "common.h"
|
|
#include "libubi.h"
|
|
|
|
/*
|
|
* WARNING! This is a dirty hack! The symbols for static functions are not
|
|
* printed in the stack backtrace. So we remove ths 'static' keyword using the
|
|
* pre-processor. This is really error-prone because this won't work if, e.g.,
|
|
* local static variables were used.
|
|
*/
|
|
#ifdef INTEGCK_DEBUG
|
|
#define static
|
|
#endif
|
|
|
|
#define MAX_RANDOM_SEED 10000000
|
|
|
|
/* The pattern for the top directory where we run the test */
|
|
#define TEST_DIR_PATTERN "integck_test_dir_%u"
|
|
|
|
/* Maximum buffer size for a single read/write operation */
|
|
#define IO_BUFFER_SIZE 32768
|
|
|
|
/*
|
|
* Check if a condition is true and die if not.
|
|
*/
|
|
#define stringify1(x) #x
|
|
#define stringify(x) stringify1(x)
|
|
#define CHECK(cond) do { \
|
|
if (!(cond)) \
|
|
check_failed(stringify(cond), __func__, __FILE__, __LINE__); \
|
|
} while(0)
|
|
|
|
/*
|
|
* In case of emulated power cut failures the FS has to return EROFS. But
|
|
* unfortunately, the Linux kernel sometimes returns EIO to user-space anyway
|
|
* (when write-back fails the return code is awayse EIO).
|
|
*/
|
|
#define pcv(fmt, ...) do { \
|
|
int __err = 1; \
|
|
if (args.power_cut_mode && (errno == EROFS || errno == EIO)) \
|
|
__err = 0; \
|
|
if (!args.power_cut_mode || args.verbose || __err) \
|
|
normsg(fmt " (line %d, error %d (%s))", \
|
|
##__VA_ARGS__, __LINE__, errno, strerror(errno)); \
|
|
CHECK(!__err); \
|
|
} while(0)
|
|
|
|
#define v(fmt, ...) do { \
|
|
if (args.verbose) \
|
|
normsg(fmt " (line %d)", ##__VA_ARGS__, __LINE__); \
|
|
} while(0)
|
|
|
|
/* The variables below are set by command line arguments */
|
|
static struct {
|
|
long repeat_cnt;
|
|
int power_cut_mode;
|
|
int verify_ops;
|
|
int reattach;
|
|
int mtdn;
|
|
int verbose;
|
|
const char *mount_point;
|
|
} args = {
|
|
.repeat_cnt = 1,
|
|
};
|
|
|
|
/*
|
|
* The below data structure describes the tested file-system.
|
|
*
|
|
* max_name_len: maximum file name length
|
|
* page_size: memory page size to use with 'mmap()'
|
|
* log10_initial_free: logarighm base 10 of the initial amount of free space in
|
|
* the tested file-system
|
|
* nospc_size_ok: file size is updated even if the write operation failed with
|
|
* ENOSPC error
|
|
* can_mmap: file-system supports share writable 'mmap()' operation
|
|
* can_remount: is it possible to re-mount the tested file-system?
|
|
* fstype: file-system type (e.g., "ubifs")
|
|
* fsdev: the underlying device mounted by the tested file-system
|
|
* mount_opts: non-standard mount options of the tested file-system (non-standard
|
|
* options are stored in string form as a comma-separated list)
|
|
* mount_flags: standard mount options of the tested file-system (standard
|
|
* options as stored as a set of flags)
|
|
* mount_point: tested file-system mount point path
|
|
* test_dir: the directory on the tested file-system where we test
|
|
*/
|
|
static struct {
|
|
int max_name_len;
|
|
int page_size;
|
|
unsigned int log10_initial_free;
|
|
unsigned int nospc_size_ok:1;
|
|
unsigned int can_mmap:1;
|
|
unsigned int can_remount:1;
|
|
char *fstype;
|
|
char *fsdev;
|
|
char *mount_opts;
|
|
unsigned long mount_flags;
|
|
char *mount_point;
|
|
char *test_dir;
|
|
} fsinfo = {
|
|
.nospc_size_ok = 1,
|
|
.can_mmap = 1,
|
|
};
|
|
|
|
/* Structures to store data written to the test file system,
|
|
so that we can check whether the file system is correct. */
|
|
|
|
struct write_info /* Record of random data written into a file */
|
|
{
|
|
struct write_info *next;
|
|
off_t offset; /* Where in the file the data was written */
|
|
union {
|
|
off_t random_offset; /* Call rand_r() this number of times first */
|
|
off_t new_length; /* For truncation records new file length */
|
|
};
|
|
size_t size; /* Number of bytes written */
|
|
unsigned int random_seed; /* Seed for rand_r() to create random data. If
|
|
greater than MAX_RANDOM_SEED then this is
|
|
a truncation record (raw_writes only) */
|
|
};
|
|
|
|
struct dir_entry_info;
|
|
|
|
struct file_info /* Each file has one of these */
|
|
{
|
|
struct write_info *writes; /* Record accumulated writes to the file */
|
|
struct write_info *raw_writes;
|
|
/* Record in order all writes to the file */
|
|
struct fd_info *fds; /* All open file descriptors for this file */
|
|
struct dir_entry_info *links;
|
|
off_t length;
|
|
int link_count;
|
|
unsigned int check_run_no; /* Run number used when checking */
|
|
unsigned int no_space_error:1; /* File has incurred a ENOSPC error */
|
|
unsigned int clean:1; /* Non-zero if the file is synchronized */
|
|
};
|
|
|
|
struct symlink_info /* Each symlink has one of these */
|
|
{
|
|
char *target_pathname;
|
|
struct dir_entry_info *entry; /* dir entry of this symlink */
|
|
};
|
|
|
|
struct dir_info /* Each directory has one of these */
|
|
{
|
|
struct dir_info *parent; /* Parent directory or null
|
|
for our top directory */
|
|
unsigned int number_of_entries;
|
|
struct dir_entry_info *first;
|
|
struct dir_entry_info *entry; /* Dir entry of this dir */
|
|
unsigned int clean:1; /* Non-zero if the directory is synchronized */
|
|
};
|
|
|
|
struct dir_entry_info /* Each entry in a directory has one of these */
|
|
{
|
|
struct dir_entry_info *next; /* List of entries in directory */
|
|
struct dir_entry_info *prev; /* List of entries in directory */
|
|
struct dir_entry_info *next_link; /* List of hard links for same file */
|
|
struct dir_entry_info *prev_link; /* List of hard links for same file */
|
|
char *name;
|
|
struct dir_info *parent; /* Parent directory */
|
|
union {
|
|
struct file_info *file;
|
|
struct dir_info *dir;
|
|
struct symlink_info *symlink;
|
|
void *target;
|
|
};
|
|
char type; /* f => file, d => dir, s => symlink */
|
|
char checked; /* Temporary flag used when checking */
|
|
};
|
|
|
|
struct fd_info /* We keep a number of files open */
|
|
{
|
|
struct fd_info *next;
|
|
struct file_info *file;
|
|
int fd;
|
|
};
|
|
|
|
struct open_file_info /* We keep a list of open files */
|
|
{
|
|
struct open_file_info *next;
|
|
struct fd_info *fdi;
|
|
};
|
|
|
|
static struct dir_info *top_dir = NULL; /* Our top directory */
|
|
|
|
static struct open_file_info *open_files = NULL; /* We keep a list of
|
|
open files */
|
|
static size_t open_files_count = 0;
|
|
|
|
static int grow = 1; /* Should we try to grow files and directories */
|
|
static int shrink = 0; /* Should we try to shrink files and directories */
|
|
static int full = 0; /* Flag that the file system is full */
|
|
static uint64_t operation_count = 0; /* Number of operations used to fill
|
|
up the file system */
|
|
static unsigned int check_run_no;
|
|
static unsigned int random_seed;
|
|
|
|
/*
|
|
* A buffer which is used by 'make_name()' to return the generated random name.
|
|
*/
|
|
static char *random_name_buf;
|
|
|
|
/*
|
|
* This is a helper for the 'CHECK()' macro - prints a scary error message and
|
|
* terminates the program.
|
|
*/
|
|
static void check_failed(const char *cond, const char *func, const char *file,
|
|
int line)
|
|
{
|
|
int error = errno, count;
|
|
void *addresses[128];
|
|
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
errmsg("condition '%s' failed in %s() at %s:%d",
|
|
cond, func, file, line);
|
|
normsg("error %d (%s)", error, strerror(error));
|
|
/*
|
|
* Note, to make this work well you need:
|
|
* 1. Make all functions non-static - add "#define static'
|
|
* 2. Compile with -rdynamic and -g gcc options
|
|
* 3. Preferrably compile with -O0 to avoid inlining
|
|
*/
|
|
count = backtrace(addresses, 128);
|
|
backtrace_symbols_fd(addresses, count, fileno(stdout));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Is this 'struct write_info' actually holds information about a truncation?
|
|
*/
|
|
static int is_truncation(struct write_info *w)
|
|
{
|
|
return w->random_seed > MAX_RANDOM_SEED;
|
|
}
|
|
|
|
/*
|
|
* Return a random number between 0 and max - 1.
|
|
*/
|
|
static unsigned int random_no(unsigned int max)
|
|
{
|
|
assert(max < RAND_MAX);
|
|
if (max == 0)
|
|
return 0;
|
|
return rand_r(&random_seed) % max;
|
|
}
|
|
|
|
/*
|
|
* Allocate a buffer of 'size' bytes and fill it with zeroes.
|
|
*/
|
|
static void *zalloc(size_t size)
|
|
{
|
|
void *buf = malloc(size);
|
|
|
|
CHECK(buf != NULL);
|
|
memset(buf, 0, size);
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* Duplicate a string.
|
|
*/
|
|
static char *dup_string(const char *s)
|
|
{
|
|
char *str;
|
|
|
|
assert(s != NULL);
|
|
str = strdup(s);
|
|
CHECK(str != NULL);
|
|
return str;
|
|
}
|
|
|
|
static char *cat_strings(const char *a, const char *b)
|
|
{
|
|
char *str;
|
|
size_t sz;
|
|
|
|
if (a && !b)
|
|
return dup_string(a);
|
|
if (b && !a)
|
|
return dup_string(b);
|
|
if (!a && !b)
|
|
return NULL;
|
|
sz = strlen(a) + strlen(b) + 1;
|
|
str = malloc(sz);
|
|
CHECK(str != NULL);
|
|
strcpy(str, a);
|
|
strcat(str, b);
|
|
return str;
|
|
}
|
|
|
|
static char *cat_paths(const char *a, const char *b)
|
|
{
|
|
char *str;
|
|
size_t sz, na, nb;
|
|
int as = 0, bs = 0;
|
|
|
|
assert(a != NULL);
|
|
assert(b != NULL);
|
|
|
|
na = strlen(a);
|
|
nb = strlen(b);
|
|
if (na && a[na - 1] == '/')
|
|
as = 1;
|
|
if (nb && b[0] == '/')
|
|
bs = 1;
|
|
if ((as && !bs) || (!as && bs))
|
|
return cat_strings(a, b);
|
|
if (as && bs)
|
|
return cat_strings(a, b + 1);
|
|
|
|
sz = na + nb + 2;
|
|
str = malloc(sz);
|
|
CHECK(str != NULL);
|
|
strcpy(str, a);
|
|
strcat(str, "/");
|
|
strcat(str, b);
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* Get the free space for the tested file system.
|
|
*/
|
|
static void get_fs_space(uint64_t *total, uint64_t *free)
|
|
{
|
|
struct statvfs st;
|
|
|
|
CHECK(statvfs(fsinfo.mount_point, &st) == 0);
|
|
if (total)
|
|
*total = (uint64_t)st.f_blocks * (uint64_t)st.f_frsize;
|
|
if (free)
|
|
*free = (uint64_t)st.f_bavail * (uint64_t)st.f_frsize;
|
|
}
|
|
|
|
static char *dir_path(struct dir_info *parent, const char *name)
|
|
{
|
|
char *parent_path, *path;
|
|
|
|
if (!parent)
|
|
return cat_paths(fsinfo.mount_point, name);
|
|
parent_path = dir_path(parent->parent, parent->entry->name);
|
|
path = cat_paths(parent_path, name);
|
|
free(parent_path);
|
|
return path;
|
|
}
|
|
|
|
static void open_file_add(struct fd_info *fdi)
|
|
{
|
|
struct open_file_info *ofi;
|
|
|
|
ofi = zalloc(sizeof(struct open_file_info));
|
|
ofi->next = open_files;
|
|
ofi->fdi = fdi;
|
|
open_files = ofi;
|
|
open_files_count += 1;
|
|
}
|
|
|
|
static void open_file_remove(struct fd_info *fdi)
|
|
{
|
|
struct open_file_info *ofi;
|
|
struct open_file_info **prev;
|
|
|
|
prev = &open_files;
|
|
for (ofi = open_files; ofi; ofi = ofi->next) {
|
|
if (ofi->fdi == fdi) {
|
|
*prev = ofi->next;
|
|
free(ofi);
|
|
open_files_count -= 1;
|
|
return;
|
|
}
|
|
prev = &ofi->next;
|
|
}
|
|
CHECK(0); /* We are trying to remove something that is not there */
|
|
}
|
|
|
|
static struct fd_info *add_fd(struct file_info *file, int fd)
|
|
{
|
|
struct fd_info *fdi;
|
|
|
|
fdi = zalloc(sizeof(struct fd_info));
|
|
fdi->next = file->fds;
|
|
fdi->file = file;
|
|
fdi->fd = fd;
|
|
file->fds = fdi;
|
|
open_file_add(fdi);
|
|
return fdi;
|
|
}
|
|
|
|
/*
|
|
* Free all the information about writes to a file.
|
|
*/
|
|
static void free_writes_info(struct file_info *file)
|
|
{
|
|
struct write_info *w, *next;
|
|
|
|
w = file->writes;
|
|
while (w) {
|
|
next = w->next;
|
|
free(w);
|
|
w = next;
|
|
}
|
|
|
|
w = file->raw_writes;
|
|
while (w) {
|
|
next = w->next;
|
|
free(w);
|
|
w = next;
|
|
}
|
|
}
|
|
|
|
static void *add_dir_entry(struct dir_info *parent, char type, const char *name,
|
|
void *target)
|
|
{
|
|
struct dir_entry_info *entry;
|
|
|
|
entry = zalloc(sizeof(struct dir_entry_info));
|
|
entry->type = type;
|
|
entry->name = dup_string(name);
|
|
entry->parent = parent;
|
|
|
|
entry->next = parent->first;
|
|
if (parent->first)
|
|
parent->first->prev = entry;
|
|
parent->first = entry;
|
|
parent->number_of_entries += 1;
|
|
parent->clean = 0;
|
|
|
|
if (entry->type == 'f') {
|
|
struct file_info *file = target;
|
|
|
|
if (!file)
|
|
file = zalloc(sizeof(struct file_info));
|
|
entry->file = file;
|
|
entry->next_link = file->links;
|
|
if (file->links)
|
|
file->links->prev_link = entry;
|
|
file->links = entry;
|
|
file->link_count += 1;
|
|
return file;
|
|
} else if (entry->type == 'd') {
|
|
struct dir_info *dir = target;
|
|
|
|
if (!dir)
|
|
dir = zalloc(sizeof(struct dir_info));
|
|
entry->dir = dir;
|
|
dir->entry = entry;
|
|
dir->parent = parent;
|
|
return dir;
|
|
} else if (entry->type == 's') {
|
|
struct symlink_info *symlink = target;
|
|
|
|
if (!symlink)
|
|
symlink = zalloc(sizeof(struct symlink_info));
|
|
entry->symlink = symlink;
|
|
symlink->entry = entry;
|
|
return symlink;
|
|
} else
|
|
assert(0);
|
|
}
|
|
|
|
static void remove_dir_entry(struct dir_entry_info *entry, int free_target)
|
|
{
|
|
entry->parent->clean = 0;
|
|
entry->parent->number_of_entries -= 1;
|
|
if (entry->parent->first == entry)
|
|
entry->parent->first = entry->next;
|
|
if (entry->prev)
|
|
entry->prev->next = entry->next;
|
|
if (entry->next)
|
|
entry->next->prev = entry->prev;
|
|
|
|
if (entry->type == 'f') {
|
|
struct file_info *file = entry->file;
|
|
|
|
if (entry->prev_link)
|
|
entry->prev_link->next_link = entry->next_link;
|
|
if (entry->next_link)
|
|
entry->next_link->prev_link = entry->prev_link;
|
|
if (file->links == entry)
|
|
file->links = entry->next_link;
|
|
file->link_count -= 1;
|
|
if (file->link_count == 0)
|
|
assert(file->links == NULL);
|
|
|
|
/* Free struct file_info if file is not open and not linked */
|
|
if (free_target && !file->fds && !file->links) {
|
|
free_writes_info(file);
|
|
free(file);
|
|
}
|
|
}
|
|
|
|
if (free_target) {
|
|
if (entry->type == 'd') {
|
|
free(entry->dir);
|
|
} else if (entry->type == 's') {
|
|
free(entry->symlink->target_pathname);
|
|
free(entry->symlink);
|
|
}
|
|
}
|
|
|
|
free(entry->name);
|
|
free(entry);
|
|
}
|
|
|
|
/*
|
|
* Create a new directory "name" in the parent directory described by "parent"
|
|
* and add it to the in-memory list of directories. Returns zero in case of
|
|
* success and -1 in case of failure.
|
|
*/
|
|
static int dir_new(struct dir_info *parent, const char *name)
|
|
{
|
|
char *path;
|
|
|
|
assert(parent);
|
|
|
|
path = dir_path(parent, name);
|
|
v("creating dir %s", path);
|
|
if (mkdir(path, 0777) != 0) {
|
|
if (errno == ENOSPC) {
|
|
full = 1;
|
|
free(path);
|
|
return 0;
|
|
}
|
|
pcv("cannot create directory %s", path);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
|
|
if (args.verify_ops) {
|
|
struct stat st;
|
|
|
|
CHECK(lstat(path, &st) == 0);
|
|
CHECK(S_ISDIR(st.st_mode));
|
|
}
|
|
free(path);
|
|
|
|
add_dir_entry(parent, 'd', name, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int file_delete(struct file_info *file);
|
|
static int file_unlink(struct dir_entry_info *entry);
|
|
static int symlink_remove(struct symlink_info *symlink);
|
|
|
|
static int dir_remove(struct dir_info *dir)
|
|
{
|
|
char *path;
|
|
|
|
/* Remove directory contents */
|
|
while (dir->first) {
|
|
struct dir_entry_info *entry;
|
|
int ret = 0;
|
|
|
|
entry = dir->first;
|
|
if (entry->type == 'd')
|
|
ret = dir_remove(entry->dir);
|
|
else if (entry->type == 'f')
|
|
ret = file_unlink(entry);
|
|
else if (entry->type == 's')
|
|
ret = symlink_remove(entry->symlink);
|
|
else
|
|
CHECK(0); /* Invalid struct dir_entry_info */
|
|
if (ret)
|
|
return -1;
|
|
}
|
|
|
|
/* Remove directory form the file-system */
|
|
path = dir_path(dir->parent, dir->entry->name);
|
|
if (rmdir(path) != 0) {
|
|
pcv("cannot remove directory entry %s", path);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
|
|
if (args.verify_ops) {
|
|
struct stat st;
|
|
|
|
CHECK(lstat(path, &st) == -1);
|
|
CHECK(errno == ENOENT);
|
|
}
|
|
|
|
/* Remove entry from parent directory */
|
|
remove_dir_entry(dir->entry, 1);
|
|
|
|
free(path);
|
|
return 0;
|
|
}
|
|
|
|
static int file_new(struct dir_info *parent, const char *name)
|
|
{
|
|
char *path;
|
|
mode_t mode;
|
|
int fd;
|
|
|
|
assert(parent != NULL);
|
|
|
|
path = dir_path(parent, name);
|
|
mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
|
|
v("creating file %s", path);
|
|
fd = open(path, O_CREAT | O_EXCL | O_RDWR, mode);
|
|
if (fd == -1) {
|
|
if (errno == ENOSPC) {
|
|
full = 1;
|
|
free(path);
|
|
return 0;
|
|
}
|
|
pcv("cannot create file %s", path);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
|
|
if (args.verify_ops) {
|
|
struct stat st;
|
|
|
|
CHECK(lstat(path, &st) == 0);
|
|
CHECK(S_ISREG(st.st_mode));
|
|
}
|
|
|
|
add_dir_entry(parent, 'f', name, NULL);
|
|
|
|
close(fd);
|
|
free(path);
|
|
return 0;
|
|
}
|
|
|
|
static int link_new(struct dir_info *parent, const char *name,
|
|
struct file_info *file)
|
|
{
|
|
struct dir_entry_info *entry;
|
|
char *path, *target;
|
|
int ret;
|
|
struct stat st1, st2;
|
|
|
|
entry = file->links;
|
|
if (!entry)
|
|
return 0;
|
|
|
|
path = dir_path(parent, name);
|
|
target = dir_path(entry->parent, entry->name);
|
|
|
|
if (args.verify_ops)
|
|
CHECK(lstat(target, &st1) == 0);
|
|
|
|
v("creating hardlink %s ---> %s", path, target);
|
|
ret = link(target, path);
|
|
if (ret != 0) {
|
|
if (errno == ENOSPC) {
|
|
ret = 0;
|
|
full = 1;
|
|
} else
|
|
pcv("cannot create hardlink %s in directory %s to file %s",
|
|
path, parent->entry->name, target);
|
|
free(target);
|
|
free(path);
|
|
return ret;
|
|
}
|
|
|
|
if (args.verify_ops) {
|
|
CHECK(lstat(path, &st2) == 0);
|
|
CHECK(S_ISREG(st2.st_mode));
|
|
CHECK(st1.st_ino == st2.st_ino);
|
|
CHECK(st2.st_nlink > 1);
|
|
CHECK(st2.st_nlink == st1.st_nlink + 1);
|
|
}
|
|
|
|
add_dir_entry(parent, 'f', name, file);
|
|
|
|
free(target);
|
|
free(path);
|
|
return 0;
|
|
}
|
|
|
|
static void file_close(struct fd_info *fdi);
|
|
|
|
static void file_close_all(struct file_info *file)
|
|
{
|
|
struct fd_info *fdi = file->fds;
|
|
|
|
while (fdi) {
|
|
struct fd_info *next = fdi->next;
|
|
|
|
file_close(fdi);
|
|
fdi = next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unlink a directory entry for a file.
|
|
*/
|
|
static int file_unlink(struct dir_entry_info *entry)
|
|
{
|
|
char *path;
|
|
int ret;
|
|
|
|
path = dir_path(entry->parent, entry->name);
|
|
/* Unlink the file */
|
|
ret = unlink(path);
|
|
if (ret) {
|
|
pcv("cannot unlink file %s", path);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
|
|
if (args.verify_ops) {
|
|
struct stat st;
|
|
|
|
CHECK(lstat(path, &st) == -1);
|
|
CHECK(errno == ENOENT);
|
|
}
|
|
|
|
/* Remove file entry from parent directory */
|
|
remove_dir_entry(entry, 1);
|
|
|
|
free(path);
|
|
return 0;
|
|
}
|
|
|
|
static struct dir_entry_info *pick_entry(struct file_info *file)
|
|
{
|
|
struct dir_entry_info *entry;
|
|
unsigned int r;
|
|
|
|
if (!file->link_count)
|
|
return NULL;
|
|
r = random_no(file->link_count);
|
|
entry = file->links;
|
|
while (entry && r--)
|
|
entry = entry->next_link;
|
|
return entry;
|
|
}
|
|
|
|
static int file_unlink_file(struct file_info *file)
|
|
{
|
|
struct dir_entry_info *entry;
|
|
|
|
entry = pick_entry(file);
|
|
if (!entry)
|
|
return 0;
|
|
return file_unlink(entry);
|
|
}
|
|
|
|
/*
|
|
* Close all open descriptors for a file described by 'file' and delete it by
|
|
* unlinking all its hardlinks.
|
|
*/
|
|
static int file_delete(struct file_info *file)
|
|
{
|
|
struct dir_entry_info *entry = file->links;
|
|
|
|
file_close_all(file);
|
|
while (entry) {
|
|
struct dir_entry_info *next = entry->next_link;
|
|
|
|
if (file_unlink(entry))
|
|
return -1;
|
|
entry = next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void file_info_display(struct file_info *file)
|
|
{
|
|
struct dir_entry_info *entry;
|
|
struct write_info *w;
|
|
unsigned int wcnt;
|
|
|
|
normsg("File Info:");
|
|
normsg(" Link count: %d", file->link_count);
|
|
normsg(" Links:");
|
|
entry = file->links;
|
|
while (entry) {
|
|
normsg(" Name: %s", entry->name);
|
|
normsg(" Directory: %s", entry->parent->entry->name);
|
|
entry = entry->next_link;
|
|
}
|
|
normsg(" Length: %llu", (unsigned long long)file->length);
|
|
normsg(" File was open: %s",
|
|
(file->fds == NULL) ? "false" : "true");
|
|
normsg(" File was deleted: %s",
|
|
(file->link_count == 0) ? "true" : "false");
|
|
normsg(" File was out of space: %s",
|
|
(file->no_space_error == 0) ? "false" : "true");
|
|
normsg(" File Data:");
|
|
wcnt = 0;
|
|
w = file->writes;
|
|
while (w) {
|
|
normsg(" Offset: %llu Size: %zu Seed: %llu R.Off: %llu",
|
|
(unsigned long long)w->offset, w->size,
|
|
(unsigned long long)w->random_seed,
|
|
(unsigned long long)w->random_offset);
|
|
wcnt += 1;
|
|
w = w->next;
|
|
}
|
|
normsg(" %u writes", wcnt);
|
|
normsg(" ============================================");
|
|
normsg(" Write Info:");
|
|
wcnt = 0;
|
|
w = file->raw_writes;
|
|
while (w) {
|
|
if (is_truncation(w))
|
|
normsg(" Trunc from %llu to %llu",
|
|
(unsigned long long)w->offset,
|
|
(unsigned long long)w->new_length);
|
|
else
|
|
normsg(" Offset: %llu Size: %zu Seed: %llu R.Off: %llu",
|
|
(unsigned long long)w->offset, w->size,
|
|
(unsigned long long)w->random_seed,
|
|
(unsigned long long)w->random_offset);
|
|
wcnt += 1;
|
|
w = w->next;
|
|
}
|
|
normsg(" %u writes or truncations", wcnt);
|
|
normsg(" ============================================");
|
|
}
|
|
|
|
static int file_open(struct file_info *file)
|
|
{
|
|
int fd, flags = O_RDWR;
|
|
char *path;
|
|
|
|
assert(file->links);
|
|
|
|
path = dir_path(file->links->parent, file->links->name);
|
|
if (random_no(100) == 1)
|
|
flags |= O_SYNC;
|
|
fd = open(path, flags);
|
|
if (fd == -1) {
|
|
pcv("cannot open file %s", path);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
free(path);
|
|
add_fd(file, fd);
|
|
return 0;
|
|
}
|
|
|
|
static const char *get_file_name(struct file_info *file)
|
|
{
|
|
if (file->links)
|
|
return file->links->name;
|
|
return "(unlinked file, no names)";
|
|
}
|
|
|
|
/*
|
|
* Write random 'size' bytes of random data to offset 'offset'. Seed the random
|
|
* gererator with 'seed'. Return amount of written data on success and -1 on
|
|
* failure.
|
|
*/
|
|
static ssize_t file_write_data(struct file_info *file, int fd, off_t offset,
|
|
size_t size, unsigned int seed)
|
|
{
|
|
size_t remains, actual, block;
|
|
ssize_t written;
|
|
char buf[IO_BUFFER_SIZE];
|
|
|
|
CHECK(lseek(fd, offset, SEEK_SET) != (off_t)-1);
|
|
remains = size;
|
|
actual = 0;
|
|
written = IO_BUFFER_SIZE;
|
|
v("write %zd bytes, offset %"PRIdoff_t", file %s",
|
|
size, offset, get_file_name(file));
|
|
while (remains) {
|
|
/* Fill up buffer with random data */
|
|
if (written < IO_BUFFER_SIZE) {
|
|
memmove(buf, buf + written, IO_BUFFER_SIZE - written);
|
|
written = IO_BUFFER_SIZE - written;
|
|
} else
|
|
written = 0;
|
|
for (; written < IO_BUFFER_SIZE; ++written)
|
|
buf[written] = rand_r(&seed);
|
|
/* Write a block of data */
|
|
if (remains > IO_BUFFER_SIZE)
|
|
block = IO_BUFFER_SIZE;
|
|
else
|
|
block = remains;
|
|
written = write(fd, buf, block);
|
|
if (written < 0) {
|
|
if (errno == ENOSPC) {
|
|
full = 1;
|
|
file->no_space_error = 1;
|
|
break;
|
|
}
|
|
pcv("failed to write %zu bytes to offset %llu of file %s",
|
|
block, (unsigned long long)(offset + actual),
|
|
get_file_name(file));
|
|
return -1;
|
|
}
|
|
remains -= written;
|
|
actual += written;
|
|
}
|
|
return actual;
|
|
}
|
|
|
|
static void file_check_data(struct file_info *file, int fd,
|
|
struct write_info *w);
|
|
|
|
/*
|
|
* Save the information about a file write operation and verify the write if
|
|
* necessary.
|
|
*/
|
|
static void file_write_info(struct file_info *file, int fd, off_t offset,
|
|
size_t size, unsigned int seed)
|
|
{
|
|
struct write_info *new_write, *w, **prev, *tmp;
|
|
int inserted;
|
|
off_t end, chg;
|
|
|
|
/* Create struct write_info */
|
|
new_write = zalloc(sizeof(struct write_info));
|
|
new_write->offset = offset;
|
|
new_write->size = size;
|
|
new_write->random_seed = seed;
|
|
|
|
w = zalloc(sizeof(struct write_info));
|
|
w->next = file->raw_writes;
|
|
w->offset = offset;
|
|
w->size = size;
|
|
w->random_seed = seed;
|
|
file->raw_writes = w;
|
|
|
|
/* Insert it into file->writes */
|
|
inserted = 0;
|
|
end = offset + size;
|
|
w = file->writes;
|
|
prev = &file->writes;
|
|
while (w) {
|
|
if (w->offset >= end) {
|
|
/* w comes after new_write, so insert before it */
|
|
new_write->next = w;
|
|
*prev = new_write;
|
|
inserted = 1;
|
|
break;
|
|
}
|
|
/* w does not come after new_write */
|
|
if (w->offset + w->size > offset) {
|
|
/* w overlaps new_write */
|
|
if (w->offset < offset) {
|
|
/* w begins before new_write begins */
|
|
if (w->offset + w->size <= end)
|
|
/* w ends before new_write ends */
|
|
w->size = offset - w->offset;
|
|
else {
|
|
/* w ends after new_write ends */
|
|
/* Split w */
|
|
tmp = malloc(sizeof(struct write_info));
|
|
CHECK(tmp != NULL);
|
|
*tmp = *w;
|
|
chg = end - tmp->offset;
|
|
tmp->offset += chg;
|
|
tmp->random_offset += chg;
|
|
tmp->size -= chg;
|
|
w->size = offset - w->offset;
|
|
/* Insert new struct write_info */
|
|
w->next = new_write;
|
|
new_write->next = tmp;
|
|
inserted = 1;
|
|
break;
|
|
}
|
|
} else {
|
|
/* w begins after new_write begins */
|
|
if (w->offset + w->size <= end) {
|
|
/* w is completely overlapped,
|
|
so remove it */
|
|
*prev = w->next;
|
|
tmp = w;
|
|
w = w->next;
|
|
free(tmp);
|
|
continue;
|
|
}
|
|
/* w ends after new_write ends */
|
|
chg = end - w->offset;
|
|
w->offset += chg;
|
|
w->random_offset += chg;
|
|
w->size -= chg;
|
|
continue;
|
|
}
|
|
}
|
|
prev = &w->next;
|
|
w = w->next;
|
|
}
|
|
if (!inserted)
|
|
*prev = new_write;
|
|
/* Update file length */
|
|
if (end > file->length)
|
|
file->length = end;
|
|
|
|
if (args.verify_ops && !args.power_cut_mode)
|
|
file_check_data(file, fd, new_write);
|
|
}
|
|
|
|
/* Randomly select offset and and size to write in a file */
|
|
static void get_offset_and_size(struct file_info *file,
|
|
off_t *offset,
|
|
size_t *size)
|
|
{
|
|
unsigned int r, n;
|
|
|
|
r = random_no(100);
|
|
if (r == 0 && grow)
|
|
/* 1 time in 100, when growing, write off the end of the file */
|
|
*offset = file->length + random_no(10000000);
|
|
else if (r < 4)
|
|
/* 3 (or 4) times in 100, write at the beginning of file */
|
|
*offset = 0;
|
|
else if (r < 52 || !grow)
|
|
/* 48 times in 100, write into the file */
|
|
*offset = random_no(file->length);
|
|
else
|
|
/* 48 times in 100, write at the end of the file */
|
|
*offset = file->length;
|
|
/* Distribute the size logarithmically */
|
|
if (random_no(1000) == 0)
|
|
r = random_no(fsinfo.log10_initial_free + 2);
|
|
else
|
|
r = random_no(fsinfo.log10_initial_free);
|
|
n = 1;
|
|
while (r--)
|
|
n *= 10;
|
|
*size = random_no(n);
|
|
if (!grow && *offset + *size > file->length)
|
|
*size = file->length - *offset;
|
|
if (*size == 0)
|
|
*size = 1;
|
|
}
|
|
|
|
static void file_check_hole(struct file_info *file, int fd, off_t offset,
|
|
size_t size);
|
|
|
|
static void file_truncate_info(struct file_info *file, int fd,
|
|
size_t new_length)
|
|
{
|
|
struct write_info *w, **prev, *tmp;
|
|
|
|
/* Remove / truncate file->writes */
|
|
w = file->writes;
|
|
prev = &file->writes;
|
|
while (w) {
|
|
if (w->offset >= new_length) {
|
|
/* w comes after eof, so remove it */
|
|
*prev = w->next;
|
|
tmp = w;
|
|
w = w->next;
|
|
free(tmp);
|
|
continue;
|
|
}
|
|
if (w->offset + w->size > new_length)
|
|
w->size = new_length - w->offset;
|
|
prev = &w->next;
|
|
w = w->next;
|
|
}
|
|
/* Add an entry in raw_writes for the truncation */
|
|
w = zalloc(sizeof(struct write_info));
|
|
w->next = file->raw_writes;
|
|
w->offset = file->length;
|
|
w->new_length = new_length;
|
|
w->random_seed = MAX_RANDOM_SEED + 1;
|
|
file->raw_writes = w;
|
|
|
|
if (args.verify_ops && !args.power_cut_mode &&
|
|
new_length > file->length)
|
|
file_check_hole(file, fd, file->length,
|
|
new_length - file->length);
|
|
|
|
/* Update file length */
|
|
file->length = new_length;
|
|
}
|
|
|
|
/*
|
|
* Truncate a file to length 'new_length'. If there is no enough space to
|
|
* peform the operation, this function returns 1. Returns 0 on success and -1
|
|
* on failure.
|
|
*/
|
|
static int file_ftruncate(struct file_info *file, int fd, off_t new_length)
|
|
{
|
|
if (ftruncate(fd, new_length) != 0) {
|
|
if (errno == ENOSPC) {
|
|
file->no_space_error = 1;
|
|
/* Delete errored files */
|
|
if (!fsinfo.nospc_size_ok)
|
|
if (file_delete(file))
|
|
return -1;
|
|
return 1;
|
|
} else
|
|
pcv("cannot truncate file %s to %llu",
|
|
get_file_name(file),
|
|
(unsigned long long)new_length);
|
|
return -1;
|
|
}
|
|
|
|
if (args.verify_ops)
|
|
CHECK(lseek(fd, 0, SEEK_END) == new_length);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 'mmap()' a file and randomly select where to write data.
|
|
*/
|
|
static int file_mmap_write(struct file_info *file)
|
|
{
|
|
size_t write_cnt = 0, r, i, len, size;
|
|
struct write_info *w = file->writes;
|
|
void *addr;
|
|
char *waddr, *path;
|
|
off_t offs, offset;
|
|
unsigned int seed, seed_tmp;
|
|
uint64_t free_space;
|
|
int fd;
|
|
|
|
assert(!args.power_cut_mode);
|
|
|
|
if (!file->links)
|
|
return 0;
|
|
get_fs_space(NULL, &free_space);
|
|
if (!free_space)
|
|
return 0;
|
|
|
|
/* Randomly pick a written area of the file */
|
|
if (!w)
|
|
return 0;
|
|
while (w) {
|
|
write_cnt += 1;
|
|
w = w->next;
|
|
}
|
|
r = random_no(write_cnt);
|
|
w = file->writes;
|
|
for (i = 0; w && w->next && i < r; i++)
|
|
w = w->next;
|
|
|
|
offs = (w->offset / fsinfo.page_size) * fsinfo.page_size;
|
|
len = w->size + (w->offset - offs);
|
|
if (len > 1 << 24)
|
|
len = 1 << 24;
|
|
|
|
/* Open it */
|
|
assert(file->links);
|
|
path = dir_path(file->links->parent, file->links->name);
|
|
fd = open(path, O_RDWR);
|
|
if (fd == -1) {
|
|
pcv("cannot open file %s to do mmap", path);
|
|
goto out_error;
|
|
}
|
|
|
|
/* mmap it */
|
|
addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offs);
|
|
if (addr == MAP_FAILED) {
|
|
pcv("cannot mmap file %s", path);
|
|
goto out_error;
|
|
}
|
|
|
|
/* Randomly select a part of the mmapped area to write */
|
|
size = random_no(w->size);
|
|
if (size > free_space)
|
|
size = free_space;
|
|
if (size == 0)
|
|
size = 1;
|
|
offset = w->offset + random_no(w->size - size);
|
|
|
|
/* Write it */
|
|
seed_tmp = seed = random_no(MAX_RANDOM_SEED);
|
|
waddr = addr + (offset - offs);
|
|
for (i = 0; i < size; i++)
|
|
waddr[i] = rand_r(&seed_tmp);
|
|
|
|
/* Unmap it */
|
|
if (munmap(addr, len)) {
|
|
pcv("cannot unmap file %s", path);
|
|
goto out_error;
|
|
}
|
|
|
|
/* Record what was written */
|
|
file_write_info(file, fd, offset, size, seed);
|
|
free(path);
|
|
CHECK(close(fd) == 0);
|
|
return 0;
|
|
|
|
out_error:
|
|
free(path);
|
|
CHECK(close(fd) == 0);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Write random amount of data to a random offset in an open file or randomly
|
|
* choose to truncate it.
|
|
*/
|
|
static int file_write(struct file_info *file, int fd)
|
|
{
|
|
int ret;
|
|
|
|
file->clean = 0;
|
|
|
|
if (!args.power_cut_mode && fsinfo.can_mmap && !full &&
|
|
file->link_count && random_no(100) == 1) {
|
|
/*
|
|
* Do not do 'mmap()' operations if:
|
|
* 1. we are in power cut testing mode, because an emulated
|
|
* power cut failure may cause SIGBUS when we are writing to
|
|
* the 'mmap()'ed area, and SIGBUS is not easy to ignore.
|
|
* 2. When the file-system is full, because again, writing to the
|
|
* 'mmap()'ed area may cause SIGBUS when the space allocation
|
|
* fails. This is not enough to guarantee we never get
|
|
* SIGBUS, though. For example, if we write a lot to a hole,
|
|
* this might require a lot of additional space, and we may
|
|
* fail here. But I do not know why we never observed this,
|
|
* probably this is just very unlikely.
|
|
*/
|
|
ret = file_mmap_write(file);
|
|
} else {
|
|
int truncate = 0;
|
|
off_t offset;
|
|
size_t size;
|
|
ssize_t actual;
|
|
unsigned int seed;
|
|
|
|
get_offset_and_size(file, &offset, &size);
|
|
seed = random_no(MAX_RANDOM_SEED);
|
|
actual = file_write_data(file, fd, offset, size, seed);
|
|
if (actual < 0)
|
|
return -1;
|
|
|
|
if (actual != 0)
|
|
file_write_info(file, fd, offset, actual, seed);
|
|
|
|
if (offset + actual <= file->length && shrink) {
|
|
/*
|
|
* 1 time in 100, when shrinking truncate after the
|
|
* write.
|
|
*/
|
|
if (random_no(100) == 0)
|
|
truncate = 1;
|
|
}
|
|
|
|
if (truncate) {
|
|
size_t new_length = offset + actual;
|
|
|
|
ret = file_ftruncate(file, fd, new_length);
|
|
if (ret == -1)
|
|
return -1;
|
|
if (!ret)
|
|
file_truncate_info(file, fd, new_length);
|
|
}
|
|
|
|
/* Delete errored files */
|
|
if (!fsinfo.nospc_size_ok && file->no_space_error)
|
|
return file_delete(file);
|
|
}
|
|
|
|
/* Sync sometimes */
|
|
if (random_no(100) >= 99) {
|
|
if (random_no(100) >= 50) {
|
|
v("fsyncing file %s", get_file_name(file));
|
|
ret = fsync(fd);
|
|
if (ret)
|
|
pcv("fsync failed for %s", get_file_name(file));
|
|
} else {
|
|
v("fdatasyncing file %s", get_file_name(file));
|
|
ret = fdatasync(fd);
|
|
if (ret)
|
|
pcv("fdatasync failed for %s",
|
|
get_file_name(file));
|
|
}
|
|
if (ret)
|
|
return -1;
|
|
file->clean = 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Write random amount of data to a random offset in a file or randomly
|
|
* choose to truncate it.
|
|
*/
|
|
static int file_write_file(struct file_info *file)
|
|
{
|
|
int fd, ret;
|
|
char *path;
|
|
|
|
assert(file->links);
|
|
path = dir_path(file->links->parent, file->links->name);
|
|
fd = open(path, O_RDWR);
|
|
if (fd == -1) {
|
|
pcv("cannot open file %s for writing", path);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
ret = file_write(file, fd);
|
|
CHECK(close(fd) == 0);
|
|
free(path);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Truncate an open file randomly.
|
|
*/
|
|
static int file_truncate(struct file_info *file, int fd)
|
|
{
|
|
int ret;
|
|
size_t new_length = random_no(file->length);
|
|
|
|
file->clean = 0;
|
|
ret = file_ftruncate(file, fd, new_length);
|
|
if (ret == -1)
|
|
return -1;
|
|
if (!ret)
|
|
file_truncate_info(file, fd, new_length);
|
|
return 0;
|
|
}
|
|
|
|
static int file_truncate_file(struct file_info *file)
|
|
{
|
|
int fd;
|
|
char *path;
|
|
int ret;
|
|
|
|
assert(file->links);
|
|
path = dir_path(file->links->parent, file->links->name);
|
|
fd = open(path, O_WRONLY);
|
|
if (fd == -1) {
|
|
pcv("cannot open file %s to truncate", path);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
free(path);
|
|
ret = file_truncate(file, fd);
|
|
CHECK(close(fd) == 0);
|
|
return ret;
|
|
}
|
|
|
|
static void file_close(struct fd_info *fdi)
|
|
{
|
|
struct file_info *file;
|
|
struct fd_info *fdp;
|
|
struct fd_info **prev;
|
|
|
|
/* Close file */
|
|
CHECK(close(fdi->fd) == 0);
|
|
/* Remove struct fd_info */
|
|
open_file_remove(fdi);
|
|
file = fdi->file;
|
|
prev = &file->fds;
|
|
for (fdp = file->fds; fdp; fdp = fdp->next) {
|
|
if (fdp == fdi) {
|
|
*prev = fdi->next;
|
|
free(fdi);
|
|
if (!file->link_count && !file->fds) {
|
|
free_writes_info(file);
|
|
free(file);
|
|
}
|
|
return;
|
|
}
|
|
prev = &fdp->next;
|
|
}
|
|
CHECK(0); /* Didn't find struct fd_info */
|
|
}
|
|
|
|
static void file_rewrite_data(int fd, struct write_info *w, char *buf)
|
|
{
|
|
size_t remains, block;
|
|
ssize_t written;
|
|
off_t r;
|
|
unsigned int seed = w->random_seed;
|
|
|
|
for (r = 0; r < w->random_offset; ++r)
|
|
rand_r(&seed);
|
|
CHECK(lseek(fd, w->offset, SEEK_SET) != (off_t)-1);
|
|
remains = w->size;
|
|
written = IO_BUFFER_SIZE;
|
|
while (remains) {
|
|
/* Fill up buffer with random data */
|
|
if (written < IO_BUFFER_SIZE)
|
|
memmove(buf, buf + written, IO_BUFFER_SIZE - written);
|
|
else
|
|
written = 0;
|
|
for (; written < IO_BUFFER_SIZE; ++written)
|
|
buf[written] = rand_r(&seed);
|
|
/* Write a block of data */
|
|
if (remains > IO_BUFFER_SIZE)
|
|
block = IO_BUFFER_SIZE;
|
|
else
|
|
block = remains;
|
|
written = write(fd, buf, block);
|
|
CHECK(written == block);
|
|
remains -= written;
|
|
}
|
|
}
|
|
|
|
static void save_file(int fd, struct file_info *file)
|
|
{
|
|
int w_fd;
|
|
struct write_info *w;
|
|
char buf[IO_BUFFER_SIZE];
|
|
char name[FILENAME_MAX];
|
|
const char * read_suffix = ".integ.sav.read";
|
|
const char * write_suffix = ".integ.sav.written";
|
|
size_t fname_len = strlen(get_file_name(file));
|
|
|
|
/* Open file to save contents to */
|
|
strcpy(name, "/tmp/");
|
|
if (fname_len + strlen(read_suffix) > fsinfo.max_name_len)
|
|
fname_len = fsinfo.max_name_len - strlen(read_suffix);
|
|
strncat(name, get_file_name(file), fname_len);
|
|
strcat(name, read_suffix);
|
|
normsg("Saving %sn", name);
|
|
w_fd = open(name, O_CREAT | O_WRONLY, 0777);
|
|
CHECK(w_fd != -1);
|
|
|
|
/* Start at the beginning */
|
|
CHECK(lseek(fd, 0, SEEK_SET) != (off_t)-1);
|
|
|
|
for (;;) {
|
|
ssize_t r = read(fd, buf, IO_BUFFER_SIZE);
|
|
CHECK(r != -1);
|
|
if (!r)
|
|
break;
|
|
CHECK(write(w_fd, buf, r) == r);
|
|
}
|
|
CHECK(close(w_fd) == 0);
|
|
|
|
/* Open file to save contents to */
|
|
strcpy(name, "/tmp/");
|
|
if (fname_len + strlen(write_suffix) > fsinfo.max_name_len)
|
|
fname_len = fsinfo.max_name_len - strlen(write_suffix);
|
|
strncat(name, get_file_name(file), fname_len);
|
|
strcat(name, write_suffix);
|
|
normsg("Saving %s", name);
|
|
w_fd = open(name, O_CREAT | O_WRONLY, 0777);
|
|
CHECK(w_fd != -1);
|
|
|
|
for (w = file->writes; w; w = w->next)
|
|
file_rewrite_data(w_fd, w, buf);
|
|
|
|
CHECK(close(w_fd) == 0);
|
|
}
|
|
|
|
static void file_check_hole(struct file_info *file, int fd, off_t offset,
|
|
size_t size)
|
|
{
|
|
size_t remains, block, i;
|
|
char buf[IO_BUFFER_SIZE];
|
|
|
|
CHECK(lseek(fd, offset, SEEK_SET) != (off_t)-1);
|
|
remains = size;
|
|
while (remains) {
|
|
if (remains > IO_BUFFER_SIZE)
|
|
block = IO_BUFFER_SIZE;
|
|
else
|
|
block = remains;
|
|
CHECK(read(fd, buf, block) == block);
|
|
for (i = 0; i < block; ++i) {
|
|
if (buf[i] != 0) {
|
|
errmsg("file_check_hole failed at %zu checking "
|
|
"hole at %llu size %zu", size - remains + i,
|
|
(unsigned long long)offset, size);
|
|
file_info_display(file);
|
|
save_file(fd, file);
|
|
}
|
|
CHECK(buf[i] == 0);
|
|
}
|
|
remains -= block;
|
|
}
|
|
}
|
|
|
|
static void file_check_data(struct file_info *file, int fd,
|
|
struct write_info *w)
|
|
{
|
|
size_t remains, block, i;
|
|
off_t r;
|
|
unsigned char read_buf[IO_BUFFER_SIZE];
|
|
unsigned char check_buf[IO_BUFFER_SIZE];
|
|
unsigned int seed = w->random_seed;
|
|
|
|
if (args.power_cut_mode && !file->clean)
|
|
return;
|
|
|
|
for (r = 0; r < w->random_offset; ++r)
|
|
rand_r(&seed);
|
|
CHECK(lseek(fd, w->offset, SEEK_SET) != (off_t)-1);
|
|
remains = w->size;
|
|
while (remains) {
|
|
if (remains > IO_BUFFER_SIZE)
|
|
block = IO_BUFFER_SIZE;
|
|
else
|
|
block = remains;
|
|
CHECK(read(fd, read_buf, block) == block);
|
|
for (i = 0; i < block; ++i)
|
|
check_buf[i] = (char)rand_r(&seed);
|
|
|
|
if (memcmp(check_buf, read_buf, block) != 0) {
|
|
errmsg("file_check_data failed, dumping "
|
|
"data at offset %llu size %zu",
|
|
(unsigned long long)w->offset, w->size);
|
|
|
|
fprintf (stderr, "Read data:\n");
|
|
for (r = 0; r < block; ++r)
|
|
fprintf(stderr, "%02x%c",
|
|
read_buf[r], ((r+1)%16)?' ':'\n');
|
|
fprintf(stderr, "\nExpected data:\n");
|
|
for (r = 0; r < block; ++r)
|
|
fprintf(stderr, "%02x%c",
|
|
check_buf[r], ((r+1)%16)?' ':'\n');
|
|
fprintf(stderr, " \n");
|
|
|
|
file_info_display(file);
|
|
save_file(fd, file);
|
|
CHECK(0);
|
|
}
|
|
remains -= block;
|
|
}
|
|
}
|
|
|
|
static void file_check(struct file_info *file, int fd)
|
|
{
|
|
int open_and_close = 0, link_count = 0;
|
|
char *path = NULL;
|
|
off_t pos;
|
|
struct write_info *w;
|
|
struct dir_entry_info *entry;
|
|
struct stat st;
|
|
|
|
/*
|
|
* In case of power cut emulation testing check only clean files, i.e.
|
|
* the files which have not been modified since last 'fsync()'.
|
|
*/
|
|
if (args.power_cut_mode && !file->clean)
|
|
return;
|
|
|
|
/* Do not check files that have errored */
|
|
if (!fsinfo.nospc_size_ok && file->no_space_error)
|
|
return;
|
|
/* Do not check the same file twice */
|
|
if (file->check_run_no == check_run_no)
|
|
return;
|
|
file->check_run_no = check_run_no;
|
|
if (fd == -1) {
|
|
/* Open file */
|
|
open_and_close = 1;
|
|
assert(file->links);
|
|
path = dir_path(file->links->parent, get_file_name(file));
|
|
v("checking file %s", path);
|
|
fd = open(path, O_RDONLY);
|
|
if (fd == -1) {
|
|
sys_errmsg("cannot open file %s", path);
|
|
CHECK(0);
|
|
}
|
|
} else
|
|
v("checking file %s", get_file_name(file));
|
|
|
|
/* Check length */
|
|
pos = lseek(fd, 0, SEEK_END);
|
|
if (pos != file->length) {
|
|
errmsg("file_check failed checking length expected %llu actual %llu\n",
|
|
(unsigned long long)file->length, (unsigned long long)pos);
|
|
file_info_display(file);
|
|
save_file(fd, file);
|
|
}
|
|
CHECK(pos == file->length);
|
|
/* Check each write */
|
|
pos = 0;
|
|
for (w = file->writes; w; w = w->next) {
|
|
if (w->offset > pos)
|
|
file_check_hole(file, fd, pos, w->offset - pos);
|
|
file_check_data(file, fd, w);
|
|
pos = w->offset + w->size;
|
|
}
|
|
if (file->length > pos)
|
|
file_check_hole(file, fd, pos, file->length - pos);
|
|
CHECK(fstat(fd, &st) == 0);
|
|
CHECK(file->link_count == st.st_nlink);
|
|
if (open_and_close) {
|
|
CHECK(close(fd) == 0);
|
|
free(path);
|
|
}
|
|
entry = file->links;
|
|
while (entry) {
|
|
link_count += 1;
|
|
entry = entry->next_link;
|
|
}
|
|
CHECK(link_count == file->link_count);
|
|
}
|
|
|
|
static char *symlink_path(const char *path, const char *target_pathname)
|
|
{
|
|
char *p;
|
|
size_t len, totlen, tarlen;
|
|
|
|
if (target_pathname[0] == '/')
|
|
return dup_string(target_pathname);
|
|
p = strrchr(path, '/');
|
|
len = p - path;
|
|
len += 1;
|
|
tarlen = strlen(target_pathname);
|
|
totlen = len + tarlen + 1;
|
|
p = malloc(totlen);
|
|
CHECK(p != NULL);
|
|
strncpy(p, path, len);
|
|
p[len] = '\0';
|
|
strcat(p, target_pathname);
|
|
return p;
|
|
}
|
|
|
|
void symlink_check(const struct symlink_info *symlink)
|
|
{
|
|
char *path, buf[8192], *target;
|
|
struct stat st1, st2;
|
|
ssize_t len;
|
|
int ret1, ret2;
|
|
|
|
if (args.power_cut_mode)
|
|
return;
|
|
|
|
path = dir_path(symlink->entry->parent, symlink->entry->name);
|
|
|
|
v("checking symlink %s", path);
|
|
CHECK(lstat(path, &st1) == 0);
|
|
CHECK(S_ISLNK(st1.st_mode));
|
|
CHECK(st1.st_nlink == 1);
|
|
|
|
len = readlink(path, buf, 8192);
|
|
|
|
CHECK(len > 0 && len < 8192);
|
|
buf[len] = '\0';
|
|
CHECK(strlen(symlink->target_pathname) == len);
|
|
CHECK(strncmp(symlink->target_pathname, buf, len) == 0);
|
|
|
|
/* Check symlink points where it should */
|
|
ret1 = stat(path, &st1);
|
|
target = symlink_path(path, symlink->target_pathname);
|
|
ret2 = stat(target, &st2);
|
|
CHECK(ret1 == ret2);
|
|
if (ret1 == 0) {
|
|
CHECK(st1.st_dev == st2.st_dev);
|
|
CHECK(st1.st_ino == st2.st_ino);
|
|
}
|
|
free(target);
|
|
free(path);
|
|
}
|
|
|
|
static int search_comp(const void *pa, const void *pb)
|
|
{
|
|
const struct dirent *a = (const struct dirent *) pa;
|
|
const struct dir_entry_info *b = * (const struct dir_entry_info **) pb;
|
|
return strcmp(a->d_name, b->name);
|
|
}
|
|
|
|
static void dir_entry_check(struct dir_entry_info **entry_array,
|
|
size_t number_of_entries,
|
|
struct dirent *ent)
|
|
{
|
|
struct dir_entry_info **found;
|
|
struct dir_entry_info *entry;
|
|
size_t sz;
|
|
|
|
sz = sizeof(struct dir_entry_info *);
|
|
found = bsearch(ent, entry_array, number_of_entries, sz, search_comp);
|
|
CHECK(found != NULL);
|
|
entry = *found;
|
|
CHECK(!entry->checked);
|
|
entry->checked = 1;
|
|
}
|
|
|
|
static int sort_comp(const void *pa, const void *pb)
|
|
{
|
|
const struct dir_entry_info *a = * (const struct dir_entry_info **) pa;
|
|
const struct dir_entry_info *b = * (const struct dir_entry_info **) pb;
|
|
return strcmp(a->name, b->name);
|
|
}
|
|
|
|
static void dir_check(struct dir_info *dir)
|
|
{
|
|
struct dir_entry_info *entry, **entry_array, **p;
|
|
size_t sz, n;
|
|
DIR *d;
|
|
struct dirent *ent;
|
|
unsigned int checked = 0;
|
|
char *path;
|
|
int link_count = 2; /* Parent and dot */
|
|
struct stat st;
|
|
|
|
path = dir_path(dir->parent, dir->entry->name);
|
|
|
|
if (!args.power_cut_mode || dir->clean) {
|
|
v("checking dir %s", path);
|
|
|
|
/* Create an array of entries */
|
|
sz = sizeof(struct dir_entry_info *);
|
|
n = dir->number_of_entries;
|
|
entry_array = malloc(sz * n);
|
|
CHECK(entry_array != NULL);
|
|
|
|
entry = dir->first;
|
|
p = entry_array;
|
|
while (entry) {
|
|
*p++ = entry;
|
|
entry->checked = 0;
|
|
entry = entry->next;
|
|
}
|
|
|
|
/* Sort it by name */
|
|
qsort(entry_array, n, sz, sort_comp);
|
|
|
|
/* Go through directory on file system checking entries match */
|
|
d = opendir(path);
|
|
if (!d) {
|
|
sys_errmsg("cannot open directory %s", path);
|
|
CHECK(0);
|
|
}
|
|
|
|
for (;;) {
|
|
errno = 0;
|
|
ent = readdir(d);
|
|
if (ent) {
|
|
if (strcmp(".",ent->d_name) != 0 &&
|
|
strcmp("..",ent->d_name) != 0) {
|
|
dir_entry_check(entry_array, n, ent);
|
|
checked += 1;
|
|
}
|
|
} else {
|
|
CHECK(errno == 0);
|
|
break;
|
|
}
|
|
}
|
|
free(entry_array);
|
|
CHECK(closedir(d) == 0);
|
|
|
|
CHECK(checked == dir->number_of_entries);
|
|
}
|
|
|
|
/* Now check each entry */
|
|
entry = dir->first;
|
|
while (entry) {
|
|
if (entry->type == 'd') {
|
|
dir_check(entry->dir);
|
|
link_count += 1; /* <subdir>/.. */
|
|
} else if (entry->type == 'f')
|
|
file_check(entry->file, -1);
|
|
else if (entry->type == 's')
|
|
symlink_check(entry->symlink);
|
|
else
|
|
CHECK(0);
|
|
entry = entry->next;
|
|
}
|
|
|
|
if (!args.power_cut_mode || dir->clean) {
|
|
CHECK(stat(path, &st) == 0);
|
|
if (link_count != st.st_nlink) {
|
|
errmsg("calculated link count %d, FS reports %d for dir %s",
|
|
link_count, (int)st.st_nlink, path);
|
|
CHECK(0);
|
|
}
|
|
}
|
|
|
|
free(path);
|
|
}
|
|
|
|
static void check_deleted_files(void)
|
|
{
|
|
struct open_file_info *ofi;
|
|
|
|
for (ofi = open_files; ofi; ofi = ofi->next)
|
|
if (!ofi->fdi->file->link_count)
|
|
file_check(ofi->fdi->file, ofi->fdi->fd);
|
|
}
|
|
|
|
static void close_open_files(void)
|
|
{
|
|
struct open_file_info *ofi;
|
|
|
|
for (ofi = open_files; ofi; ofi = open_files)
|
|
file_close(ofi->fdi);
|
|
}
|
|
|
|
static char *make_name(struct dir_info *dir)
|
|
{
|
|
struct dir_entry_info *entry;
|
|
int found;
|
|
|
|
do {
|
|
found = 0;
|
|
if (random_no(5) == 1) {
|
|
int i, n = random_no(fsinfo.max_name_len) + 1;
|
|
|
|
for (i = 0; i < n; i++)
|
|
random_name_buf[i] = 'a' + random_no(26);
|
|
random_name_buf[i] = '\0';
|
|
} else
|
|
sprintf(random_name_buf, "%u", random_no(1000000));
|
|
for (entry = dir->first; entry; entry = entry->next) {
|
|
if (strcmp(entry->name, random_name_buf) == 0) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
} while (found);
|
|
|
|
return random_name_buf;
|
|
}
|
|
|
|
static struct file_info *pick_file(void)
|
|
{
|
|
struct dir_info *dir = top_dir;
|
|
|
|
for (;;) {
|
|
struct dir_entry_info *entry;
|
|
unsigned int r;
|
|
|
|
r = random_no(dir->number_of_entries);
|
|
entry = dir->first;
|
|
while (entry && r) {
|
|
entry = entry->next;
|
|
--r;
|
|
}
|
|
for (;;) {
|
|
if (!entry)
|
|
return NULL;
|
|
if (entry->type == 'f')
|
|
return entry->file;
|
|
if (entry->type == 'd')
|
|
if (entry->dir->number_of_entries != 0)
|
|
break;
|
|
entry = entry->next;
|
|
}
|
|
dir = entry->dir;
|
|
}
|
|
}
|
|
|
|
static struct dir_info *pick_dir(void)
|
|
{
|
|
struct dir_info *dir = top_dir;
|
|
|
|
if (random_no(40) >= 30)
|
|
return dir;
|
|
for (;;) {
|
|
struct dir_entry_info *entry;
|
|
size_t r;
|
|
|
|
r = random_no(dir->number_of_entries);
|
|
entry = dir->first;
|
|
while (entry && r) {
|
|
entry = entry->next;
|
|
--r;
|
|
}
|
|
for (;;) {
|
|
if (!entry)
|
|
break;
|
|
if (entry->type == 'd')
|
|
break;
|
|
entry = entry->next;
|
|
}
|
|
if (!entry) {
|
|
entry = dir->first;
|
|
for (;;) {
|
|
if (!entry)
|
|
break;
|
|
if (entry->type == 'd')
|
|
break;
|
|
entry = entry->next;
|
|
}
|
|
}
|
|
if (!entry)
|
|
return dir;
|
|
dir = entry->dir;
|
|
if (random_no(40) >= 30)
|
|
return dir;
|
|
}
|
|
}
|
|
|
|
static char *pick_rename_name(struct dir_info **parent,
|
|
struct dir_entry_info **rename_entry, int isdir)
|
|
{
|
|
struct dir_info *dir = pick_dir();
|
|
struct dir_entry_info *entry;
|
|
unsigned int r;
|
|
|
|
*parent = dir;
|
|
*rename_entry = NULL;
|
|
|
|
if (grow || random_no(20) < 10)
|
|
return dup_string(make_name(dir));
|
|
|
|
r = random_no(dir->number_of_entries);
|
|
entry = dir->first;
|
|
while (entry && r) {
|
|
entry = entry->next;
|
|
--r;
|
|
}
|
|
if (!entry)
|
|
entry = dir->first;
|
|
if (!entry ||
|
|
(entry->type == 'd' && entry->dir->number_of_entries != 0))
|
|
return dup_string(make_name(dir));
|
|
|
|
if ((isdir && entry->type != 'd') ||
|
|
(!isdir && entry->type == 'd'))
|
|
return dup_string(make_name(dir));
|
|
|
|
*rename_entry = entry;
|
|
return dup_string(entry->name);
|
|
}
|
|
|
|
static int rename_entry(struct dir_entry_info *entry)
|
|
{
|
|
struct dir_entry_info *rename_entry = NULL;
|
|
struct dir_info *parent;
|
|
char *path, *to, *name;
|
|
int ret, isdir, retry;
|
|
struct stat st1, st2;
|
|
|
|
if (!entry->parent)
|
|
return 0;
|
|
|
|
for (retry = 0; retry < 3; retry++) {
|
|
path = dir_path(entry->parent, entry->name);
|
|
isdir = entry->type == 'd' ? 1 : 0;
|
|
name = pick_rename_name(&parent, &rename_entry, isdir);
|
|
to = dir_path(parent, name);
|
|
/*
|
|
* Check we are not trying to move a directory to a subdirectory
|
|
* of itself.
|
|
*/
|
|
if (isdir) {
|
|
struct dir_info *p;
|
|
|
|
for (p = parent; p; p = p->parent)
|
|
if (p == entry->dir)
|
|
break;
|
|
if (p == entry->dir) {
|
|
free(path);
|
|
free(name);
|
|
free(to);
|
|
path = NULL;
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!path)
|
|
return 0;
|
|
|
|
if (args.verify_ops)
|
|
CHECK(lstat(path, &st1) == 0);
|
|
|
|
if (rename_entry)
|
|
v("moving %s (type %c) inoto %s (type %c)",
|
|
path, entry->type, to, rename_entry->type);
|
|
else
|
|
v("renaming %s (type %c) to %s", path, entry->type, to);
|
|
|
|
ret = rename(path, to);
|
|
if (ret != 0) {
|
|
ret = 0;
|
|
if (errno == ENOSPC)
|
|
full = 1;
|
|
else if (errno != EBUSY) {
|
|
pcv("failed to rename %s to %s", path, to);
|
|
ret = -1;
|
|
}
|
|
free(path);
|
|
free(name);
|
|
free(to);
|
|
return ret;
|
|
}
|
|
|
|
if (args.verify_ops) {
|
|
CHECK(lstat(to, &st2) == 0);
|
|
CHECK(st1.st_ino == st2.st_ino);
|
|
}
|
|
|
|
free(path);
|
|
free(to);
|
|
|
|
if (rename_entry && rename_entry->type == entry->type &&
|
|
rename_entry->target == entry->target) {
|
|
free(name);
|
|
return 0;
|
|
}
|
|
|
|
add_dir_entry(parent, entry->type, name, entry->target);
|
|
if (rename_entry)
|
|
remove_dir_entry(rename_entry, 1);
|
|
remove_dir_entry(entry, 0);
|
|
free(name);
|
|
return 0;
|
|
}
|
|
|
|
static size_t str_count(const char *s, char c)
|
|
{
|
|
size_t count = 0;
|
|
char cc;
|
|
|
|
while ((cc = *s++) != '\0')
|
|
if (cc == c)
|
|
count += 1;
|
|
return count;
|
|
}
|
|
|
|
static char *relative_path(const char *path1, const char *path2)
|
|
{
|
|
const char *p1, *p2;
|
|
char *rel;
|
|
size_t up, len, len2, i;
|
|
|
|
p1 = path1;
|
|
p2 = path2;
|
|
while (*p1 == *p2 && *p1) {
|
|
p1 += 1;
|
|
p2 += 1;
|
|
}
|
|
len2 = strlen(p2);
|
|
up = str_count(p1, '/');
|
|
if (up == 0 && len2 != 0)
|
|
return dup_string(p2);
|
|
if (up == 0 && len2 == 0) {
|
|
p2 = strrchr(path2, '/');
|
|
return dup_string(p2);
|
|
}
|
|
if (up == 1 && len2 == 0)
|
|
return dup_string(".");
|
|
if (len2 == 0)
|
|
up -= 1;
|
|
len = up * 3 + len2 + 1;
|
|
rel = malloc(len);
|
|
CHECK(rel != NULL);
|
|
rel[0] = '\0';
|
|
if (up) {
|
|
strcat(rel, "..");
|
|
for (i = 1; i < up; i++)
|
|
strcat(rel, "/..");
|
|
if (len2)
|
|
strcat(rel, "/");
|
|
}
|
|
if (len2)
|
|
strcat(rel, p2);
|
|
return rel;
|
|
}
|
|
|
|
static char *pick_symlink_target(const char *symlink_path)
|
|
{
|
|
struct dir_info *dir;
|
|
struct dir_entry_info *entry;
|
|
char *path, *rel_path;
|
|
unsigned int r;
|
|
|
|
dir = pick_dir();
|
|
|
|
if (random_no(100) < 10)
|
|
return dir_path(dir, make_name(dir));
|
|
|
|
r = random_no(dir->number_of_entries);
|
|
entry = dir->first;
|
|
while (entry && r) {
|
|
entry = entry->next;
|
|
--r;
|
|
}
|
|
if (!entry)
|
|
entry = dir->first;
|
|
if (!entry)
|
|
return dir_path(dir, make_name(dir));
|
|
path = dir_path(dir, entry->name);
|
|
if (random_no(20) < 10)
|
|
return path;
|
|
rel_path = relative_path(symlink_path, path);
|
|
free(path);
|
|
return rel_path;
|
|
}
|
|
|
|
static void verify_symlink(const char *target, const char *path)
|
|
{
|
|
int bytes;
|
|
char buf[PATH_MAX + 1];
|
|
|
|
bytes = readlink(path, buf, PATH_MAX);
|
|
CHECK(bytes >= 0);
|
|
CHECK(bytes < PATH_MAX);
|
|
buf[bytes] = '\0';
|
|
CHECK(!strcmp(buf, target));
|
|
}
|
|
|
|
static int symlink_new(struct dir_info *dir, const char *nm)
|
|
{
|
|
struct symlink_info *s;
|
|
char *path, *target, *name = dup_string(nm);
|
|
|
|
/*
|
|
* Note, we need to duplicate the input 'name' string because of the
|
|
* shared random_name_buf.
|
|
*/
|
|
path = dir_path(dir, name);
|
|
target = pick_symlink_target(path);
|
|
v("creating symlink %s ---> %s", path, target);
|
|
if (symlink(target, path) != 0) {
|
|
int ret = 0;
|
|
|
|
if (errno == ENOSPC)
|
|
full = 1;
|
|
else if (errno != ENAMETOOLONG) {
|
|
pcv("cannot create symlink %s in directory %s to file %s",
|
|
path, dir->entry->name, target);
|
|
ret = -1;
|
|
}
|
|
free(target);
|
|
free(name);
|
|
free(path);
|
|
return ret;
|
|
}
|
|
|
|
if (args.verify_ops)
|
|
verify_symlink(target, path);
|
|
|
|
s = add_dir_entry(dir, 's', name, NULL);
|
|
s->target_pathname = target;
|
|
|
|
free(path);
|
|
free(name);
|
|
return 0;
|
|
}
|
|
|
|
static int symlink_remove(struct symlink_info *symlink)
|
|
{
|
|
char *path;
|
|
|
|
path = dir_path(symlink->entry->parent, symlink->entry->name);
|
|
if (unlink(path) != 0) {
|
|
pcv("cannot unlink symlink %s", path);
|
|
free(path);
|
|
return -1;
|
|
}
|
|
|
|
if (args.verify_ops) {
|
|
struct stat st;
|
|
|
|
CHECK(lstat(path, &st) == -1);
|
|
CHECK(errno == ENOENT);
|
|
}
|
|
|
|
remove_dir_entry(symlink->entry, 1);
|
|
|
|
free(path);
|
|
return 0;
|
|
}
|
|
|
|
static int operate_on_dir(struct dir_info *dir);
|
|
|
|
/* Randomly select something to do with a file */
|
|
static int operate_on_file(struct file_info *file)
|
|
{
|
|
/* Try to keep at least 10 files open */
|
|
if (open_files_count < 10)
|
|
return file_open(file);
|
|
/* Try to keep about 20 files open */
|
|
if (open_files_count < 20 && random_no(2) == 0)
|
|
return file_open(file);
|
|
/* Try to keep up to 40 files open */
|
|
if (open_files_count < 40 && random_no(20) == 0)
|
|
return file_open(file);
|
|
|
|
/* Occasionly truncate */
|
|
if (shrink && random_no(100) == 0)
|
|
return file_truncate_file(file);
|
|
/* Mostly just write */
|
|
if (file_write_file(file) != 0)
|
|
return -1;
|
|
/* Once in a while check it too */
|
|
if (random_no(100) == 1) {
|
|
int fd = -2;
|
|
|
|
if (file->links)
|
|
fd = -1;
|
|
else if (file->fds)
|
|
fd = file->fds->fd;
|
|
if (fd != -2) {
|
|
check_run_no += 1;
|
|
file_check(file, fd);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The operate on entry function is recursive because it calls
|
|
* 'operate_on_dir()' which calls 'operate_on_entry()' again. This variable is
|
|
* used to limit the recursion depth.
|
|
*/
|
|
static int recursion_depth;
|
|
|
|
/* Randomly select something to do with a directory entry */
|
|
static int operate_on_entry(struct dir_entry_info *entry)
|
|
{
|
|
int ret = 0;
|
|
|
|
recursion_depth += 1;
|
|
|
|
/* 1 time in 1000 rename */
|
|
if (random_no(1000) == 0)
|
|
ret = rename_entry(entry);
|
|
else if (entry->type == 's') {
|
|
symlink_check(entry->symlink);
|
|
/* If shrinking, 1 time in 50, remove a symlink */
|
|
if (shrink && random_no(50) == 0)
|
|
ret = symlink_remove(entry->symlink);
|
|
} else if (entry->type == 'd') {
|
|
/* If shrinking, 1 time in 50, remove a directory */
|
|
if (shrink && random_no(50) == 0)
|
|
ret = dir_remove(entry->dir);
|
|
else if (recursion_depth < 20)
|
|
ret = operate_on_dir(entry->dir);
|
|
} else if (entry->type == 'f') {
|
|
/* If shrinking, 1 time in 10, remove a file */
|
|
if (shrink && random_no(10) == 0)
|
|
ret = file_delete(entry->file);
|
|
/* If not growing, 1 time in 10, unlink a file with links > 1 */
|
|
else if (!grow && entry->file->link_count > 1 &&
|
|
random_no(10) == 0)
|
|
ret = file_unlink_file(entry->file);
|
|
else
|
|
ret = operate_on_file(entry->file);
|
|
}
|
|
|
|
recursion_depth -= 1;
|
|
return ret;
|
|
}
|
|
|
|
/* Synchronize a directory */
|
|
static int sync_directory(const char *path)
|
|
{
|
|
int fd, ret;
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd == -1) {
|
|
pcv("cannot open directory %s", path);
|
|
return -1;
|
|
}
|
|
|
|
if (random_no(100) >= 50) {
|
|
v("fsyncing dir %s", path);
|
|
ret = fsync(fd);
|
|
if (ret)
|
|
pcv("directory fsync failed for %s", path);
|
|
} else {
|
|
v("fdatasyncing dir %s", path);
|
|
ret = fdatasync(fd);
|
|
if (ret)
|
|
pcv("directory fdatasync failed for %s", path);
|
|
}
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Randomly select something to do with a directory.
|
|
*/
|
|
static int operate_on_dir(struct dir_info *dir)
|
|
{
|
|
struct dir_entry_info *entry;
|
|
struct file_info *file;
|
|
unsigned int r;
|
|
int ret = 0;
|
|
|
|
r = random_no(14);
|
|
if (r == 0 && grow)
|
|
/* When growing, 1 time in 14 create a file */
|
|
ret = file_new(dir, make_name(dir));
|
|
else if (r == 1 && grow)
|
|
/* When growing, 1 time in 14 create a directory */
|
|
ret = dir_new(dir, make_name(dir));
|
|
else if (r == 2 && grow && (file = pick_file()) != NULL)
|
|
/* When growing, 1 time in 14 create a hard link */
|
|
ret = link_new(dir, make_name(dir), file);
|
|
else if (r == 3 && grow && random_no(5) == 0)
|
|
/* When growing, 1 time in 70 create a symbolic link */
|
|
ret = symlink_new(dir, make_name(dir));
|
|
else {
|
|
/* Otherwise randomly select an entry to operate on */
|
|
r = random_no(dir->number_of_entries);
|
|
entry = dir->first;
|
|
while (entry && r) {
|
|
entry = entry->next;
|
|
--r;
|
|
}
|
|
if (entry)
|
|
ret = operate_on_entry(entry);
|
|
}
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Synchronize the directory sometimes */
|
|
if (random_no(100) >= 99) {
|
|
char *path;
|
|
|
|
path = dir_path(dir->parent, dir->entry->name);
|
|
ret = sync_directory(path);
|
|
free(path);
|
|
if (!ret)
|
|
dir->clean = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Randomly select something to do with an open file.
|
|
*/
|
|
static int operate_on_open_file(struct fd_info *fdi)
|
|
{
|
|
int ret = 0;
|
|
unsigned int r = random_no(1000);
|
|
|
|
if (shrink && r < 5)
|
|
ret = file_truncate(fdi->file, fdi->fd);
|
|
else if (r < 21)
|
|
file_close(fdi);
|
|
else if (shrink && r < 121 && fdi->file->link_count)
|
|
ret = file_delete(fdi->file);
|
|
else
|
|
ret = file_write(fdi->file, fdi->fd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Randomly select an open file and do a random operation on it.
|
|
*/
|
|
static int operate_on_an_open_file(void)
|
|
{
|
|
unsigned int r;
|
|
struct open_file_info *ofi;
|
|
|
|
/* When shrinking, close all open files 1 time in 128 */
|
|
if (shrink) {
|
|
static int x = 0;
|
|
|
|
x += 1;
|
|
x &= 127;
|
|
if (x == 0) {
|
|
close_open_files();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Close any open files that have errored */
|
|
if (!fsinfo.nospc_size_ok) {
|
|
ofi = open_files;
|
|
while (ofi) {
|
|
if (ofi->fdi->file->no_space_error) {
|
|
struct fd_info *fdi;
|
|
|
|
fdi = ofi->fdi;
|
|
ofi = ofi->next;
|
|
file_close(fdi);
|
|
} else
|
|
ofi = ofi->next;
|
|
}
|
|
}
|
|
|
|
r = random_no(open_files_count);
|
|
for (ofi = open_files; ofi; ofi = ofi->next, r--)
|
|
if (!r) {
|
|
return operate_on_open_file(ofi->fdi);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Do a random file-system operation.
|
|
*/
|
|
static int do_an_operation(void)
|
|
{
|
|
/* Half the time operate on already open files */
|
|
if (random_no(100) < 50)
|
|
return operate_on_dir(top_dir);
|
|
else
|
|
return operate_on_an_open_file();
|
|
}
|
|
|
|
/*
|
|
* Fill the tested file-system with random stuff.
|
|
*/
|
|
static int create_test_data(void)
|
|
{
|
|
uint64_t i, n;
|
|
|
|
grow = 1;
|
|
shrink = 0;
|
|
full = 0;
|
|
operation_count = 0;
|
|
while (!full) {
|
|
if (do_an_operation())
|
|
return -1;
|
|
operation_count += 1;
|
|
}
|
|
|
|
/* Drop to less than 90% full */
|
|
grow = 0;
|
|
shrink = 1;
|
|
n = operation_count / 40;
|
|
while (n--) {
|
|
uint64_t free, total;
|
|
|
|
for (i = 0; i < 10; i++)
|
|
if (do_an_operation())
|
|
return -1;
|
|
|
|
get_fs_space(&total, &free);
|
|
if ((free * 100) / total >= 10)
|
|
break;
|
|
}
|
|
|
|
grow = 0;
|
|
shrink = 0;
|
|
full = 0;
|
|
n = operation_count * 2;
|
|
for (i = 0; i < n; i++)
|
|
if (do_an_operation())
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Do more random operation on the tested file-system.
|
|
*/
|
|
static int update_test_data(void)
|
|
{
|
|
uint64_t i, n;
|
|
|
|
grow = 1;
|
|
shrink = 0;
|
|
full = 0;
|
|
while (!full)
|
|
if (do_an_operation())
|
|
return -1;
|
|
|
|
/* Drop to less than 50% full */
|
|
grow = 0;
|
|
shrink = 1;
|
|
n = operation_count / 10;
|
|
while (n--) {
|
|
uint64_t free, total;
|
|
|
|
for (i = 0; i < 10; i++)
|
|
if (do_an_operation())
|
|
return -1;
|
|
|
|
get_fs_space(&total, &free);
|
|
if ((free * 100) / total >= 50)
|
|
break;
|
|
}
|
|
|
|
grow = 0;
|
|
shrink = 0;
|
|
full = 0;
|
|
n = operation_count * 2;
|
|
for (i = 0; i < n; i++)
|
|
if (do_an_operation())
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Recursively remove a directory, just like "rm -rf" shell command.
|
|
*/
|
|
static int rm_minus_rf_dir(const char *dir_name)
|
|
{
|
|
int ret;
|
|
DIR *dir;
|
|
struct dirent *dent;
|
|
char buf[PATH_MAX];
|
|
|
|
v("removing all files");
|
|
dir = opendir(dir_name);
|
|
CHECK(dir != NULL);
|
|
CHECK(getcwd(buf, PATH_MAX) != NULL);
|
|
CHECK(chdir(dir_name) == 0);
|
|
|
|
for (;;) {
|
|
errno = 0;
|
|
dent = readdir(dir);
|
|
if (!dent) {
|
|
CHECK(errno == 0);
|
|
break;
|
|
}
|
|
|
|
if (strcmp(dent->d_name, ".") &&
|
|
strcmp(dent->d_name, "..")) {
|
|
if (dent->d_type == DT_DIR) {
|
|
ret = rm_minus_rf_dir(dent->d_name);
|
|
if (ret) {
|
|
CHECK(closedir(dir) == 0);
|
|
return -1;
|
|
}
|
|
} else {
|
|
ret = unlink(dent->d_name);
|
|
if (ret) {
|
|
pcv("cannot unlink %s", dent->d_name);
|
|
CHECK(closedir(dir) == 0);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CHECK(chdir(buf) == 0);
|
|
CHECK(closedir(dir) == 0);
|
|
|
|
if (args.verify_ops) {
|
|
dir = opendir(dir_name);
|
|
CHECK(dir != NULL);
|
|
do {
|
|
errno = 0;
|
|
dent = readdir(dir);
|
|
if (dent)
|
|
CHECK(!strcmp(dent->d_name, ".") ||
|
|
!strcmp(dent->d_name, ".."));
|
|
} while (dent);
|
|
CHECK(errno == 0);
|
|
CHECK(closedir(dir) == 0);
|
|
}
|
|
|
|
ret = rmdir(dir_name);
|
|
if (ret) {
|
|
pcv("cannot remove directory %s", dir_name);
|
|
return -1;
|
|
}
|
|
|
|
if (args.verify_ops) {
|
|
struct stat st;
|
|
|
|
CHECK(lstat(dir_name, &st) == -1);
|
|
CHECK(errno == ENOENT);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Re-mount the test file-system. This function randomly select how to
|
|
* re-mount.
|
|
*/
|
|
static int remount_tested_fs(void)
|
|
{
|
|
int ret;
|
|
unsigned long flags;
|
|
unsigned int rorw1, um, um_ro, um_rorw, rorw2;
|
|
|
|
CHECK(chdir("/") == 0);
|
|
v("remounting file-system");
|
|
|
|
/* Choose what to do */
|
|
rorw1 = random_no(2);
|
|
um = random_no(2);
|
|
um_ro = random_no(2);
|
|
um_rorw = random_no(2);
|
|
rorw2 = random_no(2);
|
|
|
|
if (rorw1 + um + rorw2 == 0)
|
|
um = 1;
|
|
|
|
if (rorw1) {
|
|
flags = fsinfo.mount_flags | MS_RDONLY | MS_REMOUNT;
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype,
|
|
flags, fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("cannot remount %s R/O (1)",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
|
|
flags = fsinfo.mount_flags | MS_REMOUNT;
|
|
flags &= ~((unsigned long)MS_RDONLY);
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype,
|
|
flags, fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("remounted %s R/O (1), but cannot re-mount it R/W",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (um) {
|
|
if (um_ro) {
|
|
flags = fsinfo.mount_flags | MS_RDONLY | MS_REMOUNT;
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point,
|
|
fsinfo.fstype, flags, fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("cannot remount %s R/O (2)",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ret = umount(fsinfo.mount_point);
|
|
if (ret) {
|
|
pcv("cannot unmount %s", fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
|
|
if (!um_rorw) {
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point,
|
|
fsinfo.fstype, fsinfo.mount_flags,
|
|
fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("unmounted %s, but cannot mount it back R/W",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
} else {
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point,
|
|
fsinfo.fstype, fsinfo.mount_flags | MS_RDONLY,
|
|
fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("unmounted %s, but cannot mount it back R/O",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
|
|
flags = fsinfo.mount_flags | MS_REMOUNT;
|
|
flags &= ~((unsigned long)MS_RDONLY);
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point,
|
|
fsinfo.fstype, flags, fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("unmounted %s, mounted R/O, but cannot re-mount it R/W",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rorw2) {
|
|
flags = fsinfo.mount_flags | MS_RDONLY | MS_REMOUNT;
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype,
|
|
flags, fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("cannot re-mount %s R/O (3)", fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
|
|
flags = fsinfo.mount_flags | MS_REMOUNT;
|
|
flags &= ~((unsigned long)MS_RDONLY);
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype,
|
|
flags, fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("remounted %s R/O (3), but cannot re-mount it back R/W",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
CHECK(chdir(fsinfo.mount_point) == 0);
|
|
return 0;
|
|
}
|
|
|
|
static void check_tested_fs(void)
|
|
{
|
|
v("checking the file-sytem");
|
|
check_run_no += 1;
|
|
dir_check(top_dir);
|
|
check_deleted_files();
|
|
}
|
|
|
|
/*
|
|
* This is a helper function which just reads whole file. We do this in case of
|
|
* emulated power cuts testing to make sure that unclean files can be at least
|
|
* read.
|
|
*/
|
|
static void read_whole_file(const char *name)
|
|
{
|
|
size_t rd;
|
|
char buf[IO_BUFFER_SIZE];
|
|
int fd;
|
|
|
|
fd = open(name, O_RDONLY);
|
|
CHECK(fd != -1);
|
|
|
|
do {
|
|
rd = read(fd, buf, IO_BUFFER_SIZE);
|
|
CHECK(rd != -1);
|
|
} while (rd);
|
|
|
|
close(fd);
|
|
}
|
|
|
|
/*
|
|
* Recursively walk whole tested file-system and make sure we can read
|
|
* everything. This is done in case of power cuts emulation testing to ensure
|
|
* that everything in the file-system is readable.
|
|
*/
|
|
static void read_all(const char *dir_name)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *dent;
|
|
char buf[PATH_MAX];
|
|
|
|
assert(args.power_cut_mode);
|
|
v("reading all files");
|
|
|
|
dir = opendir(dir_name);
|
|
if (!dir) {
|
|
errmsg("cannot open %s", dir_name);
|
|
CHECK(0);
|
|
}
|
|
CHECK(getcwd(buf, PATH_MAX) != NULL);
|
|
CHECK(chdir(dir_name) == 0);
|
|
|
|
for (;;) {
|
|
errno = 0;
|
|
dent = readdir(dir);
|
|
if (!dent) {
|
|
CHECK(errno == 0);
|
|
break;
|
|
}
|
|
|
|
if (!strcmp(dent->d_name, ".") ||
|
|
!strcmp(dent->d_name, ".."))
|
|
continue;
|
|
|
|
if (dent->d_type == DT_DIR)
|
|
read_all(dent->d_name);
|
|
else if (dent->d_type == DT_REG)
|
|
read_whole_file(dent->d_name);
|
|
else if (dent->d_type == DT_LNK) {
|
|
char b[IO_BUFFER_SIZE];
|
|
|
|
CHECK(readlink(dent->d_name, b, IO_BUFFER_SIZE) != -1);
|
|
}
|
|
}
|
|
|
|
CHECK(chdir(buf) == 0);
|
|
CHECK(closedir(dir) == 0);
|
|
}
|
|
|
|
/*
|
|
* Perform the test. Returns zero on success and -1 on failure.
|
|
*/
|
|
static int integck(void)
|
|
{
|
|
int ret;
|
|
long rpt;
|
|
|
|
CHECK(chdir(fsinfo.mount_point) == 0);
|
|
assert(!top_dir);
|
|
|
|
/* Create our top directory */
|
|
if (chdir(fsinfo.test_dir) == 0) {
|
|
CHECK(chdir("..") == 0);
|
|
ret = rm_minus_rf_dir(fsinfo.test_dir);
|
|
if (ret)
|
|
return -1;
|
|
}
|
|
|
|
v("creating top dir %s", fsinfo.test_dir);
|
|
ret = mkdir(fsinfo.test_dir, 0777);
|
|
if (ret) {
|
|
pcv("cannot create top test directory %s", fsinfo.test_dir);
|
|
return -1;
|
|
}
|
|
|
|
ret = sync_directory(fsinfo.test_dir);
|
|
if (ret)
|
|
return -1;
|
|
|
|
top_dir = zalloc(sizeof(struct dir_info));
|
|
top_dir->entry = zalloc(sizeof(struct dir_entry_info));
|
|
top_dir->entry->name = dup_string(fsinfo.test_dir);
|
|
|
|
ret = create_test_data();
|
|
if (ret)
|
|
return -1;
|
|
|
|
if (fsinfo.can_remount) {
|
|
close_open_files();
|
|
ret = remount_tested_fs();
|
|
if (ret)
|
|
return -1;
|
|
} else
|
|
assert(!args.power_cut_mode);
|
|
|
|
/* Check everything */
|
|
check_tested_fs();
|
|
|
|
for (rpt = 0; args.repeat_cnt == 0 || rpt < args.repeat_cnt; ++rpt) {
|
|
ret = update_test_data();
|
|
if (ret)
|
|
return -1;
|
|
|
|
if (fsinfo.can_remount) {
|
|
close_open_files();
|
|
ret = remount_tested_fs();
|
|
if (ret)
|
|
return -1;
|
|
}
|
|
|
|
/* Check everything */
|
|
check_tested_fs();
|
|
}
|
|
|
|
/* Tidy up by removing everything */
|
|
close_open_files();
|
|
ret = rm_minus_rf_dir(fsinfo.test_dir);
|
|
if (ret)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This is a helper function for 'get_tested_fs_info()'. It parses file-system
|
|
* mount options string, extracts standard mount options from there, and saves
|
|
* them in the 'fsinfo.mount_flags' variable, and non-standard mount options
|
|
* are saved in the 'fsinfo.mount_opts' variable. The reason for this is that
|
|
* we want to preserve mount options when unmounting the file-system and
|
|
* mounting it again. This is because 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 void parse_mount_options(const char *mount_opts)
|
|
{
|
|
char *tmp, *opts, *p;
|
|
const char *opt;
|
|
|
|
/*
|
|
* We are going to use 'strtok()' which modifies the original string,
|
|
* so duplicate it.
|
|
*/
|
|
tmp = dup_string(mount_opts);
|
|
p = opts = zalloc(strlen(mount_opts) + 1);
|
|
|
|
opt = strtok(tmp, ",");
|
|
while (opt) {
|
|
if (!strcmp(opt, "rw"))
|
|
;
|
|
else if (!strcmp(opt, "ro"))
|
|
fsinfo.mount_flags |= MS_RDONLY;
|
|
else if (!strcmp(opt, "dirsync"))
|
|
fsinfo.mount_flags |= MS_DIRSYNC;
|
|
else if (!strcmp(opt, "noatime"))
|
|
fsinfo.mount_flags |= MS_NOATIME;
|
|
else if (!strcmp(opt, "nodiratime"))
|
|
fsinfo.mount_flags |= MS_NODIRATIME;
|
|
else if (!strcmp(opt, "noexec"))
|
|
fsinfo.mount_flags |= MS_NOEXEC;
|
|
else if (!strcmp(opt, "nosuid"))
|
|
fsinfo.mount_flags |= MS_NOSUID;
|
|
else if (!strcmp(opt, "relatime"))
|
|
fsinfo.mount_flags |= MS_RELATIME;
|
|
else if (!strcmp(opt, "sync"))
|
|
fsinfo.mount_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);
|
|
fsinfo.mount_opts = opts;
|
|
}
|
|
|
|
/*
|
|
* This is a helper function which searches for the tested file-system mount
|
|
* description.
|
|
*/
|
|
static struct mntent *get_tested_fs_mntent(void)
|
|
{
|
|
const char *mp;
|
|
struct mntent *mntent;
|
|
FILE *f;
|
|
|
|
mp = "/proc/mounts";
|
|
f = fopen(mp, "rb");
|
|
if (!f) {
|
|
mp = "/etc/mtab";
|
|
f = fopen(mp, "rb");
|
|
}
|
|
CHECK(f != NULL);
|
|
|
|
while ((mntent = getmntent(f)) != NULL)
|
|
if (!strcmp(mntent->mnt_dir, fsinfo.mount_point))
|
|
break;
|
|
CHECK(fclose(f) == 0);
|
|
return mntent;
|
|
}
|
|
|
|
/*
|
|
* Fill 'fsinfo' with information about the tested file-system.
|
|
*/
|
|
static void get_tested_fs_info(void)
|
|
{
|
|
struct statfs fs_info;
|
|
struct mntent *mntent;
|
|
uint64_t z;
|
|
char *p;
|
|
unsigned int pid;
|
|
|
|
/* Remove trailing '/' symbols from the mount point */
|
|
p = dup_string(args.mount_point);
|
|
fsinfo.mount_point = p;
|
|
p += strlen(p);
|
|
while (*--p == '/');
|
|
*(p + 1) = '\0';
|
|
|
|
CHECK(statfs(fsinfo.mount_point, &fs_info) == 0);
|
|
fsinfo.max_name_len = fs_info.f_namelen;
|
|
|
|
mntent = get_tested_fs_mntent();
|
|
if (!mntent) {
|
|
errmsg("cannot find file-system info");
|
|
CHECK(0);
|
|
}
|
|
|
|
fsinfo.fstype = dup_string(mntent->mnt_type);
|
|
fsinfo.fsdev = dup_string(mntent->mnt_fsname);
|
|
parse_mount_options(mntent->mnt_opts);
|
|
|
|
/* Get memory page size for 'mmap()' */
|
|
fsinfo.page_size = sysconf(_SC_PAGE_SIZE);
|
|
CHECK(fsinfo.page_size > 0);
|
|
|
|
/*
|
|
* JFFS2 does not support shared writable mmap and it may report
|
|
* incorrect file size after "no space" errors.
|
|
*/
|
|
if (strcmp(fsinfo.fstype, "jffs2") == 0) {
|
|
fsinfo.nospc_size_ok = 0;
|
|
fsinfo.can_mmap = 0;
|
|
}
|
|
|
|
get_fs_space(NULL, &z);
|
|
for (; z >= 10; z /= 10)
|
|
fsinfo.log10_initial_free += 1;
|
|
|
|
/* Pick the test directory name */
|
|
p = malloc(sizeof(TEST_DIR_PATTERN) + 20);
|
|
CHECK(p != NULL);
|
|
pid = getpid();
|
|
CHECK(sprintf(p, "integck_test_dir_%u", pid) > 0);
|
|
fsinfo.test_dir = p;
|
|
|
|
normsg("pid %u, testing \"%s\" at \"%s\"",
|
|
pid, fsinfo.fstype, fsinfo.mount_point);
|
|
}
|
|
|
|
static const char doc[] = PROGRAM_NAME " version " PROGRAM_VERSION
|
|
" - a stress test which checks the file-system integrity.\n"
|
|
"\n"
|
|
"The test creates a directory named \"integck_test_dir_<pid>\", where where\n"
|
|
"<pid> is the process id. Then it randomly creates and deletes files,\n"
|
|
"directories, symlinks, and hardlinks, randomly writes and truncate files,\n"
|
|
"sometimes makes holes in files, sometimes fsync()'s them. Then it un-mounts and\n"
|
|
"re-mounts the tested file-system and checks the contents - everything (files,\n"
|
|
"directories, etc) should be there and the contents of the files should be\n"
|
|
"correct. This is repeated a number of times (set with -n, default 1).\n"
|
|
"\n"
|
|
"By default the test does not verify file-system modifications and assumes they\n"
|
|
"are done correctly if the file-system returns success. However, the -e flag\n"
|
|
"enables additional verifications and the test verifies all the file-system\n"
|
|
"operations it performs.\n"
|
|
"\n"
|
|
"This test is also able to perform power cut testing. The underlying file-system\n"
|
|
"or the device driver should be able to emulate power-cuts, by switching to R/O\n"
|
|
"mode at random moments. And the file-system should return EROFS (read-only\n"
|
|
"file-system error) for all operations which modify it. In this case this test\n"
|
|
"program re-mounts the file-system and checks that all files and directories\n"
|
|
"which have been successfully synchronized before the power cut. And the test\n"
|
|
"continues forever.\n";
|
|
|
|
static const char optionsstr[] =
|
|
"-n, --repeat=<count> repeat count, default is 1; zero value - repeat forever\n"
|
|
"-p, --power-cut power cut testing mode (-n parameter is ignored and the\n"
|
|
" test continues forever)\n"
|
|
"-e, --verify-ops verify all operations, e.g., every time a file is written\n"
|
|
" to, read the data back and verify it, every time a\n"
|
|
" directory is created, check that it exists, etc\n"
|
|
"-v, --verbose be verbose\n"
|
|
"-m, --reattach=<mtd> re-attach mtd device number <mtd> (only if doing UBIFS power\n"
|
|
" cut emulation testing)\n"
|
|
"-h, -?, --help print help message\n"
|
|
"-V, --version print program version\n";
|
|
|
|
static const struct option long_options[] = {
|
|
{ .name = "repeat", .has_arg = 1, .flag = NULL, .val = 'n' },
|
|
{ .name = "power-cut", .has_arg = 0, .flag = NULL, .val = 'p' },
|
|
{ .name = "verify-ops", .has_arg = 0, .flag = NULL, .val = 'e' },
|
|
{ .name = "reattach", .has_arg = 1, .flag = NULL, .val = 'm' },
|
|
{ .name = "verbose", .has_arg = 0, .flag = NULL, .val = 'v' },
|
|
{ .name = "help", .has_arg = 0, .flag = NULL, .val = 'h' },
|
|
{ .name = "version", .has_arg = 0, .flag = NULL, .val = 'V' },
|
|
{ NULL, 0, NULL, 0},
|
|
};
|
|
|
|
/*
|
|
* Parse and validate input command-line options. Returns zero on success and
|
|
* -1 on error.
|
|
*/
|
|
static int parse_opts(int argc, char * const argv[])
|
|
{
|
|
struct stat st;
|
|
|
|
while (1) {
|
|
int key, error = 0;
|
|
|
|
key = getopt_long(argc, argv, "n:pm:evVh?", long_options, NULL);
|
|
if (key == -1)
|
|
break;
|
|
|
|
switch (key) {
|
|
case 'n':
|
|
args.repeat_cnt = simple_strtoul(optarg, &error);
|
|
if (error || args.repeat_cnt < 0)
|
|
return errmsg("bad repeat count: \"%s\"", optarg);
|
|
break;
|
|
case 'p':
|
|
args.power_cut_mode = 1;
|
|
break;
|
|
case 'm':
|
|
args.mtdn = simple_strtoul(optarg, &error);
|
|
if (error || args.mtdn < 0)
|
|
return errmsg("bad mtd device number: \"%s\"", optarg);
|
|
args.reattach = 1;
|
|
break;
|
|
case 'e':
|
|
args.verify_ops = 1;
|
|
break;
|
|
case 'v':
|
|
args.verbose = 1;
|
|
break;
|
|
case 'V':
|
|
fprintf(stderr, "%s\n", PROGRAM_VERSION);
|
|
exit(EXIT_SUCCESS);
|
|
|
|
case 'h':
|
|
case '?':
|
|
fprintf(stderr, "%s\n\n", doc);
|
|
fprintf(stderr, "%s\n", optionsstr);
|
|
exit(EXIT_SUCCESS);
|
|
case ':':
|
|
return errmsg("parameter is missing");
|
|
|
|
default:
|
|
fprintf(stderr, "Use -h for help\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (optind == argc)
|
|
return errmsg("test file-system was not specified (use -h for help)");
|
|
else if (optind != argc - 1)
|
|
return errmsg("more then one test file-system specified (use -h for help)");
|
|
|
|
if (args.reattach && !args.power_cut_mode)
|
|
return errmsg("-m makes sense only together with -e");
|
|
|
|
if (args.power_cut_mode)
|
|
/* Repeat forever if we are in power cut testing mode */
|
|
args.repeat_cnt = 0;
|
|
|
|
args.mount_point = argv[optind];
|
|
|
|
if (chdir(args.mount_point) != 0 || lstat(args.mount_point, &st) != 0)
|
|
return errmsg("invalid test file system mount directory: %s",
|
|
args.mount_point);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Free all the in-memory information about the tested file-system contents
|
|
* starting from sub-directory 'dir'.
|
|
*/
|
|
static void free_fs_info(struct dir_info *dir)
|
|
{
|
|
struct dir_entry_info *entry;
|
|
|
|
/* Now check each entry */
|
|
while (dir->first) {
|
|
entry = dir->first;
|
|
if (entry->type == 'd') {
|
|
struct dir_info *d = entry->dir;
|
|
|
|
remove_dir_entry(entry, 0);
|
|
free_fs_info(d);
|
|
free(d);
|
|
} else if (entry->type == 'f') {
|
|
remove_dir_entry(entry, 1);
|
|
} else if (entry->type == 's') {
|
|
remove_dir_entry(entry, 1);
|
|
} else
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Detach the MTD device from UBI and attach it back. This function is used
|
|
* whed performing emulated power cut testing andthe power cuts are amulated by
|
|
* UBI, not by UBIFS. In this case, to recover from the emulated power cut we
|
|
* have to unmount UBIFS and re-attach the MTD device.
|
|
*/
|
|
static int reattach(void)
|
|
{
|
|
int err = 0;
|
|
libubi_t libubi;
|
|
struct ubi_attach_request req;
|
|
|
|
libubi = libubi_open();
|
|
if (!libubi) {
|
|
if (errno == 0)
|
|
return errmsg("UBI is not present in the system");
|
|
return sys_errmsg("cannot open libubi");
|
|
}
|
|
|
|
err = ubi_detach_mtd(libubi, "/dev/ubi_ctrl", args.mtdn);
|
|
if (err) {
|
|
sys_errmsg("cannot detach mtd%d", args.mtdn);
|
|
goto out;
|
|
}
|
|
|
|
req.dev_num = UBI_DEV_NUM_AUTO;
|
|
req.mtd_num = args.mtdn;
|
|
req.vid_hdr_offset = 0;
|
|
req.mtd_dev_node = NULL;
|
|
req.max_beb_per1024 = 0;
|
|
|
|
err = ubi_attach(libubi, "/dev/ubi_ctrl", &req);
|
|
if (err)
|
|
sys_errmsg("cannot attach mtd%d", args.mtdn);
|
|
|
|
out:
|
|
libubi_close(libubi);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Recover the tested file-system from an emulated power cut failure by
|
|
* unmounting it and mounting it again.
|
|
*/
|
|
static int recover_tested_fs(void)
|
|
{
|
|
int ret;
|
|
unsigned long flags;
|
|
unsigned int um_rorw, rorw2;
|
|
struct mntent *mntent;
|
|
|
|
CHECK(chdir("/") == 0);
|
|
|
|
/* Choose what to do */
|
|
um_rorw = random_no(2);
|
|
rorw2 = random_no(2);
|
|
|
|
/*
|
|
* At this point we do not know for sure whether the tested FS is
|
|
* mounted, because the emulated power cut error could have happened
|
|
* while mounting in 'remount_tested_fs()'.
|
|
*/
|
|
mntent = get_tested_fs_mntent();
|
|
if (mntent)
|
|
CHECK(umount(fsinfo.mount_point) != -1);
|
|
|
|
if (args.reattach)
|
|
CHECK(reattach() == 0);
|
|
|
|
if (!um_rorw) {
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point,
|
|
fsinfo.fstype, fsinfo.mount_flags,
|
|
fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("unmounted %s, but cannot mount it back R/W",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
} else {
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point,
|
|
fsinfo.fstype, fsinfo.mount_flags | MS_RDONLY,
|
|
fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("unmounted %s, but cannot mount it back R/O",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
|
|
flags = fsinfo.mount_flags | MS_REMOUNT;
|
|
flags &= ~((unsigned long)MS_RDONLY);
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point,
|
|
fsinfo.fstype, flags, fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("unmounted %s, mounted R/O, but cannot re-mount it R/W",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (rorw2) {
|
|
flags = fsinfo.mount_flags | MS_RDONLY | MS_REMOUNT;
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype,
|
|
flags, fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("cannot re-mount %s R/O", fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
|
|
flags = fsinfo.mount_flags | MS_REMOUNT;
|
|
flags &= ~((unsigned long)MS_RDONLY);
|
|
ret = mount(fsinfo.fsdev, fsinfo.mount_point, fsinfo.fstype,
|
|
flags, fsinfo.mount_opts);
|
|
if (ret) {
|
|
pcv("remounted %s R/O, but cannot re-mount it back R/W",
|
|
fsinfo.mount_point);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void free_test_data(void)
|
|
{
|
|
if (top_dir) {
|
|
free_fs_info(top_dir);
|
|
free(top_dir->entry->name);
|
|
free(top_dir->entry);
|
|
free(top_dir);
|
|
top_dir = NULL;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int ret;
|
|
long rpt;
|
|
|
|
ret = parse_opts(argc, argv);
|
|
if (ret)
|
|
return EXIT_FAILURE;
|
|
|
|
get_tested_fs_info();
|
|
|
|
/* Seed the random generator with out PID */
|
|
random_seed = getpid();
|
|
|
|
random_name_buf = malloc(fsinfo.max_name_len + 1);
|
|
CHECK(random_name_buf != NULL);
|
|
|
|
/* Refuse the file-system if it is mounted R/O */
|
|
if (fsinfo.mount_flags & MS_RDONLY) {
|
|
ret = -1;
|
|
errmsg("the file-system is mounted read-only");
|
|
goto out_free;
|
|
}
|
|
|
|
/* Check whether we can re-mount the tested FS */
|
|
do {
|
|
ret = recover_tested_fs();
|
|
} while (ret && args.power_cut_mode && errno == EROFS);
|
|
|
|
if (!ret) {
|
|
fsinfo.can_remount = 1;
|
|
} else {
|
|
warnmsg("file-system %s cannot be unmounted (%s)",
|
|
fsinfo.mount_point, strerror(errno));
|
|
if (args.power_cut_mode) {
|
|
/*
|
|
* When testing emulated power cuts we have to be able
|
|
* to re-mount the file-system to clean the EROFS
|
|
* state.
|
|
*/
|
|
errmsg("power cut mode requers unmountable FS");
|
|
goto out_free;
|
|
}
|
|
}
|
|
|
|
/* Do the actual test */
|
|
for (rpt = 0; ; rpt++) {
|
|
ret = integck();
|
|
|
|
/*
|
|
* Iterate forever only in case of power-cut emulation testing.
|
|
*/
|
|
if (!args.power_cut_mode) {
|
|
CHECK(!ret);
|
|
break;
|
|
}
|
|
|
|
CHECK(ret);
|
|
CHECK(errno == EROFS || errno == EIO);
|
|
|
|
close_open_files();
|
|
|
|
do {
|
|
ret = recover_tested_fs();
|
|
if (ret) {
|
|
CHECK(errno == EROFS);
|
|
rpt += 1;
|
|
}
|
|
/*
|
|
* Mount may also fail due to an emulated power cut
|
|
* while mounting - keep re-starting.
|
|
*/
|
|
} while (ret);
|
|
|
|
CHECK(chdir(fsinfo.mount_point) == 0);
|
|
|
|
/* Make sure everything is readable after an emulated power cut */
|
|
if (top_dir) {
|
|
/* Check the clean data */
|
|
check_tested_fs();
|
|
read_all(fsinfo.test_dir);
|
|
}
|
|
|
|
free_test_data();
|
|
|
|
/*
|
|
* The file-system became read-only and we are in power cut
|
|
* testing mode. Re-mount the file-system and re-start the
|
|
* test.
|
|
*/
|
|
if (args.verbose)
|
|
normsg("re-mount the FS and re-start - count %ld", rpt);
|
|
|
|
}
|
|
|
|
close_open_files();
|
|
free_test_data();
|
|
|
|
out_free:
|
|
free(random_name_buf);
|
|
free(fsinfo.mount_opts);
|
|
free(fsinfo.mount_point);
|
|
free(fsinfo.fstype);
|
|
free(fsinfo.fsdev);
|
|
free(fsinfo.test_dir);
|
|
return ret ? EXIT_FAILURE : EXIT_SUCCESS;
|
|
}
|