diff options
Diffstat (limited to 'tests/fs-tests/integrity')
| -rw-r--r-- | tests/fs-tests/integrity/Makefile | 22 | ||||
| -rw-r--r-- | tests/fs-tests/integrity/integck.c | 1395 | 
2 files changed, 1417 insertions, 0 deletions
| diff --git a/tests/fs-tests/integrity/Makefile b/tests/fs-tests/integrity/Makefile new file mode 100644 index 0000000..a35f4d0 --- /dev/null +++ b/tests/fs-tests/integrity/Makefile @@ -0,0 +1,22 @@ + +ifeq ($(origin CC),default) +CC = gcc +endif + +CFLAGS := $(CFLAGS) -Wall -g -O2 -I../lib + +LDFLAGS := $(LDFLAGS) + +TARGETS = integck + +all: $(TARGETS) + +$(TARGETS): ../lib/tests.o + +../lib/tests.o: ../lib/tests.h + +clean: +	rm -f *.o $(TARGETS) + +tests: all +	./integck diff --git a/tests/fs-tests/integrity/integck.c b/tests/fs-tests/integrity/integck.c new file mode 100644 index 0000000..23ad9bc --- /dev/null +++ b/tests/fs-tests/integrity/integck.c @@ -0,0 +1,1395 @@ +/* + * 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 "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 */ +}; + +struct file_info /* Each file has one of these */ +{ +	char *name; +	struct dir_info *parent; /* Parent directory */ +	struct write_info *writes; /* Record all writes to the file */ +	struct fd_info *fds; /* All open file descriptors for this file */ +	off_t length; +	int deleted; /* File has been deleted but is still open */ +	int no_space_error; /* File has incurred a ENOSPC error */ +}; + +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 /* Each entry in a directory has one of these */ +{ +	struct dir_entry_info *next; +	char type; /* f => file, d=> dir */ +	int checked; /* Temporary flag used when checking */ +	union entry_ +	{ +		struct file_info *file; +		struct dir_info *dir; +	} 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 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 struct dir_entry_info *dir_entry_new(void) +{ +	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); +	return entry; +} + +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 *fd_new(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 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) { +		struct dir_entry_info *entry; + +		entry = dir_entry_new(); +		entry->type = 'd'; +		entry->entry.dir = dir; +		entry->next = parent->first; +		parent->first = entry; +		parent->number_of_entries += 1; +	} +	return dir; +} + +static void file_delete(struct file_info *file); + +static void dir_remove(struct dir_info *dir) +{ +	char *path; +	struct dir_entry_info *entry; +	struct dir_entry_info **prev; +	int found; + +	/* 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_delete(entry->entry.file); +		else +			CHECK(0); /* Invalid struct dir_entry_info */ +	} +	/* Remove entry from parent directory */ +	found = 0; +	prev = &dir->parent->first; +	for (entry = dir->parent->first; entry; entry = entry->next) { +		if (entry->type == 'd' && entry->entry.dir == dir) { +			dir->parent->number_of_entries -= 1; +			*prev = entry->next; +			free(entry); +			found = 1; +			break; +		} +		prev = &entry->next; +	} +	CHECK(found); /* Check the file is in the parent directory */ +	/* Remove directory itself */ +	path = dir_path(dir->parent, dir->name); +	CHECK(rmdir(path) != -1); +} + +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; +	struct dir_entry_info *entry; + +	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); +	file->parent = parent; + +	fd_new(file, fd); + +	entry = dir_entry_new(); +	entry->type = 'f'; +	entry->entry.file = file; +	entry->next = parent->first; +	parent->first = entry; +	parent->number_of_entries += 1; + +	return file; +} + +static void file_delete(struct file_info *file) +{ +	char *path; +	struct dir_entry_info *entry; +	struct dir_entry_info **prev; +	int found; + +	/* Remove file entry from parent directory */ +	found = 0; +	prev = &file->parent->first; +	for (entry = file->parent->first; entry; entry = entry->next) { +		if (entry->type == 'f' && entry->entry.file == file) { +			file->parent->number_of_entries -= 1; +			*prev = entry->next; +			free(entry); +			found = 1; +			break; +		} +		prev = &entry->next; +	} +	CHECK(found); /* Check the file is in the parent directory */ + +	/* Delete the file */ +	path = dir_path(file->parent, file->name); +	CHECK(unlink(path) != -1); +	free(path); + +	/* Free struct file_info if file is not open */ +	if (!file->fds) { +		struct write_info *w, *next; + +		free(file->name); +		w = file->writes; +		while (w) { +			next = w->next; +			free(w); +			w = next; +		} +		free(file); +	} else +		file->deleted = 1; +} + +static void file_info_display(struct file_info *file) +{ +	struct write_info *w; +	unsigned wcnt; + +	fprintf(stderr, "File Info:\n"); +	fprintf(stderr, "    Name: %s\n", file->name); +	fprintf(stderr, "    Directory: %s\n", file->parent->name); +	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, "    Write Info:\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"); +} + +static struct fd_info *file_open(struct file_info *file) +{ +	int fd; +	char *path; + +	path = dir_path(file->parent, file->name); +	fd = open(path, O_RDWR); +	CHECK(fd != -1); +	free(path); +	return fd_new(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); +		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; + +	/* 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 void file_close(struct fd_info *fdi); + +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 (!file->deleted) { +			struct fd_info *fdi; + +			fdi = file->fds; +			while (fdi) { +				file_close(fdi); +				fdi = file->fds; +			} +			file_delete(file); +		} +		return 0; +	} +	return 1; +} + +static void file_write(struct file_info *file, int fd) +{ +	off_t offset; +	size_t size, actual; +	unsigned seed; +	int truncate = 0; + +	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 (file->no_space_error) { +		if (!file->deleted) { +			struct fd_info *fdi; + +			fdi = file->fds; +			while (fdi) { +				file_close(fdi); +				fdi = file->fds; +			} +			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->parent, file->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; + +	/* 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; +	} +	/* 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->parent, file->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; +	char *path = NULL; +	off_t pos; +	struct write_info *w; + +	/* Do not check files that have errored */ +	if (file->no_space_error) +		return; +	if (fd == -1) +		open_and_close = 1; +	if (open_and_close) { +		/* Open file */ +		path = dir_path(file->parent, file->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); +	if (open_and_close) { +		CHECK(close(fd) != -1); +		free(path); +	} +} + +static const char *dir_entry_name(const struct dir_entry_info *entry) +{ +	CHECK(entry != NULL); +	if (entry->type == 'd') +		return entry->entry.dir->name; +	else if (entry->type == 'f') +		return entry->entry.file->name; +	else { +		CHECK(0); +		return NULL; +	} +} + +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, dir_entry_name(b)); +} + +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(dir_entry_name(a), dir_entry_name(b)); +} + +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; + +	/* 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); +	free(path); + +	/* Now check each entry */ +	entry = dir->first; +	while (entry) { +		if (entry->type == 'd') +			dir_check(entry->entry.dir); +		else if (entry->type == 'f') +			file_check(entry->entry.file, -1); +		else +			CHECK(0); +		entry = entry->next; +	} + +	free(entry_array); +} + +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; +		sprintf(name, "%u", (unsigned) tests_random_no(1000000)); +		for (entry = dir->first; entry; entry = entry->next) { +			if (strcmp(dir_entry_name(entry), name) == 0) { +				found = 1; +				break; +			} +		} +	} while (found); +	return name; +} + +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) +{ +	/* If shrinking, 1 time in 50, remove a directory */ +	if (entry->type == 'd') { +		if (shrink && tests_random_no(50) == 0) { +			dir_remove(entry->entry.dir); +			return; +		} +		operate_on_dir(entry->entry.dir); +	} +	/* If shrinking, 1 time in 10, remove a file */ +	if (entry->type == 'f') { +		if (shrink && tests_random_no(10) == 0) { +			file_delete(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; + +	r = tests_random_no(12); +	if (r == 0 && grow) +		/* When growing, 1 time in 12 create a file */ +		file_new(dir, make_name(dir)); +	else if (r == 1 && grow) +		/* When growing, 1 time in 12 create a directory */ +		dir_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); +} + +/* 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 == 0) +		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); +} + +/* Select an open file at random */ +static void operate_on_an_open_file(void) +{ +	size_t r; +	struct open_file_info *ofi; + +	/* Close any open files that have errored */ +	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; + +	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 */ +	for (;;) { +		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 < 90) +			break; +	} +	grow = 0; +	shrink = 0; +	full = 0; +	for (i = 0; i < operation_count * 2; ++i) +		do_an_operation(); +} + +static void update_test_data(void) +{ +	uint64_t i; + +	grow = 1; +	shrink = 0; +	full = 0; +	while (!full) +		do_an_operation(); +	grow = 0; +	shrink = 1; +	/* Drop to less than 50% full */ +	for (;;) { +		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; +	for (i = 0; i < operation_count * 2; ++i) +		do_an_operation(); +} + +void integck(void) +{ +	pid_t pid; +	int64_t rpt; +	uint64_t z; +	char dir_name[256]; + +	/* Make our top directory */ +	pid = getpid(); +	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 */ +	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 */ +		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(); +	/* Do the actual test */ +	integck(); +	return 0; +} | 
