blob: 8badd1fe7b8288d723a01fb0e7b88afb57979b91 [file] [log] [blame] [edit]
/*
* 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