#include <stdio.h>
#include <math.h>
#include <stdlib.h>
+#include <errno.h>
#include <float.h>
#include <limits.h>
#include <ctype.h>
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 ="
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;
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;
+}
#include <libcfs/util/string.h>
#include <linux/lustre/lustre_fid.h>
+#include <lnetconfig/cyaml.h>
#include <lustre/lustreapi.h>
+#include "lstddef.h"
#include "pid_file.h"
/* Progress reporting period */
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;
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;
.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,
};
" each line of <list_file> consists of <old_FID> <new_FID>\n"
" %s [options] --max-sequence <fsname>\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 <bw> 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"
" -u, --update-interval <s> 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);
}
.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,
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': {
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;
break;
}
+ if (opt.o_action == CA_ARCHIVE_UPGRADE)
+ return 0;
+
if (argc != optind + 1) {
rc = -EINVAL;
CT_ERROR(rc, "no mount point specified");
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;
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)
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)
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;
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'",
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;
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;