/* * 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; }