These tests create unlinked files and produce conflicting file system objects which complicate restart for naieve implementations of checkpoint/restart. It further complicates matters by allowing test driver scripts to set special inode flags (append, immutable, undelete). Signed-off-by: Matt Helsley <matthltc@xxxxxxxxxx> --- fs/dir.c | 134 ++++++++++++++++++++++++++++++++ fs/file.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/libfstest.c | 12 +++ fs/libfstest.h | 34 ++++++++ fs/module.mk | 12 +++ 5 files changed, 428 insertions(+), 0 deletions(-) create mode 100644 fs/dir.c create mode 100644 fs/file.c create mode 100644 fs/libfstest.c create mode 100644 fs/libfstest.h create mode 100644 fs/module.mk diff --git a/fs/dir.c b/fs/dir.c new file mode 100644 index 0000000..daa85fc --- /dev/null +++ b/fs/dir.c @@ -0,0 +1,134 @@ +/* + * Test checkpoint/restart of unlinked files. + * Currently expected to fail. + */ +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <getopt.h> +#include <sys/syscall.h> /* SYS_getdents */ + +/* open() */ +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +/* waitpid() and W* status macros */ +#include <sys/wait.h> + +#define LOG_FILE "log.unlinked.dir" +#include "libfstest.h" + +const char *descr = "Create an unlinked file to checkpoint/restore."; + +void usage(FILE *pout, const char *prog) +{ + fprintf(pout, "\n%s [-L] [-N] [-h|--help] [-l LABEL] [-n NUM]\n" +"%s\n" +"\n" +"\t-L\tPrint the valid LABELs in order and exit.\n" +"\t-l\tWait for checkpoint at LABEL.\n" +"\t-N\tPrint the maximum label number and exit.\n" +"\t-n\tWait for checkpoint at NUM.\n" +"\n" +"You may only specify one LABEL or NUM and you may not specify both.\n" +"Label numbers are integers in the range 0-%d\n" +"Valid label numbers and their corresponding LABELs are:\n", prog, + descr, num_labels - 1); + print_labels(pout); +} + +const struct option long_options[] = { + { "print-labels", 0, 0, 'L'}, + { "print-max-label-no", 0, 0, 'N'}, + { "help", 0, 0, 'h'}, + { "label", 1, 0, 'l'}, + { "num", 1, 0, 'n'}, + {0, 0, 0, 0}, +}; + +void parse_args(int argc, char **argv) +{ + ckpt_op_num = num_labels - 1; + ckpt_label = labels[ckpt_op_num]; + while (1) { + char c; + c = getopt_long(argc, argv, "LNhl:n:", long_options, NULL); + if (c == -1) + break; + switch(c) { + case 'L': + print_labels(stdout); + exit(EXIT_SUCCESS); + break; + case 'N': + printf("%d\n", num_labels - 1); + exit(EXIT_SUCCESS); + break; + case 'h': + usage(stdout, argv[0]); + exit(EXIT_SUCCESS); + break; + case 'l': + ckpt_label = optarg; + break; + case 'n': + if ((sscanf(optarg, "%d", &ckpt_op_num) < 1) || + (ckpt_op_num < 0) || + (ckpt_op_num >= num_labels)) { + fprintf(stderr, "Option -n requires an argument in the range 0-%d. Got %d\n", num_labels - 1, ckpt_op_num); + usage(stderr, argv[0]); + exit(EXIT_FAILURE); + } + break; + default: /* unknown option */ + break; + } + } +} + +/* + * A LABEL is a point in the program we can goto where it's interesting to + * checkpoint. These enable us to have a set of labels that can be specified + * on the commandline. + */ +int main (int argc, char **argv) +{ + const char *pathname = "trash"; + const char buffer[] = "hello world!\n"; + char fdcontents[sizeof(buffer)]; + int fd, ret, op_num = 0; + + parse_args(argc, argv); + /* FIXME eventually stdio streams should be harmless */ + close(0); + logfp = fopen(LOG_FILE, "w"); + if (!logfp) { + perror("could not open logfile"); + exit(1); + } + dup2(fileno(logfp), 1); /* redirect stdout and stderr to the log file */ + dup2(fileno(logfp), 2); + if (!move_to_cgroup("freezer", "1", getpid())) { + log_error("move_to_cgroup"); + exit(2); + } + +label(mkdir, ret, mkdir(pathname, S_IRWXU)); +label(open, fd, open(pathname, O_RDONLY|O_DIRECTORY)); +label(rmdir, ret, rmdir(pathname)); +label(getdent,ret, syscall(SYS_getdents, fd, buffer, sizeof(buffer))); +label(close, ret, close(fd)); + + if (strcmp(buffer, fdcontents) != 0) { + log("FAIL", "contents don't match."); + ret = EXIT_FAILURE; + } else + ret = EXIT_SUCCESS; +out: + close(fd); + fclose(logfp); + exit(ret); +} diff --git a/fs/file.c b/fs/file.c new file mode 100644 index 0000000..bb17058 --- /dev/null +++ b/fs/file.c @@ -0,0 +1,236 @@ +/* + * Test checkpoint/restart of unlinked files. + * + * TODO with append extended attribute, with immutable extended attribute, + * with undelete extended attribute (not honored by extX) + * Currently expected to fail. + */ +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <getopt.h> + +/* open() */ +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +/* set inode flags */ +#include <sys/ioctl.h> +#include <linux/fs.h> + +/* waitpid() and W* status macros */ +#include <sys/wait.h> + +#define LOG_FILE "log.unlinked.file" +#include "libfstest.h" + +const char *descr = "Create an unlinked file to checkpoint/restore."; + +void usage(FILE *pout, const char *prog) +{ + fprintf(pout, "\n%s [-L] [-N] [-h|--help] [-l LABEL] [-n NUM]\n" +"%s\n" +"\n" +"\t-L\tPrint the valid LABELs in order and exit.\n" +"\t-l\tWait for checkpoint at LABEL.\n" +"\t-N\tPrint the maximum label number and exit.\n" +"\t-n\tWait for checkpoint at NUM.\n" +"\t-a\tSet the append-only flag (extX filesystems, needs cap_linux_immutable).\n" +"\t-i\tSet the immutable flag (extX filesystems, needs cap_linux_immutable).\n" +"\t-u\tSet the undeleteable (recoverable) flag (extX filesystems).\n" +"\n" +"You may only specify one LABEL or NUM and you may not specify both.\n" +"Label numbers are integers in the range 0-%d\n" +"Valid label numbers and their corresponding LABELs are:\n", prog, + descr, num_labels - 1); + print_labels(pout); +} + +const struct option long_options[] = { + { "print-labels", 0, 0, 'L'}, + { "print-max-label-no", 0, 0, 'N'}, + { "help", 0, 0, 'h'}, + { "append", 0, 0, 'a'}, /* Need CAP_LINUX_IMMUTABLE */ + { "immutable", 0, 0, 'i'}, /* Need CAP_LINUX_IMMUTABLE */ + { "undelete", 0, 0, 'u'}, /* unsupported */ + { "label", 1, 0, 'l'}, + { "num", 1, 0, 'n'}, + {0, 0, 0, 0}, +}; + +static int inode_flags = 0; +static int oflags = O_RDWR; + +void parse_args(int argc, char **argv) +{ + ckpt_op_num = num_labels - 1; + ckpt_label = labels[ckpt_op_num]; + while (1) { + char c; + c = getopt_long(argc, argv, "LNhl:n:aiu", long_options, NULL); + if (c == -1) + break; + switch(c) { + case 'L': + print_labels(stdout); + exit(EXIT_SUCCESS); + break; + case 'N': + printf("%d\n", num_labels - 1); + exit(EXIT_SUCCESS); + break; + case 'h': + usage(stdout, argv[0]); + exit(EXIT_SUCCESS); + break; + case 'a': + oflags |= O_APPEND; + inode_flags |= FS_APPEND_FL; + break; + case 'i': + inode_flags |= FS_IMMUTABLE_FL; + break; + case 'u': + inode_flags |= FS_UNRM_FL; + break; + case 'l': + ckpt_label = optarg; + break; + case 'n': + if ((sscanf(optarg, "%d", &ckpt_op_num) < 1) || + (ckpt_op_num < 0) || + (ckpt_op_num >= num_labels)) { + fprintf(stderr, "Option -n requires an argument in the range 0-%d. Got %d\n", num_labels - 1, ckpt_op_num); + usage(stderr, argv[0]); + exit(EXIT_FAILURE); + } + break; + default: /* unknown option */ + break; + } + } +} + +static int setflags(int fd) +{ + int flags = inode_flags; + + if (!flags) + return 0; +#ifdef FS_IOC_SETFLAGS + return ioctl(fd, FS_IOC_SETFLAGS, &flags); +#else + errno = ENOTSUP; + return -1; +#endif +} + +int main (int argc, char **argv) +{ + const char *pathname = "trash"; + const char buffer[] = "hello world!\n"; + const char buffer2[] = "goodbye old world, hello new world!\n"; + char fdcontents[sizeof(buffer)]; + char fdcontents2[sizeof(buffer2)]; + int fd, fd2 = -1, ret, op_num = 0; + + parse_args(argc, argv); + /* FIXME eventually stdio streams should be harmless */ + close(0); + logfp = fopen(LOG_FILE, "w"); + if (!logfp) { + perror("could not open logfile"); + exit(1); + } + dup2(fileno(logfp), 1); /* redirect stdout and stderr to the log file */ + dup2(fileno(logfp), 2); + if (!move_to_cgroup("freezer", "1", getpid())) { + log_error("move_to_cgroup"); + exit(2); + } + +label(creat1,fd, creat(pathname, oflags)); + if (fd < 0) + exit(EXIT_FAILURE); +label(setflags1, ret, setflags(fd)); + if (ret < 0) + goto out; +label(write1, ret, write(fd, buffer, sizeof(buffer))); + if (ret < 0) + goto out; +label(unlink, ret, unlink(pathname)); + if (ret < 0) + goto out; + + /* Simple unlinked file */ + /* This is a good time to do_ckpt(); */ + + /* Check file contents */ +label(lseek, ret, lseek(fd, 0, SEEK_SET)); + if (ret < 0) + goto out; +label(read, ret, read(fd, fdcontents, sizeof(fdcontents))); + if (ret < 0) + goto out; + if (strcmp(buffer, fdcontents) != 0) { + log("FAIL", "original file contents don't match."); + ret = EXIT_FAILURE; + goto out; + } + + /* + * Create a second file where the first used to be but do not + * destroy the data of the original. + */ +label(creat2,fd2, creat(pathname, O_RDWR)); + ret = fd2; + if (fd2 < 0) + goto out; +label(setflags2, ret, setflags(fd)); + if (ret < 0) + goto out; +label(write2, ret, write(fd2, buffer2, sizeof(buffer2))); + if (ret < 0) + goto out; + + /* This is a good time to do_ckpt(); */ + + /* Check file contents (both this time) */ +label(lseek2, ret, lseek(fd, 0, SEEK_SET)); + if (ret < 0) + goto out; +label(read2, ret, read(fd, fdcontents, sizeof(fdcontents))); + if (ret < 0) + goto out; + + if (strcmp(buffer, fdcontents) != 0) { + log("FAIL", "original file contents don't match."); + ret = EXIT_FAILURE; + goto out; + } +label(lseek3, ret, lseek(fd2, 0, SEEK_SET)); + if (ret < 0) + goto out; +label(read3, ret, read(fd2, fdcontents2, sizeof(fdcontents2))); + if (ret < 0) + goto out; + + if (strcmp(buffer2, fdcontents2) != 0) { + log("FAIL", "second file contents don't match."); + ret = EXIT_FAILURE; + goto out; + } + ret = EXIT_SUCCESS; +out: + if (ret != EXIT_SUCCESS) + perror("ERROR"); + close(fd); + if (fd2 > -1) + close(fd2); + unlink(pathname); + fclose(logfp); + exit(ret); +} diff --git a/fs/libfstest.c b/fs/libfstest.c new file mode 100644 index 0000000..200dc23 --- /dev/null +++ b/fs/libfstest.c @@ -0,0 +1,12 @@ +#include "libfstest.h" + +FILE *logfp = NULL; + +/* Signal ready for and await the checkpoint */ +void do_ckpt(void) +{ + set_checkpoint_ready(); + while (!test_checkpoint_done()) + usleep(10000); + +} diff --git a/fs/libfstest.h b/fs/libfstest.h new file mode 100644 index 0000000..1ef3c5c --- /dev/null +++ b/fs/libfstest.h @@ -0,0 +1,34 @@ +/* epoll syscalls */ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#include <sys/epoll.h> + +#include "libcrtest/libcrtest.h" +#include "libcrtest/labels.h" + +extern FILE *logfp; + +/* + * Log output with a tag (INFO, WARN, FAIL, PASS) and a format. + * Adds information about the thread originating the message. + * + * Flush the log after every write to make sure we get consistent, and + * complete logs. + */ +#define log(tag, fmt, ...) \ +do { \ + pid_t __tid = getpid(); \ + fprintf(logfp, ("%s: thread %d: " fmt), (tag), __tid, ##__VA_ARGS__ ); \ + fflush(logfp); \ + fsync(fileno(logfp)); \ +} while(0) + +/* like perror() except to the log */ +#define log_error(s) log("FAIL", "%s: %s\n", (s), strerror(errno)) + +/* Signal ready for and await the checkpoint */ +void do_ckpt(void); + +#define HELLO "Hello world!\n" diff --git a/fs/module.mk b/fs/module.mk new file mode 100644 index 0000000..d0a9f26 --- /dev/null +++ b/fs/module.mk @@ -0,0 +1,12 @@ +local_dir := fs +local_progs := $(addprefix $(local_dir)/, file dir) + +sources += $(addprefix $(local_dir)/,libeptest.c) +progs += $(local_progs) +test_clean += $(local_dir)/cr_fs* + +# fs tests include libcrtest/libcrtest.h +$(local_progs): CFLAGS += -D_GNU_SOURCE=1 +$(local_progs): CPPFLAGS += -I . +$(local_progs): LDFLAGS += -Xlinker -dT -Xlinker libcrtest/labels.lds +$(local_progs): $(local_dir)/libfstest.o libcrtest/libcrtest.a -- 1.6.3.3 _______________________________________________ Containers mailing list Containers@xxxxxxxxxxxxxxxxxxxxxxxxxx https://lists.linux-foundation.org/mailman/listinfo/containers