diff options
Diffstat (limited to 'tests/zfs-tests/cmd/xattrtest/xattrtest.c')
-rw-r--r-- | tests/zfs-tests/cmd/xattrtest/xattrtest.c | 641 |
1 files changed, 641 insertions, 0 deletions
diff --git a/tests/zfs-tests/cmd/xattrtest/xattrtest.c b/tests/zfs-tests/cmd/xattrtest/xattrtest.c new file mode 100644 index 000000000..22f798db2 --- /dev/null +++ b/tests/zfs-tests/cmd/xattrtest/xattrtest.c @@ -0,0 +1,641 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2016 Lawrence Livermore National Security, LLC. + */ + +/* + * An extended attribute (xattr) correctness test. This program creates + * N files and sets M attrs on them of size S. Optionally is will verify + * a pattern stored in the xattr. + */ +#include <stdlib.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <fcntl.h> +#include <time.h> +#include <unistd.h> +#include <attr/xattr.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <linux/limits.h> + +extern char *program_invocation_short_name; + +#define ERROR(fmt, ...) \ + fprintf(stderr, "%s: %s:%d: %s: " fmt "\n", \ + program_invocation_short_name, __FILE__, __LINE__, \ + __func__, ## __VA_ARGS__); + +static const char shortopts[] = "hvycdn:f:x:s:p:t:e:rRk"; +static const struct option longopts[] = { + { "help", no_argument, 0, 'h' }, + { "verbose", no_argument, 0, 'v' }, + { "verify", no_argument, 0, 'y' }, + { "nth", required_argument, 0, 'n' }, + { "files", required_argument, 0, 'f' }, + { "xattrs", required_argument, 0, 'x' }, + { "size", required_argument, 0, 's' }, + { "path", required_argument, 0, 'p' }, + { "synccaches", no_argument, 0, 'c' }, + { "dropcaches", no_argument, 0, 'd' }, + { "script", required_argument, 0, 't' }, + { "seed", required_argument, 0, 'e' }, + { "random", no_argument, 0, 'r' }, + { "randomvalue", no_argument, 0, 'R' }, + { "keep", no_argument, 0, 'k' }, + { 0, 0, 0, 0 } +}; + +static int verbose = 0; +static int verify = 0; +static int synccaches = 0; +static int dropcaches = 0; +static int nth = 0; +static int files = 1000; +static int xattrs = 1; +static int size = 1; +static int size_is_random = 0; +static int value_is_random = 0; +static int keep_files = 0; +static char path[PATH_MAX] = "/tmp/xattrtest"; +static char script[PATH_MAX] = "/bin/true"; + +static int +usage(int argc, char **argv) { + fprintf(stderr, + "usage: %s [-hvycdrRk] [-n <nth>] [-f <files>] [-x <xattrs>]\n" + " [-s <bytes>] [-p <path>] [-t <script> ]\n", argv[0]); + fprintf(stderr, + " --help -h This help\n" + " --verbose -v Increase verbosity\n" + " --verify -y Verify xattr contents\n" + " --nth -n <nth> Print every nth file\n" + " --files -f <files> Set xattrs on N files\n" + " --xattrs -x <xattrs> Set N xattrs on each file\n" + " --size -s <bytes> Set N bytes per xattr\n" + " --path -p <path> Path to files\n" + " --synccaches -c Sync caches between phases\n" + " --dropcaches -d Drop caches between phases\n" + " --script -t <script> Exec script between phases\n" + " --seed -e <seed> Random seed value\n" + " --random -r Randomly sized xattrs [16-size]\n" + " --randomvalue -R Random xattr values\n" + " --keep -k Don't unlink files\n\n"); + + return (0); +} + +static int +parse_args(int argc, char **argv) +{ + long seed = time(NULL); + int c; + int rc; + + while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) { + switch (c) { + case 'h': + return (usage(argc, argv)); + case 'v': + verbose++; + break; + case 'y': + verify = 1; + if (value_is_random != 0) { + fprintf(stderr, + "Error: -y and -R are incompatible.\n"); + rc = 1; + } + break; + case 'n': + nth = strtol(optarg, NULL, 0); + break; + case 'f': + files = strtol(optarg, NULL, 0); + break; + case 'x': + xattrs = strtol(optarg, NULL, 0); + break; + case 's': + size = strtol(optarg, NULL, 0); + if (size > XATTR_SIZE_MAX) { + fprintf(stderr, "Error: the size may not be " + "greater than %d\n", XATTR_SIZE_MAX); + rc = 1; + } + break; + case 'p': + strncpy(path, optarg, PATH_MAX); + break; + case 'c': + synccaches = 1; + break; + case 'd': + dropcaches = 1; + break; + case 't': + strncpy(script, optarg, PATH_MAX); + break; + case 'e': + seed = strtol(optarg, NULL, 0); + break; + case 'r': + size_is_random = 1; + break; + case 'R': + value_is_random = 1; + if (verify != 0) { + fprintf(stderr, + "Error: -y and -R are incompatible.\n"); + rc = 1; + } + break; + case 'k': + keep_files = 1; + break; + default: + rc = 1; + break; + } + } + + if (rc != 0) + return (rc); + + srandom(seed); + + if (verbose) { + fprintf(stdout, "verbose: %d\n", verbose); + fprintf(stdout, "verify: %d\n", verify); + fprintf(stdout, "nth: %d\n", nth); + fprintf(stdout, "files: %d\n", files); + fprintf(stdout, "xattrs: %d\n", xattrs); + fprintf(stdout, "size: %d\n", size); + fprintf(stdout, "path: %s\n", path); + fprintf(stdout, "synccaches: %d\n", synccaches); + fprintf(stdout, "dropcaches: %d\n", dropcaches); + fprintf(stdout, "script: %s\n", script); + fprintf(stdout, "seed: %ld\n", seed); + fprintf(stdout, "random size: %d\n", size_is_random); + fprintf(stdout, "random value: %d\n", value_is_random); + fprintf(stdout, "keep: %d\n", keep_files); + fprintf(stdout, "%s", "\n"); + } + + return (rc); +} + +static int +drop_caches(void) +{ + char file[] = "/proc/sys/vm/drop_caches"; + int fd, rc; + + fd = open(file, O_WRONLY); + if (fd == -1) { + ERROR("Error %d: open(\"%s\", O_WRONLY)\n", errno, file); + return (errno); + } + + rc = write(fd, "3", 1); + if ((rc == -1) || (rc != 1)) { + ERROR("Error %d: write(%d, \"3\", 1)\n", errno, fd); + return (errno); + } + + rc = close(fd); + if (rc == -1) { + ERROR("Error %d: close(%d)\n", errno, fd); + return (errno); + } + + return (0); +} + +static int +run_process(const char *path, char *argv[]) +{ + pid_t pid; + int rc, devnull_fd; + + pid = vfork(); + if (pid == 0) { + devnull_fd = open("/dev/null", O_WRONLY); + + if (devnull_fd < 0) + _exit(-1); + + (void) dup2(devnull_fd, STDOUT_FILENO); + (void) dup2(devnull_fd, STDERR_FILENO); + close(devnull_fd); + + (void) execvp(path, argv); + _exit(-1); + } else if (pid > 0) { + int status; + + while ((rc = waitpid(pid, &status, 0)) == -1 && errno == EINTR); + + if (rc < 0 || !WIFEXITED(status)) + return (-1); + + return (WEXITSTATUS(status)); + } + + return (-1); +} + +static int +post_hook(char *phase) +{ + char *argv[3] = { script, phase, (char *)0 }; + int rc; + + if (synccaches) + sync(); + + if (dropcaches) { + rc = drop_caches(); + if (rc) + return (rc); + } + + rc = run_process(script, argv); + if (rc) + return (rc); + + return (0); +} + +#define USEC_PER_SEC 1000000 + +static void +timeval_normalize(struct timeval *tv, time_t sec, suseconds_t usec) +{ + while (usec >= USEC_PER_SEC) { + usec -= USEC_PER_SEC; + sec++; + } + + while (usec < 0) { + usec += USEC_PER_SEC; + sec--; + } + + tv->tv_sec = sec; + tv->tv_usec = usec; +} + +static void +timeval_sub(struct timeval *delta, struct timeval *tv1, struct timeval *tv2) +{ + timeval_normalize(delta, + tv1->tv_sec - tv2->tv_sec, + tv1->tv_usec - tv2->tv_usec); +} + +static int +create_files(void) +{ + int i, rc; + char *file = NULL; + struct timeval start, stop, delta; + + file = malloc(PATH_MAX); + if (file == NULL) { + rc = ENOMEM; + ERROR("Error %d: malloc(%d) bytes for file name\n", + rc, PATH_MAX); + goto out; + } + + (void) gettimeofday(&start, NULL); + + for (i = 1; i <= files; i++) { + (void) sprintf(file, "%s/file-%d", path, i); + + if (nth && ((i % nth) == 0)) + fprintf(stdout, "create: %s\n", file); + + rc = unlink(file); + if ((rc == -1) && (errno != ENOENT)) { + ERROR("Error %d: unlink(%s)\n", errno, file); + rc = errno; + goto out; + } + + rc = open(file, O_CREAT, 0644); + if (rc == -1) { + ERROR("Error %d: open(%s, O_CREATE, 0644)\n", + errno, file); + rc = errno; + goto out; + } + + rc = close(rc); + if (rc == -1) { + ERROR("Error %d: close(%d)\n", errno, rc); + rc = errno; + goto out; + } + } + + (void) gettimeofday(&stop, NULL); + timeval_sub(&delta, &stop, &start); + fprintf(stdout, "create: %d.%d seconds\n", + (int)delta.tv_sec, (int)delta.tv_usec); + + rc = post_hook("post"); +out: + if (file) + free(file); + + return (rc); +} + +static int +get_random_bytes(char *buf, size_t bytes) +{ + int rand; + ssize_t bytes_read = 0; + + rand = open("/dev/urandom", O_RDONLY); + + if (rand < 0) + return (rand); + + while (bytes_read < bytes) { + ssize_t rc = read(rand, buf + bytes_read, bytes - bytes_read); + if (rc < 0) + break; + bytes_read += rc; + } + + (void) close(rand); + + return (bytes_read); +} + +static int +setxattrs(void) +{ + int i, j, rnd_size = size, shift, rc = 0; + char name[XATTR_NAME_MAX]; + char *value = NULL; + char *file = NULL; + struct timeval start, stop, delta; + + value = malloc(XATTR_SIZE_MAX); + if (value == NULL) { + rc = ENOMEM; + ERROR("Error %d: malloc(%d) bytes for xattr value\n", + rc, XATTR_SIZE_MAX); + goto out; + } + + file = malloc(PATH_MAX); + if (file == NULL) { + rc = ENOMEM; + ERROR("Error %d: malloc(%d) bytes for file name\n", + rc, PATH_MAX); + goto out; + } + + (void) gettimeofday(&start, NULL); + + for (i = 1; i <= files; i++) { + (void) sprintf(file, "%s/file-%d", path, i); + + if (nth && ((i % nth) == 0)) + fprintf(stdout, "setxattr: %s\n", file); + + for (j = 1; j <= xattrs; j++) { + if (size_is_random) + rnd_size = (random() % (size - 16)) + 16; + + (void) sprintf(name, "user.%d", j); + if (value_is_random) { + rc = get_random_bytes(value, rnd_size); + if (rc < rnd_size) { + ERROR("Error %d: get_random_bytes() " + "wanted %d got %d\n", errno, + rnd_size, rc); + goto out; + } + } else { + shift = sprintf(value, "size=%d ", rnd_size); + memset(value + shift, 'x', XATTR_SIZE_MAX - + shift); + } + + rc = lsetxattr(file, name, value, rnd_size, 0); + if (rc == -1) { + ERROR("Error %d: lsetxattr(%s, %s, ..., %d)\n", + errno, file, name, rnd_size); + goto out; + } + } + } + + (void) gettimeofday(&stop, NULL); + timeval_sub(&delta, &stop, &start); + fprintf(stdout, "setxattr: %d.%d seconds\n", + (int)delta.tv_sec, (int)delta.tv_usec); + + rc = post_hook("post"); +out: + if (file) + free(file); + + if (value) + free(value); + + return (rc); +} + +static int +getxattrs(void) +{ + int i, j, rnd_size, shift, rc = 0; + char name[XATTR_NAME_MAX]; + char *verify_value = NULL; + char *value = NULL; + char *file = NULL; + struct timeval start, stop, delta; + + verify_value = malloc(XATTR_SIZE_MAX); + if (verify_value == NULL) { + rc = ENOMEM; + ERROR("Error %d: malloc(%d) bytes for xattr verify\n", + rc, XATTR_SIZE_MAX); + goto out; + } + + value = malloc(XATTR_SIZE_MAX); + if (value == NULL) { + rc = ENOMEM; + ERROR("Error %d: malloc(%d) bytes for xattr value\n", + rc, XATTR_SIZE_MAX); + goto out; + } + + file = malloc(PATH_MAX); + if (file == NULL) { + rc = ENOMEM; + ERROR("Error %d: malloc(%d) bytes for file name\n", + rc, PATH_MAX); + goto out; + } + + (void) gettimeofday(&start, NULL); + + for (i = 1; i <= files; i++) { + (void) sprintf(file, "%s/file-%d", path, i); + + if (nth && ((i % nth) == 0)) + fprintf(stdout, "getxattr: %s\n", file); + + for (j = 1; j <= xattrs; j++) { + (void) sprintf(name, "user.%d", j); + + rc = lgetxattr(file, name, value, XATTR_SIZE_MAX); + if (rc == -1) { + ERROR("Error %d: lgetxattr(%s, %s, ..., %d)\n", + errno, file, name, XATTR_SIZE_MAX); + goto out; + } + + if (verify) { + sscanf(value, "size=%d [a-z]", &rnd_size); + shift = sprintf(verify_value, "size=%d ", + rnd_size); + memset(verify_value + shift, 'x', + XATTR_SIZE_MAX - shift); + + if (rnd_size != rc || + memcmp(verify_value, value, rnd_size)) { + ERROR("Error %d: verify failed\n " + "verify: %s\nvalue: %s\n", + EINVAL, verify_value, value); + goto out; + } + } + } + } + + (void) gettimeofday(&stop, NULL); + timeval_sub(&delta, &stop, &start); + fprintf(stdout, "getxattr: %d.%d seconds\n", + (int)delta.tv_sec, (int)delta.tv_usec); + + rc = post_hook("post"); +out: + if (file) + free(file); + + if (value) + free(value); + + if (verify_value) + free(verify_value); + + return (rc); +} + +static int +unlink_files(void) +{ + int i, rc; + char *file = NULL; + struct timeval start, stop, delta; + + file = malloc(PATH_MAX); + if (file == NULL) { + rc = ENOMEM; + ERROR("Error %d: malloc(%d) bytes for file name\n", + rc, PATH_MAX); + goto out; + } + + (void) gettimeofday(&start, NULL); + + for (i = 1; i <= files; i++) { + (void) sprintf(file, "%s/file-%d", path, i); + + if (nth && ((i % nth) == 0)) + fprintf(stdout, "unlink: %s\n", file); + + rc = unlink(file); + if ((rc == -1) && (errno != ENOENT)) { + ERROR("Error %d: unlink(%s)\n", errno, file); + return (errno); + } + } + + (void) gettimeofday(&stop, NULL); + timeval_sub(&delta, &stop, &start); + fprintf(stdout, "unlink: %d.%d seconds\n", + (int)delta.tv_sec, (int)delta.tv_usec); + + rc = post_hook("post"); +out: + if (file) + free(file); + + return (rc); +} + +int +main(int argc, char **argv) +{ + int rc; + + rc = parse_args(argc, argv); + if (rc) + return (rc); + + rc = create_files(); + if (rc) + return (rc); + + rc = setxattrs(); + if (rc) + return (rc); + + rc = getxattrs(); + if (rc) + return (rc); + + if (!keep_files) { + rc = unlink_files(); + if (rc) + return (rc); + } + + return (0); +} |