From a2364f288888a3c5877069f5cc7eb99bcf3c4bfe Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Fri, 3 May 2024 13:38:55 -0400 Subject: [PATCH] Copy a tree using parallel restore library. This tool compies a source tree (whether it's scoutfs or not) into an offline scoutfs meta device. It has only those 2 parameters and does a single-process walk of the tree to restore all items while preservice as much of the metadata as possible. Signed-off-by: Hunter Shaffer Signed-off-by: Auke Kok --- tests/Makefile | 5 +- tests/golden/restore_copy | 64 +++ tests/sequence | 1 + tests/src/restore_copy.c | 959 ++++++++++++++++++++++++++++++++++++ tests/tests/restore_copy.sh | 99 ++++ 5 files changed, 1127 insertions(+), 1 deletion(-) create mode 100644 tests/golden/restore_copy create mode 100644 tests/src/restore_copy.c create mode 100644 tests/tests/restore_copy.sh diff --git a/tests/Makefile b/tests/Makefile index 4dad4b9d..fe7c5b27 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -14,7 +14,8 @@ BIN := src/createmany \ src/fragmented_data_extents \ src/o_tmpfile_umask \ src/o_tmpfile_linkat \ - src/parallel_restore + src/parallel_restore \ + src/restore_copy DEPS := $(wildcard src/*.d) @@ -26,6 +27,8 @@ endif src/parallel_restore_cflags := ../utils/src/scoutfs_parallel_restore.a -lm src/parallel_restore: ../utils/src/scoutfs_parallel_restore.a +src/restore_copy_cflags := ../utils/src/scoutfs_parallel_restore.a -lm +src/restore_copy : ../utils/src/scoutfs_parallel_restore.a $(BIN): %: %.c Makefile gcc $(CFLAGS) -MD -MP -MF $*.d $< -o $@ $($(@)_cflags) diff --git a/tests/golden/restore_copy b/tests/golden/restore_copy new file mode 100644 index 00000000..186b7e0d --- /dev/null +++ b/tests/golden/restore_copy @@ -0,0 +1,64 @@ +== restore_copy content verification +d /mnt/test/data/d +f /mnt/test/data/f +l /mnt/test/data/l -> broken +f /mnt/test/data/h +l /mnt/test/data/F -> f +b /mnt/test/data/b +c /mnt/test/data/c +c /mnt/test/data/u +p /mnt/test/data/p +f /mnt/test/data/f4096 +f /mnt/test/data/falloc +f /mnt/test/data/truncate +s /mnt/test/data/s +f /mnt/test/data/mode_t +f /mnt/test/data/uidgid +f /mnt/test/data/retention +f /mnt/test/data/proj +d /mnt/test/data +Quota rule: 7 13,L,- 0,L,- 0,L,- I 33 - +Quota rule: 7 11,L,- 0,L,- 0,L,- I 33 - +Quota rule: 7 12,L,- 0,L,- 0,L,- I 33 - +Quota rule: 7 10,L,- 0,L,- 0,L,- I 33 - +Quota rule: 7 15,L,- 0,L,- 0,L,- I 33 - +Quota rule: 7 14,L,- 0,L,- 0,L,- I 33 - +Wrote 1 directories, 0 files, 458752 bytes total +== verify metadata bits on restored fs +total 16516 +-rw-r--r--. 1 33333 33333 0 uidgid +crw-r--r--. 1 0 0 2, 2 u +-rw-r--r--. 1 0 0 16777216 truncate +srwxr-xr-x. 1 0 0 0 s +-rw-r--r--. 1 0 0 0 retention +-rw-r--r--. 1 0 0 0 proj +prw-r--r--. 1 0 0 0 p +-rwsrwsrwx. 1 0 0 0 mode_t +lrwxrwxrwx. 1 0 0 7 l -> broken +-rw-r--r--. 1 0 0 0 h +-rw-r--r--. 1 0 0 131072 falloc +-rw-r--r--. 1 0 0 4096 f4096 +-rw-r--r--. 1 0 0 0 f +drwxr-xr-x. 2 0 0 0 d +crw-r--r--. 1 0 0 0, 0 c +brw-r--r--. 1 0 0 1, 1 b +lrwxrwxrwx. 1 0 0 2 F -> f +1 +12345 +0: offset: 0 length: 1 flags: O.L +extents: 1 +0: offset: 0 length: 32 flags: O.L +extents: 1 +0: offset: 0 length: 4096 flags: O.L +extents: 1 + 7 15,L,- 0,L,- 0,L,- I 33 - + 7 14,L,- 0,L,- 0,L,- I 33 - + 7 13,L,- 0,L,- 0,L,- I 33 - + 7 12,L,- 0,L,- 0,L,- I 33 - + 7 11,L,- 0,L,- 0,L,- I 33 - + 7 10,L,- 0,L,- 0,L,- I 33 - + Type Size Total Used Free Use% +MetaData 64KB 4194304 34698 4159606 0 + Data 4KB 67108864 64 67108800 0 +== umount restored fs and check +== cleanup diff --git a/tests/sequence b/tests/sequence index f7d6cd88..1d81e378 100644 --- a/tests/sequence +++ b/tests/sequence @@ -56,4 +56,5 @@ block-stale-reads.sh inode-deletion.sh renameat2-noreplace.sh parallel_restore.sh +restore_copy.sh xfstests.sh diff --git a/tests/src/restore_copy.c b/tests/src/restore_copy.c new file mode 100644 index 00000000..f474aa2d --- /dev/null +++ b/tests/src/restore_copy.c @@ -0,0 +1,959 @@ +#define _GNU_SOURCE /* O_DIRECT */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../utils/src/sparse.h" +#include "../../utils/src/util.h" +#include "../../utils/src/list.h" +#include "../../utils/src/parse.h" +#include "../../kmod/src/format.h" +#include "../../kmod/src/ioctl.h" +#include "../../utils/src/parallel_restore.h" + +/* + * XXX: + */ + +#define ERRF " errno %d (%s)" +#define ERRA errno, strerror(errno) + +#define error_exit(cond, fmt, args...) \ +do { \ + if (cond) { \ + printf("error: "fmt"\n", ##args); \ + exit(1); \ + } \ +} while (0) + +#define REG_MODE (S_IFREG | 0644) +#define DIR_MODE (S_IFDIR | 0755) +#define LNK_MODE (S_IFLNK | 0777) + +/* + * At about 1k files we seem to be writing about 1MB of data, so + * set buffer sizes adequately above that. + */ +#define BATCH_FILES 1024 +#define BUF_SIZ 2 * 1024 * 1024 + +/* + * We can't make duplicate inodes for hardlinked files, so we + * will need to track these as we generate them. Not too costly + * to do, since it's just an integer, and sorting shouldn't matter + * until we get into the millions of entries, hopefully. + */ +static struct list_head hardlinks; +struct hardlink_head { + struct list_head head; + u64 ino; +}; + +struct opts { + char *meta_path; + char *source_dir; +}; + +static bool warn_scoutfs = false; + +static void usage(void) +{ + printf("usage:\n" + " -m PATH | path to metadata device\n" + " -s PATH | path to source directory\n" + ); +} + +static size_t write_bufs(struct scoutfs_parallel_restore_writer *wri, + void *buf, int dev_fd) +{ + size_t total = 0; + size_t count; + off_t off; + int ret; + + do { + ret = scoutfs_parallel_restore_write_buf(wri, buf, BUF_SIZ, &off, &count); + error_exit(ret, "write buf %d", ret); + + if (count > 0) { + ret = pwrite(dev_fd, buf, count, off); + error_exit(ret != count, "pwrite count %zu ret %d", count, ret); + total += ret; + } + } while (count > 0); + + return total; +} + +struct write_result { + struct scoutfs_parallel_restore_progress prog; + struct scoutfs_parallel_restore_slice slice; + __le64 files_created; + __le64 dirs_created; + __le64 bytes_written; + bool complete; +}; + +static void write_bufs_and_send(struct scoutfs_parallel_restore_writer *wri, + void *buf, int dev_fd, + struct write_result *res, bool get_slice, int pair_fd) +{ + size_t total; + int ret; + + total = write_bufs(wri, buf, dev_fd); + le64_add_cpu(&res->bytes_written, total); + + ret = scoutfs_parallel_restore_get_progress(wri, &res->prog); + error_exit(ret, "get prog %d", ret); + + if (get_slice) { + ret = scoutfs_parallel_restore_get_slice(wri, &res->slice); + error_exit(ret, "thread get slice %d", ret); + } + + ret = write(pair_fd, res, sizeof(struct write_result)); + error_exit(ret != sizeof(struct write_result), "result send error"); + + memset(res, 0, sizeof(struct write_result)); +} + +/* + * Adding xattrs is supported for files and directories only. + * + * If the filesystem on which the path resides isn't scoutfs, we omit the + * scoutfs specific ioctl to fetch hidden xattrs. + * + * Untested if the hidden xattr ioctl works on directories or symlinks. + */ +static void add_xattrs(struct scoutfs_parallel_restore_writer *wri, char *path, u64 ino, bool is_scoutfs) +{ + struct scoutfs_ioctl_listxattr_hidden lxh; + struct scoutfs_parallel_restore_xattr *xattr; + char *buf = NULL; + char *name = NULL; + int fd = -1; + int bytes; + int len; + int value_len; + int ret; + int pos = 0; + + if (!is_scoutfs) + goto normal_xattrs; + + fd = open(path, O_RDONLY); + error_exit(fd < 0, "open"ERRF, ERRA); + + memset(&lxh, 0, sizeof(lxh)); + lxh.id_pos = 0; + lxh.hash_pos = 0; + lxh.buf_bytes = 256 * 1024; + + buf = malloc(lxh.buf_bytes); + error_exit(!buf, "alloc xattr_hidden buf"); + lxh.buf_ptr = (unsigned long)buf; + + /* hidden */ + for (;;) { + ret = ioctl(fd, SCOUTFS_IOC_LISTXATTR_HIDDEN, &lxh); + if (ret == 0) /* done */ + break; + error_exit(ret < 0, "listxattr_hidden"ERRF, ERRA); + bytes = ret; + error_exit(bytes > lxh.buf_bytes, "listxattr_hidden overflow"); + error_exit(buf[bytes - 1] != '\0', "listxattr_hidden didn't term"); + + name = buf; + + do { + len = strlen(name); + error_exit(len == 0, "listxattr_hidden empty name"); + error_exit(len > SCOUTFS_XATTR_MAX_NAME_LEN, "listxattr_hidden long name"); + + /* get value len */ + value_len = fgetxattr(fd, name, NULL, 0); + error_exit(value_len < 0, "malloc value hidden"ERRF, ERRA); + + /* allocate everything at once */ + xattr = malloc(sizeof(struct scoutfs_parallel_restore_xattr) + len + value_len); + error_exit(!xattr, "error allocating generated xattr"); + + *xattr = (struct scoutfs_parallel_restore_xattr) { + .ino = ino, + .pos = pos++, + .name_len = len, + .value_len = value_len, + }; + xattr->name = (void *)(xattr + 1); + xattr->value = (void *)(xattr->name + len); + + /* get value into xattr directly */ + ret = fgetxattr(fd, name, (void *)(xattr->name + len), value_len); + error_exit(ret != value_len, "fgetxattr value"ERRF, ERRA); + + memcpy(xattr->name, name, len); + + ret = scoutfs_parallel_restore_add_xattr(wri, xattr); + error_exit(ret, "add hidden xattr %d", ret); + + free(xattr); + + name += len + 1; + bytes -= len + 1; + } while (bytes > 0); + } + + free(buf); + close(fd); + +normal_xattrs: + value_len = listxattr(path, NULL, 0); + error_exit(value_len < 0, "hidden listxattr "ERRF, ERRA); + if (value_len == 0) + return; + + buf = calloc(1, value_len); + error_exit(!buf, "malloc value"ERRF, ERRA); + + ret = listxattr(path, buf, value_len); + error_exit(ret < 0, "hidden listxattr %d", ret); + + name = buf; + bytes = ret; + do { + len = strlen(name); + + error_exit(len == 0, "listxattr_hidden empty name"); + error_exit(len > SCOUTFS_XATTR_MAX_NAME_LEN, "listxattr_hidden long name"); + + value_len = getxattr(path, name, NULL, 0); + error_exit(value_len < 0, "value "ERRF, ERRA); + + xattr = malloc(sizeof(struct scoutfs_parallel_restore_xattr) + len + value_len); + error_exit(!xattr, "error allocating generated xattr"); + + *xattr = (struct scoutfs_parallel_restore_xattr) { + .ino = ino, + .pos = pos++, + .name_len = len, + .value_len = value_len, + }; + xattr->name = (void *)(xattr + 1); + xattr->value = (void *)(xattr->name + len); + + ret = getxattr(path, name, (void *)(xattr->name + len), value_len); + error_exit(ret != value_len, "fgetxattr value"ERRF, ERRA); + + memcpy(xattr->name, name, len); + + ret = scoutfs_parallel_restore_add_xattr(wri, xattr); + error_exit(ret, "add xattr %d", ret); + + free(xattr); + + name += len + 1; + bytes -= len + 1; + } while (bytes > 0); + + free(buf); +} + +/* + * We can't store the same inode multiple times, so we need to make + * sure to account for hardlinks. Maintain a LL that stores the first + * hardlink inode we encounter, and every subsequent hardlink to this + * inode will omit inserting an inode, and just adds another entry + */ +static bool is_new_inode_item(bool nlink, u64 ino) +{ + struct hardlink_head *hh_tmp; + struct hardlink_head *hh; + + if (!nlink) + return true; + + /* lineair search, pretty awful, should be a binary tree */ + list_for_each_entry_safe(hh, hh_tmp, &hardlinks, head) { + if (hh->ino == ino) + return false; + } + + /* insert item */ + hh = malloc(sizeof(struct hardlink_head)); + error_exit(!hh, "malloc"); + hh->ino = ino; + list_add_tail(&hh->head, &hardlinks); + + /* + * XXX + * + * We can be confident that if we don't traverse filesystems + * that once we've created N entries of an N-linked inode, that + * it can be removed from the LL. This would significantly + * improve the manageability of the list. + * + * All we'd need to do is add a counter and compare it to the nr_links + * field of the inode. + */ + + return true; +} + +/* + * create the inode data for a given path as best as possible + * duplicating the exact data from the source path + */ +static struct scoutfs_parallel_restore_inode *read_inode_data(char *path, u64 ino, bool *nlink, bool is_scoutfs) +{ + struct scoutfs_parallel_restore_inode *inode = NULL; + struct scoutfs_ioctl_stat_more stm; + struct scoutfs_ioctl_inode_attr_x iax; + struct stat st; + int ret; + int fd; + + inode = calloc(1, sizeof(struct scoutfs_parallel_restore_inode)); + error_exit(!inode, "failure allocating inode"); + + ret = lstat(path, &st); + error_exit(ret, "failure stat inode"); + + /* use exact inode numbers from path, except for root ino */ + if (ino != SCOUTFS_ROOT_INO) + inode->ino = st.st_ino; + else + inode->ino = SCOUTFS_ROOT_INO; + + inode->mode = st.st_mode; + inode->uid = st.st_uid; + inode->gid = st.st_gid; + inode->atime = st.st_atim; + inode->ctime = st.st_ctim; + inode->mtime = st.st_mtim; + inode->size = st.st_size; + + inode->rdev = st.st_rdev; + + /* scoutfs specific */ + inode->meta_seq = 0; + inode->data_seq = 0; + inode->crtime = st.st_ctim; + + if (S_ISREG(inode->mode)) { + if (inode->size > 0) + inode->offline = true; + + if (is_scoutfs) { + fd = open(path, O_RDONLY); + error_exit(!fd, "open failure"ERRF, ERRA); + + ret = ioctl(fd, SCOUTFS_IOC_STAT_MORE, &stm); + error_exit(ret, "failure SCOUTFS_IOC_STAT_MORE inode"); + + inode->meta_seq = stm.meta_seq; + inode->data_seq = stm.data_seq; + inode->crtime = (struct timespec){.tv_sec = stm.crtime_sec, .tv_nsec = stm.crtime_nsec}; + + /* project ID, retention bit */ + memset(&iax, 0, sizeof(iax)); + + iax.x_flags = 0; + iax.x_mask = SCOUTFS_IOC_IAX_PROJECT_ID | SCOUTFS_IOC_IAX__BITS; + iax.bits = SCOUTFS_IOC_IAX_B_RETENTION; + + ret = ioctl(fd, SCOUTFS_IOC_GET_ATTR_X, &iax); + error_exit(ret, "failure SCOUTFS_IOC_GET_ATTR_X inode"); + + inode->proj = iax.project_id; + inode->flags |= (iax.bits & SCOUTFS_IOC_IAX_B_RETENTION) ? SCOUTFS_INO_FLAG_RETENTION : 0; + + close(fd); + } + + } + + /* pass whether item is hardlinked or not */ + *nlink = (st.st_nlink > 1); + + return inode; +} + +typedef int (*quota_ioctl_in)(struct scoutfs_ioctl_quota_rule *irules, + struct scoutfs_ioctl_get_quota_rules *gqr, + size_t nr, int fd); + +static int get_quota_ioctl(struct scoutfs_ioctl_quota_rule *irules, + struct scoutfs_ioctl_get_quota_rules *rules_in, + size_t nr, int fd) +{ + struct scoutfs_ioctl_get_quota_rules *gqr = rules_in; + int ret; + + gqr->rules_ptr = (intptr_t)irules; + gqr->rules_nr = nr; + + ret = ioctl(fd, SCOUTFS_IOC_GET_QUOTA_RULES, gqr); + error_exit(ret < 0, "quota ioctl error"); + + return ret; +} + +static char opc[] = { + [SQ_OP_DATA] = 'D', + [SQ_OP_INODE] = 'I', +}; + +static char nsc[] = { + [SQ_NS_LITERAL] = 'L', + [SQ_NS_PROJ] = 'P', + [SQ_NS_UID] = 'U', + [SQ_NS_GID] = 'G', +}; + +static int insert_quota_rule(struct scoutfs_parallel_restore_writer *wri, + struct scoutfs_ioctl_quota_rule *irule) +{ + struct scoutfs_parallel_restore_quota_rule *prule = NULL; + int ret; + int i; + + prule = calloc(1, sizeof(struct scoutfs_parallel_restore_quota_rule)); + error_exit(!prule, "quota rule alloc failed"); + prule->limit = irule->limit; + prule->prio = irule->prio; + prule->op = irule->op; + prule->rule_flags = irule->rule_flags; + prule->names[0].val = irule->name_val[0]; + prule->names[0].source = irule->name_source[0]; + prule->names[0].flags = irule->name_flags[0]; + prule->names[1].val = irule->name_val[1]; + prule->names[1].source = irule->name_source[1]; + prule->names[1].flags = irule->name_flags[1]; + prule->names[2].val = irule->name_val[2]; + prule->names[2].source = irule->name_source[2]; + prule->names[2].flags = irule->name_flags[2]; + + /* print out the rule */ + printf("Quota rule: %3u ", irule->prio); + for (i = 0; i < array_size(irule->name_val); i++) { + printf("%llu,%c,%c ", + irule->name_val[i], + nsc[irule->name_source[i]], + (irule->name_flags[i] & SQ_NF_SELECT) ? 'S' : '-'); + } + printf("%c %llu %c\n", + opc[irule->op], irule->limit, (irule->rule_flags & SQ_RF_TOTL_COUNT) ? 'C' : '-'); + + ret = scoutfs_parallel_restore_add_quota_rule(wri, prule); + error_exit(ret, "quota add rule %d", ret); + free(prule); + return ret; +} + +static int restore_quotas(struct scoutfs_parallel_restore_writer *wri, + quota_ioctl_in quota_in, char *path) +{ + struct scoutfs_ioctl_get_quota_rules gqr = {{0,}}; + struct scoutfs_ioctl_quota_rule *irules = NULL; + size_t rule_alloc = 0; + size_t rule_nr = 0; + size_t rule_count; + size_t i; + int fd = -1; + int ret; + + fd = open(path, O_RDONLY); + error_exit(fd < 0, "open"ERRF, ERRA); + + for (;;) { + if (rule_nr == rule_alloc) { + rule_alloc += 1024; + irules = realloc(irules, rule_alloc * sizeof(irules[0])); + error_exit(!irules, "irule realloc failed rule_nr:%zu alloced:%zu", rule_nr, rule_alloc); + if (!irules) { + ret = -errno; + fprintf(stderr, "memory allocation failed: %s (%d)\n", + strerror(errno), errno); + goto out; + } + } + + ret = quota_in(&irules[rule_nr], &gqr, rule_alloc - rule_nr, fd); + if (ret == 0) + break; + if (ret < 0) + goto out; + + rule_count = ret; + + for (i = 0; i < rule_count; i++) { + ret = insert_quota_rule(wri, &irules[i]); + if (ret < 0) + goto out; + } + } + + ret = 0; +out: + if (fd >= 0) + close(fd); + if (irules) + free(irules); + return ret; +} + +struct writer_args { + struct list_head head; + + int dev_fd; + int pair_fd; + + struct scoutfs_parallel_restore_slice slice; +}; + +static void restore_path(struct scoutfs_parallel_restore_writer *wri, struct writer_args *args, struct write_result *res, void *buf, char *path, u64 ino) +{ + struct scoutfs_parallel_restore_inode *inode; + struct scoutfs_parallel_restore_entry *entry; + DIR *dirp = NULL; + char *subdir = NULL; + char link[PATH_MAX + 1]; + struct dirent *ent; + struct statfs stf; + int ret = 0; + int subdir_count = 0, file_count = 0; + size_t ent_len = 0; + size_t pos = 0; + bool nlink = false; + char ind = '?'; + u64 mode; + bool is_scoutfs = false; + + /* get fs info once per path */ + ret = statfs(path, &stf); + error_exit(ret != 0, "statfs"ERRF, ERRA); + is_scoutfs = (stf.f_type == 0x554f4353); + + if (!is_scoutfs && !warn_scoutfs) { + warn_scoutfs = true; + fprintf(stderr, "Non-scoutfs source path detected: scoutfs specific features disabled\n"); + } + + + /* traverse the entire tree */ + dirp = opendir(path); + errno = 0; + while ((ent = readdir(dirp))) { + if (ent->d_type == DT_DIR) { + if ((strcmp(ent->d_name, ".") == 0) || + (strcmp(ent->d_name, "..") == 0)) { + /* position still matters */ + pos++; + continue; + } + + /* recurse into subdir */ + ret = asprintf(&subdir, "%s/%s", path, ent->d_name); + error_exit(ret == -1, "asprintf subdir"ERRF, ERRA); + restore_path(wri, args, res, buf, subdir, ent->d_ino); + + subdir_count++; + + ent_len += strlen(ent->d_name); + + entry = malloc(sizeof(struct scoutfs_parallel_restore_entry) + strlen(ent->d_name)); + error_exit(!entry, "error allocating generated entry"); + + *entry = (struct scoutfs_parallel_restore_entry) { + .dir_ino = ino, + .pos = pos++, + .ino = ent->d_ino, + .mode = DIR_MODE, + .name = (void *)(entry + 1), + .name_len = strlen(ent->d_name), + }; + + memcpy(entry->name, ent->d_name, strlen(ent->d_name)); + ret = scoutfs_parallel_restore_add_entry(wri, entry); + error_exit(ret, "add entry %d", ret); + free(entry); + + add_xattrs(wri, subdir, ent->d_ino, is_scoutfs); + + free(subdir); + + le64_add_cpu(&res->dirs_created, 1); + } else if (ent->d_type == DT_REG) { + + file_count++; + + ent_len += strlen(ent->d_name); + + entry = malloc(sizeof(struct scoutfs_parallel_restore_entry) + strlen(ent->d_name)); + error_exit(!entry, "error allocating generated entry"); + + *entry = (struct scoutfs_parallel_restore_entry) { + .dir_ino = ino, + .pos = pos++, + .ino = ent->d_ino, + .mode = REG_MODE, + .name = (void *)(entry + 1), + .name_len = strlen(ent->d_name), + }; + + memcpy(entry->name, ent->d_name, strlen(ent->d_name)); + ret = scoutfs_parallel_restore_add_entry(wri, entry); + error_exit(ret, "add entry %d", ret); + free(entry); + + ret = asprintf(&subdir, "%s/%s", path, ent->d_name); + error_exit(ret == -1, "asprintf subdir"ERRF, ERRA); + + /* file inode */ + inode = read_inode_data(subdir, ent->d_ino, &nlink, is_scoutfs); + fprintf(stdout, "f %s/%s\n", path, ent->d_name); + if (is_new_inode_item(nlink, ent->d_ino)) { + ret = scoutfs_parallel_restore_add_inode(wri, inode); + error_exit(ret, "add reg file inode %d", ret); + + /* xattrs */ + add_xattrs(wri, subdir, ent->d_ino, is_scoutfs); + } + free(inode); + + free(subdir); + + le64_add_cpu(&res->files_created, 1); + } else if (ent->d_type == DT_LNK) { + /* readlink */ + + ret = asprintf(&subdir, "%s/%s", path, ent->d_name); + error_exit(ret == -1, "asprintf subdir"ERRF, ERRA); + + ent_len += strlen(ent->d_name); + + ret = readlink(subdir, link, PATH_MAX); + error_exit(ret < 0, "readlink %d", ret); + /* must 0-terminate if we want to print it */ + link[ret] = 0; + + entry = malloc(sizeof(struct scoutfs_parallel_restore_entry) + strlen(ent->d_name)); + error_exit(!entry, "error allocating generated entry"); + + *entry = (struct scoutfs_parallel_restore_entry) { + .dir_ino = ino, + .pos = pos++, + .ino = ent->d_ino, + .mode = LNK_MODE, + .name = (void *)(entry + 1), + .name_len = strlen(ent->d_name), + }; + + memcpy(entry->name, ent->d_name, strlen(ent->d_name)); + ret = scoutfs_parallel_restore_add_entry(wri, entry); + error_exit(ret, "add symlink entry %d", ret); + + /* link inode */ + inode = read_inode_data(subdir, ent->d_ino, &nlink, is_scoutfs); + + fprintf(stdout, "l %s/%s -> %s\n", path, ent->d_name, link); + + inode->mode = LNK_MODE; + inode->target = link; + inode->target_len = strlen(link) + 1; /* scoutfs null terminates symlinks */ + + ret = scoutfs_parallel_restore_add_inode(wri, inode); + error_exit(ret, "add syml inode %d", ret); + + free(inode); + free(subdir); + + le64_add_cpu(&res->files_created, 1); + } else { + /* odd stuff */ + switch(ent->d_type) { + case DT_CHR: + ind = 'c'; + mode = S_IFCHR; + break; + case DT_BLK: + ind = 'b'; + mode = S_IFBLK; + break; + case DT_FIFO: + ind = 'p'; + mode = S_IFIFO; + break; + case DT_SOCK: + ind = 's'; + mode = S_IFSOCK; + break; + default: + error_exit(true, "Unknown readdir entry type"); + ;; + } + + file_count++; + + ent_len += strlen(ent->d_name); + + entry = malloc(sizeof(struct scoutfs_parallel_restore_entry) + strlen(ent->d_name)); + error_exit(!entry, "error allocating generated entry"); + + *entry = (struct scoutfs_parallel_restore_entry) { + .dir_ino = ino, + .pos = pos++, + .ino = ent->d_ino, + .mode = mode, + .name = (void *)(entry + 1), + .name_len = strlen(ent->d_name), + }; + + memcpy(entry->name, ent->d_name, strlen(ent->d_name)); + ret = scoutfs_parallel_restore_add_entry(wri, entry); + error_exit(ret, "add entry %d", ret); + + free(entry); + + ret = asprintf(&subdir, "%s/%s", path, ent->d_name); + error_exit(ret == -1, "asprintf subdir"ERRF, ERRA); + + /* file inode */ + inode = read_inode_data(subdir, ent->d_ino, &nlink, is_scoutfs); + fprintf(stdout, "%c %s/%s\n", ind, path, ent->d_name); + if (is_new_inode_item(nlink, ent->d_ino)) { + ret = scoutfs_parallel_restore_add_inode(wri, inode); + error_exit(ret, "add reg file inode %d", ret); + } + free(inode); + + free(subdir); + + le64_add_cpu(&res->files_created, 1); + } + + /* batch out changes, will be about 1M */ + if (le64_to_cpu(res->files_created) > BATCH_FILES) { + write_bufs_and_send(wri, buf, args->dev_fd, res, false, args->pair_fd); + } + + } + if (ent != NULL) + error_exit(errno, "readdir"ERRF, ERRA); + closedir(dirp); + + /* create the dir itself */ + inode = read_inode_data(path, ino, &nlink, is_scoutfs); + inode->nr_subdirs = subdir_count; + inode->total_entry_name_bytes = ent_len; + fprintf(stdout, "d %s\n", path); + + ret = scoutfs_parallel_restore_add_inode(wri, inode); + error_exit(ret, "add dir inode %d", ret); + + free(inode); + + /* No need to send, we'll send final after last directory is complete */ +} + +static int do_restore(struct opts *opts) +{ + struct scoutfs_parallel_restore_writer *pwri, *wri = NULL; + struct scoutfs_parallel_restore_slice *slices = NULL; + struct scoutfs_super_block *super = NULL; + struct writer_args *args; + struct write_result res; + int pair[2] = {-1, -1}; + LIST_HEAD(writers); + void *buf = NULL; + void *bufp = NULL; + int dev_fd = -1; + pid_t pid; + int ret; + u64 tot_bytes; + u64 tot_dirs; + u64 tot_files; + + ret = socketpair(PF_LOCAL, SOCK_STREAM, 0, pair); + error_exit(ret, "socketpair error "ERRF, ERRA); + + dev_fd = open(opts->meta_path, O_DIRECT | (O_RDWR|O_EXCL)); + error_exit(dev_fd < 0, "error opening '%s': "ERRF, opts->meta_path, ERRA); + + errno = posix_memalign((void **)&super, 4096, SCOUTFS_BLOCK_SM_SIZE) ?: + posix_memalign((void **)&buf, 4096, BUF_SIZ); + error_exit(errno, "error allocating block bufs "ERRF, ERRA); + + ret = pread(dev_fd, super, SCOUTFS_BLOCK_SM_SIZE, + SCOUTFS_SUPER_BLKNO << SCOUTFS_BLOCK_SM_SHIFT); + error_exit(ret != SCOUTFS_BLOCK_SM_SIZE, "error reading super, ret %d", ret); + + error_exit((super->flags & SCOUTFS_FLAG_IS_META_BDEV) == 0, "super block is not meta dev"); + + ret = scoutfs_parallel_restore_create_writer(&wri); + error_exit(ret, "create writer %d", ret); + + ret = scoutfs_parallel_restore_import_super(wri, super, dev_fd); + error_exit(ret, "import super %d", ret); + + slices = calloc(2, sizeof(struct scoutfs_parallel_restore_slice)); + error_exit(!slices, "alloc slices"); + + scoutfs_parallel_restore_init_slices(wri, slices, 2); + + ret = scoutfs_parallel_restore_add_slice(wri, &slices[0]); + error_exit(ret, "add slices[0] %d", ret); + + args = calloc(1, sizeof(struct writer_args)); + error_exit(!args, "alloc writer args"); + + args->dev_fd = dev_fd; + args->slice = slices[1]; + args->pair_fd = pair[1]; + list_add_tail(&args->head, &writers); + + /* fork writer process */ + pid = fork(); + error_exit(pid == -1, "fork error"); + + if (pid == 0) { + ret = prctl(PR_SET_PDEATHSIG, SIGHUP); + error_exit(ret < 0, "failed to set parent death sig"); + + errno = posix_memalign((void **)&bufp, 4096, BUF_SIZ); + error_exit(errno, "error allocating block bufp "ERRF, ERRA); + + ret = scoutfs_parallel_restore_create_writer(&pwri); + error_exit(ret, "create pwriter %d", ret); + + ret = scoutfs_parallel_restore_add_slice(pwri, &args->slice); + error_exit(ret, "add pslice %d", ret); + + memset(&res, 0, sizeof(res)); + + restore_path(pwri, args, &res, bufp, opts->source_dir, SCOUTFS_ROOT_INO); + + ret = restore_quotas(pwri, get_quota_ioctl, opts->source_dir); + error_exit(ret, "quota add %d", ret); + + res.complete = true; + + write_bufs_and_send(pwri, buf, args->dev_fd, &res, true, args->pair_fd); + + scoutfs_parallel_restore_destroy_writer(&pwri); + free(bufp); + + exit(0); + }; + + /* read results and wait for writer to finish */ + tot_bytes = 0; + tot_dirs = 1; + tot_files = 0; + for (;;) { + ret = read(pair[0], &res, sizeof(struct write_result)); + error_exit(ret != sizeof(struct write_result), "result read error %d", ret); + + ret = scoutfs_parallel_restore_add_progress(wri, &res.prog); + error_exit(ret, "add thr prog %d", ret); + + if (res.slice.meta_len != 0) { + ret = scoutfs_parallel_restore_add_slice(wri, &res.slice); + error_exit(ret, "add thr slice %d", ret); + + if (res.complete) + break; + } + + tot_bytes += le64_to_cpu(res.bytes_written); + tot_files += le64_to_cpu(res.files_created); + tot_dirs += le64_to_cpu(res.dirs_created); + } + + tot_bytes += write_bufs(wri, buf, args->dev_fd); + + fprintf(stdout, "Wrote %lld directories, %lld files, %lld bytes total\n", + tot_dirs, tot_files, tot_bytes); + + /* write super to finalize */ + ret = scoutfs_parallel_restore_export_super(wri, super); + error_exit(ret, "update super %d", ret); + + ret = pwrite(dev_fd, super, SCOUTFS_BLOCK_SM_SIZE, + SCOUTFS_SUPER_BLKNO << SCOUTFS_BLOCK_SM_SHIFT); + error_exit(ret != SCOUTFS_BLOCK_SM_SIZE, "error writing super, ret %d", ret); + + scoutfs_parallel_restore_destroy_writer(&wri); + + if (dev_fd >= 0) + close(dev_fd); + if (pair[0] > 0) + close(pair[0]); + if (pair[1] > 0) + close(pair[1]); + free(super); + free(args); + free(slices); + free(buf); + + return 0; +} + +int main(int argc, char **argv) +{ + struct opts opts = (struct opts){ 0 }; + struct hardlink_head *hh_tmp; + struct hardlink_head *hh; + int ret; + int c; + + INIT_LIST_HEAD(&hardlinks); + + while ((c = getopt(argc, argv, "b:m:s:")) != -1) { + switch(c) { + case 'm': + opts.meta_path = strdup(optarg); + break; + case 's': + opts.source_dir = strdup(optarg); + break; + case '?': + printf("Unknown option '%c'\n", optopt); + usage(); + exit(1); + } + } + + error_exit(!opts.meta_path, "must specify metadata device path with -m"); + error_exit(!opts.source_dir, "must specify source directory path with -s"); + + ret = do_restore(&opts); + + free(opts.meta_path); + free(opts.source_dir); + + list_for_each_entry_safe(hh, hh_tmp, &hardlinks, head) { + list_del_init(&hh->head); + free(hh); + } + + return ret == 0 ? 0 : 1; +} diff --git a/tests/tests/restore_copy.sh b/tests/tests/restore_copy.sh new file mode 100644 index 00000000..2fe4be24 --- /dev/null +++ b/tests/tests/restore_copy.sh @@ -0,0 +1,99 @@ +# +# validate parallel restore library - using restore_copy.c +# + +t_require_commands scoutfs restore_copy find xargs + +SCR="$T_TMPDIR/mnt.scratch" +mkdir -p "$SCR" + +scratch_mkfs() { + scoutfs mkfs $@ \ + -A -f -Q 0,127.0.0.1,53000 $T_EX_META_DEV $T_EX_DATA_DEV +} + +scratch_check() { + # give ample time for writes to commit + sleep 1 + sync + scoutfs check -d ${T_TMPDIR}/check.debug $T_EX_META_DEV $T_EX_DATA_DEV +} + +scratch_mount() { + mount -t scoutfs -o metadev_path=$T_EX_META_DEV,quorum_slot_nr=0 $T_EX_DATA_DEV $SCR +} + +echo "== restore_copy content verification" +mkdir "$T_M0/data" + +# create all supported inode types: +mkdir -p "$T_M0/data/d" +touch "$T_M0/data/f" +ln -sf "broken" "$T_M0/data/l" +ln "$T_M0/data/f" "$T_M0/data/h" +ln -sf "f" "$T_M0/data/F" +mknod "$T_M0/data/b" b 1 1 +mknod "$T_M0/data/c" c 0 0 +mknod "$T_M0/data/u" u 2 2 +mknod "$T_M0/data/p" p + +# some files with data +dd if=/dev/zero of="$T_M0/data/f4096" bs=4096 count=1 status=none +touch "$T_M0/data/falloc" "$T_M0/data/truncate" +xfs_io -C "falloc 65536 65536" "$T_M0/data/falloc" +xfs_io -C "truncate $((4096 * 4096))" "$T_M0/data/truncate" + +# socket (could have used python but avoids python/python2/python3 problem) +perl -e "use IO::Socket; my \$s = IO::Socket::UNIX->new(Type=>SOCK_STREAM,Local=>'$T_M0/data/s') or die 'sock';" +# set all mode_t bits +touch "$T_M0/data/mode_t" +chmod 6777 "$T_M0/data/mode_t" +# uid/gid +touch "$T_M0/data/uidgid" +chown 33333:33333 "$T_M0/data/uidgid" +# set retention bit +touch "$T_M0/data/retention" +scoutfs set-attr-x -t 1 "$T_M0/data/retention" +# set project ID +touch "$T_M0/data/proj" +scoutfs set-attr-x -p 12345 "$T_M0/data/proj" +# quotas +for a in $(seq 10 15); do + scoutfs quota-add -p "$T_M0" -r "7 $a,L,- 0,L,- 0,L,- I 33 -" +done + +scratch_mkfs -V 2 > $T_TMP.mkfs.out 2>&1 || t_fail "mkfs failed" +restore_copy -m $T_EX_META_DEV -s "$T_M0/data" | t_filter_fs +scratch_check || t_fail "check before mount failed" + +scratch_mount + +echo "== verify metadata bits on restored fs" +inspect() { + ls -Alnr --time-style=+"" + scoutfs get-attr-x -t "retention" + scoutfs get-attr-x -p "proj" + scoutfs get-fiemap -L "f4096" + scoutfs get-fiemap -L "falloc" + scoutfs get-fiemap -L "truncate" + scoutfs quota-list -p "." +} + +( cd "$SCR" ; inspect ) + +scoutfs df -p "$SCR" + +echo "== umount restored fs and check" +umount "$SCR" +scratch_check || t_fail "check after mount failed" + +#scoutfs print $T_META_DEVICE +#scoutfs print $T_EX_META_DEV + +echo "== cleanup" +rmdir "$SCR" +scoutfs set-attr-x -t 0 "$T_M0/data/retention" +rm -rf "$T_M0/data" +scoutfs quota-wipe -p "$T_M0" + +t_pass