From: John L. Hammond Date: Fri, 22 Jan 2021 16:56:06 +0000 (-0600) Subject: LU-14359 hsm: support a flatter HSM archive format X-Git-Tag: 2.14.52~90 X-Git-Url: https://git.whamcloud.com/?p=fs%2Flustre-release.git;a=commitdiff_plain;h=65062463199fa76b6313e9452e3ab9590cbedaa2 LU-14359 hsm: support a flatter HSM archive format Add versioning (v1 and v2) to the HSM archive format (directory layout): v1: (oid & 0xffff)/-/-/-/-/-/FID v2: ((oid ^ seq) & 0xffff)/FID v1 is the original layout and the default. v2 is the new layout which should be selected for new installs. Add an option --archive-format to select the archive format. Add YAML configuration file support to lhsmtool_posix with properties achive_format and archive_path. Add an option --config to set the config file. Adapt sanity-hsm and test-framework to allow testing of both archive formats. Signed-off-by: John L. Hammond Change-Id: I6d6bd0c8817a491848b554fa76078d876549cc1f Reviewed-on: https://review.whamcloud.com/41312 Reviewed-by: Andreas Dilger Tested-by: jenkins Tested-by: Maloo Reviewed-by: Oleg Drokin --- diff --git a/lnet/utils/lnetconfig/cyaml.c b/lnet/utils/lnetconfig/cyaml.c index 8839315..0c859a1 100644 --- a/lnet/utils/lnetconfig/cyaml.c +++ b/lnet/utils/lnetconfig/cyaml.c @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -1302,57 +1303,27 @@ failed: fprintf(stderr, "error:\n\tfatal: out of memory\n"); } -struct cYAML *cYAML_build_tree(char *yaml_file, - const char *yaml_blk, - size_t yaml_blk_size, - struct cYAML **err_rc, - bool debug) +static struct cYAML * +cYAML_parser_to_tree(yaml_parser_t *parser, struct cYAML **err_rc, bool debug) { - yaml_parser_t parser; yaml_token_t token; struct cYAML_tree_node tree; enum cYAML_handler_error rc; yaml_token_type_t token_type; char err_str[256]; - FILE *input = NULL; int done = 0; memset(&tree, 0, sizeof(struct cYAML_tree_node)); INIT_LIST_HEAD(&tree.ll); - /* Create the Parser object. */ - yaml_parser_initialize(&parser); - - /* file always takes precedence */ - if (yaml_file != NULL) { - /* Set a file input. */ - input = fopen(yaml_file, "rb"); - if (input == NULL) { - snprintf(err_str, sizeof(err_str), - "Failed to open file: %s", yaml_file); - cYAML_build_error(-1, -1, "yaml", "builder", - err_str, - err_rc); - return NULL; - } - - yaml_parser_set_input_file(&parser, input); - } else if (yaml_blk != NULL) { - yaml_parser_set_input_string(&parser, - (const unsigned char *) yaml_blk, - yaml_blk_size); - } else - /* assume that we're getting our input froms stdin */ - yaml_parser_set_input_file(&parser, stdin); - /* Read the event sequence. */ while (!done) { /* * Go through the parser and build a cYAML representation * of the passed in YAML text */ - yaml_parser_scan(&parser, &token); + yaml_parser_scan(parser, &token); if (debug) fprintf(stderr, "tree.state(%p:%d) = %s, token.type =" @@ -1380,12 +1351,6 @@ struct cYAML *cYAML_build_tree(char *yaml_file, yaml_token_delete(&token); } - /* Destroy the Parser object. */ - yaml_parser_delete(&parser); - - if (input != NULL) - fclose(input); - if (token_type == YAML_STREAM_END_TOKEN && rc == CYAML_ERROR_NONE) return tree.root; @@ -1394,3 +1359,66 @@ struct cYAML *cYAML_build_tree(char *yaml_file, return NULL; } + +struct cYAML *cYAML_load(FILE *file, struct cYAML **err_rc, bool debug) +{ + yaml_parser_t parser; + struct cYAML *yaml; + + yaml_parser_initialize(&parser); + yaml_parser_set_input_file(&parser, file); + + yaml = cYAML_parser_to_tree(&parser, err_rc, debug); + + yaml_parser_delete(&parser); + + return yaml; +} + +struct cYAML *cYAML_build_tree(char *path, + const char *yaml_blk, + size_t yaml_blk_size, + struct cYAML **err_rc, + bool debug) +{ + yaml_parser_t parser; + struct cYAML *yaml; + char err_str[256]; + FILE *input = NULL; + + /* Create the Parser object. */ + yaml_parser_initialize(&parser); + + /* file always takes precedence */ + if (path != NULL) { + /* Set a file input. */ + input = fopen(path, "rb"); + if (input == NULL) { + snprintf(err_str, sizeof(err_str), + "cannot open '%s': %s", path, strerror(errno)); + cYAML_build_error(-1, -1, "yaml", "builder", + err_str, + err_rc); + return NULL; + } + + yaml_parser_set_input_file(&parser, input); + } else if (yaml_blk != NULL) { + yaml_parser_set_input_string(&parser, + (const unsigned char *) yaml_blk, + yaml_blk_size); + } else { + /* assume that we're getting our input froms stdin */ + yaml_parser_set_input_file(&parser, stdin); + } + + yaml = cYAML_parser_to_tree(&parser, err_rc, debug); + + /* Destroy the Parser object. */ + yaml_parser_delete(&parser); + + if (input != NULL) + fclose(input); + + return yaml; +} diff --git a/lnet/utils/lnetconfig/cyaml.h b/lnet/utils/lnetconfig/cyaml.h index 7da7389..a43fa5a 100644 --- a/lnet/utils/lnetconfig/cyaml.h +++ b/lnet/utils/lnetconfig/cyaml.h @@ -77,15 +77,23 @@ typedef void (*cYAML_user_data_free_cb)(void *); typedef bool (*cYAML_walk_cb)(struct cYAML *, void *, void**); /* + * cYAML_load() + * Build a tree representation of the YAML formatted text in file. + * + * file - YAML file to parse and build tree representation + */ +struct cYAML *cYAML_load(FILE *file, struct cYAML **err_rc, bool debug); + +/* * cYAML_build_tree * Build a tree representation of the YAML formatted text passed in. * - * yaml_file - YAML file to parse and build tree representation + * path - YAML file to parse and build tree representation * yaml_blk - blk of YAML. yaml_file takes precedence if both * are defined. * yaml_blk_size - length of the yaml block (obtained via strlen) */ -struct cYAML *cYAML_build_tree(char *yaml_file, const char *yaml_blk, +struct cYAML *cYAML_build_tree(char *path, const char *yaml_blk, size_t yaml_blk_size, struct cYAML **err_str, bool debug); diff --git a/lustre/tests/sanity-hsm.sh b/lustre/tests/sanity-hsm.sh index be7276b..f378963 100755 --- a/lustre/tests/sanity-hsm.sh +++ b/lustre/tests/sanity-hsm.sh @@ -147,10 +147,13 @@ fid2archive() { local fid="$1" - case "$HSMTOOL" in - *lhsmtool_posix) - printf "%s" "$(hsm_root)/*/*/*/*/*/*/$fid" - ;; + case "$HSMTOOL_ARCHIVE_FORMAT" in + v1) + printf "%s" "$(hsm_root)/*/*/*/*/*/*/$fid" + ;; + v2) + printf "%s" "$(hsm_root)/*/$fid" + ;; esac } diff --git a/lustre/tests/sanity-pcc.sh b/lustre/tests/sanity-pcc.sh index 3ed0599..48fc119 100644 --- a/lustre/tests/sanity-pcc.sh +++ b/lustre/tests/sanity-pcc.sh @@ -17,6 +17,7 @@ ALWAYS_EXCEPT+="" # UPDATE THE COMMENT ABOVE WITH BUG NUMBERS WHEN CHANGING ALWAYS_EXCEPT! ENABLE_PROJECT_QUOTAS=${ENABLE_PROJECT_QUOTAS:-true} +HSMTOOL_ARCHIVE_FORMAT=v1 LUSTRE=${LUSTRE:-$(cd $(dirname $0)/..; echo $PWD)} @@ -113,21 +114,24 @@ lpcc_fid2path() local lustre_path="$2" local fid=$(path2fid $lustre_path) - local -a f_seq - local -a f_oid - local -a f_ver - - f_seq=$(echo $fid | awk -F ':' '{print $1}') - f_oid=$(echo $fid | awk -F ':' '{print $2}') - f_ver=$(echo $fid | awk -F ':' '{print $3}') - - printf "%s/%04x/%04x/%04x/%04x/%04x/%04x/%s" \ - $hsm_root $(($f_oid & 0xFFFF)) \ - $(($f_oid >> 16 & 0xFFFF)) \ - $(($f_seq & 0xFFFF)) \ - $(($f_seq >> 16 & 0xFFFF)) \ - $(($f_seq >> 32 & 0xFFFF)) \ - $(($f_seq >> 48 & 0xFFFF)) $fid + local seq=$(echo $fid | awk -F ':' '{print $1}') + local oid=$(echo $fid | awk -F ':' '{print $2}') + local ver=$(echo $fid | awk -F ':' '{print $3}') + + case "$HSMTOOL_ARCHIVE_FORMAT" in + v1) + printf "%s/%04x/%04x/%04x/%04x/%04x/%04x/%s" \ + $hsm_root $((oid & 0xFFFF)) \ + $((oid >> 16 & 0xFFFF)) \ + $((seq & 0xFFFF)) \ + $((seq >> 16 & 0xFFFF)) \ + $((seq >> 32 & 0xFFFF)) \ + $((seq >> 48 & 0xFFFF)) $fid + ;; + v2) + printf "%s/%04x/%s" $hsm_root $(((oid ^ seq) & 0xFFFF)) $fid + ;; + esac } check_lpcc_state() diff --git a/lustre/tests/test-framework.sh b/lustre/tests/test-framework.sh index c2e0daf..2385ef7 100755 --- a/lustre/tests/test-framework.sh +++ b/lustre/tests/test-framework.sh @@ -10109,6 +10109,7 @@ init_agt_vars() { export HSMTOOL_UPDATE_INTERVAL=${HSMTOOL_UPDATE_INTERVAL:=""} export HSMTOOL_EVENT_FIFO=${HSMTOOL_EVENT_FIFO:=""} export HSMTOOL_TESTDIR + export HSMTOOL_ARCHIVE_FORMAT=${HSMTOOL_ARCHIVE_FORMAT:-v2} if ! [[ $HSMTOOL =~ hsmtool ]]; then echo "HSMTOOL = '$HSMTOOL' does not contain 'hsmtool', GLWT" >&2 @@ -10200,27 +10201,27 @@ copytool_logfile() __lhsmtool_rebind() { - do_facet $facet $HSMTOOL -p "$hsm_root" --rebind "$@" "$mountpoint" + do_facet $facet $HSMTOOL "${hsmtool_options[@]}" --rebind "$@" "$mountpoint" } __lhsmtool_import() { mkdir -p "$(dirname "$2")" || error "cannot create directory '$(dirname "$2")'" - do_facet $facet $HSMTOOL -p "$hsm_root" --import "$@" "$mountpoint" + do_facet $facet $HSMTOOL "${hsmtool_options[@]}" --import "$@" "$mountpoint" } __lhsmtool_setup() { local host="$(facet_host "$facet")" - local cmd="$HSMTOOL $HSMTOOL_VERBOSE --daemon --pid-file=$HSMTOOL_PID_FILE --hsm-root \"$hsm_root\"" + local cmd="$HSMTOOL ${hsmtool_options[@]} --daemon --pid-file=$HSMTOOL_PID_FILE" [ -n "$bandwidth" ] && cmd+=" --bandwidth $bandwidth" [ -n "$archive_id" ] && cmd+=" --archive $archive_id" - [ ${#misc_options[@]} -gt 0 ] && - cmd+=" $(IFS=" " echo "$@")" - cmd+=" \"$mountpoint\"" +# [ ${#misc_options[@]} -gt 0 ] && +# cmd+=" $(IFS=" " echo "$@")" + cmd+=" $@ \"$mountpoint\"" - echo "Starting copytool '$facet' on '$host'" + echo "Starting copytool '$facet' on '$host' with cmdline '$cmd'" stack_trap "pkill_copytools $host TERM || true" EXIT do_node "$host" "$cmd < /dev/null > \"$(copytool_logfile $facet)\" 2>&1" } @@ -10255,7 +10256,17 @@ copytool() # Parse arguments local fail_on_error=true - local -a misc_options + local -a hsmtool_options=("--hsm-root=$hsm_root") + local -a action_options=() + + if [[ -n "$HSMTOOL_ARCHIVE_FORMAT" ]]; then + hsmtool_options+=("--archive-format=$HSMTOOL_ARCHIVE_FORMAT") + fi + + if [[ -n "$HSMTOOL_VERBOSE" ]]; then + hsmtool_options+=("$HSMTOOL_VERBOSE") + fi + while [ $# -gt 0 ]; do case "$1" in -f|--facet) @@ -10283,7 +10294,7 @@ copytool() ;; *) # Uncommon(/copytool dependent) option - misc_options+=("$1") + action_options+=("$1") ;; esac shift @@ -10299,7 +10310,7 @@ copytool() ;; esac - __${copytool}_${action} "${misc_options[@]}" + __${copytool}_${action} "${action_options[@]}" if [ $? -ne 0 ]; then local error_msg @@ -10309,8 +10320,8 @@ copytool() error_msg="Failed to start copytool $facet on '$host'" ;; import) - local src="${misc_options[0]}" - local dest="${misc_options[1]}" + local src="${action_options[0]}" + local dest="${action_options[1]}" error_msg="Failed to import '$src' to '$dest'" ;; rebind) diff --git a/lustre/utils/Makefile.am b/lustre/utils/Makefile.am index df3c2c9..898a36f 100644 --- a/lustre/utils/Makefile.am +++ b/lustre/utils/Makefile.am @@ -233,7 +233,8 @@ l_getidentity_LDADD := $(top_builddir)/libcfs/libcfs/libcfs.la l_getidentity_DEPENDENCIES := $(top_builddir)/libcfs/libcfs/libcfs.la lhsmtool_posix_SOURCES = lhsmtool_posix.c pid_file.c pid_file.h -lhsmtool_posix_LDADD := liblustreapi.la $(PTHREAD_LIBS) +lhsmtool_posix_LDADD := liblustreapi.la $(PTHREAD_LIBS) \ + $(top_builddir)/lnet/utils/lnetconfig/liblnetconfig.la lhsmtool_posix_DEPENDENCIES := liblustreapi.la l_getsepol_SOURCES = l_getsepol.c diff --git a/lustre/utils/lhsmtool_posix.c b/lustre/utils/lhsmtool_posix.c index 707cbab..ae6e608 100644 --- a/lustre/utils/lhsmtool_posix.c +++ b/lustre/utils/lhsmtool_posix.c @@ -58,7 +58,9 @@ #include #include +#include #include +#include "lstddef.h" #include "pid_file.h" /* Progress reporting period */ @@ -78,8 +80,48 @@ enum ct_action { CA_IMPORT = 1, CA_REBIND, CA_MAXSEQ, + CA_ARCHIVE_UPGRADE, }; +enum ct_archive_format { + /* v1 (original) using 6 directories (oid & 0xffff)/-/-/-/-/-/FID. + * Places only one FID per directory. See ct_path_archive() below. */ + CT_ARCHIVE_FORMAT_V1 = 1, + /* v2 using 1 directory (oid & 0xffff)/FID. */ + CT_ARCHIVE_FORMAT_V2 = 2, +}; + +static const char *ct_archive_format_name[] = { + [CT_ARCHIVE_FORMAT_V1] = "v1", + [CT_ARCHIVE_FORMAT_V2] = "v2", +}; + +static int +ct_str_to_archive_format(const char *str, enum ct_archive_format *pctaf) +{ + enum ct_archive_format ctaf; + + for (ctaf = 0; ctaf < ARRAY_SIZE(ct_archive_format_name); ctaf++) { + if (ct_archive_format_name[ctaf] != NULL && + strcmp(ct_archive_format_name[ctaf], str) == 0) { + *pctaf = ctaf; + return 0; + } + } + + return -EINVAL; +} + +static const char *ct_archive_format_to_str(enum ct_archive_format ctaf) +{ + if (0 <= ctaf && + ctaf < ARRAY_SIZE(ct_archive_format_name) && + ct_archive_format_name[ctaf] != NULL) + return ct_archive_format_name[ctaf]; + + return "null"; +} + struct options { int o_copy_attrs; int o_daemonize; @@ -88,6 +130,8 @@ struct options { int o_shadow_tree; int o_verbose; int o_copy_xattrs; + const char *o_config_path; + enum ct_archive_format o_archive_format; int o_archive_id_used; int o_archive_id_cnt; int *o_archive_id; @@ -110,6 +154,7 @@ struct options opt = { .o_shadow_tree = 1, .o_verbose = LLAPI_MSG_INFO, .o_copy_xattrs = 1, + .o_archive_format = CT_ARCHIVE_FORMAT_V1, .o_report_int = REPORT_INTERVAL_DEFAULT, .o_chunk_size = ONE_MB, }; @@ -193,8 +238,13 @@ static void usage(const char *name, int rc) " each line of consists of \n" " %s [options] --max-sequence \n" " return the max fid sequence of archived files\n" + " %s [options] --archive-upgrade=VER\n" + " Upgrade or downgrade the archive to version VER\n" + "Options:\n" " --abort-on-error Abort operation on major error\n" " -A, --archive <#> Archive number (repeatable)\n" + " -C, --config=PATH Read config from PATH\n" + " -F, --archive-format=VER Use archive format VER\n" " -b, --bandwidth Limit I/O bandwidth (unit can be used\n," " default is MB)\n" " --dry-run Don't run, just show what would be done\n" @@ -207,7 +257,7 @@ static void usage(const char *name, int rc) " -u, --update-interval Interval between progress reports sent\n" " to Coordinator\n" " -v, --verbose Produce more verbose output\n", - cmd_name, cmd_name, cmd_name, cmd_name, cmd_name); + cmd_name, cmd_name, cmd_name, cmd_name, cmd_name, cmd_name); exit(rc); } @@ -221,10 +271,13 @@ static int ct_parseopts(int argc, char * const *argv) .flag = &opt.o_abort_on_error, .has_arg = no_argument }, { .val = 'A', .name = "archive", .has_arg = required_argument }, { .val = 'b', .name = "bandwidth", .has_arg = required_argument }, + { .val = 'C', .name = "config", .has_arg = required_argument }, { .val = 'c', .name = "chunk-size", .has_arg = required_argument }, { .val = 'c', .name = "chunk_size", .has_arg = required_argument }, { .val = 1, .name = "daemon", .has_arg = no_argument, .flag = &opt.o_daemonize }, + { .val = 'F', .name = "archive-format", .has_arg = required_argument }, + { .val = 'U', .name = "archive-upgrade", .has_arg = required_argument }, { .val = 'f', .name = "event-fifo", .has_arg = required_argument }, { .val = 'f', .name = "event_fifo", .has_arg = required_argument }, { .val = 1, .name = "dry-run", .has_arg = no_argument, @@ -271,7 +324,7 @@ static int ct_parseopts(int argc, char * const *argv) if (opt.o_archive_id == NULL) return -ENOMEM; repeat: - while ((c = getopt_long(argc, argv, "A:b:c:f:hiMp:P:qru:v", + while ((c = getopt_long(argc, argv, "A:b:C:c:F:f:hiMp:P:qrU:u:v", long_opts, NULL)) != -1) { switch (c) { case 'A': { @@ -334,6 +387,25 @@ repeat: else opt.o_bandwidth = value; break; + case 'C': + opt.o_config_path = optarg; + break; + case 'F': + rc = ct_str_to_archive_format(optarg, &opt.o_archive_format); + if (rc < 0) { + CT_ERROR(rc, "invalid archive format '%s'", optarg); + return -EINVAL; + } + break; + case 'U': + rc = ct_str_to_archive_format(optarg, &opt.o_archive_format); + if (rc < 0) { + CT_ERROR(rc, "invalid archive format '%s'", optarg); + return -EINVAL; + } + opt.o_action = CA_ARCHIVE_UPGRADE; + break; + case 'f': opt.o_event_fifo = optarg; break; @@ -407,6 +479,9 @@ repeat: break; } + if (opt.o_action == CA_ARCHIVE_UPGRADE) + return 0; + if (argc != optind + 1) { rc = -EINVAL; CT_ERROR(rc, "no mount point specified"); @@ -419,12 +494,6 @@ repeat: CT_TRACE("action=%d src=%s dst=%s mount_point=%s", opt.o_action, opt.o_src, opt.o_dst, opt.o_mnt); - if (opt.o_hsm_root == NULL) { - rc = -EINVAL; - CT_ERROR(rc, "must specify a root directory for the backend"); - return rc; - } - if (opt.o_action == CA_IMPORT) { if (opt.o_src && opt.o_src[0] == '/') { rc = -EINVAL; @@ -443,36 +512,62 @@ repeat: return 0; } -/* mkdir -p path */ -static int ct_mkdir_p(const char *path) +static int ct_mkdirat_p(int fd, char *path, mode_t mode) { - char *saved, *ptr; - int rc; + char *split; + int rc; + + if (strlen(path) == 0 || strcmp(path, ".") == 0) + return 0; - ptr = strdup(path); - if (ptr == NULL) + rc = mkdirat(fd, path, mode); + if (rc == 0 || errno == EEXIST) + return 0; + + if (errno != ENOENT) return -errno; - saved = ptr; - while (*ptr == '/') - ptr++; + split = strrchr(path, '/'); + if (split == NULL) + return -ENOENT; - while ((ptr = strchr(ptr, '/')) != NULL) { - *ptr = '\0'; - rc = mkdir(saved, DIR_PERM); - *ptr = '/'; - if (rc < 0 && errno != EEXIST) { - rc = -errno; - CT_ERROR(rc, "cannot mkdir '%s'", path); - free(saved); - return rc; - } - ptr++; + *split = '\0'; + rc = ct_mkdirat_p(fd, path, mode); + *split = '/'; + if (rc < 0) + return rc; + + rc = mkdirat(fd, path, mode); + if (rc == 0 || errno == EEXIST) + return 0; + + return -errno; +} + +/* XXX Despite the name, this is 'mkdir -p $(dirname path)' */ +static int ct_mkdir_p(const char *path) +{ + char *path2; + char *split; + int rc; + + path2 = strdup(path); + if (path2 == NULL) + return -ENOMEM; + + split = strrchr(path2, '/'); + if (split == NULL) { + rc = 0; + goto out; } - free(saved); + *split = '\0'; - return 0; + rc = ct_mkdirat_p(AT_FDCWD, path2, DIR_PERM); +out: + free(path2); + + return rc; } static int ct_save_stripe(int src_fd, const char *src, const char *dst) @@ -855,18 +950,41 @@ static int ct_path_lustre(char *buf, int sz, const char *mnt, dot_lustre_name, PFID(fid)); } -static int ct_path_archive(char *buf, int sz, const char *archive_dir, - const struct lu_fid *fid) +static int ct_path_archive_v(char *buf, size_t buf_size, + enum ct_archive_format ctaf, + const char *archive_path, + const struct lu_fid *fid, + const char *suffix) +{ + switch (ctaf) { + case CT_ARCHIVE_FORMAT_V1: + return scnprintf(buf, buf_size, + "%s/%04x/%04x/%04x/%04x/%04x/%04x/"DFID_NOBRACE"%s", + archive_path, + (fid)->f_oid & 0xFFFF, + (fid)->f_oid >> 16 & 0xFFFF, + (unsigned int)((fid)->f_seq & 0xFFFF), + (unsigned int)((fid)->f_seq >> 16 & 0xFFFF), + (unsigned int)((fid)->f_seq >> 32 & 0xFFFF), + (unsigned int)((fid)->f_seq >> 48 & 0xFFFF), + PFID(fid), + suffix); + case CT_ARCHIVE_FORMAT_V2: + return scnprintf(buf, buf_size, "%s/%04x/"DFID_NOBRACE"%s", + archive_path, + (unsigned int)((fid)->f_oid ^ (fid)->f_seq) & 0xFFFF, + PFID(fid), + suffix); + default: + return -EINVAL; + } +} + +static int ct_path_archive(char *buf, size_t buf_size, + const char *archive_path, const struct lu_fid *fid) { - return scnprintf(buf, sz, "%s/%04x/%04x/%04x/%04x/%04x/%04x/" - DFID_NOBRACE, archive_dir, - (fid)->f_oid & 0xFFFF, - (fid)->f_oid >> 16 & 0xFFFF, - (unsigned int)((fid)->f_seq & 0xFFFF), - (unsigned int)((fid)->f_seq >> 16 & 0xFFFF), - (unsigned int)((fid)->f_seq >> 32 & 0xFFFF), - (unsigned int)((fid)->f_seq >> 48 & 0xFFFF), - PFID(fid)); + return ct_path_archive_v(buf, buf_size, opt.o_archive_format, + archive_path, fid, ""); } static bool ct_is_retryable(int err) @@ -1783,6 +1901,178 @@ static int ct_rebind(void) return rc; } +static int ct_opendirat(int parent_fd, const char *name, DIR **pdir) +{ + DIR *dir = NULL; + int fd = -1; + int rc; + + fd = openat(parent_fd, name, O_RDONLY|O_DIRECTORY); + if (fd < 0) + return -errno; + + dir = fdopendir(fd); + if (dir == NULL) { + rc = -errno; + goto out; + } + + *pdir = dir; + fd = -1; + rc = 0; +out: + if (!(fd < 0)) + close(fd); + + return rc; +} + +static int ct_archive_upgrade_reg(int arc_fd, enum ct_archive_format ctaf, + int old_fd, const char *name) +{ + char new_path[PATH_MAX]; + struct lu_fid fid; + char *split; + int scan_count; + int suffix_offset = -1; + int rc; + + /* Formatted fix with optional suffix. We do not inspect + * suffixes. */ + scan_count = sscanf(name, SFID"%n", RFID(&fid), &suffix_offset); + if (scan_count != 3 || suffix_offset < 0) { + rc = 0; + CT_TRACE("ignoring unexpected file '%s' in archive", name); + goto out; + } + + ct_path_archive_v(new_path, sizeof(new_path), + ctaf, ".", &fid, name + suffix_offset); + + rc = renameat(old_fd, name, arc_fd, new_path); + if (rc == 0) + goto out; + + if (errno != ENOENT) { + rc = -errno; + goto out; + } + + /* Create parent directory and try again. */ + split = strrchr(new_path, '/'); + + *split = '\0'; + rc = ct_mkdirat_p(arc_fd, new_path, DIR_PERM); + *split = '/'; + if (rc < 0) + goto out; + + rc = renameat(old_fd, name, arc_fd, new_path); + if (rc < 0) + rc = -errno; +out: + if (rc < 0) + CT_ERROR(rc, "cannot rename '%s' to '%s'", name, new_path); + else + CT_TRACE("renamed '%s' to '%s'", name, new_path); + + return rc; +} + +static const char *d_type_name(unsigned int type) +{ + static const char *name[] = { + [DT_UNKNOWN] = "unknown", + [DT_FIFO] = "fifo", + [DT_CHR] = "chr", + [DT_DIR] = "dir", + [DT_BLK] = "blk", + [DT_REG] = "reg", + [DT_LNK] = "lnk", + [DT_SOCK] = "sock", + [DT_WHT] = "wht", + }; + + if (type < ARRAY_SIZE(name) && name[type] != NULL) + return name[type]; + + return name[DT_UNKNOWN]; +} + +static int ct_archive_upgrade_dir(int arc_fd, enum ct_archive_format ctaf, + int parent_fd, const char *dir_name) +{ + DIR *dir = NULL; + struct dirent *d; + int rc = 0; + int rc2; + + rc = ct_opendirat(parent_fd, dir_name, &dir); + if (rc < 0) { + CT_ERROR(rc, "cannot open archive dir '%s'", dir_name); + goto out; + } + + while ((d = readdir(dir)) != NULL) { + CT_TRACE("archive upgrade found %s '%s' (%ld)\n", + d_type_name(d->d_type), d->d_name, d->d_ino); + + switch (d->d_type) { + case DT_DIR: + if (strcmp(d->d_name, ".") == 0 || + strcmp(d->d_name, "..") == 0) + continue; + + if (strlen(d->d_name) != 4 || + strspn(d->d_name, "0123456789abcdef") != 4) + goto ignore; + + rc2 = ct_archive_upgrade_dir(arc_fd, ctaf, + dirfd(dir), d->d_name); + if (rc2 < 0) { + rc = rc2; + CT_ERROR(rc, "cannot upgrade dir '%s' (%ld)", + d->d_name, d->d_ino); + } + + rc2 = unlinkat(dirfd(dir), d->d_name, AT_REMOVEDIR); + CT_TRACE("unlink dir '%s' (%ld): %s", + d->d_name, d->d_ino, strerror(rc2 < 0 ? errno: 0)); + if (rc2 < 0 && errno != ENOTEMPTY) + rc = -errno; + + break; + case DT_REG: + rc2 = ct_archive_upgrade_reg(arc_fd, ctaf, + dirfd(dir), d->d_name); + if (rc2 < 0) + rc = rc2; + + break; + default: +ignore: + CT_TRACE("ignoring unexpected %s '%s' (%ld) in archive", + d_type_name(d->d_type), d->d_name, d->d_ino); + break; + } + } +out: + if (dir != NULL) + closedir(dir); + + return rc; +} + +/* Recursive inplace upgrade (or downgrade) of archive to format + * ctaf. Prunes empty archive subdirectories. Idempotent. */ +static int ct_archive_upgrade(int arc_fd, enum ct_archive_format ctaf) +{ + /* FIXME Handle shadow tree. */ + CT_TRACE("upgrade archive to format %s", ct_archive_format_to_str(ctaf)); + + return ct_archive_upgrade_dir(arc_fd, ctaf, arc_fd, "."); +} + static int ct_dir_level_max(const char *dirpath, __u16 *sub_seqmax) { DIR *dir; @@ -1988,20 +2278,121 @@ static int ct_run(void) return rc; } -static int ct_setup(void) +static int ct_config_get_str(struct cYAML *obj, const char *key, char **pvalue) { - int rc; + struct cYAML *cy; + char *value; - /* set llapi message level */ - llapi_msg_set_level(opt.o_verbose); + if (obj->cy_type != CYAML_TYPE_OBJECT) + return -EINVAL; - arc_fd = open(opt.o_hsm_root, O_RDONLY); - if (arc_fd < 0) { + for (cy = obj->cy_child; cy != NULL; cy = cy->cy_next) { + if (cy->cy_string != NULL && strcmp(cy->cy_string, key) == 0) { + if (cy->cy_type != CYAML_TYPE_STRING) + return -EINVAL; + + if (cy->cy_valuestring == NULL) + return -EINVAL; + + value = strdup(cy->cy_valuestring); + if (value == NULL) + return -ENOMEM; + + *pvalue = value; + + return 0; + } + } + + return -ENOENT; +} + +static int ct_config_archive_format(struct cYAML *config) +{ + char *value = NULL; + int rc; + + rc = ct_config_get_str(config, "archive_format", &value); + if (rc < 0) + return (rc == -ENOENT) ? 0 : rc; + + rc = ct_str_to_archive_format(value, &opt.o_archive_format); + if (rc < 0) + goto out; + + CT_TRACE("setting archive format to %s", + ct_archive_format_to_str(opt.o_archive_format)); + +out: + free(value); + + return 0; +} + +static int ct_config_archive_path(struct cYAML *config) +{ + int rc; + + rc = ct_config_get_str(config, "archive_path", &opt.o_hsm_root); + if (rc < 0) + return (rc == -ENOENT) ? 0 : rc; + + CT_TRACE("setting archive path to '%s'", opt.o_hsm_root); + + return 0; +} + +static int ct_config(const char *path) +{ + FILE *file = NULL; + struct cYAML *config = NULL; + int rc; + + if (path == NULL) + return 0; + + file = fopen(path, "r"); + if (file == NULL) { rc = -errno; - CT_ERROR(rc, "cannot open archive at '%s'", opt.o_hsm_root); - return rc; + CT_ERROR(rc, "cannot open '%s'", path); + goto out; } + config = cYAML_load(file, NULL, false); + if (config == NULL) { + rc = -EINVAL; + CT_ERROR(rc, "cannot load archive config from '%s'", path); + goto out; + } + + rc = ct_config_archive_format(config); + if (rc < 0) { + CT_ERROR(rc, "cannot load archive format from '%s'", path); + goto out; + } + + rc = ct_config_archive_path(config); + if (rc < 0) { + CT_ERROR(rc, "cannot load archive path from '%s'", path); + goto out; + } +out: + if (config != NULL) + cYAML_free_tree(config); + + if (file != NULL) + fclose(file); + + return rc; +} + +static int ct_setup(void) +{ + int rc; + + if (opt.o_action == CA_ARCHIVE_UPGRADE) + return 0; + rc = llapi_search_fsname(opt.o_mnt, fs_name); if (rc < 0) { CT_ERROR(rc, "cannot find a Lustre filesystem mounted at '%s'", @@ -2062,6 +2453,25 @@ int main(int argc, char **argv) return -rc; } + llapi_msg_set_level(opt.o_verbose); + + rc = ct_config(opt.o_config_path); + if (rc < 0) + return -rc; + + if (opt.o_hsm_root == NULL) { + rc = -EINVAL; + CT_ERROR(rc, "must specify a root directory for the backend"); + return -rc; + } + + arc_fd = open(opt.o_hsm_root, O_RDONLY); + if (arc_fd < 0) { + rc = -errno; + CT_ERROR(rc, "cannot open archive at '%s'", opt.o_hsm_root); + return rc; + } + rc = ct_setup(); if (rc < 0) goto error_cleanup; @@ -2076,6 +2486,9 @@ int main(int argc, char **argv) case CA_MAXSEQ: rc = ct_max_sequence(); break; + case CA_ARCHIVE_UPGRADE: + rc = ct_archive_upgrade(arc_fd, opt.o_archive_format); + break; default: rc = ct_run(); break;