blob: 145557f10d2b19e28517c24e1dd86b3216e4971e [file] [log] [blame]
/*
* 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 <sys/mman.h>
#include "tests.h"
/* 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 */
size_t size; /* Number of bytes written */
unsigned random_seed; /* Seed for rand() to create random data */
off_t random_offset; /* Call rand() this number of times first */
int trunc; /* Records a truncation (raw_writes only) */
};
struct dir_entry_info;
struct file_info /* Each file has one of these */
{
char *name; /* Original name */
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;
int link_count;
off_t length;
int deleted; /* File has been deleted but is still open */
int no_space_error; /* File has incurred a ENOSPC error */
uint64_t check_run_no; /* Run number used when checking */
};
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 */
{
char *name;
struct dir_info *parent; /* Parent directory or null
for our top directory */
unsigned number_of_entries;
struct dir_entry_info *first;
struct dir_entry_info *entry; /* Dir entry of this dir */
};
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 */
char type; /* f => file, d => dir, s => symlink */
int checked; /* Temporary flag used when checking */
union entry_
{
struct file_info *file;
struct dir_info *dir;
struct symlink_info *symlink;
void *target;
} entry;
};
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 uint64_t initial_free_space = 0; /* Free space on file system when
test starts */
static unsigned log10_initial_free_space = 0; /* log10 of initial_free_space */
static int check_nospc_files = 0; /* Also check data in files that incurred a
"no space" error */
static int can_mmap = 0; /* Can write via mmap */
static long mem_page_size; /* Page size for mmap */
static uint64_t check_run_no;
static char *copy_string(const char *s)
{
char *str;
if (!s)
return NULL;
str = (char *) malloc(strlen(s) + 1);
CHECK(str != NULL);
strcpy(str, s);
return str;
}
static char *cat_strings(const char *a, const char *b)
{
char *str;
size_t sz;
if (a && !b)
return copy_string(a);
if (b && !a)
return copy_string(b);
if (!a && !b)
return NULL;
sz = strlen(a) + strlen(b) + 1;
str = (char *) 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;
int as, bs;
size_t na, nb;
if (a && !b)
return copy_string(a);
if (b && !a)
return copy_string(b);
if (!a && !b)
return NULL;
as = 0;
bs = 0;
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 = (char *) malloc(sz);
CHECK(str != NULL);
strcpy(str, a);
strcat(str, "/");
strcat(str, b);
return str;
}
static char *dir_path(struct dir_info *parent, const char *name)
{
char *parent_path;
char *path;
if (!parent)
return cat_paths(tests_file_system_mount_dir, name);
parent_path = dir_path(parent->parent, parent->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;
size_t sz;
sz = sizeof(struct open_file_info);
ofi = (struct open_file_info *) malloc(sz);
CHECK(ofi != NULL);
memset(ofi, 0, sz);
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;
size_t sz;
sz = sizeof(struct fd_info);
fdi = (struct fd_info *) malloc(sz);
CHECK(fdi != NULL);
memset(fdi, 0, sz);
fdi->next = file->fds;
fdi->file = file;
fdi->fd = fd;
file->fds = fdi;
open_file_add(fdi);
return fdi;
}
static void add_dir_entry(struct dir_info *parent, char type, const char *name,
void *target)
{
struct dir_entry_info *entry;
size_t sz;
sz = sizeof(struct dir_entry_info);
entry = (struct dir_entry_info *) malloc(sz);
CHECK(entry != NULL);
memset(entry, 0, sz);
entry->type = type;
entry->name = copy_string(name);
entry->parent = parent;
entry->next = parent->first;
if (parent->first)
parent->first->prev = entry;
parent->first = entry;
parent->number_of_entries += 1;
if (entry->type == 'f') {
struct file_info *file = target;
entry->entry.file = file;
entry->next_link = file->links;
if (file->links)
file->links->prev_link = entry;
file->links = entry;
file->link_count += 1;
} else if (entry->type == 'd') {
struct dir_info *dir = target;
entry->entry.dir = dir;
dir->entry = entry;
dir->name = copy_string(name);
dir->parent = parent;
} else if (entry->type == 's') {
struct symlink_info *symlink = target;
entry->entry.symlink = symlink;
symlink->entry = entry;
}
}
static void remove_dir_entry(struct dir_entry_info *entry)
{
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->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)
file->deleted = 1;
}
free(entry->name);
free(entry);
}
static struct dir_info *dir_new(struct dir_info *parent, const char *name)
{
struct dir_info *dir;
size_t sz;
char *path;
path = dir_path(parent, name);
if (mkdir(path, 0777) == -1) {
CHECK(errno == ENOSPC);
full = 1;
free(path);
return NULL;
}
free(path);
sz = sizeof(struct dir_info);
dir = (struct dir_info *) malloc(sz);
CHECK(dir != NULL);
memset(dir, 0, sz);
dir->name = copy_string(name);
dir->parent = parent;
if (parent)
add_dir_entry(parent, 'd', name, dir);
return dir;
}
static void file_delete(struct file_info *file);
static void file_unlink(struct dir_entry_info *entry);
static void symlink_remove(struct symlink_info *symlink);
static void dir_remove(struct dir_info *dir)
{
char *path;
/* Remove directory contents */
while (dir->first) {
struct dir_entry_info *entry;
entry = dir->first;
if (entry->type == 'd')
dir_remove(entry->entry.dir);
else if (entry->type == 'f')
file_unlink(entry);
else if (entry->type == 's')
symlink_remove(entry->entry.symlink);
else
CHECK(0); /* Invalid struct dir_entry_info */
}
/* Remove entry from parent directory */
remove_dir_entry(dir->entry);
/* Remove directory itself */
path = dir_path(dir->parent, dir->name);
CHECK(rmdir(path) != -1);
free(dir);
}
static struct file_info *file_new(struct dir_info *parent, const char *name)
{
struct file_info *file = NULL;
char *path;
mode_t mode;
int fd;
size_t sz;
CHECK(parent != NULL);
path = dir_path(parent, name);
mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
fd = open(path, O_CREAT | O_EXCL | O_RDWR, mode);
if (fd == -1) {
CHECK(errno == ENOSPC);
free(path);
full = 1;
return NULL;
}
free(path);
sz = sizeof(struct file_info);
file = (struct file_info *) malloc(sz);
CHECK(file != NULL);
memset(file, 0, sz);
file->name = copy_string(name);
add_dir_entry(parent, 'f', name, file);
add_fd(file, fd);
return file;
}
static void link_new(struct dir_info *parent, const char *name,
struct file_info *file)
{
struct dir_entry_info *entry;
char *path, *target;
int ret;
if (!file)
return;
entry = file->links;
if (!entry)
return;
path = dir_path(parent, name);
target = dir_path(entry->parent, entry->name);
ret = link(target, path);
if (ret == -1) {
CHECK(errno == ENOSPC);
free(target);
free(path);
full = 1;
return;
}
free(target);
free(path);
add_dir_entry(parent, 'f', name, file);
}
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;
}
}
static void file_unlink(struct dir_entry_info *entry)
{
struct file_info *file = entry->entry.file;
char *path;
path = dir_path(entry->parent, entry->name);
/* Remove file entry from parent directory */
remove_dir_entry(entry);
/* Unlink the file */
CHECK(unlink(path) != -1);
free(path);
/* Free struct file_info if file is not open and not linked */
if (!file->fds && !file->links) {
struct write_info *w, *next;
free(file->name);
w = file->writes;
while (w) {
next = w->next;
free(w);
w = next;
}
free(file);
} else if (!file->links)
file->deleted = 1;
}
static struct dir_entry_info *pick_entry(struct file_info *file)
{
struct dir_entry_info *entry;
size_t r;
if (!file->link_count)
return NULL;
r = tests_random_no(file->link_count);
entry = file->links;
while (entry && r--)
entry = entry->next_link;
return entry;
}
static void file_unlink_file(struct file_info *file)
{
struct dir_entry_info *entry;
entry = pick_entry(file);
if (!entry)
return;
file_unlink(entry);
}
static void 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;
file_unlink(entry);
entry = next;
}
}
static void file_info_display(struct file_info *file)
{
struct dir_entry_info *entry;
struct write_info *w;
unsigned wcnt;
fprintf(stderr, "File Info:\n");
fprintf(stderr, " Original name: %s\n", file->name);
fprintf(stderr, " Link count: %d\n", file->link_count);
fprintf(stderr, " Links:\n");
entry = file->links;
while (entry) {
fprintf(stderr, " Name: %s\n", entry->name);
fprintf(stderr, " Directory: %s\n", entry->parent->name);
entry = entry->next_link;
}
fprintf(stderr, " Length: %u\n", (unsigned) file->length);
fprintf(stderr, " File was open: %s\n",
(file->fds == NULL) ? "false" : "true");
fprintf(stderr, " File was deleted: %s\n",
(file->deleted == 0) ? "false" : "true");
fprintf(stderr, " File was out of space: %s\n",
(file->no_space_error == 0) ? "false" : "true");
fprintf(stderr, " File Data:\n");
wcnt = 0;
w = file->writes;
while (w) {
fprintf(stderr, " Offset: %u Size: %u Seed: %u"
" R.Off: %u\n",
(unsigned) w->offset,
(unsigned) w->size,
(unsigned) w->random_seed,
(unsigned) w->random_offset);
wcnt += 1;
w = w->next;
}
fprintf(stderr, " %u writes\n", wcnt);
fprintf(stderr, " ============================================\n");
fprintf(stderr, " Write Info:\n");
wcnt = 0;
w = file->raw_writes;
while (w) {
if (w->trunc)
fprintf(stderr, " Trunc from %u to %u\n",
(unsigned) w->offset,
(unsigned) w->random_offset);
else
fprintf(stderr, " Offset: %u Size: %u Seed: %u"
" R.Off: %u\n",
(unsigned) w->offset,
(unsigned) w->size,
(unsigned) w->random_seed,
(unsigned) w->random_offset);
wcnt += 1;
w = w->next;
}
fprintf(stderr, " %u writes or truncations\n", wcnt);
fprintf(stderr, " ============================================\n");
}
static struct fd_info *file_open(struct file_info *file)
{
int fd, flags = O_RDWR;
char *path;
path = dir_path(file->links->parent, file->links->name);
if (tests_random_no(100) == 1)
flags |= O_SYNC;
fd = open(path, flags);
CHECK(fd != -1);
free(path);
return add_fd(file, fd);
}
#define BUFFER_SIZE 32768
static size_t file_write_data( struct file_info *file,
int fd,
off_t offset,
size_t size,
unsigned seed)
{
size_t remains, actual, block;
ssize_t written;
char buf[BUFFER_SIZE];
srand(seed);
CHECK(lseek(fd, offset, SEEK_SET) != (off_t) -1);
remains = size;
actual = 0;
written = BUFFER_SIZE;
while (remains) {
/* Fill up buffer with random data */
if (written < BUFFER_SIZE) {
memmove(buf, buf + written, BUFFER_SIZE - written);
written = BUFFER_SIZE - written;
} else
written = 0;
for (; written < BUFFER_SIZE; ++written)
buf[written] = rand();
/* Write a block of data */
if (remains > BUFFER_SIZE)
block = BUFFER_SIZE;
else
block = remains;
written = write(fd, buf, block);
if (written < 0) {
CHECK(errno == ENOSPC); /* File system full */
full = 1;
file->no_space_error = 1;
break;
}
remains -= written;
actual += written;
}
return actual;
}
static void file_write_info(struct file_info *file,
off_t offset,
size_t size,
unsigned seed)
{
struct write_info *new_write, *w, **prev, *tmp;
int inserted;
size_t sz;
off_t end, chg;
/* Create struct write_info */
sz = sizeof(struct write_info);
new_write = (struct write_info *) malloc(sz);
CHECK(new_write != NULL);
memset(new_write, 0, sz);
new_write->offset = offset;
new_write->size = size;
new_write->random_seed = seed;
w = (struct write_info *) malloc(sz);
CHECK(w != NULL);
memset(w, 0, sz);
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 = (struct write_info *) malloc(sz);
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;
}
/* 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)
{
size_t r, n;
r = tests_random_no(100);
if (r == 0 && grow)
/* 1 time in 100, when growing, write off the end of the file */
*offset = file->length + tests_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 = tests_random_no(file->length);
else
/* 48 times in 100, write at the end of the file */
*offset = file->length;
/* Distribute the size logarithmically */
if (tests_random_no(1000) == 0)
r = tests_random_no(log10_initial_free_space + 2);
else
r = tests_random_no(log10_initial_free_space);
n = 1;
while (r--)
n *= 10;
*size = tests_random_no(n);
if (!grow && *offset + *size > file->length)
*size = file->length - *offset;
if (*size == 0)
*size = 1;
}
static void file_truncate_info(struct file_info *file, size_t new_length);
static int file_ftruncate(struct file_info *file, int fd, off_t new_length)
{
if (ftruncate(fd, new_length) == -1) {
CHECK(errno = ENOSPC);
file->no_space_error = 1;
/* Delete errored files */
if (!check_nospc_files)
file_delete(file);
return 0;
}
return 1;
}
static void 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;
off_t offs, offset;
unsigned seed;
uint64_t free_space;
int fd;
char *path;
if (!file->links)
return;
free_space = tests_get_free_space();
if (!free_space)
return;
/* Randomly pick a written area of the file */
if (!w)
return;
while (w) {
write_cnt += 1;
w = w->next;
}
r = tests_random_no(write_cnt);
w = file->writes;
for (i = 0; w && w->next && i < r; i++)
w = w->next;
offs = (w->offset / mem_page_size) * mem_page_size;
len = w->size + (w->offset - offs);
if (len > 1 << 24)
len = 1 << 24;
/* Open it */
path = dir_path(file->links->parent, file->links->name);
fd = open(path, O_RDWR);
CHECK(fd != -1);
free(path);
/* mmap it */
addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offs);
CHECK(close(fd) != -1);
CHECK(addr != MAP_FAILED);
/* Randomly select a part of the mmapped area to write */
size = tests_random_no(w->size);
if (size > free_space)
size = free_space;
if (size == 0)
size = 1;
offset = w->offset + tests_random_no(w->size - size);
/* Write it */
seed = tests_random_no(10000000);
srand(seed);
waddr = addr + (offset - offs);
for (i = 0; i < size; i++)
waddr[i] = rand();
/* Unmap it */
CHECK(munmap(addr, len) != -1);
/* Record what was written */
file_write_info(file, offset, size, seed);
}
static void file_write(struct file_info *file, int fd)
{
off_t offset;
size_t size, actual;
unsigned seed;
int truncate = 0;
if (can_mmap && !full && !file->deleted && tests_random_no(100) == 1) {
file_mmap_write(file);
return;
}
get_offset_and_size(file, &offset, &size);
seed = tests_random_no(10000000);
actual = file_write_data(file, fd, offset, size, seed);
if (offset + actual <= file->length && shrink)
/* 1 time in 100, when shrinking
truncate after the write */
if (tests_random_no(100) == 0)
truncate = 1;
if (actual != 0)
file_write_info(file, offset, actual, seed);
/* Delete errored files */
if (!check_nospc_files && file->no_space_error) {
file_delete(file);
return;
}
if (truncate) {
size_t new_length = offset + actual;
if (file_ftruncate(file, fd, new_length))
file_truncate_info(file, new_length);
}
}
static void file_write_file(struct file_info *file)
{
int fd;
char *path;
path = dir_path(file->links->parent, file->links->name);
fd = open(path, O_WRONLY);
CHECK(fd != -1);
file_write(file, fd);
CHECK(close(fd) != -1);
free(path);
}
static void file_truncate_info(struct file_info *file, size_t new_length)
{
struct write_info *w, **prev, *tmp;
size_t sz;
/* 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 */
sz = sizeof(struct write_info);
w = (struct write_info *) malloc(sz);
CHECK(w != NULL);
memset(w, 0, sz);
w->next = file->raw_writes;
w->offset = file->length;
w->random_offset = new_length; /* Abuse random_offset */
w->trunc = 1;
file->raw_writes = w;
/* Update file length */
file->length = new_length;
}
static void file_truncate(struct file_info *file, int fd)
{
size_t new_length;
new_length = tests_random_no(file->length);
if (file_ftruncate(file, fd, new_length))
file_truncate_info(file, new_length);
}
static void file_truncate_file(struct file_info *file)
{
int fd;
char *path;
path = dir_path(file->links->parent, file->links->name);
fd = open(path, O_WRONLY);
CHECK(fd != -1);
file_truncate(file, fd);
CHECK(close(fd) != -1);
free(path);
}
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) != -1);
/* 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->deleted && !file->fds) {
/* Closing deleted file */
struct write_info *w, *next;
w = file->writes;
while (w) {
next = w->next;
free(w);
w = next;
}
free(file->name);
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;
srand(w->random_seed);
for (r = 0; r < w->random_offset; ++r)
rand();
CHECK(lseek(fd, w->offset, SEEK_SET) != (off_t) -1);
remains = w->size;
written = BUFFER_SIZE;
while (remains) {
/* Fill up buffer with random data */
if (written < BUFFER_SIZE)
memmove(buf, buf + written, BUFFER_SIZE - written);
else
written = 0;
for (; written < BUFFER_SIZE; ++written)
buf[written] = rand();
/* Write a block of data */
if (remains > BUFFER_SIZE)
block = 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[BUFFER_SIZE];
char name[256];
/* Open file to save contents to */
strcpy(name, "/tmp/");
strcat(name, file->name);
strcat(name, ".integ.sav.read");
fprintf(stderr, "Saving %s\n", 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, BUFFER_SIZE);
CHECK(r != -1);
if (!r)
break;
CHECK(write(w_fd, buf, r) == r);
}
CHECK(close(w_fd) != -1);
/* Open file to save contents to */
strcpy(name, "/tmp/");
strcat(name, file->name);
strcat(name, ".integ.sav.written");
fprintf(stderr, "Saving %s\n", 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) != -1);
}
static void file_check_hole( struct file_info *file,
int fd, off_t offset,
size_t size)
{
size_t remains, block, i;
char buf[BUFFER_SIZE];
CHECK(lseek(fd, offset, SEEK_SET) != (off_t) -1);
remains = size;
while (remains) {
if (remains > BUFFER_SIZE)
block = BUFFER_SIZE;
else
block = remains;
CHECK(read(fd, buf, block) == block);
for (i = 0; i < block; ++i) {
if (buf[i] != 0) {
fprintf(stderr, "file_check_hole failed at %u "
"checking hole at %u size %u\n",
(unsigned) (size - remains + i),
(unsigned) offset,
(unsigned) 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;
char buf[BUFFER_SIZE];
srand(w->random_seed);
for (r = 0; r < w->random_offset; ++r)
rand();
CHECK(lseek(fd, w->offset, SEEK_SET) != (off_t) -1);
remains = w->size;
while (remains) {
if (remains > BUFFER_SIZE)
block = BUFFER_SIZE;
else
block = remains;
CHECK(read(fd, buf, block) == block);
for (i = 0; i < block; ++i) {
char c = (char) rand();
if (buf[i] != c) {
fprintf(stderr, "file_check_data failed at %u "
"checking data at %u size %u\n",
(unsigned) (w->size - remains + i),
(unsigned) w->offset,
(unsigned) w->size);
file_info_display(file);
save_file(fd, file);
}
CHECK(buf[i] == c);
}
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;
/* Do not check files that have errored */
if (!check_nospc_files && 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_and_close = 1;
if (open_and_close) {
/* Open file */
path = dir_path(file->links->parent, file->links->name);
fd = open(path, O_RDONLY);
CHECK(fd != -1);
}
/* Check length */
pos = lseek(fd, 0, SEEK_END);
if (pos != file->length) {
fprintf(stderr, "file_check failed checking length "
"expected %u actual %u\n",
(unsigned) file->length,
(unsigned) 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) != -1);
CHECK(file->link_count == st.st_nlink);
if (open_and_close) {
CHECK(close(fd) != -1);
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 copy_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;
path = dir_path(symlink->entry->parent, symlink->entry->name);
CHECK(lstat(path, &st1) != -1);
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 != -1) {
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_array, **p;
size_t sz, n;
struct dir_entry_info *entry;
DIR *d;
struct dirent *ent;
unsigned checked = 0;
char *path;
int link_count = 2; /* Parent and dot */
struct stat st;
/* Create an array of entries */
sz = sizeof(struct dir_entry_info *);
n = dir->number_of_entries;
entry_array = (struct dir_entry_info **) 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 */
path = dir_path(dir->parent, dir->name);
d = opendir(path);
CHECK(d != NULL);
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;
}
}
CHECK(closedir(d) != -1);
CHECK(checked == dir->number_of_entries);
/* Now check each entry */
entry = dir->first;
while (entry) {
if (entry->type == 'd') {
dir_check(entry->entry.dir);
link_count += 1; /* <subdir>/.. */
} else if (entry->type == 'f')
file_check(entry->entry.file, -1);
else if (entry->type == 's')
symlink_check(entry->entry.symlink);
else
CHECK(0);
entry = entry->next;
}
CHECK(stat(path, &st) != -1);
CHECK(link_count == st.st_nlink);
free(entry_array);
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->deleted)
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)
{
static char name[256];
struct dir_entry_info *entry;
int found;
do {
found = 0;
if (tests_random_no(5) == 1) {
int i, n = tests_random_no(tests_max_fname_len) + 1;
CHECK(n > 0 && n < 256);
for (i = 0; i < n; i++)
name[i] = 'a' + tests_random_no(26);
name[i] = '\0';
} else
sprintf(name, "%u", (unsigned) tests_random_no(1000000));
for (entry = dir->first; entry; entry = entry->next) {
if (strcmp(entry->name, name) == 0) {
found = 1;
break;
}
}
} while (found);
return name;
}
static struct file_info *pick_file(void)
{
struct dir_info *dir = top_dir;
for (;;) {
struct dir_entry_info *entry;
size_t r;
r = tests_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->entry.file;
if (entry->type == 'd')
if (entry->entry.dir->number_of_entries != 0)
break;
entry = entry->next;
}
dir = entry->entry.dir;
}
}
static struct dir_info *pick_dir(void)
{
struct dir_info *dir = top_dir;
if (tests_random_no(40) >= 30)
return dir;
for (;;) {
struct dir_entry_info *entry;
size_t r;
r = tests_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->entry.dir;
if (tests_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;
size_t r;
*parent = dir;
*rename_entry = NULL;
if (grow || tests_random_no(20) < 10)
return copy_string(make_name(dir));
r = tests_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->entry.dir->number_of_entries != 0))
return copy_string(make_name(dir));
if ((isdir && entry->type != 'd') ||
(!isdir && entry->type == 'd'))
return copy_string(make_name(dir));
*rename_entry = entry;
return copy_string(entry->name);
}
static void 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;
if (!entry->parent)
return;
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->entry.dir)
break;
if (p == entry->entry.dir) {
free(path);
free(name);
free(to);
path = NULL;
continue;
}
}
break;
}
if (!path)
return;
ret = rename(path, to);
if (ret == -1) {
if (errno == ENOSPC)
full = 1;
CHECK(errno == ENOSPC || errno == EBUSY);
free(path);
free(name);
free(to);
return;
}
free(path);
free(to);
if (rename_entry && rename_entry->type == entry->type &&
rename_entry->entry.target == entry->entry.target) {
free(name);
return;
}
add_dir_entry(parent, entry->type, name, entry->entry.target);
if (rename_entry)
remove_dir_entry(rename_entry);
remove_dir_entry(entry);
free(name);
}
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 copy_string(p2);
if (up == 0 && len2 == 0) {
p2 = strrchr(path2, '/');
return copy_string(p2);
}
if (up == 1 && len2 == 0)
return copy_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;
size_t r;
char *path, *rel_path;
dir = pick_dir();
if (tests_random_no(100) < 10)
return dir_path(dir, make_name(dir));
r = tests_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 (tests_random_no(20) < 10)
return path;
rel_path = relative_path(symlink_path, path);
free(path);
return rel_path;
}
static void symlink_new(struct dir_info *dir, const char *name_)
{
struct symlink_info *s;
char *path, *target, *name = copy_string(name_);
size_t sz;
path = dir_path(dir, name);
target = pick_symlink_target(path);
if (symlink(target, path) == -1) {
CHECK(errno == ENOSPC || errno == ENAMETOOLONG);
if (errno == ENOSPC)
full = 1;
free(target);
free(path);
free(name);
return;
}
free(path);
sz = sizeof(struct symlink_info);
s = malloc(sz);
CHECK(s != NULL);
memset(s, 0, sz);
add_dir_entry(dir, 's', name, s);
s->target_pathname = target;
free(name);
}
static void symlink_remove(struct symlink_info *symlink)
{
char *path;
path = dir_path(symlink->entry->parent, symlink->entry->name);
remove_dir_entry(symlink->entry);
CHECK(unlink(path) != -1);
free(path);
}
static void operate_on_dir(struct dir_info *dir);
static void operate_on_file(struct file_info *file);
/* Randomly select something to do with a directory entry */
static void operate_on_entry(struct dir_entry_info *entry)
{
/* 1 time in 1000 rename */
if (tests_random_no(1000) == 0) {
rename_entry(entry);
return;
}
if (entry->type == 's') {
symlink_check(entry->entry.symlink);
/* If shrinking, 1 time in 50, remove a symlink */
if (shrink && tests_random_no(50) == 0)
symlink_remove(entry->entry.symlink);
return;
}
if (entry->type == 'd') {
/* If shrinking, 1 time in 50, remove a directory */
if (shrink && tests_random_no(50) == 0) {
dir_remove(entry->entry.dir);
return;
}
operate_on_dir(entry->entry.dir);
}
if (entry->type == 'f') {
/* If shrinking, 1 time in 10, remove a file */
if (shrink && tests_random_no(10) == 0) {
file_delete(entry->entry.file);
return;
}
/* If not growing, 1 time in 10, unlink a file with links > 1 */
if (!grow && entry->entry.file->link_count > 1 &&
tests_random_no(10) == 0) {
file_unlink_file(entry->entry.file);
return;
}
operate_on_file(entry->entry.file);
}
}
/* Randomly select something to do with a directory */
static void operate_on_dir(struct dir_info *dir)
{
size_t r;
struct dir_entry_info *entry;
struct file_info *file;
r = tests_random_no(14);
if (r == 0 && grow)
/* When growing, 1 time in 14 create a file */
file_new(dir, make_name(dir));
else if (r == 1 && grow)
/* When growing, 1 time in 14 create a directory */
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 */
link_new(dir, make_name(dir), file);
else if (r == 3 && grow && tests_random_no(5) == 0)
/* When growing, 1 time in 70 create a symbolic link */
symlink_new(dir, make_name(dir));
else {
/* Otherwise randomly select an entry to operate on */
r = tests_random_no(dir->number_of_entries);
entry = dir->first;
while (entry && r) {
entry = entry->next;
--r;
}
if (entry)
operate_on_entry(entry);
}
}
/* Randomly select something to do with a file */
static void operate_on_file(struct file_info *file)
{
/* Try to keep at least 10 files open */
if (open_files_count < 10) {
file_open(file);
return;
}
/* Try to keep about 20 files open */
if (open_files_count < 20 && tests_random_no(2) == 0) {
file_open(file);
return;
}
/* Try to keep up to 40 files open */
if (open_files_count < 40 && tests_random_no(20) == 0) {
file_open(file);
return;
}
/* Occasionly truncate */
if (shrink && tests_random_no(100) == 0) {
file_truncate_file(file);
return;
}
/* Mostly just write */
file_write_file(file);
/* Once in a while check it too */
if (tests_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);
}
}
}
/* Randomly select something to do with an open file */
static void operate_on_open_file(struct fd_info *fdi)
{
size_t r;
r = tests_random_no(1000);
if (shrink && r < 5)
file_truncate(fdi->file, fdi->fd);
else if (r < 21)
file_close(fdi);
else if (shrink && r < 121 && !fdi->file->deleted)
file_delete(fdi->file);
else {
file_write(fdi->file, fdi->fd);
if (r >= 999) {
if (tests_random_no(100) >= 50)
CHECK(fsync(fdi->fd) != -1);
else
CHECK(fdatasync(fdi->fd) != -1);
}
}
}
/* Select an open file at random */
static void operate_on_an_open_file(void)
{
size_t 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;
}
}
/* Close any open files that have errored */
if (!check_nospc_files) {
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 = tests_random_no(open_files_count);
for (ofi = open_files; ofi; ofi = ofi->next, --r)
if (!r) {
operate_on_open_file(ofi->fdi);
return;
}
}
static void do_an_operation(void)
{
/* Half the time operate on already open files */
if (tests_random_no(100) < 50)
operate_on_dir(top_dir);
else
operate_on_an_open_file();
}
static void create_test_data(void)
{
uint64_t i, n;
grow = 1;
shrink = 0;
full = 0;
operation_count = 0;
while (!full) {
do_an_operation();
++operation_count;
}
grow = 0;
shrink = 1;
/* Drop to less than 90% full */
n = operation_count / 40;
while (n--) {
uint64_t free;
uint64_t total;
for (i = 0; i < 10; ++i)
do_an_operation();
free = tests_get_free_space();
total = tests_get_total_space();
if ((free * 100) / total >= 10)
break;
}
grow = 0;
shrink = 0;
full = 0;
n = operation_count * 2;
for (i = 0; i < n; ++i)
do_an_operation();
}
static void update_test_data(void)
{
uint64_t i, n;
grow = 1;
shrink = 0;
full = 0;
while (!full)
do_an_operation();
grow = 0;
shrink = 1;
/* Drop to less than 50% full */
n = operation_count / 10;
while (n--) {
uint64_t free;
uint64_t total;
for (i = 0; i < 10; ++i)
do_an_operation();
free = tests_get_free_space();
total = tests_get_total_space();
if ((free * 100) / total >= 50)
break;
}
grow = 0;
shrink = 0;
full = 0;
n = operation_count * 2;
for (i = 0; i < n; ++i)
do_an_operation();
}
void integck(void)
{
pid_t pid;
int64_t rpt;
uint64_t z;
char dir_name[256];
/* Get memory page size for mmap */
mem_page_size = sysconf(_SC_PAGE_SIZE);
CHECK(mem_page_size > 0);
/* Make our top directory */
pid = getpid();
printf("pid is %u\n", (unsigned) pid);
tests_cat_pid(dir_name, "integck_test_dir_", pid);
if (chdir(dir_name) != -1) {
/* Remove it if it is already there */
tests_clear_dir(".");
CHECK(chdir("..") != -1);
CHECK(rmdir(dir_name) != -1);
}
initial_free_space = tests_get_free_space();
log10_initial_free_space = 0;
for (z = initial_free_space; z >= 10; z /= 10)
++log10_initial_free_space;
top_dir = dir_new(NULL, dir_name);
if (!top_dir)
return;
srand(pid);
create_test_data();
if (!tests_fs_is_rootfs()) {
close_open_files();
tests_remount(); /* Requires root access */
}
/* Check everything */
check_run_no += 1;
dir_check(top_dir);
check_deleted_files();
for (rpt = 0; tests_repeat_parameter == 0 ||
rpt < tests_repeat_parameter; ++rpt) {
update_test_data();
if (!tests_fs_is_rootfs()) {
close_open_files();
tests_remount(); /* Requires root access */
}
/* Check everything */
check_run_no += 1;
dir_check(top_dir);
check_deleted_files();
}
/* Tidy up by removing everything */
close_open_files();
tests_clear_dir(dir_name);
CHECK(rmdir(dir_name) != -1);
}
/* Title of this test */
const char *integck_get_title(void)
{
return "Test file system integrity";
}
/* Description of this test */
const char *integck_get_description(void)
{
return
"Create a directory named integck_test_dir_pid " \
"where pid is the process id. " \
"Randomly create and delete files and directories. " \
"Randomly write to and truncate files. " \
"Un-mount and re-mount test file " \
"system (if it is not the root file system ). " \
"Check data. Make more random changes. " \
"Un-mount and re-mount again. Check again. " \
"Repeat some number of times. "
"The repeat count is set by the -n or --repeat option, " \
"otherwise it defaults to 1. " \
"A repeat count of zero repeats forever.";
}
int main(int argc, char *argv[])
{
int run_test;
/* Set default test repetition */
tests_repeat_parameter = 1;
/* Handle common arguments */
run_test = tests_get_args(argc, argv, integck_get_title(),
integck_get_description(), "n");
if (!run_test)
return 1;
/* Change directory to the file system and check it is ok for testing */
tests_check_test_file_system();
/*
* We expect accurate file size from ubifs even after "no space"
* errors. And we can mmap.
*/
if (strcmp(tests_file_system_type, "ubifs") == 0) {
check_nospc_files = 1;
can_mmap = 1;
}
/* Do the actual test */
integck();
return 0;
}