diff options
36 files changed, 5702 insertions, 49 deletions
@@ -17,6 +17,10 @@ else BUILDDIR := $(CROSS:-=) endif +ifeq ($(WITHOUT_XATTR), 1) + CFLAGS += -DWITHOUT_XATTR +endif + RAWTARGETS = ftl_format flash_erase flash_eraseall nanddump doc_loadbios \ mkfs.jffs ftl_check mkfs.jffs2 flash_lock flash_unlock flash_info \ flash_otp_info flash_otp_dump mtd_debug flashcp nandwrite \ diff --git a/feature-removal-schedule.txt b/feature-removal-schedule.txt new file mode 100644 index 0000000..8667424 --- /dev/null +++ b/feature-removal-schedule.txt @@ -0,0 +1,14 @@ +The following is a list of files and features that are going to be +removed in the mtd-utils source tree. Every entry should contain what +exactly is going away, why it is happening, and who is going to be doing +the work. When the feature is removed from the utils, it should also +be removed from this file. + +--------------------------- + +What: mkfs.jffs.c +When: Shortly after kernel 2.6.21 is released +Why: Support for the JFFS filesystem is being removed from the kernel +Who: Josh Boyer <jwboyer@gmail.com> + +--------------------------- diff --git a/include/mtd/mtd-abi.h b/include/mtd/mtd-abi.h index 7ccadb1..aaae170 100644 --- a/include/mtd/mtd-abi.h +++ b/include/mtd/mtd-abi.h @@ -7,11 +7,6 @@ #ifndef __MTD_ABI_H__ #define __MTD_ABI_H__ -#ifndef __KERNEL__ /* Urgh. The whole point of splitting this out into - separate files was to avoid #ifdef __KERNEL__ */ -#define __user -#endif - struct erase_info_user { uint32_t start; uint32_t length; @@ -20,18 +15,20 @@ struct erase_info_user { struct mtd_oob_buf { uint32_t start; uint32_t length; - unsigned char __user *ptr; + unsigned char *ptr; }; #define MTD_ABSENT 0 +#define MTD_RAM 1 +#define MTD_ROM 2 #define MTD_NORFLASH 3 #define MTD_NANDFLASH 4 #define MTD_DATAFLASH 6 -#define MTD_GENERIC_TYPE 7 #define MTD_WRITEABLE 0x400 /* Device is writeable */ #define MTD_BIT_WRITEABLE 0x800 /* Single bits can be flipped */ #define MTD_NO_ERASE 0x1000 /* No erase necessary */ +#define MTD_STUPID_LOCK 0x2000 /* Always locked after reset */ // Some common devices / combinations of capabilities #define MTD_CAP_ROM 0 @@ -39,12 +36,6 @@ struct mtd_oob_buf { #define MTD_CAP_NORFLASH (MTD_WRITEABLE | MTD_BIT_WRITEABLE) #define MTD_CAP_NANDFLASH (MTD_WRITEABLE) - -// Types of automatic ECC/Checksum available -#define MTD_ECC_NONE 0 // No automatic ECC available -#define MTD_ECC_RS_DiskOnChip 1 // Automatic ECC on DiskOnChip -#define MTD_ECC_SW 2 // SW ECC for Toshiba & Samsung devices - /* ECC byte placement */ #define MTD_NANDECC_OFF 0 // Switch off ECC (Not recommended) #define MTD_NANDECC_PLACE 1 // Use the given placement in the structure (YAFFS1 legacy mode) @@ -64,6 +55,8 @@ struct mtd_info_user { uint32_t erasesize; uint32_t writesize; uint32_t oobsize; // Amount of OOB data per block (e.g. 16) + /* The below two fields are obsolete and broken, do not use them + * (TODO: remove at some point) */ uint32_t ecctype; uint32_t eccsize; }; @@ -131,7 +124,7 @@ struct nand_ecclayout { }; /** - * struct mtd_ecc_stats - error correction status + * struct mtd_ecc_stats - error correction stats * * @corrected: number of corrected bits * @failed: number of uncorrectable errors diff --git a/mkfs.jffs2.c b/mkfs.jffs2.c index 468f89e..bc9901e 100644 --- a/mkfs.jffs2.c +++ b/mkfs.jffs2.c @@ -64,8 +64,10 @@ #include <ctype.h> #include <time.h> #include <getopt.h> +#ifndef WITHOUT_XATTR #include <sys/xattr.h> #include <sys/acl.h> +#endif #include <byteswap.h> #define crc32 __complete_crap #include <zlib.h> @@ -1030,6 +1032,7 @@ static void write_special_file(struct filesystem_entry *e) padword(); } +#ifndef WITHOUT_XATTR typedef struct xattr_entry { struct xattr_entry *next; uint32_t xid; @@ -1259,6 +1262,10 @@ static void write_xattr_entry(struct filesystem_entry *e) } } +#else /* WITHOUT_XATTR */ +#define write_xattr_entry(x) +#endif + static void recursive_populate_directory(struct filesystem_entry *dir) { struct filesystem_entry *e; @@ -1416,9 +1423,11 @@ static struct option long_options[] = { {"test-compression", 0, NULL, 't'}, {"compressor-priority", 1, NULL, 'y'}, {"incremental", 1, NULL, 'i'}, +#ifndef WITHOUT_XATTR {"with-xattr", 0, NULL, 1000 }, {"with-selinux", 0, NULL, 1001 }, {"with-posix-acl", 0, NULL, 1002 }, +#endif {NULL, 0, NULL, 0} }; @@ -1451,9 +1460,11 @@ static char *helptext = " -q, --squash Squash permissions and owners making all files be owned by root\n" " -U, --squash-uids Squash owners making all files be owned by root\n" " -P, --squash-perms Squash permissions on all files\n" +#ifndef WITHOUT_XATTR " --with-xattr stuff all xattr entries into image\n" " --with-selinux stuff only SELinux Labels into jffs2 image\n" " --with-posix-acl stuff only POSIX ACL entries into jffs2 image\n" +#endif " -h, --help Display this help text\n" " -v, --verbose Verbose operation\n" " -V, --version Display version information\n" @@ -1772,6 +1783,7 @@ int main(int argc, char **argv) perror_msg_and_die("cannot open (incremental) file"); } break; +#ifndef WITHOUT_XATTR case 1000: /* --with-xattr */ enable_xattr |= (1 << JFFS2_XPREFIX_USER) | (1 << JFFS2_XPREFIX_SECURITY) @@ -1786,6 +1798,7 @@ int main(int argc, char **argv) enable_xattr |= (1 << JFFS2_XPREFIX_ACL_ACCESS) | (1 << JFFS2_XPREFIX_ACL_DEFAULT); break; +#endif } } if (out_fd == -1) { diff --git a/mtd_debug.c b/mtd_debug.c index e0cc7d7..b44e952 100644 --- a/mtd_debug.c +++ b/mtd_debug.c @@ -3,8 +3,6 @@ * Written by Abraham vd Merwe <abraham@2d3d.co.za> * All rights reserved. * - * $Id: mtd_debug.c,v 1.5 2004/05/05 11:57:55 dwmw2 Exp $ - * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -259,14 +257,20 @@ int showinfo (int fd) case MTD_ABSENT: printf ("MTD_ABSENT"); break; + case MTD_RAM: + printf ("MTD_RAM"); + break; + case MTD_ROM: + printf ("MTD_ROM"); + break; case MTD_NORFLASH: printf ("MTD_NORFLASH"); break; case MTD_NANDFLASH: printf ("MTD_NANDFLASH"); break; - case MTD_GENERIC_TYPE: - printf ("MTD_GENERIC_TYPE"); + case MTD_DATAFLASH: + printf ("MTD_DATAFLASH"); break; default: printf ("(unknown type - new MTD API maybe?)"); @@ -320,22 +324,6 @@ int showinfo (int fd) printf ("\nmtd.oobsize = "); printsize (mtd.oobsize); - printf ("\nmtd.ecctype = "); - switch (mtd.ecctype) - { - case MTD_ECC_NONE: - printf ("MTD_ECC_NONE"); - break; - case MTD_ECC_RS_DiskOnChip: - printf ("MTD_ECC_RS_DiskOnChip"); - break; - case MTD_ECC_SW: - printf ("MTD_ECC_SW"); - break; - default: - printf ("(unknown ECC type - new MTD API maybe?)"); - } - printf ("\n" "regions = %d\n" "\n", @@ -91,6 +91,7 @@ void process_options (int argc, char *argv[]) static const struct option long_options[] = { {"help", no_argument, 0, 0}, {"version", no_argument, 0, 0}, + {"file", required_argument, 0, 'f'}, {"ignoreerrors", no_argument, 0, 'i'}, {"prettyprint", no_argument, 0, 'p'}, {"omitoob", no_argument, 0, 'o'}, @@ -170,7 +171,7 @@ int main(int argc, char **argv) { unsigned long ofs, end_addr = 0; unsigned long long blockstart = 1; - int i, fd, ofd, bs, badblock = 0; + int ret, i, fd, ofd, bs, badblock = 0; struct mtd_oob_buf oob = {0, 16, oobbuf}; mtd_info_t meminfo; char pretty_buf[80]; @@ -196,6 +197,7 @@ int main(int argc, char **argv) /* Make sure device page sizes are valid */ if (!(meminfo.oobsize == 64 && meminfo.writesize == 2048) && + !(meminfo.oobsize == 32 && meminfo.writesize == 1024) && !(meminfo.oobsize == 16 && meminfo.writesize == 512) && !(meminfo.oobsize == 8 && meminfo.writesize == 256)) { fprintf(stderr, "Unknown flash (not normal NAND)\n"); @@ -206,9 +208,12 @@ int main(int argc, char **argv) oob.length = meminfo.oobsize; if (noecc) { - switch (ioctl(fd, MTDFILEMODE, (void *) MTD_MODE_RAW)) { - - case -ENOTTY: + ret = ioctl(fd, MTDFILEMODE, (void *) MTD_MODE_RAW); + if (ret == 0) { + oobinfochanged = 2; + } else { + switch (errno) { + case ENOTTY: if (ioctl (fd, MEMGETOOBSEL, &old_oobinfo) != 0) { perror ("MEMGETOOBSEL"); close (fd); @@ -221,14 +226,11 @@ int main(int argc, char **argv) } oobinfochanged = 1; break; - - case 0: - oobinfochanged = 2; - break; default: perror ("MTDFILEMODE"); close (fd); exit (1); + } } } else { diff --git a/nandwrite.c b/nandwrite.c index f4c399d..f74581d 100644 --- a/nandwrite.c +++ b/nandwrite.c @@ -271,9 +271,12 @@ int main(int argc, char **argv) } if (noecc) { - switch (ioctl(fd, MTDFILEMODE, (void *) MTD_MODE_RAW)) { - - case -ENOTTY: + ret = ioctl(fd, MTDFILEMODE, (void *) MTD_MODE_RAW); + if (ret == 0) { + oobinfochanged = 2; + } else { + switch (errno) { + case ENOTTY: if (ioctl (fd, MEMGETOOBSEL, &old_oobinfo) != 0) { perror ("MEMGETOOBSEL"); close (fd); @@ -286,14 +289,11 @@ int main(int argc, char **argv) } oobinfochanged = 1; break; - - case 0: - oobinfochanged = 2; - break; default: perror ("MTDFILEMODE"); close (fd); exit (1); + } } } diff --git a/tests/fs-tests/Makefile b/tests/fs-tests/Makefile new file mode 100644 index 0000000..d188796 --- /dev/null +++ b/tests/fs-tests/Makefile @@ -0,0 +1,8 @@ + +SUBDIRS = lib simple stress integrity utils + +all clean tests: $(SUBDIRS) + +.PHONY: $(SUBDIRS) +$(SUBDIRS): + $(MAKE) -C $@ $(MAKECMDGOALS) diff --git a/tests/fs-tests/help_all.sh b/tests/fs-tests/help_all.sh new file mode 100755 index 0000000..34b890b --- /dev/null +++ b/tests/fs-tests/help_all.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +echo ------------------------------------------------------------------------------- +./simple/test_1 -h +echo ------------------------------------------------------------------------------- +./simple/test_2 -h +echo ------------------------------------------------------------------------------- +./stress/atoms/stress_1 -h +echo ------------------------------------------------------------------------------- +./stress/atoms/stress_2 -h +echo ------------------------------------------------------------------------------- +./stress/atoms/stress_3 -h +echo ------------------------------------------------------------------------------- +./stress/atoms/fwrite00 -h +echo ------------------------------------------------------------------------------- +./stress/atoms/gcd_hupper -h +echo ------------------------------------------------------------------------------- +./stress/atoms/pdfrun -h +echo ------------------------------------------------------------------------------- +./stress/atoms/rmdir00 -h +echo ------------------------------------------------------------------------------- +./stress/atoms/rndrm00 -h +echo ------------------------------------------------------------------------------- +./stress/atoms/rndwrite00 -h +echo ------------------------------------------------------------------------------- +./integrity/integck -h +echo ------------------------------------------------------------------------------- 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; +} diff --git a/tests/fs-tests/lib/Makefile b/tests/fs-tests/lib/Makefile new file mode 100644 index 0000000..8d57824 --- /dev/null +++ b/tests/fs-tests/lib/Makefile @@ -0,0 +1,18 @@ + +ifeq ($(origin CC),default) +CC = gcc +endif + +CFLAGS := $(CFLAGS) -Wall -g -O2 + +LDFLAGS := $(LDFLAGS) + +all: tests.o + +tests.o: tests.h + +clean: + rm -f *.o + +tests: + echo diff --git a/tests/fs-tests/lib/tests.c b/tests/fs-tests/lib/tests.c new file mode 100644 index 0000000..2f513d7 --- /dev/null +++ b/tests/fs-tests/lib/tests.c @@ -0,0 +1,1063 @@ +/* + * 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 <sys/vfs.h> +#include <sys/statvfs.h> +#include <linux/jffs2.h> +#if 0 +#include <linux/jffs3.h> +#endif +#include <libgen.h> +#include <dirent.h> +#include <ctype.h> +#include <limits.h> +#include <sys/mount.h> +#include <mntent.h> +#include <time.h> + +#include "tests.h" + +char *tests_file_system_mount_dir = TESTS_DEFAULT_FILE_SYSTEM_MOUNT_DIR; + +char *tests_file_system_type = TESTS_DEFAULT_FILE_SYSTEM_TYPE; + +int tests_ok_to_sync = 0; /* Whether to use fsync */ + +/* General purpose test parameter to specify some aspect of test size. + May be used by different tests in different ways or not at all. + Set by the -z or --size option. */ +int64_t tests_size_parameter = 0; + +/* General purpose test parameter to specify some aspect of test repetition. + May be used by different tests in different ways or not at all. + Set by the -n, --repeat options. */ +int64_t tests_repeat_parameter = 0; + +/* General purpose test parameter to specify some aspect of test sleeping. + May be used by different tests in different ways or not at all. + Set by the -p, --sleep options. */ +int64_t tests_sleep_parameter = 0; + +/* Program name from argv[0] */ +char *program_name = "unknown"; + +/* General purpose test parameter to specify a file should be unlinked. + May be used by different tests in different ways or not at all. */ +int tests_unlink_flag = 0; + +/* General purpose test parameter to specify a file should be closed. + May be used by different tests in different ways or not at all. */ +int tests_close_flag = 0; + +/* General purpose test parameter to specify a file should be deleted. + May be used by different tests in different ways or not at all. */ +int tests_delete_flag = 0; + +/* General purpose test parameter to specify a file have a hole. + May be used by different tests in different ways or not at all. */ +int tests_hole_flag = 0; + +/* Whether it is ok to test on the root file system */ +static int rootok = 0; + +/* Function invoked by the CHECK macro */ +void tests_test(int test,const char *msg,const char *file,unsigned line) +{ + int eno; + time_t t; + + if (test) + return; + eno = errno; + time(&t); + fprintf(stderr, "Test failed: %s on %s" + "Test failed: %s in %s at line %u\n", + program_name, ctime(&t), msg, file, line); + if (eno) { + fprintf(stderr,"errno = %d\n",eno); + fprintf(stderr,"strerror = %s\n",strerror(eno)); + } + exit(1); +} + +static int is_zero(const char *p) +{ + for (;*p;++p) + if (*p != '0') + return 0; + return 1; +} + +static void fold(const char *text, int width) +{ + int pos, bpos = 0; + const char *p; + char line[1024]; + + if (width > 1023) { + printf("%s\n", text); + return; + } + p = text; + pos = 0; + while (p[pos]) { + while (!isspace(p[pos])) { + line[pos] = p[pos]; + if (!p[pos]) + break; + ++pos; + if (pos == width) { + line[pos] = '\0'; + printf("%s\n", line); + p += pos; + pos = 0; + } + } + while (pos < width) { + line[pos] = p[pos]; + if (!p[pos]) { + bpos = pos; + break; + } + if (isspace(p[pos])) + bpos = pos; + ++pos; + } + line[bpos] = '\0'; + printf("%s\n", line); + p += bpos; + pos = 0; + while (p[pos] && isspace(p[pos])) + ++p; + } +} + +/* Handle common program options */ +int tests_get_args(int argc, + char *argv[], + const char *title, + const char *desc, + const char *opts) +{ + int run_test = 0; + int display_help = 0; + int display_title = 0; + int display_description = 0; + int i; + char *s; + + program_name = argv[0]; + + s = getenv("TEST_FILE_SYSTEM_MOUNT_DIR"); + if (s) + tests_file_system_mount_dir = strdup(s); + s = getenv("TEST_FILE_SYSTEM_TYPE"); + if (s) + tests_file_system_type = strdup(s); + + run_test = 1; + rootok = 1; + for (i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-h") == 0) + display_help = 1; + else if (strcmp(argv[i], "--title") == 0 || + strcmp(argv[i], "-t") == 0) + display_title = 1; + else if (strcmp(argv[i], "--description") == 0 || + strcmp(argv[i], "-d") == 0) + display_description = 1; + else if (strcmp(argv[i], "--sync") == 0 || + strcmp(argv[i], "-s") == 0) + tests_ok_to_sync = 1; + else if (strncmp(argv[i], "--size", 6) == 0 || + strncmp(argv[i], "-z", 2) == 0) { + int64_t n; + char *p; + if (i+1 < argc && !isdigit(argv[i][strlen(argv[i])-1])) + ++i; + p = argv[i]; + while (*p && !isdigit(*p)) + ++p; + n = atoll(p); + if (n) + tests_size_parameter = n; + else { + int all_zero = 1; + for (; all_zero && *p; ++p) + if (*p != '0') + all_zero = 0; + if (all_zero) + tests_size_parameter = 0; + else + display_help = 1; + } + } else if (strncmp(argv[i], "--repeat", 8) == 0 || + strncmp(argv[i], "-n", 2) == 0) { + int64_t n; + char *p; + if (i+1 < argc && !isdigit(argv[i][strlen(argv[i])-1])) + ++i; + p = argv[i]; + while (*p && !isdigit(*p)) + ++p; + n = atoll(p); + if (n || is_zero(p)) + tests_repeat_parameter = n; + else + display_help = 1; + } else if (strncmp(argv[i], "--sleep", 7) == 0 || + strncmp(argv[i], "-p", 2) == 0) { + int64_t n; + char *p; + if (i+1 < argc && !isdigit(argv[i][strlen(argv[i])-1])) + ++i; + p = argv[i]; + while (*p && !isdigit(*p)) + ++p; + n = atoll(p); + if (n || is_zero(p)) + tests_sleep_parameter = n; + else + display_help = 1; + } else if (strcmp(argv[i], "--unlink") == 0 || + strcmp(argv[i], "-u") == 0) + tests_unlink_flag = 1; + else if (strcmp(argv[i], "--hole") == 0 || + strcmp(argv[i], "-o") == 0) + tests_hole_flag = 1; + else if (strcmp(argv[i], "--close") == 0 || + strcmp(argv[i], "-c") == 0) + tests_close_flag = 1; + else if (strcmp(argv[i], "--delete") == 0 || + strcmp(argv[i], "-e") == 0) + tests_delete_flag = 1; + else + display_help = 1; + } + + if (display_help) { + run_test = 0; + display_title = 0; + display_description = 0; + if (!opts) + opts = ""; + printf("File System Test Program\n\n"); + printf("Test Title: %s\n\n", title); + printf("Usage is: %s [ options ]\n",argv[0]); + printf(" Options are:\n"); + printf(" -h, --help "); + printf("Display this help\n"); + printf(" -t, --title "); + printf("Display the test title\n"); + printf(" -d, --description "); + printf("Display the test description\n"); + if (strchr(opts, 's')) { + printf(" -s, --sync "); + printf("Make use of fsync\n"); + } + if (strchr(opts, 'z')) { + printf(" -z, --size "); + printf("Set size parameter\n"); + } + if (strchr(opts, 'n')) { + printf(" -n, --repeat "); + printf("Set repeat parameter\n"); + } + if (strchr(opts, 'p')) { + printf(" -p, --sleep "); + printf("Set sleep parameter\n"); + } + if (strchr(opts, 'u')) { + printf(" -u, --unlink "); + printf("Unlink file\n"); + } + if (strchr(opts, 'o')) { + printf(" -o, --hole "); + printf("Create a hole in a file\n"); + } + if (strchr(opts, 'c')) { + printf(" -c, --close "); + printf("Close file\n"); + } + if (strchr(opts, 'e')) { + printf(" -e, --delete "); + printf("Delete file\n"); + } + printf("\nBy default, testing is done in directory "); + printf("/mnt/test_file_system. To change this\nuse "); + printf("environmental variable "); + printf("TEST_FILE_SYSTEM_MOUNT_DIR. By default, "); + printf("the file\nsystem tested is jffs2. To change this "); + printf("set TEST_FILE_SYSTEM_TYPE.\n\n"); + printf("Test Description:\n"); + fold(desc, 80); + } else { + if (display_title) + printf("%s\n", title); + if (display_description) + printf("%s\n", desc); + if (display_title || display_description) + if (argc == 2 || (argc == 3 && + display_title && + display_description)) + run_test = 0; + } + return run_test; +} + +/* Return the number of files (or directories) in the given directory */ +unsigned tests_count_files_in_dir(const char *dir_name) +{ + DIR *dir; + struct dirent *entry; + unsigned count = 0; + + dir = opendir(dir_name); + CHECK(dir != NULL); + for (;;) { + errno = 0; + entry = readdir(dir); + if (entry) { + if (strcmp(".",entry->d_name) != 0 && + strcmp("..",entry->d_name) != 0) + ++count; + } else { + CHECK(errno == 0); + break; + } + } + CHECK(closedir(dir) != -1); + return count; +} + +/* Change to the file system mount directory, check that it is empty, + matches the file system type, and is not the root file system */ +void tests_check_test_file_system(void) +{ + struct statfs fs_info; + struct stat f_info; + struct stat root_f_info; + + if (chdir(tests_file_system_mount_dir) == -1 || + statfs(tests_file_system_mount_dir, &fs_info) == -1) { + fprintf(stderr, "Invalid test file system mount directory:" + " %s\n", tests_file_system_mount_dir); + fprintf(stderr, "Use environment variable " + "TEST_FILE_SYSTEM_MOUNT_DIR\n"); + CHECK(0); + } + if (strcmp(tests_file_system_type, "jffs2") != 0 && + strcmp(tests_file_system_type, "jffs3") != 0) { + fprintf(stderr, "Invalid test file system type:" + " %s\n", tests_file_system_type); + fprintf(stderr, "Must be jffs2 or jffs3\n"); + CHECK(0); + } + if (strcmp(tests_file_system_type, "jffs2") == 0 && + fs_info.f_type != JFFS2_SUPER_MAGIC) { + fprintf(stderr, "File system type is not jffs2\n"); + CHECK(0); + } +#ifdef JFFS3_SUPER_MAGIC + if (strcmp(tests_file_system_type, "jffs3") == 0 && + fs_info.f_type != JFFS3_SUPER_MAGIC) { + fprintf(stderr, "File system type is not jffs3\n"); + CHECK(0); + } +#endif + /* Check that the test file system is not the root file system */ + if (!rootok) { + CHECK(stat(tests_file_system_mount_dir, &f_info) != -1); + CHECK(stat("/", &root_f_info) != -1); + CHECK(f_info.st_dev != root_f_info.st_dev); + } +} + +/* Get the free space for the file system of the current directory */ +uint64_t tests_get_free_space(void) +{ + struct statvfs fs_info; + + CHECK(statvfs(tests_file_system_mount_dir, &fs_info) != -1); + return (uint64_t) fs_info.f_bavail * (uint64_t) fs_info.f_frsize; +} + +/* Get the total space for the file system of the current directory */ +uint64_t tests_get_total_space(void) +{ + struct statvfs fs_info; + + CHECK(statvfs(tests_file_system_mount_dir, &fs_info) != -1); + return (uint64_t) fs_info.f_blocks * (uint64_t) fs_info.f_frsize; +} + +#define WRITE_BUFFER_SIZE 32768 + +static char write_buffer[WRITE_BUFFER_SIZE]; + +static void init_write_buffer() +{ + static int init = 0; + + if (!init) { + int i, d; + uint64_t u; + + u = RAND_MAX; + u += 1; + u /= 256; + d = (int) u; + srand(1); + for (i = 0; i < WRITE_BUFFER_SIZE; ++i) + write_buffer[i] = rand() / d; + init = 1; + } +} + +/* Write size random bytes into file descriptor fd at the current position, + returning the number of bytes actually written */ +uint64_t tests_fill_file(int fd, uint64_t size) +{ + ssize_t written; + size_t sz; + unsigned start = 0, length; + uint64_t remains; + uint64_t actual_size = 0; + + init_write_buffer(); + remains = size; + while (remains > 0) { + length = WRITE_BUFFER_SIZE - start; + if (remains > length) + sz = length; + else + sz = (size_t) remains; + written = write(fd, write_buffer + start, sz); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + break; + } + remains -= written; + actual_size += written; + if (written == sz) + start = 0; + else + start += written; + } + tests_maybe_sync(fd); + return actual_size; +} + +/* Write size random bytes into file descriptor fd at offset, + returning the number of bytes actually written */ +uint64_t tests_write_filled_file(int fd, off_t offset, uint64_t size) +{ + ssize_t written; + size_t sz; + unsigned start = 0, length; + uint64_t remains; + uint64_t actual_size = 0; + + CHECK(lseek(fd, offset, SEEK_SET) == offset); + + init_write_buffer(); + remains = size; + start = offset % WRITE_BUFFER_SIZE; + while (remains > 0) { + length = WRITE_BUFFER_SIZE - start; + if (remains > length) + sz = length; + else + sz = (size_t) remains; + written = write(fd, write_buffer + start, sz); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + break; + } + remains -= written; + actual_size += written; + if (written == sz) + start = 0; + else + start += written; + } + tests_maybe_sync(fd); + return actual_size; +} + +/* Check that a file written using tests_fill_file() and/or + tests_write_filled_file() and/or tests_create_file() + contains the expected random data */ +void tests_check_filled_file_fd(int fd) +{ + ssize_t sz; + char buf[WRITE_BUFFER_SIZE]; + + do { + sz = read(fd,buf,WRITE_BUFFER_SIZE); + CHECK(sz >= 0); + CHECK(memcmp(buf,write_buffer,sz) == 0); + } while (sz); +} + +/* Check that a file written using tests_fill_file() and/or + tests_write_filled_file() and/or tests_create_file() + contains the expected random data */ +void tests_check_filled_file(const char *file_name) +{ + int fd; + + fd = open(file_name, O_RDONLY); + CHECK(fd != -1); + tests_check_filled_file_fd(fd); + CHECK(close(fd) != -1); +} + +void tests_sync_directory(const char *file_name) +{ + char *path; + char *dir; + int fd; + + if (!tests_ok_to_sync) + return; + + path = strdup(file_name); + dir = dirname(path); + fd = open(dir,O_RDONLY | tests_maybe_sync_flag()); + CHECK(fd != -1); + CHECK(fsync(fd) != -1); + CHECK(close(fd) != -1); + free(path); +} + +/* Delete a file */ +void tests_delete_file(const char *file_name) +{ + CHECK(unlink(file_name) != -1); + tests_sync_directory(file_name); +} + +/* Create a file of size file_size */ +uint64_t tests_create_file(const char *file_name, uint64_t file_size) +{ + int fd; + int flags; + mode_t mode; + uint64_t actual_size; /* Less than size if the file system is full */ + + flags = O_CREAT | O_TRUNC | O_WRONLY | tests_maybe_sync_flag(); + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; + fd = open(file_name, flags, mode); + if (fd == -1 && errno == ENOSPC) { + errno = 0; + return 0; /* File system full */ + } + CHECK(fd != -1); + actual_size = tests_fill_file(fd, file_size); + CHECK(close(fd) != -1); + if (file_size != 0 && actual_size == 0) + tests_delete_file(file_name); + else + tests_sync_directory(file_name); + return actual_size; +} + +/* Calculate: free_space * numerator / denominator */ +uint64_t tests_get_big_file_size(unsigned numerator, unsigned denominator) +{ + if (denominator == 0) + denominator = 1; + if (numerator > denominator) + numerator = denominator; + return numerator * (tests_get_free_space() / denominator); +} + +/* Write size bytes at offset to the file "fragment_n" where n is the + file_number and file_number also determines the random data written + i.e. seed for random numbers */ +unsigned tests_write_fragment_file(unsigned file_number, + int fd, + off_t offset, + unsigned size) +{ + int i, d; + uint64_t u; + ssize_t written; + off_t pos; + char buf[WRITE_BUFFER_SIZE]; + + if (size > WRITE_BUFFER_SIZE) + size = WRITE_BUFFER_SIZE; + + pos = lseek(fd, 0, SEEK_END); + CHECK(pos != (off_t) -1); + if (offset > pos) + offset = pos; + + pos = lseek(fd, offset, SEEK_SET); + CHECK(pos != (off_t) -1); + CHECK(pos == offset); + + srand(file_number); + while (offset--) + rand(); + + u = RAND_MAX; + u += 1; + u /= 256; + d = (int) u; + for (i = 0; i < size; ++i) + buf[i] = rand() / d; + + written = write(fd, buf, size); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + written = 0; + } + tests_maybe_sync(fd); + return (unsigned) written; +} + +/* Write size bytes to the end of file descriptor fd using file_number + to determine the random data written i.e. seed for random numbers */ +unsigned tests_fill_fragment_file(unsigned file_number, int fd, unsigned size) +{ + off_t offset; + + offset = lseek(fd, 0, SEEK_END); + CHECK(offset != (off_t) -1); + + return tests_write_fragment_file(file_number, fd, offset, size); +} + +/* Write size bytes to the end of file "fragment_n" where n is the file_number + and file_number also determines the random data written + i.e. seed for random numbers */ +unsigned tests_append_to_fragment_file(unsigned file_number, + unsigned size, + int create) +{ + int fd; + int flags; + mode_t mode; + unsigned actual_growth; + char file_name[256]; + + sprintf(file_name, "fragment_%u", file_number); + if (create) + flags = O_CREAT | O_EXCL | O_WRONLY | tests_maybe_sync_flag(); + else + flags = O_WRONLY | tests_maybe_sync_flag(); + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; + fd = open(file_name, flags, mode); + if (fd == -1 && errno == ENOSPC) { + errno = 0; + return 0; /* File system full */ + } + CHECK(fd != -1); + actual_growth = tests_fill_fragment_file(file_number, fd, size); + CHECK(close(fd) != -1); + if (create && !actual_growth) + tests_delete_fragment_file(file_number); + return actual_growth; +} + +/* Write size bytes at offset to the file "fragment_n" where n is the + file_number and file_number also determines the random data written + i.e. seed for random numbers */ +unsigned tests_overwite_fragment_file( unsigned file_number, + off_t offset, + unsigned size) +{ + int fd; + unsigned actual_size; + char file_name[256]; + + sprintf(file_name, "fragment_%u", file_number); + fd = open(file_name, O_RDWR | tests_maybe_sync_flag()); + if (fd == -1 && errno == ENOSPC) { + errno = 0; + return 0; /* File system full */ + } + CHECK(fd != -1); + actual_size = tests_write_fragment_file(file_number, + fd, offset, size); + CHECK(close(fd) != -1); + return actual_size; +} + +/* Delete file "fragment_n" where n is the file_number */ +void tests_delete_fragment_file(unsigned file_number) +{ + char file_name[256]; + + sprintf(file_name, "fragment_%u", file_number); + tests_delete_file(file_name); +} + +/* Check the random data in file "fragment_n" is what is expected */ +void tests_check_fragment_file(unsigned file_number) +{ + int fd; + ssize_t sz, i; + int d; + uint64_t u; + char file_name[256]; + char buf[8192]; + + sprintf(file_name, "fragment_%u", file_number); + fd = open(file_name, O_RDONLY); + CHECK(fd != -1); + srand(file_number); + u = RAND_MAX; + u += 1; + u /= 256; + d = (int) u; + for (;;) { + sz = read(fd, buf, 8192); + if (sz == 0) + break; + CHECK(sz >= 0); + for (i = 0; i < sz; ++i) + CHECK(buf[i] == (char) (rand() / d)); + } + CHECK(close(fd) != -1); +} + +/* Central point to decide whether to use fsync */ +void tests_maybe_sync(int fd) +{ + if (tests_ok_to_sync) + CHECK(fsync(fd) != -1); +} + +/* Return O_SYNC if ok to sync otherwise return 0 */ +int tests_maybe_sync_flag(void) +{ + if (tests_ok_to_sync) + return O_SYNC; + return 0; +} + +/* Return random number from 0 to n - 1 */ +size_t tests_random_no(size_t n) +{ + uint64_t a, b; + + if (!n) + return 0; + if (n - 1 <= RAND_MAX) { + a = rand(); + b = RAND_MAX; + b += 1; + } else { + const uint64_t u = 1 + (uint64_t) RAND_MAX; + a = rand(); + a *= u; + a += rand(); + b = u * u; + CHECK(n <= b); + } + if (RAND_MAX <= UINT32_MAX && n <= UINT32_MAX) + return a * n / b; + else /*if (RAND_MAX <= UINT64_MAX && n <= UINT64_MAX)*/ { + uint64_t x, y; + if (a < n) { + x = a; + y = n; + } else { + x = n; + y = a; + } + return (x * (y / b)) + ((x * (y % b)) / b); + } +} + +/* Make a directory empty */ +void tests_clear_dir(const char *dir_name) +{ + DIR *dir; + struct dirent *entry; + char buf[4096]; + + dir = opendir(dir_name); + CHECK(dir != NULL); + CHECK(getcwd(buf, 4096) != NULL); + CHECK(chdir(dir_name) != -1); + for (;;) { + errno = 0; + entry = readdir(dir); + if (entry) { + if (strcmp(".",entry->d_name) != 0 && + strcmp("..",entry->d_name) != 0) { + if (entry->d_type == DT_DIR) { + tests_clear_dir(entry->d_name); + CHECK(rmdir(entry->d_name) != -1); + } else + CHECK(unlink(entry->d_name) != -1); + } + } else { + CHECK(errno == 0); + break; + } + } + CHECK(chdir(buf) != -1); + CHECK(closedir(dir) != -1); +} + +/* Create an empty sub-directory or small file in the current directory */ +int64_t tests_create_entry(char *return_name) +{ + int fd; + char name[256]; + + for (;;) { + sprintf(name, "%u", (unsigned) tests_random_no(10000000)); + fd = open(name, O_RDONLY); + if (fd == -1) + break; + close(fd); + } + if (return_name) + strcpy(return_name, name); + if (tests_random_no(2)) { + return tests_create_file(name, tests_random_no(4096)); + } else { + if (mkdir(name, 0777) == -1) { + CHECK(errno == ENOSPC); + errno = 0; + return 0; + } + return TESTS_EMPTY_DIR_SIZE; + } +} + +/* Remove a random file of empty sub-directory from the current directory */ +int64_t tests_remove_entry(void) +{ + DIR *dir; + struct dirent *entry; + unsigned count = 0, pos; + int64_t result = 0; + + dir = opendir("."); + CHECK(dir != NULL); + for (;;) { + errno = 0; + entry = readdir(dir); + if (entry) { + if (strcmp(".",entry->d_name) != 0 && + strcmp("..",entry->d_name) != 0) + ++count; + } else { + CHECK(errno == 0); + break; + } + } + pos = tests_random_no(count); + count = 0; + rewinddir(dir); + for (;;) { + errno = 0; + entry = readdir(dir); + if (!entry) { + CHECK(errno == 0); + break; + } + if (strcmp(".",entry->d_name) != 0 && + strcmp("..",entry->d_name) != 0) { + if (count == pos) { + if (entry->d_type == DT_DIR) { + tests_clear_dir(entry->d_name); + CHECK(rmdir(entry->d_name) != -1); + result = TESTS_EMPTY_DIR_SIZE; + } else { + struct stat st; + CHECK(stat(entry->d_name, &st) != -1); + result = st.st_size; + CHECK(unlink(entry->d_name) != -1); + } + } + ++count; + } + } + CHECK(closedir(dir) != -1); + return result; +} + +/* Read mount information from /proc/mounts or /etc/mtab */ +int tests_get_mount_info(struct mntent *info) +{ + FILE *f; + struct mntent *entry; + int found = 0; + + f = fopen("/proc/mounts", "rb"); + if (!f) + f = fopen("/etc/mtab", "rb"); + CHECK(f != NULL); + while (!found) { + entry = getmntent(f); + if (entry) { + if (strcmp(entry->mnt_dir, + tests_file_system_mount_dir) == 0) { + found = 1; + *info = *entry; + } + } else + break; + } + CHECK(fclose(f) == 0); + return found; +} + +/* Un-mount and re-mount test file system */ +void tests_remount(void) +{ + struct mntent mount_info; + char *source; + char *target; + char *filesystemtype; + unsigned long mountflags; + void *data; + char cwd[4096]; + + CHECK(tests_get_mount_info(&mount_info)); + + if (strcmp(mount_info.mnt_dir,"/") == 0) + return; + + CHECK(getcwd(cwd, 4096) != NULL); + CHECK(chdir("/") != -1); + + CHECK(umount(tests_file_system_mount_dir) != -1); + + source = mount_info.mnt_fsname; + target = tests_file_system_mount_dir; + filesystemtype = tests_file_system_type; + mountflags = 0; + data = NULL; + + CHECK(mount(source, target, filesystemtype, mountflags, data) != -1); + + CHECK(chdir(cwd) != -1); +} + +/* Check whether the test file system is also the root file system */ +int tests_fs_is_rootfs(void) +{ + struct stat f_info; + struct stat root_f_info; + + CHECK(stat(tests_file_system_mount_dir, &f_info) != -1); + CHECK(stat("/", &root_f_info) != -1); + if (f_info.st_dev == root_f_info.st_dev) + return 1; + else + return 0; +} + +/* Try to make a directory empty */ +void tests_try_to_clear_dir(const char *dir_name) +{ + DIR *dir; + struct dirent *entry; + char buf[4096]; + + dir = opendir(dir_name); + if (dir == NULL) + return; + if (getcwd(buf, 4096) == NULL || chdir(dir_name) == -1) { + closedir(dir); + return; + } + for (;;) { + errno = 0; + entry = readdir(dir); + if (entry) { + if (strcmp(".",entry->d_name) != 0 && + strcmp("..",entry->d_name) != 0) { + if (entry->d_type == DT_DIR) { + tests_try_to_clear_dir(entry->d_name); + rmdir(entry->d_name); + } else + unlink(entry->d_name); + } + } else { + CHECK(errno == 0); + break; + } + } + chdir(buf); + closedir(dir); +} + +/* Check whether the test file system is also the current file system */ +int tests_fs_is_currfs(void) +{ + struct stat f_info; + struct stat curr_f_info; + + CHECK(stat(tests_file_system_mount_dir, &f_info) != -1); + CHECK(stat(".", &curr_f_info) != -1); + if (f_info.st_dev == curr_f_info.st_dev) + return 1; + else + return 0; +} + +#define PID_BUF_SIZE 64 + +/* Concatenate a pid to a string in a signal safe way */ +void tests_cat_pid(char *buf, const char *name, pid_t pid) +{ + char *p; + unsigned x; + const char digits[] = "0123456789"; + char pid_buf[PID_BUF_SIZE]; + + x = (unsigned) pid; + p = pid_buf + PID_BUF_SIZE; + *--p = '\0'; + if (x) + while (x) { + *--p = digits[x % 10]; + x /= 10; + } + else + *--p = '0'; + buf[0] = '\0'; + strcat(buf, name); + strcat(buf, p); +} diff --git a/tests/fs-tests/lib/tests.h b/tests/fs-tests/lib/tests.h new file mode 100644 index 0000000..36ca310 --- /dev/null +++ b/tests/fs-tests/lib/tests.h @@ -0,0 +1,185 @@ +/* + * 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 + */ + +#ifndef included_tests_tests_h__ +#define included_tests_tests_h__ + +#include <stdint.h> + +/* Main macro for testing */ +#define CHECK(x) tests_test((x),__func__,__FILE__,__LINE__) + +/* The default directory in which tests are conducted */ +#define TESTS_DEFAULT_FILE_SYSTEM_MOUNT_DIR "/mnt/test_file_system" + +/* The default file system type to test */ +#define TESTS_DEFAULT_FILE_SYSTEM_TYPE "jffs2" + +/* Estimated size of an empty directory */ +#define TESTS_EMPTY_DIR_SIZE 128 + +/* Function invoked by the CHECK macro */ +void tests_test(int test,const char *msg,const char *file,unsigned line); + +/* Handle common program options */ +int tests_get_args(int argc, + char *argv[], + const char *title, + const char *desc, + const char *opts); + +/* Return the number of files (or directories) in the given directory */ +unsigned tests_count_files_in_dir(const char *dir_name); + +/* Change to the file system mount directory, check that it is empty, + matches the file system type, and is not the root file system */ +void tests_check_test_file_system(void); + +/* Get the free space for the file system of the current directory */ +uint64_t tests_get_free_space(void); + +/* Get the total space for the file system of the current directory */ +uint64_t tests_get_total_space(void); + +/* Write size random bytes into file descriptor fd at the current position, + returning the number of bytes actually written */ +uint64_t tests_fill_file(int fd, uint64_t size); + +/* Write size random bytes into file descriptor fd at offset, + returning the number of bytes actually written */ +uint64_t tests_write_filled_file(int fd, off_t offset, uint64_t size); + +/* Check that a file written using tests_fill_file() and/or + tests_write_filled_file() and/or tests_create_file() + contains the expected random data */ +void tests_check_filled_file_fd(int fd); + +/* Check that a file written using tests_fill_file() and/or + tests_write_filled_file() and/or tests_create_file() + contains the expected random data */ +void tests_check_filled_file(const char *file_name); + +/* Delete a file */ +void tests_delete_file(const char *file_name); + +/* Create a file of size file_size */ +uint64_t tests_create_file(const char *file_name, uint64_t file_size); + +/* Calculate: free_space * numerator / denominator */ +uint64_t tests_get_big_file_size(unsigned numerator, unsigned denominator); + +/* Write size bytes to the end of file descriptor fd using file_number + to determine the random data written i.e. seed for random numbers */ +unsigned tests_fill_fragment_file(unsigned file_number, + int fd, + unsigned size); + +/* Write size bytes to the end of file "fragment_n" where n is the file_number + and file_number also determines the random data written + i.e. seed for random numbers */ +unsigned tests_append_to_fragment_file(unsigned file_number, + unsigned size, + int create); + +/* Write size bytes at offset to the file "fragment_n" where n is the + file_number and file_number also determines the random data written + i.e. seed for random numbers */ +unsigned tests_overwite_fragment_file( unsigned file_number, + off_t offset, + unsigned size); + +/* Delete file "fragment_n" where n is the file_number */ +void tests_delete_fragment_file(unsigned file_number); + +/* Check the random data in file "fragment_n" is what is expected */ +void tests_check_fragment_file(unsigned file_number); + +/* Central point to decide whether to use fsync */ +void tests_maybe_sync(int fd); + +/* Return O_SYNC if ok to sync otherwise return 0 */ +int tests_maybe_sync_flag(void); + +/* Return random number from 0 to n - 1 */ +size_t tests_random_no(size_t n); + +/* Make a directory empty */ +void tests_clear_dir(const char *dir_name); + +/* Create an empty sub-directory or small file in the current directory */ +int64_t tests_create_entry(char *return_name); + +/* Remove a random file of empty sub-directory from the current directory */ +int64_t tests_remove_entry(void); + +/* Un-mount and re-mount test file system */ +void tests_remount(void); + +/* Check whether the test file system is also the root file system */ +int tests_fs_is_rootfs(void); + +/* Try to make a directory empty */ +void tests_try_to_clear_dir(const char *dir_name); + +/* Check whether the test file system is also the current file system */ +int tests_fs_is_currfs(void); + +/* Concatenate a pid to a string in a signal safe way */ +void tests_cat_pid(char *buf, const char *name, pid_t pid); + +extern char *tests_file_system_mount_dir; + +extern char *tests_file_system_type; + +/* General purpose test parameter to specify some aspect of test size. + May be used by different tests in different ways. + Set by the -z, --size options. */ +extern int64_t tests_size_parameter; + +/* General purpose test parameter to specify some aspect of test repetition. + May be used by different tests in different ways. + Set by the -n, --repeat options. */ +extern int64_t tests_repeat_parameter; + +/* General purpose test parameter to specify some aspect of test sleeping. + May be used by different tests in different ways. + Set by the -p, --sleep options. */ +extern int64_t tests_sleep_parameter; + +/* General purpose test parameter to specify a file should be unlinked. + May be used by different tests in different ways or not at all. */ +extern int tests_unlink_flag; + +/* General purpose test parameter to specify a file should be closed. + May be used by different tests in different ways or not at all. */ +extern int tests_close_flag; + +/* General purpose test parameter to specify a file should be deleted. + May be used by different tests in different ways or not at all. */ +extern int tests_delete_flag; + +/* General purpose test parameter to specify a file have a hole. + May be used by different tests in different ways or not at all. */ +extern int tests_hole_flag; + +/* Program name from argv[0] */ +extern char *program_name; + +#endif diff --git a/tests/fs-tests/run_all.sh b/tests/fs-tests/run_all.sh new file mode 100755 index 0000000..e79993a --- /dev/null +++ b/tests/fs-tests/run_all.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +TEST_DIR=$TEST_FILE_SYSTEM_MOUNT_DIR +if test -z "$TEST_DIR"; +then + TEST_DIR="/mnt/test_file_system" +fi + +rm -rf ${TEST_DIR}/* + +./simple/test_1 || exit 1 + +rm -rf ${TEST_DIR}/* + +./simple/test_2 || exit 1 + +rm -rf ${TEST_DIR}/* + +./integrity/integck || exit 1 + +rm -rf ${TEST_DIR}/* + +./stress/atoms/rndrm00 -z0 || exit 1 + +rm -rf ${TEST_DIR}/* + +./stress/atoms/rmdir00 -z0 || exit 1 + +rm -rf ${TEST_DIR}/* + +./stress/atoms/stress_1 -z10000000 -e || exit 1 + +rm -rf ${TEST_DIR}/* + +./stress/atoms/stress_2 -z10000000 || exit 1 + +rm -rf ${TEST_DIR}/* + +./stress/atoms/stress_3 -z1000000000 -e || exit 1 + +rm -rf ${TEST_DIR}/* + +cd stress || exit 1 + +./stress00.sh 3600 || exit 1 + +./stress01.sh 3600 || exit 1 + +cd .. || exit 1 diff --git a/tests/fs-tests/simple/Makefile b/tests/fs-tests/simple/Makefile new file mode 100644 index 0000000..07ddf65 --- /dev/null +++ b/tests/fs-tests/simple/Makefile @@ -0,0 +1,26 @@ + +ifeq ($(origin CC),default) +CC = gcc +endif + +CFLAGS := $(CFLAGS) -Wall -g -O2 -I../lib + +LDFLAGS := $(LDFLAGS) + +TARGETS = test_1 \ + test_2 \ + ftrunc + +all: $(TARGETS) + +$(TARGETS): ../lib/tests.o + +../lib/tests.o: ../lib/tests.h + +clean: + rm -f *.o $(TARGETS) + +tests: all + ./test_1 --sync + ./test_2 --sync + ./ftrunc diff --git a/tests/fs-tests/simple/ftrunc.c b/tests/fs-tests/simple/ftrunc.c new file mode 100644 index 0000000..86edf65 --- /dev/null +++ b/tests/fs-tests/simple/ftrunc.c @@ -0,0 +1,111 @@ +/* + * 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 "tests.h" + +#define WRITE_BUFFER_SIZE 32768 + +void ftrunc(void) +{ + int fd, i; + pid_t pid; + ssize_t written; + int64_t remains; + size_t block; + char *file_name; + off_t actual; + char buf[WRITE_BUFFER_SIZE]; + + file_name = "ftrunc_test_file"; + fd = open(file_name, O_CREAT | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + CHECK(fd != -1); + pid = getpid(); + srand(pid); + for (i = 0; i < WRITE_BUFFER_SIZE;++i) + buf[i] = rand(); + remains = tests_size_parameter; + actual = 0; + while (remains > 0) { + if (remains > WRITE_BUFFER_SIZE) + block = WRITE_BUFFER_SIZE; + else + block = remains; + written = write(fd, buf, block); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + break; + } + remains -= written; + actual += written; + } + CHECK(ftruncate(fd, (actual ? actual - 1 : actual)) != -1); + CHECK(close(fd) != -1); + CHECK(unlink(file_name) != -1); +} + +/* Title of this test */ + +const char *ftrunc_get_title(void) +{ + return "Truncate a large test file"; +} + +/* Description of this test */ + +const char *ftrunc_get_description(void) +{ + return + "Create a file named ftrunc_test_file. " \ + "Truncate the file to reduce its length by 1. " \ + "Then remove the truncated file. " + "The size is given by the -z or --size option, " \ + "otherwise it defaults to 1000000."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test file size */ + tests_size_parameter = 1000000; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, ftrunc_get_title(), + ftrunc_get_description(), "z"); + 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 */ + ftrunc(); + return 0; +} diff --git a/tests/fs-tests/simple/test_1.c b/tests/fs-tests/simple/test_1.c new file mode 100644 index 0000000..69eafe4 --- /dev/null +++ b/tests/fs-tests/simple/test_1.c @@ -0,0 +1,150 @@ +/* + * 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 <stdio.h> +#include <string.h> +#include <stdint.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "tests.h" + +void test_1(void) +{ + int fd; + pid_t pid; + uint64_t i; + uint64_t block; + uint64_t actual_size; + char name[256]; + char old[16]; + char buf[16]; + off_t old_len; + char dir_name[256]; + + /* Create a directory to test in */ + pid = getpid(); + tests_cat_pid(dir_name, "test_1_test_dir_", pid); + if (chdir(dir_name) == -1) + CHECK(mkdir(dir_name, 0777) != -1); + CHECK(chdir(dir_name) != -1); + /* Create a file that fills half the free space on the file system */ + tests_create_file("big_file", tests_get_big_file_size(1,2)); + CHECK(tests_count_files_in_dir(".") == 1); + fd = open("big_file", O_RDWR | tests_maybe_sync_flag()); + CHECK(fd != -1); + CHECK(read(fd, old, 5) == 5); + CHECK(lseek(fd, 0, SEEK_SET) != (off_t) -1); + CHECK(write(fd,"start", 5) == 5); + CHECK(lseek(fd,0,SEEK_END) != (off_t) -1); + CHECK(write(fd, "end", 3) == 3); + tests_maybe_sync(fd); + /* Delete the file while it is still open */ + tests_delete_file("big_file"); + CHECK(tests_count_files_in_dir(".") == 0); + /* Create files to file up the file system */ + for (block = 1000000, i = 1; ; block /= 10) { + while (i != 0) { + sprintf(name, "fill_up_%llu", i); + actual_size = tests_create_file(name, block); + if (actual_size != 0) + ++i; + if (actual_size != block) + break; + } + if (block == 1) + break; + } + /* Check the big file */ + CHECK(lseek(fd, 0, SEEK_SET) != (off_t) -1); + CHECK(read(fd, buf, 5) == 5); + CHECK(strncmp(buf, "start", 5) == 0); + CHECK(lseek(fd, -3, SEEK_END) != (off_t) -1); + CHECK(read(fd, buf, 3) == 3); + CHECK(strncmp(buf, "end", 3) == 0); + /* Check the other files and delete them */ + i -= 1; + CHECK(tests_count_files_in_dir(".") == i); + for (; i > 0; --i) { + sprintf(name, "fill_up_%llu", i); + tests_check_filled_file(name); + tests_delete_file(name); + } + CHECK(tests_count_files_in_dir(".") == 0); + /* Check the big file again */ + CHECK(lseek(fd, 0, SEEK_SET) != (off_t) -1); + CHECK(read(fd, buf, 5) == 5); + CHECK(strncmp(buf, "start", 5) == 0); + CHECK(lseek(fd, -3, SEEK_END) != (off_t) -1); + CHECK(read(fd, buf, 3) == 3); + CHECK(strncmp(buf, "end", 3) == 0); + CHECK(lseek(fd, 0, SEEK_SET) != (off_t) -1); + CHECK(write(fd,old, 5) == 5); + old_len = lseek(fd, -3, SEEK_END); + CHECK(old_len != (off_t) -1); + CHECK(ftruncate(fd,old_len) != -1); + tests_check_filled_file_fd(fd); + /* Close the big file*/ + CHECK(close(fd) != -1); + CHECK(tests_count_files_in_dir(".") == 0); + CHECK(chdir("..") != -1); + CHECK(rmdir(dir_name) != -1); +} + +/* Title of this test */ + +const char *test_1_get_title(void) +{ + return "Fill file system while holding deleted big file descriptor"; +} + +/* Description of this test */ + +const char *test_1_get_description(void) +{ + return + "Create a directory named test_1_test_dir_pid, where " \ + "pid is the process id. Within that directory, " \ + "create a big file (approx. half the file system in size), " \ + "open it, and unlink it. " \ + "Create many smaller files until the file system is full. " \ + "Check the big file is ok. " \ + "Delete all the smaller files. " \ + "Check the big file again. " \ + "Finally delete the big file and directory."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, test_1_get_title(), + test_1_get_description(), "s"); + 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 */ + test_1(); + return 0; +} diff --git a/tests/fs-tests/simple/test_2.c b/tests/fs-tests/simple/test_2.c new file mode 100644 index 0000000..2094460 --- /dev/null +++ b/tests/fs-tests/simple/test_2.c @@ -0,0 +1,201 @@ +/* + * 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 <stdio.h> +#include <string.h> +#include <stdint.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "tests.h" + +void test_2(void) +{ + pid_t pid; + int create, full; + unsigned i, number_of_files; + unsigned growth; + unsigned size; + uint64_t big_file_size; + int fd; + off_t offset; + char dir_name[256]; + + /* Create a directory to test in */ + pid = getpid(); + tests_cat_pid(dir_name, "test_2_test_dir_", pid); + if (chdir(dir_name) == -1) + CHECK(mkdir(dir_name, 0777) != -1); + CHECK(chdir(dir_name) != -1); + /* Create up to 1000 files appending 400 bytes at a time to each file */ + /* until the file system is full.*/ + create = 1; + full = 0; + number_of_files = 1000; + while (!full) { + for (i = 0; i < number_of_files; ++i) { + growth = tests_append_to_fragment_file(i, 400, create); + if (!growth) { + full = 1; + if (create) + number_of_files = i; + break; + } + } + create = 0; + } + /* Check the files */ + CHECK(tests_count_files_in_dir(".") == number_of_files); + for (i = 0; i < number_of_files; ++i) + tests_check_fragment_file(i); + /* Delete half of them */ + for (i = 1; i < number_of_files; i += 2) + tests_delete_fragment_file(i); + /* Check them again */ + CHECK(tests_count_files_in_dir(".") == (number_of_files + 1) / 2); + for (i = 0; i < number_of_files; i += 2) + tests_check_fragment_file(i); + CHECK(tests_count_files_in_dir(".") == (number_of_files + 1) / 2); + /* Create a big file that fills two thirds of the free space */ + big_file_size = tests_get_big_file_size(2,3); + /* Check the big file */ + tests_create_file("big_file", big_file_size); + CHECK(tests_count_files_in_dir(".") == 1 + (number_of_files + 1) / 2); + tests_check_filled_file("big_file"); + /* Open the big file */ + fd = open("big_file",O_RDWR | tests_maybe_sync_flag()); + CHECK(fd != -1); + /* Delete the big file while it is still open */ + tests_delete_file("big_file"); + /* Check the big file again */ + CHECK(tests_count_files_in_dir(".") == (number_of_files + 1) / 2); + tests_check_filled_file_fd(fd); + + /* Write parts of the files and check them */ + + offset = 100; /* Offset to write at, in the small files */ + size = 200; /* Number of bytes to write at the offset */ + + for (i = 0; i < number_of_files; i += 2) + tests_overwite_fragment_file(i, offset, size); + /* Rewrite the big file entirely */ + tests_write_filled_file(fd, 0, big_file_size); + for (i = 0; i < number_of_files; i += 2) + tests_check_fragment_file(i); + tests_check_filled_file_fd(fd); + + offset = 300; /* Offset to write at, in the small files */ + size = 400; /* Number of bytes to write at the offset */ + + for (i = 0; i < number_of_files; i += 2) + tests_overwite_fragment_file(i, offset, size); + /* Rewrite the big file entirely */ + tests_write_filled_file(fd, 0, big_file_size); + for (i = 0; i < number_of_files; i += 2) + tests_check_fragment_file(i); + tests_check_filled_file_fd(fd); + + offset = 110; /* Offset to write at, in the small files */ + size = 10; /* Number of bytes to write at the offset */ + + for (i = 0; i < number_of_files; i += 2) + tests_overwite_fragment_file(i, offset, size); + /* Rewrite the big file entirely */ + tests_write_filled_file(fd, 0, big_file_size); + for (i = 0; i < number_of_files; i += 2) + tests_check_fragment_file(i); + tests_check_filled_file_fd(fd); + + offset = 10; /* Offset to write at, in the small files */ + size = 1000; /* Number of bytes to write at the offset */ + + for (i = 0; i < number_of_files; i += 2) + tests_overwite_fragment_file(i, offset, size); + /* Rewrite the big file entirely */ + tests_write_filled_file(fd, 0, big_file_size); + for (i = 0; i < number_of_files; i += 2) + tests_check_fragment_file(i); + tests_check_filled_file_fd(fd); + + offset = 0; /* Offset to write at, in the small files */ + size = 100000; /* Number of bytes to write at the offset */ + + for (i = 0; i < number_of_files; i += 2) + tests_overwite_fragment_file(i, offset, size); + /* Rewrite the big file entirely */ + tests_write_filled_file(fd, 0, big_file_size); + for (i = 0; i < number_of_files; i += 2) + tests_check_fragment_file(i); + tests_check_filled_file_fd(fd); + + /* Close the big file*/ + CHECK(close(fd) != -1); + /* Check the small files */ + CHECK(tests_count_files_in_dir(".") == (number_of_files + 1) / 2); + for (i = 0; i < number_of_files; i += 2) + tests_check_fragment_file(i); + /* Delete the small files */ + for (i = 0; i < number_of_files; i += 2) + tests_delete_fragment_file(i); + CHECK(tests_count_files_in_dir(".") == 0); + CHECK(chdir("..") != -1); + CHECK(rmdir(dir_name) != -1); +} + +/* Title of this test */ + +const char *test_2_get_title(void) +{ + return "Repeated write many small files and one big deleted file"; +} + +/* Description of this test */ + +const char *test_2_get_description(void) +{ + return + "Create a directory named test_2_test_dir_pid, where " \ + "pid is the process id. Within that directory, " \ + "create about 1000 files. Append 400 bytes to each until " \ + "the file system is full. Then delete half of them. Then " \ + "create a big file that uses about 2/3 of the remaining free " \ + "space. Get a file descriptor for the big file, and delete " \ + "the big file. Then repeatedly write to the small files " \ + "and the big file. " \ + "Finally delete the big file and directory."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, test_2_get_title(), + test_2_get_description(), "s"); + 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 */ + test_2(); + return 0; +} diff --git a/tests/fs-tests/stress/Makefile b/tests/fs-tests/stress/Makefile new file mode 100644 index 0000000..c24ff3f --- /dev/null +++ b/tests/fs-tests/stress/Makefile @@ -0,0 +1,11 @@ + +SUBDIRS = atoms + +all tests: $(SUBDIRS) + +clean: $(SUBDIRS) + rm -rf run_pdf_test_file_* + +.PHONY: $(SUBDIRS) +$(SUBDIRS): + $(MAKE) -C $@ $(MAKECMDGOALS) diff --git a/tests/fs-tests/stress/atoms/Makefile b/tests/fs-tests/stress/atoms/Makefile new file mode 100644 index 0000000..9fbfd39 --- /dev/null +++ b/tests/fs-tests/stress/atoms/Makefile @@ -0,0 +1,40 @@ + +ifeq ($(origin CC),default) +CC = gcc +endif + +CFLAGS := $(CFLAGS) -Wall -g -O2 -I../../lib + +LDFLAGS := $(LDFLAGS) + +TARGETS = stress_1 \ + stress_2 \ + stress_3 \ + pdfrun \ + rndwrite00 \ + fwrite00 \ + rmdir00 \ + rndrm00 \ + rndrm99 \ + gcd_hupper + +all: $(TARGETS) + +$(TARGETS): ../../lib/tests.o + +../lib/tests.o: ../../lib/tests.h + +clean: + rm -f *.o $(TARGETS) run_pdf_test_file + +tests: all + ./stress_1 -e + ./stress_2 + ./stress_3 -e + ./pdfrun + ./rndwrite00 -e + ./fwrite00 + ./rmdir00 + ./rndrm00 + ./rndrm99 + ./gcd_hupper diff --git a/tests/fs-tests/stress/atoms/fwrite00.c b/tests/fs-tests/stress/atoms/fwrite00.c new file mode 100644 index 0000000..2f40b3d --- /dev/null +++ b/tests/fs-tests/stress/atoms/fwrite00.c @@ -0,0 +1,209 @@ +/* + * 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 "tests.h" + +#define WRITE_BUFFER_SIZE 32768 + +#define HOLE_BLOCK_SIZE 10000000 + +void filestress00(void) +{ + int fd, i, deleted; + pid_t pid; + ssize_t written; + int64_t remains; + int64_t repeat; + size_t block; + char file_name[256]; + char buf[WRITE_BUFFER_SIZE]; + + fd = -1; + deleted = 1; + pid = getpid(); + tests_cat_pid(file_name, "filestress00_test_file_", pid); + srand(pid); + repeat = tests_repeat_parameter; + for (;;) { + /* Open the file */ + if (fd == -1) { + fd = open(file_name, O_CREAT | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + CHECK(fd != -1); + deleted = 0; + if (tests_unlink_flag) { + CHECK(unlink(file_name) != -1); + deleted = 1; + } + } + /* Get a different set of random data */ + for (i = 0; i < WRITE_BUFFER_SIZE;++i) + buf[i] = rand(); + if (tests_hole_flag) { + /* Make a hole */ + CHECK(lseek(fd, tests_size_parameter, SEEK_SET) != -1); + written = write(fd, "!", 1); + if (written <= 0) { + /* File system full */ + CHECK(errno == ENOSPC); + errno = 0; + } + CHECK(lseek(fd, 0, SEEK_SET) != -1); + /* Write at set points into the hole */ + remains = tests_size_parameter; + while (remains > HOLE_BLOCK_SIZE) { + CHECK(lseek(fd, HOLE_BLOCK_SIZE, + SEEK_CUR) != -1); + written = write(fd, "!", 1); + remains -= HOLE_BLOCK_SIZE; + if (written <= 0) { + /* File system full */ + CHECK(errno == ENOSPC); + errno = 0; + break; + } + } + } else { + /* Write data into the file */ + CHECK(lseek(fd, 0, SEEK_SET) != -1); + remains = tests_size_parameter; + while (remains > 0) { + if (remains > WRITE_BUFFER_SIZE) + block = WRITE_BUFFER_SIZE; + else + block = remains; + written = write(fd, buf, block); + if (written <= 0) { + /* File system full */ + CHECK(errno == ENOSPC); + errno = 0; + break; + } + remains -= written; + } + } + /* Break if repeat count exceeded */ + if (tests_repeat_parameter > 0 && --repeat <= 0) + break; + /* Close if tests_close_flag */ + if (tests_close_flag) { + CHECK(close(fd) != -1); + fd = -1; + } + /* Sleep */ + if (tests_sleep_parameter > 0) { + unsigned us = tests_sleep_parameter * 1000; + unsigned rand_divisor = RAND_MAX / us; + unsigned s = (us / 2) + (rand() / rand_divisor); + usleep(s); + } + /* Delete if tests_delete flag */ + if (!deleted && tests_delete_flag) { + CHECK(unlink(file_name) != -1); + deleted = 1; + } + } + CHECK(close(fd) != -1); + /* Sleep */ + if (tests_sleep_parameter > 0) { + unsigned us = tests_sleep_parameter * 1000; + unsigned rand_divisor = RAND_MAX / us; + unsigned s = (us / 2) + (rand() / rand_divisor); + usleep(s); + } + /* Tidy up */ + if (!deleted) + CHECK(unlink(file_name) != -1); +} + +/* Title of this test */ + +const char *filestress00_get_title(void) +{ + return "File stress test 00"; +} + +/* Description of this test */ + +const char *filestress00_get_description(void) +{ + return + "Create a file named filestress00_test_file_pid, where " \ + "pid is the process id. If the unlink option " \ + "(-u or --unlink) is specified, " \ + "unlink the file while holding the open file descriptor. " \ + "If the hole option (-o or --hole) is specified, " \ + "write a single character at the end of the file, creating a " \ + "hole. " \ + "Write a single character in the hole every 10 million " \ + "bytes. " \ + "If the hole option is not specified, then the file is " \ + "filled with random data. " \ + "If the close option (-c or --close) is specified the file " \ + "is closed. " \ + "If a sleep value is specified, the process sleeps. " \ + "If the delete option (-e or --delete) is specified, then " \ + "the file is deleted. " \ + "If a repeat count is specified, then the task repeats " \ + "that number of times. " \ + "The repeat count is given by the -n or --repeat option, " \ + "otherwise it defaults to 1. " \ + "A repeat count of zero repeats forever. " \ + "The file size is given by the -z or --size option, " \ + "otherwise it defaults to 1000000. " \ + "The sleep value is given by the -p or --sleep option, " \ + "otherwise it defaults to 0. " \ + "Sleep is specified in milliseconds."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test file size */ + tests_size_parameter = 1000000; + + /* Set default test repetition */ + tests_repeat_parameter = 1; + + /* Set default test sleep */ + tests_sleep_parameter = 0; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, filestress00_get_title(), + filestress00_get_description(), "znpuoce"); + 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 */ + filestress00(); + return 0; +} diff --git a/tests/fs-tests/stress/atoms/gcd_hupper.c b/tests/fs-tests/stress/atoms/gcd_hupper.c new file mode 100644 index 0000000..31c175d --- /dev/null +++ b/tests/fs-tests/stress/atoms/gcd_hupper.c @@ -0,0 +1,259 @@ +/* + * 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 <ctype.h> +#include <dirent.h> +#include <mntent.h> +#include <signal.h> + +#include "tests.h" + +#define MAX_NAME_SIZE 1024 + +struct gcd_pid +{ + struct gcd_pid *next; + int pid; + char *name; + int mtd_index; +}; + +struct gcd_pid *gcd_pid_list = NULL; + +int add_gcd_pid(const char *number) +{ + int pid; + FILE *f; + char file_name[MAX_NAME_SIZE]; + char program_name[MAX_NAME_SIZE]; + + pid = atoi(number); + if (pid <= 0) + return 0; + snprintf(file_name, MAX_NAME_SIZE, "/proc/%s/stat", number); + f = fopen(file_name, "r"); + if (f == NULL) + return 0; + if (fscanf(f, "%d %s", &pid, program_name) != 2) { + fclose(f); + return 0; + } + if (strncmp(program_name, "(jffs2_gcd_mtd", 14) != 0) + pid = 0; + if (pid) { + size_t sz; + struct gcd_pid *g; + + sz = sizeof(struct gcd_pid); + g = (struct gcd_pid *) malloc(sz); + g->pid = pid; + g->name = (char *) malloc(strlen(program_name) + 1); + if (g->name) + strcpy(g->name, program_name); + else + exit(1); + g->mtd_index = atoi(program_name + 14); + g->next = gcd_pid_list; + gcd_pid_list = g; + } + fclose(f); + return pid; +} + +int get_pid_list(void) +{ + DIR *dir; + struct dirent *entry; + + dir = opendir("/proc"); + if (dir == NULL) + return 1; + for (;;) { + entry = readdir(dir); + if (entry) { + if (strcmp(".",entry->d_name) != 0 && + strcmp("..",entry->d_name) != 0) + add_gcd_pid(entry->d_name); + } else + break; + } + closedir(dir); + return 0; +} + +int parse_index_number(const char *name) +{ + const char *p, *q; + int all_zero; + int index; + + p = name; + while (*p && !isdigit(*p)) + ++p; + if (!*p) + return -1; + all_zero = 1; + for (q = p; *q; ++q) { + if (!isdigit(*q)) + return -1; + if (*q != '0') + all_zero = 0; + } + if (all_zero) + return 0; + index = atoi(p); + if (index <= 0) + return -1; + return index; +} + +int get_mtd_index(void) +{ + FILE *f; + struct mntent *entry; + struct stat f_info; + struct stat curr_f_info; + int found; + int mtd_index = -1; + + if (stat(tests_file_system_mount_dir, &f_info) == -1) + return -1; + f = fopen("/proc/mounts", "rb"); + if (!f) + f = fopen("/etc/mtab", "rb"); + if (f == NULL) + return -1; + found = 0; + for (;;) { + entry = getmntent(f); + if (!entry) + break; + if (stat(entry->mnt_dir, &curr_f_info) == -1) + continue; + if (f_info.st_dev == curr_f_info.st_dev) { + int i; + + i = parse_index_number(entry->mnt_fsname); + if (i != -1) { + if (found && i != mtd_index) + return -1; + found = 1; + mtd_index = i; + } + } + } + fclose(f); + return mtd_index; +} + +int get_gcd_pid() +{ + struct gcd_pid *g; + int mtd_index; + + if (get_pid_list()) + return 0; + mtd_index = get_mtd_index(); + if (mtd_index == -1) + return 0; + for (g = gcd_pid_list; g; g = g->next) + if (g->mtd_index == mtd_index) + return g->pid; + return 0; +} + +void gcd_hupper(void) +{ + int64_t repeat; + int pid; + + pid = get_gcd_pid(); + CHECK(pid != 0); + repeat = tests_repeat_parameter; + for (;;) { + CHECK(kill(pid, SIGHUP) != -1); + /* Break if repeat count exceeded */ + if (tests_repeat_parameter > 0 && --repeat <= 0) + break; + /* Sleep */ + if (tests_sleep_parameter > 0) { + unsigned us = tests_sleep_parameter * 1000; + unsigned rand_divisor = RAND_MAX / us; + unsigned s = (us / 2) + (rand() / rand_divisor); + usleep(s); + } + } +} + +/* Title of this test */ + +const char *gcd_hupper_get_title(void) +{ + return "Send HUP signals to gcd"; +} + +/* Description of this test */ + +const char *gcd_hupper_get_description(void) +{ + return + "Determine the PID of the gcd process. " \ + "Send it SIGHUP (may require root privileges). " \ + "If a sleep value is specified, the process sleeps. " \ + "If a repeat count is specified, then the task repeats " \ + "that number of times. " \ + "The repeat count is given by the -n or --repeat option, " \ + "otherwise it defaults to 1. " \ + "A repeat count of zero repeats forever. " \ + "The sleep value is given by the -p or --sleep option, " \ + "otherwise it defaults to 1. " + "Sleep is specified in milliseconds."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test repetition */ + tests_repeat_parameter = 1; + + /* Set default test sleep */ + tests_sleep_parameter = 1; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, gcd_hupper_get_title(), + gcd_hupper_get_description(), "np"); + 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 */ + gcd_hupper(); + return 0; +} diff --git a/tests/fs-tests/stress/atoms/pdfrun.c b/tests/fs-tests/stress/atoms/pdfrun.c new file mode 100644 index 0000000..3536580 --- /dev/null +++ b/tests/fs-tests/stress/atoms/pdfrun.c @@ -0,0 +1,143 @@ +/* + * 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 "tests.h" + +#define WRITE_BUFFER_SIZE 32768 + +void adjust_size(void) +{ + char dummy[1024]; + unsigned long total_memory; + FILE *f; + + total_memory = 0; + f = fopen("/proc/meminfo", "r"); + fscanf(f, "%s %lu", dummy, &total_memory); + fclose(f); + if (total_memory > 0 && tests_size_parameter > total_memory / 2) + tests_size_parameter = total_memory / 2; +} + +void run_pdf(void) +{ + int fd, i; + pid_t pid; + int64_t repeat; + ssize_t written; + int64_t remains; + size_t block; + char file_name[256]; + char buf[WRITE_BUFFER_SIZE]; + + if (tests_fs_is_currfs()) + return; + adjust_size(); + pid = getpid(); + tests_cat_pid(file_name, "run_pdf_test_file_", pid); + fd = open(file_name, O_CREAT | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + CHECK(fd != -1); + pid = getpid(); + srand(pid); + repeat = tests_repeat_parameter; + for (;;) { + for (i = 0; i < WRITE_BUFFER_SIZE;++i) + buf[i] = rand(); + remains = tests_size_parameter; + while (remains > 0) { + if (remains > WRITE_BUFFER_SIZE) + block = WRITE_BUFFER_SIZE; + else + block = remains; + written = write(fd, buf, block); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + break; + } + remains -= written; + } + /* Break if repeat count exceeded */ + if (tests_repeat_parameter > 0 && --repeat <= 0) + break; + CHECK(lseek(fd, 0, SEEK_SET) == 0); + } + CHECK(close(fd) != -1); + CHECK(unlink(file_name) != -1); +} + +/* Title of this test */ + +const char *run_pdf_get_title(void) +{ + return "Create / overwrite a large file in the current directory"; +} + +/* Description of this test */ + +const char *run_pdf_get_description(void) +{ + return + "Create a file named run_pdf_test_file_pid, " \ + "where pid is the process id. The file is created " \ + "in the current directory, " \ + "if the current directory is NOT on the test " \ + "file system, otherwise no action is taken. " \ + "If a repeat count is specified, then the task repeats " \ + "that number of times. " \ + "The repeat count is given by the -n or --repeat option, " \ + "otherwise it defaults to 1. " \ + "A repeat count of zero repeats forever. " \ + "The size is given by the -z or --size option, " \ + "otherwise it defaults to 1000000. " \ + "The size is adjusted so that it is not more than " \ + "half the size of total memory."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test file size */ + tests_size_parameter = 1000000; + + /* Set default test repetition */ + tests_repeat_parameter = 1; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, run_pdf_get_title(), + run_pdf_get_description(), "zn"); + if (!run_test) + return 1; + /* Do the actual test */ + run_pdf(); + return 0; +} diff --git a/tests/fs-tests/stress/atoms/rmdir00.c b/tests/fs-tests/stress/atoms/rmdir00.c new file mode 100644 index 0000000..c1d0729 --- /dev/null +++ b/tests/fs-tests/stress/atoms/rmdir00.c @@ -0,0 +1,133 @@ +/* + * 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 "tests.h" + +void rmdir00(void) +{ + int64_t repeat; + int64_t size, this_size; + pid_t pid; + char dir_name[256]; + + /* Create a directory to test in */ + pid = getpid(); + tests_cat_pid(dir_name, "rmdir00_test_dir_", pid); + if (chdir(dir_name) == -1) + CHECK(mkdir(dir_name, 0777) != -1); + CHECK(chdir(dir_name) != -1); + /* Repeat loop */ + repeat = tests_repeat_parameter; + size = 0; + for (;;) { + /* Remove everything in the directory */ + tests_clear_dir("."); + /* Fill with sub-dirs and small files */ + do { + this_size = tests_create_entry(NULL); + if (!this_size) + break; + size += this_size; + } while (this_size && + (tests_size_parameter == 0 || + size < tests_size_parameter)); + /* Break if repeat count exceeded */ + if (tests_repeat_parameter > 0 && --repeat <= 0) + break; + /* Sleep */ + if (tests_sleep_parameter > 0) { + unsigned us = tests_sleep_parameter * 1000; + unsigned rand_divisor = RAND_MAX / us; + unsigned s = (us / 2) + (rand() / rand_divisor); + usleep(s); + } + } + /* Tidy up by removing everything */ + tests_clear_dir("."); + CHECK(chdir("..") != -1); + CHECK(rmdir(dir_name) != -1); +} + +/* Title of this test */ + +const char *rmdir00_get_title(void) +{ + return "Create and remove directories and files"; +} + +/* Description of this test */ + +const char *rmdir00_get_description(void) +{ + return + "Create a directory named rmdir00_test_dir_pid, where " \ + "pid is the process id. Within that directory, create " \ + "a number of sub-directories and small files. " \ + "The total size of all sub-directories and files " \ + "is specified by the size parameter. " \ + "The size parameter is given by the -z or --size option, " \ + "otherwise it defaults to 1000000. " \ + "A size of zero fills the file system until there is no " + "space left. " \ + "The task repeats, sleeping in between each iteration, " \ + "and then removing the sub-directories and files created " \ + "during the last iteration. " \ + "The repeat count is set by the -n or --repeat option, " \ + "otherwise it defaults to 1. " \ + "A repeat count of zero repeats forever. " \ + "The sleep value is given by the -p or --sleep option, " \ + "otherwise it defaults to 0. " + "Sleep is specified in milliseconds."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test size */ + tests_size_parameter = 1000000; + + /* Set default test repetition */ + tests_repeat_parameter = 1; + + /* Set default test sleep */ + tests_sleep_parameter = 0; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, rmdir00_get_title(), + rmdir00_get_description(), "znp"); + 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 */ + rmdir00(); + return 0; +} diff --git a/tests/fs-tests/stress/atoms/rndrm00.c b/tests/fs-tests/stress/atoms/rndrm00.c new file mode 100644 index 0000000..724b1c3 --- /dev/null +++ b/tests/fs-tests/stress/atoms/rndrm00.c @@ -0,0 +1,157 @@ +/* + * 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 "tests.h" + +void rndrm00(void) +{ + int64_t repeat; + int64_t size, this_size; + pid_t pid; + char dir_name[256]; + + /* Create a directory to test in */ + pid = getpid(); + tests_cat_pid(dir_name, "rndrm00_test_dir_", pid); + if (chdir(dir_name) == -1) + CHECK(mkdir(dir_name, 0777) != -1); + CHECK(chdir(dir_name) != -1); + /* Repeat loop */ + repeat = tests_repeat_parameter; + size = 0; + for (;;) { + /* Create and remove sub-dirs and small files, */ + /* but tending to grow */ + do { + if (tests_random_no(3)) { + this_size = tests_create_entry(NULL); + if (!this_size) + break; + size += this_size; + } else { + this_size = tests_remove_entry(); + size -= this_size; + if (size < 0) + size = 0; + if (!this_size) + this_size = 1; + } + } while (this_size && + (tests_size_parameter == 0 || + size < tests_size_parameter)); + /* Create and remove sub-dirs and small files, but */ + /* but tending to shrink */ + do { + if (!tests_random_no(3)) { + this_size = tests_create_entry(NULL); + size += this_size; + } else { + this_size = tests_remove_entry(); + size -= this_size; + if (size < 0) + size = 0; + } + } while ((tests_size_parameter != 0 && + size > tests_size_parameter / 10) || + (tests_size_parameter == 0 && size > 100000)); + /* Break if repeat count exceeded */ + if (tests_repeat_parameter > 0 && --repeat <= 0) + break; + /* Sleep */ + if (tests_sleep_parameter > 0) { + unsigned us = tests_sleep_parameter * 1000; + unsigned rand_divisor = RAND_MAX / us; + unsigned s = (us / 2) + (rand() / rand_divisor); + usleep(s); + } + } + /* Tidy up by removing everything */ + tests_clear_dir("."); + CHECK(chdir("..") != -1); + CHECK(rmdir(dir_name) != -1); +} + +/* Title of this test */ + +const char *rndrm00_get_title(void) +{ + return "Randomly create and remove directories and files"; +} + +/* Description of this test */ + +const char *rndrm00_get_description(void) +{ + return + "Create a directory named rndrm00_test_dir_pid, where " \ + "pid is the process id. Within that directory, " \ + "randomly create and remove " \ + "a number of sub-directories and small files, " \ + "but do more creates than removes. " \ + "When the total size of all sub-directories and files " \ + "is greater than the size specified by the size parameter, " \ + "start to do more removes than creates. " \ + "The size parameter is given by the -z or --size option, " \ + "otherwise it defaults to 1000000. " \ + "A size of zero fills the file system until there is no " + "space left. " \ + "The task repeats, sleeping in between each iteration. " \ + "The repeat count is set by the -n or --repeat option, " \ + "otherwise it defaults to 1. " \ + "A repeat count of zero repeats forever. " \ + "The sleep value is given by the -p or --sleep option, " \ + "otherwise it defaults to 0. " + "Sleep is specified in milliseconds."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test size */ + tests_size_parameter = 1000000; + + /* Set default test repetition */ + tests_repeat_parameter = 1; + + /* Set default test sleep */ + tests_sleep_parameter = 0; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, rndrm00_get_title(), + rndrm00_get_description(), "znp"); + 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 */ + rndrm00(); + return 0; +} diff --git a/tests/fs-tests/stress/atoms/rndrm99.c b/tests/fs-tests/stress/atoms/rndrm99.c new file mode 100644 index 0000000..7751839 --- /dev/null +++ b/tests/fs-tests/stress/atoms/rndrm99.c @@ -0,0 +1,431 @@ +/* + * 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 <sys/time.h> +#include <time.h> +#include <sys/vfs.h> +#include <sys/statvfs.h> +#include <dirent.h> +#include <ctype.h> +#include <limits.h> + +#include "tests.h" + +uint32_t files_created = 0; +uint32_t files_removed = 0; +uint32_t dirs_created = 0; +uint32_t dirs_removed = 0; +int64_t *size_ptr = 0; + +void display_stats(void) +{ + printf( "\nrndrm99 stats:\n" + "\tNumber of files created = %u\n" + "\tNumber of files deleted = %u\n" + "\tNumber of directories created = %u\n" + "\tNumber of directories deleted = %u\n" + "\tCurrent net size of creates and deletes = %lld\n", + (unsigned) files_created, + (unsigned) files_removed, + (unsigned) dirs_created, + (unsigned) dirs_removed, + (long long) (size_ptr ? *size_ptr : 0)); + fflush(stdout); +} + +struct timeval tv_before; +struct timeval tv_after; + +void before(void) +{ + CHECK(gettimeofday(&tv_before, NULL) != -1); +} + +void after(const char *msg) +{ + time_t diff; + CHECK(gettimeofday(&tv_after, NULL) != -1); + diff = tv_after.tv_sec - tv_before.tv_sec; + if (diff >= 8) { + printf("\nrndrm99: the following fn took more than 8 seconds: %s (took %u secs)\n",msg,(unsigned) diff); + fflush(stdout); + display_stats(); + } +} + +#define WRITE_BUFFER_SIZE 32768 + +static char write_buffer[WRITE_BUFFER_SIZE]; + +static void init_write_buffer() +{ + static int init = 0; + + if (!init) { + int i, d; + uint64_t u; + + u = RAND_MAX; + u += 1; + u /= 256; + d = (int) u; + srand(1); + for (i = 0; i < WRITE_BUFFER_SIZE; ++i) + write_buffer[i] = rand() / d; + init = 1; + } +} + +/* Write size random bytes into file descriptor fd at the current position, + returning the number of bytes actually written */ +uint64_t fill_file(int fd, uint64_t size) +{ + ssize_t written; + size_t sz; + unsigned start = 0, length; + uint64_t remains; + uint64_t actual_size = 0; + + init_write_buffer(); + remains = size; + while (remains > 0) { + length = WRITE_BUFFER_SIZE - start; + if (remains > length) + sz = length; + else + sz = (size_t) remains; + before(); + written = write(fd, write_buffer + start, sz); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + after("write"); + fprintf(stderr,"\nrndrm99: write failed with ENOSPC\n");fflush(stderr); + display_stats(); + break; + } + after("write"); + remains -= written; + actual_size += written; + if ((size_t) written == sz) + start = 0; + else + start += written; + } + return actual_size; +} + +/* Create a file of size file_size */ +uint64_t create_file(const char *file_name, uint64_t file_size) +{ + int fd; + int flags; + mode_t mode; + uint64_t actual_size; /* Less than size if the file system is full */ + + flags = O_CREAT | O_TRUNC | O_WRONLY; + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; + before(); + fd = open(file_name, flags, mode); + if (fd == -1 && errno == ENOSPC) { + errno = 0; + after("open"); + fprintf(stderr,"\nrndrm99: open failed with ENOSPC\n");fflush(stderr); + display_stats(); + return 0; /* File system full */ + } + CHECK(fd != -1); + after("open"); + actual_size = fill_file(fd, file_size); + before(); + CHECK(close(fd) != -1); + after("close"); + if (file_size != 0 && actual_size == 0) { + printf("\nrndrm99: unlinking zero size file\n");fflush(stdout); + before(); + CHECK(unlink(file_name) != -1); + after("unlink (create_file)"); + } + return actual_size; +} + +/* Create an empty sub-directory or small file in the current directory */ +int64_t create_entry(char *return_name) +{ + int fd; + char name[256]; + int64_t res; + + for (;;) { + sprintf(name, "%u", (unsigned) tests_random_no(10000000)); + before(); + fd = open(name, O_RDONLY); + after("open (create_entry)"); + if (fd == -1) + break; + before(); + close(fd); + after("close (create_entry)"); + } + if (return_name) + strcpy(return_name, name); + if (tests_random_no(2)) { + res = create_file(name, tests_random_no(4096)); + if (res > 0) + files_created += 1; + return res; + } else { + before(); + if (mkdir(name, 0777) == -1) { + CHECK(errno == ENOSPC); + after("mkdir"); + errno = 0; + fprintf(stderr,"\nrndrm99: mkdir failed with ENOSPC\n");fflush(stderr); + display_stats(); + return 0; + } + after("mkdir"); + dirs_created += 1; + return TESTS_EMPTY_DIR_SIZE; + } +} + +/* Remove a random file of empty sub-directory from the current directory */ +int64_t remove_entry(void) +{ + DIR *dir; + struct dirent *entry; + unsigned count = 0, pos; + int64_t result = 0; + + before(); + dir = opendir("."); + CHECK(dir != NULL); + after("opendir"); + for (;;) { + errno = 0; + before(); + entry = readdir(dir); + if (entry) { + after("readdir 1"); + if (strcmp(".",entry->d_name) != 0 && + strcmp("..",entry->d_name) != 0) + ++count; + } else { + CHECK(errno == 0); + after("readdir 1"); + break; + } + } + pos = tests_random_no(count); + count = 0; + before(); + rewinddir(dir); + after("rewinddir"); + for (;;) { + errno = 0; + before(); + entry = readdir(dir); + if (!entry) { + CHECK(errno == 0); + after("readdir 2"); + break; + } + after("readdir 2"); + if (strcmp(".",entry->d_name) != 0 && + strcmp("..",entry->d_name) != 0) { + if (count == pos) { + if (entry->d_type == DT_DIR) { + before(); + tests_clear_dir(entry->d_name); + after("tests_clear_dir"); + before(); + CHECK(rmdir(entry->d_name) != -1); + after("rmdir"); + result = TESTS_EMPTY_DIR_SIZE; + dirs_removed += 1; + } else { + struct stat st; + before(); + CHECK(stat(entry->d_name, &st) != -1); + after("stat"); + result = st.st_size; + before(); + CHECK(unlink(entry->d_name) != -1); + after("unlink"); + files_removed += 1; + } + } + ++count; + } + } + before(); + CHECK(closedir(dir) != -1); + after("closedir"); + return result; +} + +void rndrm99(void) +{ + int64_t repeat, loop_cnt; + int64_t size, this_size; + pid_t pid; + char dir_name[256]; + + size_ptr = &size; + /* Create a directory to test in */ + pid = getpid(); + tests_cat_pid(dir_name, "rndrm99_test_dir_", pid); + if (chdir(dir_name) == -1) + CHECK(mkdir(dir_name, 0777) != -1); + CHECK(chdir(dir_name) != -1); + /* Repeat loop */ + repeat = tests_repeat_parameter; + size = 0; + for (;;) { + /* Create and remove sub-dirs and small files, */ + /* but tending to grow */ + printf("\nrndrm99: growing\n");fflush(stdout); + loop_cnt = 0; + do { + if (loop_cnt++ % 2000 == 0) + display_stats(); + if (tests_random_no(3)) { + this_size = create_entry(NULL); + if (!this_size) + break; + size += this_size; + } else { + this_size = remove_entry(); + size -= this_size; + if (size < 0) + size = 0; + if (!this_size) + this_size = 1; + } + } while (this_size && + (tests_size_parameter == 0 || + size < tests_size_parameter)); + /* Create and remove sub-dirs and small files, but */ + /* but tending to shrink */ + printf("\nrndrm99: shrinking\n");fflush(stdout); + loop_cnt = 0; + do { + if (loop_cnt++ % 2000 == 0) + display_stats(); + if (!tests_random_no(3)) { + this_size = create_entry(NULL); + size += this_size; + } else { + this_size = remove_entry(); + size -= this_size; + if (size < 0) + size = 0; + } + } while ((tests_size_parameter != 0 && + size > tests_size_parameter / 10) || + (tests_size_parameter == 0 && size > 100000)); + /* Break if repeat count exceeded */ + if (tests_repeat_parameter > 0 && --repeat <= 0) + break; + /* Sleep */ + if (tests_sleep_parameter > 0) { + unsigned us = tests_sleep_parameter * 1000; + unsigned rand_divisor = RAND_MAX / us; + unsigned s = (us / 2) + (rand() / rand_divisor); + printf("\nrndrm99: sleeping\n");fflush(stdout); + usleep(s); + } + } + printf("\nrndrm99: tidying\n");fflush(stdout); + display_stats(); + /* Tidy up by removing everything */ + tests_clear_dir("."); + CHECK(chdir("..") != -1); + CHECK(rmdir(dir_name) != -1); + size_ptr = 0; +} + +/* Title of this test */ + +const char *rndrm99_get_title(void) +{ + return "Randomly create and remove directories and files"; +} + +/* Description of this test */ + +const char *rndrm99_get_description(void) +{ + return + "Create a directory named rndrm99_test_dir_pid, where " \ + "pid is the process id. Within that directory, " \ + "randomly create and remove " \ + "a number of sub-directories and small files, " \ + "but do more creates than removes. " \ + "When the total size of all sub-directories and files " \ + "is greater than the size specified by the size parameter, " \ + "start to do more removes than creates. " \ + "The size parameter is given by the -z or --size option, " \ + "otherwise it defaults to 1000000. " \ + "A size of zero fills the file system until there is no " + "space left. " \ + "The task repeats, sleeping in between each iteration. " \ + "The repeat count is set by the -n or --repeat option, " \ + "otherwise it defaults to 1. " \ + "A repeat count of zero repeats forever. " \ + "The sleep value is given by the -p or --sleep option, " \ + "otherwise it defaults to 0. " + "Sleep is specified in milliseconds."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test size */ + tests_size_parameter = 1000000; + + /* Set default test repetition */ + tests_repeat_parameter = 1; + + /* Set default test sleep */ + tests_sleep_parameter = 0; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, rndrm99_get_title(), + rndrm99_get_description(), "znp"); + 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 */ + rndrm99(); + return 0; +} diff --git a/tests/fs-tests/stress/atoms/rndwrite00.c b/tests/fs-tests/stress/atoms/rndwrite00.c new file mode 100644 index 0000000..655d9cc --- /dev/null +++ b/tests/fs-tests/stress/atoms/rndwrite00.c @@ -0,0 +1,201 @@ +/* + * 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 "tests.h" + +#define BLOCK_SIZE 32768 +#define BUFFER_SIZE 32768 + +static void check_file(int fd, char *data, size_t length) +{ + size_t n, i; + char buf[BUFFER_SIZE]; + + CHECK(lseek(fd, 0, SEEK_SET) != -1); + n = 0; + for (;;) { + i = read(fd, buf, BUFFER_SIZE); + CHECK(i >= 0); + if (i == 0) + break; + CHECK(memcmp(buf, data + n, i) == 0); + n += i; + } + CHECK(n == length); +} + +void rndwrite00(void) +{ + int fd; + pid_t pid; + ssize_t written; + size_t remains; + size_t block; + size_t actual_size; + size_t check_every; + char *data, *p, *q; + off_t offset; + size_t size; + int64_t repeat; + char file_name[256]; + char buf[4096]; + + /* Create file */ + pid = getpid(); + tests_cat_pid(file_name, "rndwrite00_test_file_", pid); + fd = open(file_name, O_CREAT | O_RDWR | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + CHECK(fd != -1); + /* Allocate memory to hold file data */ + CHECK(tests_size_parameter > 0); + CHECK(tests_size_parameter <= SIZE_MAX); + data = (char *) malloc(tests_size_parameter); + CHECK(data != NULL); + /* Fill with random data */ + srand(pid); + for (p = data, q = data + tests_size_parameter; p != q; ++p) + *p = rand(); + /* Write to file */ + p = data; + remains = tests_size_parameter; + while (remains > 0) { + if (remains > BLOCK_SIZE) + block = BLOCK_SIZE; + else + block = remains; + written = write(fd, p, block); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + break; + } + remains -= written; + p += written; + } + actual_size = p - data; + /* Repeating bit */ + repeat = tests_repeat_parameter; + check_every = actual_size / 8192; + for (;;) { + offset = tests_random_no(actual_size); + size = tests_random_no(4096); + /* Don't change the file size */ + if (offset + size > actual_size) + size = actual_size - offset; + if (!size) + continue; + for (p = buf, q = p + size; p != q; ++p) + *p = rand(); + CHECK(lseek(fd, offset, SEEK_SET) != -1); + written = write(fd, buf, size); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + } else + memcpy(data + offset, buf, written); + /* Break if repeat count exceeded */ + if (tests_repeat_parameter > 0 && --repeat <= 0) + break; + if (repeat % check_every == 0) + check_file(fd, data, actual_size); + /* Sleep */ + if (tests_sleep_parameter > 0) { + unsigned us = tests_sleep_parameter * 1000; + unsigned rand_divisor = RAND_MAX / us; + unsigned s = (us / 2) + (rand() / rand_divisor); + usleep(s); + } + } + /* Check and close file */ + check_file(fd, data, actual_size); + CHECK(close(fd) != -1); + if (tests_delete_flag) + CHECK(unlink(file_name) != -1); +} + +/* Title of this test */ + +const char *rndwrite00_get_title(void) +{ + return "Randomly write a large test file"; +} + +/* Description of this test */ + +const char *rndwrite00_get_description(void) +{ + return + "Create a file named rndwrite00_test_file_pid, where " \ + "pid is the process id. " \ + "The file is filled with random data. " \ + "The size of the file is given by the -z or --size option, " \ + "otherwise it defaults to 1000000. " \ + "Then a randomly sized block of random data is written at a " \ + "random location in the file. "\ + "The block size is always in the range 1 to 4095. " \ + "If a sleep value is specified, the process sleeps. " \ + "The number of writes is given by the repeat count. " \ + "The repeat count is set by the -n or --repeat option, " \ + "otherwise it defaults to 10000. " \ + "A repeat count of zero repeats forever. " \ + "The sleep value is given by the -p or --sleep option, " \ + "otherwise it defaults to 0. " + "Sleep is specified in milliseconds. " \ + "Periodically the data in the file is checked with a copy " \ + "held in memory. " \ + "If the delete option is specified the file is finally " \ + "deleted."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test file size */ + tests_size_parameter = 1000000; + + /* Set default test repetition */ + tests_repeat_parameter = 10000; + + /* Set default test sleep */ + tests_sleep_parameter = 0; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, rndwrite00_get_title(), + rndwrite00_get_description(), "zne"); + 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 */ + rndwrite00(); + return 0; +} diff --git a/tests/fs-tests/stress/atoms/stress_1.c b/tests/fs-tests/stress/atoms/stress_1.c new file mode 100644 index 0000000..86f94c2 --- /dev/null +++ b/tests/fs-tests/stress/atoms/stress_1.c @@ -0,0 +1,109 @@ +/* + * 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 "tests.h" + +#define WRITE_BUFFER_SIZE 32768 + +void stress_1(void) +{ + int fd, i; + pid_t pid; + ssize_t written; + int64_t remains; + size_t block; + char file_name[256]; + char buf[WRITE_BUFFER_SIZE]; + + pid = getpid(); + tests_cat_pid(file_name, "stress_1_test_file_", pid); + fd = open(file_name, O_CREAT | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + CHECK(fd != -1); + srand(pid); + for (i = 0; i < WRITE_BUFFER_SIZE;++i) + buf[i] = rand(); + remains = tests_size_parameter; + while (remains > 0) { + if (remains > WRITE_BUFFER_SIZE) + block = WRITE_BUFFER_SIZE; + else + block = remains; + written = write(fd, buf, block); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + break; + } + remains -= written; + } + CHECK(close(fd) != -1); + if (tests_delete_flag) + CHECK(unlink(file_name) != -1); +} + +/* Title of this test */ + +const char *stress_1_get_title(void) +{ + return "Create / overwrite a large file"; +} + +/* Description of this test */ + +const char *stress_1_get_description(void) +{ + return + "Create a file named stress_1_test_file_pid, " \ + "where pid is the process id. " \ + "The size is given by the -z or --size option, " \ + "otherwise it defaults to 1000000. " \ + "The file will be deleted if the delete option " \ + "is specified. "; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test file size */ + tests_size_parameter = 1000000; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, stress_1_get_title(), + stress_1_get_description(), "ze"); + 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 */ + stress_1(); + return 0; +} diff --git a/tests/fs-tests/stress/atoms/stress_2.c b/tests/fs-tests/stress/atoms/stress_2.c new file mode 100644 index 0000000..a9bc31a --- /dev/null +++ b/tests/fs-tests/stress/atoms/stress_2.c @@ -0,0 +1,120 @@ +/* + * 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 "tests.h" + +#define WRITE_BUFFER_SIZE 32768 + +void stress_2(void) +{ + int fd, i; + pid_t pid; + ssize_t written; + int64_t remains; + int64_t repeat; + size_t block; + char *file_name; + char buf[WRITE_BUFFER_SIZE]; + + file_name = "stress_2_test_file"; + fd = open(file_name, O_CREAT | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + CHECK(fd != -1); + CHECK(unlink(file_name) != -1); + pid = getpid(); + srand(pid); + repeat = tests_repeat_parameter; + for (;;) { + for (i = 0; i < WRITE_BUFFER_SIZE;++i) + buf[i] = rand(); + CHECK(lseek(fd, 0, SEEK_SET) != -1); + remains = tests_size_parameter; + while (remains > 0) { + if (remains > WRITE_BUFFER_SIZE) + block = WRITE_BUFFER_SIZE; + else + block = remains; + written = write(fd, buf, block); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + break; + } + remains -= written; + } + if (tests_repeat_parameter > 0 && --repeat <= 0) + break; + } + CHECK(close(fd) != -1); +} + +/* Title of this test */ + +const char *stress_2_get_title(void) +{ + return "Create / overwrite a large deleted file"; +} + +/* Description of this test */ + +const char *stress_2_get_description(void) +{ + return + "Create a file named stress_2_test_file. " \ + "Open it, delete it while holding the open file descriptor, " \ + "then fill it with random data. " \ + "Repeated re-write the file some number of times. " \ + "The repeat count is given by the -n or --repeat option, " \ + "otherwise it defaults to 10. " \ + "The file size is given by the -z or --size option, " \ + "otherwise it defaults to 1000000."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test file size */ + tests_size_parameter = 1000000; + + /* Set default test repetition */ + tests_repeat_parameter = 10; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, stress_2_get_title(), + stress_2_get_description(), "zn"); + 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 */ + stress_2(); + return 0; +} diff --git a/tests/fs-tests/stress/atoms/stress_3.c b/tests/fs-tests/stress/atoms/stress_3.c new file mode 100644 index 0000000..99fb05d --- /dev/null +++ b/tests/fs-tests/stress/atoms/stress_3.c @@ -0,0 +1,122 @@ +/* + * 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 "tests.h" + +#define WRITE_BUFFER_SIZE 32768 + +void stress_3(void) +{ + int fd, i; + pid_t pid; + ssize_t written; + int64_t remains; + size_t block; + char file_name[256]; + char buf[WRITE_BUFFER_SIZE]; + + pid = getpid(); + tests_cat_pid(file_name, "stress_3_test_file_", pid); + fd = open(file_name, O_CREAT | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + CHECK(fd != -1); + pid = getpid(); + srand(pid); + for (i = 0; i < WRITE_BUFFER_SIZE;++i) + buf[i] = rand(); + CHECK(lseek(fd, tests_size_parameter, SEEK_SET) != -1); + CHECK(write(fd, "!", 1) == 1); + CHECK(lseek(fd, 0, SEEK_SET) != -1); + remains = tests_size_parameter; + while (remains > 0) { + if (remains > WRITE_BUFFER_SIZE) + block = WRITE_BUFFER_SIZE; + else + block = remains; + written = write(fd, buf, block); + if (written <= 0) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + break; + } + remains -= written; + } + if (ftruncate(fd, 0) == -1) { + CHECK(errno == ENOSPC); /* File system full */ + errno = 0; + } + CHECK(close(fd) != -1); + if (tests_delete_flag) + CHECK(unlink(file_name) != -1); +} + +/* Title of this test */ + +const char *stress_3_get_title(void) +{ + return "Create a file with a large hole and fill it"; +} + +/* Description of this test */ + +const char *stress_3_get_description(void) +{ + return + "Create a file named stress_3_test_file_pid, " \ + "where pid is the process id. " \ + "Write a single character past the end of the file, " \ + "based on the specified file size, " \ + "which creates a hole in the file. " + "Fill the hole with random data. " \ + "Then truncate the file length to zero. " \ + "The size is given by the -z or --size option, " \ + "otherwise it defaults to 1000000. " \ + "The file will be deleted if the delete option " \ + "is specified."; +} + +int main(int argc, char *argv[]) +{ + int run_test; + + /* Set default test file size */ + tests_size_parameter = 1000000; + + /* Handle common arguments */ + run_test = tests_get_args(argc, argv, stress_3_get_title(), + stress_3_get_description(), "ze"); + 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 */ + stress_3(); + return 0; +} diff --git a/tests/fs-tests/stress/stress00.sh b/tests/fs-tests/stress/stress00.sh new file mode 100755 index 0000000..be95d7c --- /dev/null +++ b/tests/fs-tests/stress/stress00.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +TEST_DIR=$TEST_FILE_SYSTEM_MOUNT_DIR +if test -z "$TEST_DIR"; +then + TEST_DIR="/mnt/test_file_system" +fi + +FREESPACE=`../utils/free_space "$TEST_DIR"` + +if test -z "$FREESPACE"; +then + echo "Failed to determine free space" + exit 1 +fi + +if test -n "$1"; +then + DURATION="-d$1"; +else + DURATION=""; +fi + +FWRITE00=atoms/fwrite00 +RNDWR=atoms/rndwrite00 +GCHUP=atoms/gcd_hupper +PDFLUSH=atoms/pdfrun +FSIZE=$(( $FREESPACE/15 )); + +../utils/fstest_monitor $DURATION \ +"$FWRITE00 -z $FSIZE -n0 -p 20" \ +"$FWRITE00 -z $FSIZE -n0 -p 10 -s" \ +"$FWRITE00 -z $FSIZE -n0 -p 20 -u" \ +"$FWRITE00 -z $FSIZE -n0 -p 70 -o" \ +"$FWRITE00 -z $FSIZE -n0 -p 15 -s -o -u" \ +"$FWRITE00 -z $FSIZE -n0 -p 10 -u -c" \ +"$FWRITE00 -z $FSIZE -n0 -p 10 -u -o -c" \ +"$FWRITE00 -z $FSIZE -n0 -p 10 -o -c" \ +"$FWRITE00 -z $FSIZE -n0 -p 100 -o -u" \ +"$FWRITE00 -z $FSIZE -n0 -p 100 -s -o -u -c" \ +"$FWRITE00 -z $FSIZE -n0 -p 100 -o -u" \ +"$FWRITE00 -z $FSIZE -n0 -p 100 -u" \ +"$FWRITE00 -z $FSIZE -n0 -p 100 -s -o" \ +"$RNDWR -z $FSIZE -n0 -p 10 -e" \ +"$RNDWR -z $FSIZE -n0 -p 100 -e" \ +"$PDFLUSH -z 1073741824 -n0" \ +"$GCHUP -n0" + +STATUS=$? + +rm -rf ${TEST_DIR}/* + +exit $STATUS diff --git a/tests/fs-tests/stress/stress01.sh b/tests/fs-tests/stress/stress01.sh new file mode 100755 index 0000000..5913c1c --- /dev/null +++ b/tests/fs-tests/stress/stress01.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +TEST_DIR=$TEST_FILE_SYSTEM_MOUNT_DIR +if test -z "$TEST_DIR"; +then + TEST_DIR="/mnt/test_file_system" +fi + +FREESPACE=`../utils/free_space "$TEST_DIR"` + +if test -z "$FREESPACE"; +then + echo "Failed to determine free space" + exit 1 +fi + +if test -n "$1"; +then + DURATION="-d$1"; +else + DURATION=""; +fi + +FWRITE00=atoms/fwrite00 +RNDWR=atoms/rndwrite00 +PDFLUSH=atoms/pdfrun +FSIZE=$(( $FREESPACE/15 )); + +../utils/fstest_monitor $DURATION \ +"$FWRITE00 -z $FSIZE -n0 -p 300" \ +"$FWRITE00 -z $FSIZE -n0 -u" \ +"$FWRITE00 -z $FSIZE -n0 -u -c" \ +"$FWRITE00 -z $FSIZE -n0 -s -o" \ +"$RNDWR -z $FSIZE -n0 -e" + +STATUS=$? + +rm -rf ${TEST_DIR}/* + +exit $STATUS diff --git a/tests/fs-tests/utils/Makefile b/tests/fs-tests/utils/Makefile new file mode 100644 index 0000000..9fb60b5 --- /dev/null +++ b/tests/fs-tests/utils/Makefile @@ -0,0 +1,19 @@ + +ifeq ($(origin CC),default) +CC = gcc +endif + +CFLAGS := $(CFLAGS) -Wall -g -O2 -I../lib + +LDFLAGS := $(LDFLAGS) + +TARGETS = fstest_monitor free_space + +all: $(TARGETS) + +clean: + rm -f *.o $(TARGETS) + +tests: all + ./fstest_monitor + ./free_space > /dev/null diff --git a/tests/fs-tests/utils/free_space.c b/tests/fs-tests/utils/free_space.c new file mode 100644 index 0000000..88036aa --- /dev/null +++ b/tests/fs-tests/utils/free_space.c @@ -0,0 +1,56 @@ +/* + * 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 <stdio.h> +#include <stdint.h> +#include <string.h> +#include <sys/statvfs.h> + +int main(int argc, char *argv[]) +{ + char *dir_name = "."; + uint64_t free_space; + struct statvfs fs_info; + + if (argc > 1) { + if (strncmp(argv[1], "--help", 6) == 0 || + strncmp(argv[1], "-h", 2) == 0) { + printf( "Usage is: " + "free_space [directory]\n" + "\n" + "Display the free space of the file system " + "of the directory given\n" + "or the current directory if no " + "directory is given.\nThe value output is " + "in bytes.\n" + ); + return 1; + } + dir_name = argv[1]; + } + if (statvfs(dir_name, &fs_info) == -1) + return 1; + + free_space = (uint64_t) fs_info.f_bavail * (uint64_t) fs_info.f_frsize; + + printf("%llu\n", (unsigned long long) free_space); + + return 0; +} diff --git a/tests/fs-tests/utils/fstest_monitor.c b/tests/fs-tests/utils/fstest_monitor.c new file mode 100644 index 0000000..298ee26 --- /dev/null +++ b/tests/fs-tests/utils/fstest_monitor.c @@ -0,0 +1,281 @@ +/* + * 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 <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <stdio.h> +#include <signal.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <errno.h> + +struct child_info { + struct child_info *next; + pid_t pid; + int terminated; + int killed; + int gone; +}; + +struct child_info *children = 0; + +void kill_children(void) +{ + struct child_info *child; + + child = children; + while (child) { + if (!child->gone) { + if (!child->terminated) { + child->terminated = 1; + kill(child->pid, SIGTERM); + } /*else if (!child->killed) { + child->killed = 1; + kill(child->pid, SIGKILL); + }*/ + } + child = child->next; + } +} + +void add_child(pid_t child_pid) +{ + struct child_info *child; + size_t sz; + + sz = sizeof(struct child_info); + child = (struct child_info *) malloc(sz); + memset(child, 0, sz); + child->pid = child_pid; + child->next = children; + children = child; +} + +void mark_child_gone(pid_t child_pid) +{ + struct child_info *child; + + child = children; + while (child) { + if (child->pid == child_pid) { + child->gone = 1; + break; + } + child = child->next; + } +} + +int have_children(void) +{ + struct child_info *child; + + child = children; + while (child) { + if (!child->gone) + return 1; + child = child->next; + } + return 0; +} + +int parse_command_line(char *cmdline, int *pargc, char ***pargv) +{ + char **tmp; + char *p, *v, *q; + size_t sz; + int argc = 0; + int state = 0; + char *argv[1024]; + + if (!cmdline) + return 1; + q = v = (char *) malloc(strlen(cmdline) + 1024); + if (!v) + return 1; + p = cmdline; + for (;;) { + char c = *p++; + if (!c) { + *v++ = 0; + break; + } + switch (state) { + case 0: /* Between args */ + if (isspace(c)) + break; + argv[argc++] = v; + if (c == '"') { + state = 2; + break; + } else if (c == '\'') { + state = 3; + break; + } + state = 1; + case 1: /* Not quoted */ + if (c == '\\') { + if (*p) + *v++ = *p; + } else if (isspace(c)) { + *v++ = 0; + state = 0; + } else + *v++ = c; + break; + case 2: /* Double quoted */ + if (c == '\\' && *p == '"') { + *v++ = '"'; + ++p; + } else if (c == '"') { + *v++ = 0; + state = 0; + } else + *v++ = c; + break; + case 3: /* Single quoted */ + if (c == '\'') { + *v++ = 0; + state = 0; + } else + *v++ = c; + break; + } + } + argv[argc] = 0; + sz = sizeof(char *) * (argc + 1); + tmp = (char **) malloc(sz); + if (!tmp) { + free(q); + return 1; + } + if (argc == 0) + free(q); + memcpy(tmp, argv, sz); + *pargc = argc; + *pargv = tmp; + return 0; +} + +void signal_handler(int signum) +{ + kill_children(); +} + +int result = 0; +int alarm_gone_off = 0; + +void alarm_handler(int signum) +{ + if (!result) + alarm_gone_off = 1; + kill_children(); +} + +int main(int argc, char *argv[], char **env) +{ + int p; + pid_t child_pid; + int status; + int duration = 0; + + p = 1; + if (argc > 1) { + if (strncmp(argv[p], "--help", 6) == 0 || + strncmp(argv[p], "-h", 2) == 0) { + printf( "Usage is: " + "fstest_monitor options programs...\n" + " Options are:\n" + " -h, --help " + "This help message\n" + " -d, --duration arg " + "Stop after arg seconds\n" + "\n" + "Run programs and wait for them." + " If duration is specified,\n" + "kill all programs" + " after that number of seconds have elapsed.\n" + "Example: " + "fstest_monitor \"/bin/ls -l\" /bin/date\n" + ); + return 1; + } + if (strncmp(argv[p], "--duration", 10) == 0 || + strncmp(argv[p], "-d", 2) == 0) { + char *s; + if (p+1 < argc && !isdigit(argv[p][strlen(argv[p])-1])) + ++p; + s = argv[p]; + while (*s && !isdigit(*s)) + ++s; + duration = atoi(s); + ++p; + } + } + + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); + for (; p < argc; ++p) { + child_pid = fork(); + if (child_pid) { + /* Parent */ + if (child_pid == (pid_t) -1) { + kill_children(); + result = 1; + break; + } + add_child(child_pid); + } else { + /* Child */ + int cargc; + char **cargv; + + if (parse_command_line(argv[p], &cargc, &cargv)) + return 1; + execve(cargv[0], cargv, env); + return 1; + } + } + if (!result && duration > 0) { + signal(SIGALRM, alarm_handler); + alarm(duration); + } + while (have_children()) { + status = 0; + child_pid = wait(&status); + if (child_pid == (pid_t) -1) { + if (errno == EINTR) + continue; + kill_children(); + return 1; + } + mark_child_gone(child_pid); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + result = 1; + kill_children(); + } + } + + if (alarm_gone_off) + return 0; + + return result; +} |