static char *pick_rename_name(struct dir_info **parent, struct dir_entry_info **rename_entry, int isdir) { struct dir_info *dir = pick_dir(); struct dir_entry_info *entry; size_t r; *parent = dir; *rename_entry = NULL; if (grow || tests_random_no(20) < 10) return copy_string(make_name(dir)); r = tests_random_no(dir->number_of_entries); entry = dir->first; while (entry && r) { entry = entry->next; --r; } if (!entry) entry = dir->first; if (!entry || (entry->type == 'd' && entry->entry.dir->number_of_entries != 0)) return copy_string(make_name(dir)); if ((isdir && entry->type != 'd') || (!isdir && entry->type == 'd')) return copy_string(make_name(dir)); *rename_entry = entry; return copy_string(entry->name); }
static char *pick_symlink_target(const char *symlink_path) { struct dir_info *dir; struct dir_entry_info *entry; size_t r; char *path, *rel_path; dir = pick_dir(); if (tests_random_no(100) < 10) return dir_path(dir, make_name(dir)); r = tests_random_no(dir->number_of_entries); entry = dir->first; while (entry && r) { entry = entry->next; --r; } if (!entry) entry = dir->first; if (!entry) return dir_path(dir, make_name(dir)); path = dir_path(dir, entry->name); if (tests_random_no(20) < 10) return path; rel_path = relative_path(symlink_path, path); free(path); return rel_path; }
/* 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); }
/* 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; } }
/* Randomly select something to do with a directory */ static void operate_on_dir(struct dir_info *dir) { size_t r; struct dir_entry_info *entry; struct file_info *file; r = tests_random_no(14); if (r == 0 && grow) /* When growing, 1 time in 14 create a file */ file_new(dir, make_name(dir)); else if (r == 1 && grow) /* When growing, 1 time in 14 create a directory */ dir_new(dir, make_name(dir)); else if (r == 2 && grow && (file = pick_file()) != NULL) /* When growing, 1 time in 14 create a hard link */ link_new(dir, make_name(dir), file); else if (r == 3 && grow && tests_random_no(5) == 0) /* When growing, 1 time in 70 create a symbolic link */ symlink_new(dir, make_name(dir)); else { /* Otherwise randomly select an entry to operate on */ r = tests_random_no(dir->number_of_entries); entry = dir->first; while (entry && r) { entry = entry->next; --r; } if (entry) operate_on_entry(entry); } }
static char *make_name(struct dir_info *dir) { static char name[256]; struct dir_entry_info *entry; int found; do { found = 0; if (tests_random_no(5) == 1) { int i, n = tests_random_no(255) + 1; CHECK(n > 0 && n < 256); for (i = 0; i < n; i++) name[i] = 'a' + tests_random_no(26); name[i] = '\0'; } else sprintf(name, "%u", (unsigned) tests_random_no(1000000)); for (entry = dir->first; entry; entry = entry->next) { if (strcmp(entry->name, name) == 0) { found = 1; break; } } } while (found); return name; }
static struct file_info *pick_file(void) { struct dir_info *dir = top_dir; for (;;) { struct dir_entry_info *entry; size_t r; r = tests_random_no(dir->number_of_entries); entry = dir->first; while (entry && r) { entry = entry->next; --r; } for (;;) { if (!entry) return NULL; if (entry->type == 'f') return entry->entry.file; if (entry->type == 'd') if (entry->entry.dir->number_of_entries != 0) break; entry = entry->next; } dir = entry->entry.dir; } }
static 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(); }
/* 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(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); }
/* 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); } }
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); } }
/* Randomly select something to do with an open file */ static void operate_on_open_file(struct fd_info *fdi) { size_t r; r = tests_random_no(1000); if (shrink && r < 5) file_truncate(fdi->file, fdi->fd); else if (r < 21) file_close(fdi); else if (shrink && r < 121 && !fdi->file->deleted) file_delete(fdi->file); else { file_write(fdi->file, fdi->fd); if (r >= 999) { if (tests_random_no(100) >= 50) CHECK(fsync(fdi->fd) != -1); else CHECK(fdatasync(fdi->fd) != -1); } } }
static struct dir_info *pick_dir(void) { struct dir_info *dir = top_dir; if (tests_random_no(40) >= 30) return dir; for (;;) { struct dir_entry_info *entry; size_t r; r = tests_random_no(dir->number_of_entries); entry = dir->first; while (entry && r) { entry = entry->next; --r; } for (;;) { if (!entry) break; if (entry->type == 'd') break; entry = entry->next; } if (!entry) { entry = dir->first; for (;;) { if (!entry) break; if (entry->type == 'd') break; entry = entry->next; } } if (!entry) return dir; dir = entry->entry.dir; if (tests_random_no(40) >= 30) return dir; } }
static struct fd_info *file_open(struct file_info *file) { int fd, flags = O_RDWR; char *path; path = dir_path(file->links->parent, file->links->name); if (tests_random_no(100) == 1) flags |= O_SYNC; fd = open(path, flags); CHECK(fd != -1); free(path); return add_fd(file, fd); }
/* Create an empty sub-directory or small file in the current directory */ static 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; } }
static struct dir_entry_info *pick_entry(struct file_info *file) { struct dir_entry_info *entry; size_t r; if (!file->link_count) return NULL; r = tests_random_no(file->link_count); entry = file->links; while (entry && r--) entry = entry->next_link; return entry; }
/* 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; }
/* Randomly select something to do with a file */ static void operate_on_file(struct file_info *file) { /* Try to keep at least 10 files open */ if (open_files_count < 10) { file_open(file); return; } /* Try to keep about 20 files open */ if (open_files_count < 20 && tests_random_no(2) == 0) { file_open(file); return; } /* Try to keep up to 40 files open */ if (open_files_count < 40 && tests_random_no(20) == 0) { file_open(file); return; } /* Occasionly truncate */ if (shrink && tests_random_no(100) == 0) { file_truncate_file(file); return; } /* Mostly just write */ file_write_file(file); /* Once in a while check it too */ if (tests_random_no(100) == 1) { int fd = -2; if (file->links) fd = -1; else if (file->fds) fd = file->fds->fd; if (fd != -2) { check_run_no += 1; file_check(file, fd); } } }
/* Randomly select something to do with an open file */ static void operate_on_open_file(struct fd_info *fdi) { size_t r; r = tests_random_no(1000); if (shrink && r == 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); }
/* Randomly select something to do with a directory entry */ static void operate_on_entry(struct dir_entry_info *entry) { /* 1 time in 1000 rename */ if (tests_random_no(1000) == 0) { rename_entry(entry); return; } if (entry->type == 's') { symlink_check(entry->entry.symlink); /* If shrinking, 1 time in 50, remove a symlink */ if (shrink && tests_random_no(50) == 0) symlink_remove(entry->entry.symlink); return; } if (entry->type == 'd') { /* If shrinking, 1 time in 50, remove a directory */ if (shrink && tests_random_no(50) == 0) { dir_remove(entry->entry.dir); return; } operate_on_dir(entry->entry.dir); } if (entry->type == 'f') { /* If shrinking, 1 time in 10, remove a file */ if (shrink && tests_random_no(10) == 0) { file_delete(entry->entry.file); return; } /* If not growing, 1 time in 10, unlink a file with links > 1 */ if (!grow && entry->entry.file->link_count > 1 && tests_random_no(10) == 0) { file_unlink_file(entry->entry.file); return; } operate_on_file(entry->entry.file); } }
static void file_write(struct file_info *file, int fd) { off_t offset; size_t size, actual; unsigned seed; int truncate = 0; if (can_mmap && !full && !file->deleted && tests_random_no(100) == 1) { file_mmap_write(file); return; } get_offset_and_size(file, &offset, &size); seed = tests_random_no(10000000); actual = file_write_data(file, fd, offset, size, seed); if (offset + actual <= file->length && shrink) /* 1 time in 100, when shrinking truncate after the write */ if (tests_random_no(100) == 0) truncate = 1; if (actual != 0) file_write_info(file, offset, actual, seed); /* Delete errored files */ if (!check_nospc_files && file->no_space_error) { file_delete(file); return; } if (truncate) { size_t new_length = offset + actual; if (file_ftruncate(file, fd, new_length)) file_truncate_info(file, new_length); } }
/* 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); } }
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; }
/* Select an open file at random */ static void operate_on_an_open_file(void) { size_t r; struct open_file_info *ofi; /* When shrinking, close all open files 1 time in 128 */ if (shrink) { static int x = 0; x += 1; x &= 127; if (x == 0) { close_open_files(); return; } } /* Close any open files that have errored */ if (!check_nospc_files) { ofi = open_files; while (ofi) { if (ofi->fdi->file->no_space_error) { struct fd_info *fdi; fdi = ofi->fdi; ofi = ofi->next; file_close(fdi); } else ofi = ofi->next; } } r = tests_random_no(open_files_count); for (ofi = open_files; ofi; ofi = ofi->next, --r) if (!r) { operate_on_open_file(ofi->fdi); return; } }
/* 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; } }
/* * Re-mount test file system. Randomly choose how to do this: re-mount R/O then * re-mount R/W, or unmount, then mount R/W, or unmount then mount R/O then * re-mount R/W, etc. This should improve test coverage. */ void tests_remount(void) { int err; struct mntent mount_info; char *source, *target, *filesystemtype, *data; char cwd[4096]; unsigned long mountflags, flags; unsigned int rorw1, um, um_ro, um_rorw, rorw2; CHECK(tests_get_mount_info(&mount_info)); if (strcmp(mount_info.mnt_dir,"/") == 0) return; /* Save current working directory */ CHECK(getcwd(cwd, 4096) != NULL); /* Temporarily change working directory to '/' */ CHECK(chdir("/") != -1); /* Choose what to do */ rorw1 = tests_random_no(2); um = tests_random_no(2); um_ro = tests_random_no(2); um_rorw = tests_random_no(2); rorw2 = tests_random_no(2); if (rorw1 + um + rorw2 == 0) um = 1; source = mount_info.mnt_fsname; target = tests_file_system_mount_dir; filesystemtype = tests_file_system_type; data = mount_info.mnt_opts; process_mount_options(&data, &mountflags); if (rorw1) { /* Re-mount R/O and then re-mount R/W */ flags = mountflags | MS_RDONLY | MS_REMOUNT; err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); flags = mountflags | MS_REMOUNT; flags &= ~((unsigned long)MS_RDONLY); err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); } if (um) { /* Unmount and mount */ if (um_ro) { /* But re-mount R/O before unmounting */ flags = mountflags | MS_RDONLY | MS_REMOUNT; err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); } CHECK(umount(target) != -1); if (!um_rorw) { /* Mount R/W straight away */ err = mount(source, target, filesystemtype, mountflags, data); CHECK(err != -1); } else { /* Mount R/O and then re-mount R/W */ err = mount(source, target, filesystemtype, mountflags | MS_RDONLY, data); CHECK(err != -1); flags = mountflags | MS_REMOUNT; flags &= ~((unsigned long)MS_RDONLY); err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); } } if (rorw2) { /* Re-mount R/O and then re-mount R/W */ flags = mountflags | MS_RDONLY | MS_REMOUNT; err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); flags = mountflags | MS_REMOUNT; flags &= ~((unsigned long)MS_RDONLY); err = mount(source, target, filesystemtype, flags, data); CHECK(err != -1); } /* Restore the previous working directory */ CHECK(chdir(cwd) != -1); }
static void file_mmap_write(struct file_info *file) { size_t write_cnt = 0, r, i, len, size; struct write_info *w = file->writes; void *addr; char *waddr; off_t offs, offset; unsigned seed; uint64_t free_space; int fd; char *path; if (!file->links) return; free_space = tests_get_free_space(); if (!free_space) return; /* Randomly pick a written area of the file */ if (!w) return; while (w) { write_cnt += 1; w = w->next; } r = tests_random_no(write_cnt); w = file->writes; for (i = 0; w && w->next && i < r; i++) w = w->next; offs = (w->offset / mem_page_size) * mem_page_size; len = w->size + (w->offset - offs); if (len > 1 << 24) len = 1 << 24; /* Open it */ path = dir_path(file->links->parent, file->links->name); fd = open(path, O_RDWR); CHECK(fd != -1); free(path); /* mmap it */ addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offs); CHECK(close(fd) != -1); CHECK(addr != MAP_FAILED); /* Randomly select a part of the mmapped area to write */ size = tests_random_no(w->size); if (size > free_space) size = free_space; if (size == 0) size = 1; offset = w->offset + tests_random_no(w->size - size); /* Write it */ seed = tests_random_no(10000000); srand(seed); waddr = addr + (offset - offs); for (i = 0; i < size; i++) waddr[i] = rand(); /* Unmap it */ CHECK(munmap(addr, len) != -1); /* Record what was written */ file_write_info(file, offset, size, seed); }
/* Remove a random file of empty sub-directory from the current directory */ static 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; }
static 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; }