From: Matthew Dillon Date: Wed, 14 Oct 2009 18:44:59 +0000 (-0700) Subject: HAMMER - Add version 3 meta-data features X-Git-Tag: v2.7.1~473 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/83f2a3aae851887c1143dd5174eade5b66682dce HAMMER - Add version 3 meta-data features * These features are available for filesystem version 3. Version 2 may be upgraded to version 3 in-place. These features are not usable until you upgrade. * Definitively store snapshots in filesystem meta-data. Softlinks still work. The new snapshot directives (snap, snaplo, snapq, etc) also allow you to specify up to a 64-character note for each snapshot you create. The snapls directive may be used to list all snapshots stored in meta-data. 'hammer cleanup' will move all softlink-based snapshots residing in the /snapshots directory to meta-data when it next snapshots the filesystem (within a day of upgrading, usually). The snapshot softlinks are left intact. Storing snapshot information in meta-data means that accidental wipes of your /snapshots directory will NOT cause later hammer cleanup runs to destroy your snapshots! The meta-data snapshots are also removed if you do a prune-everything, or through normal pruning expirations, and thus 'hammer snapls' will definitively list your valid snapshots. This feature also means that you can obtain a definitive list of snapshots available on mirroring slaves. * Definitively store the hammer cleanup configuration file in filesystem meta-data. This meta-data is not mirrored. 'hammer cleanup' will move /snapshots/config to the new meta-data config and deletes /snapshots/config after you've upgraded the filesystem. You can edit the configuration with the 'viconfig' directive. * The HAMMER utility has new directives: snap, snaplo, snapq, snaprm, snapls, config, and viconfig. * WARNING! Filesystems mounted 'nohistory' and files chflagged similarly do not have snapshots, but the hammer utility still allows the directives to be run. This is a bug that needs to be fixed. --- diff --git a/sbin/hammer/Makefile b/sbin/hammer/Makefile index 32ae468d13..ee17982e9d 100644 --- a/sbin/hammer/Makefile +++ b/sbin/hammer/Makefile @@ -7,7 +7,8 @@ SRCS= hammer.c ondisk.c blockmap.c cache.c misc.c cycle.c \ cmd_blockmap.c cmd_reblock.c cmd_rebalance.c \ cmd_synctid.c cmd_stats.c \ cmd_pseudofs.c cmd_snapshot.c cmd_mirror.c cmd_status.c \ - cmd_cleanup.c cmd_info.c cmd_version.c cmd_expand.c + cmd_cleanup.c cmd_info.c cmd_version.c cmd_expand.c \ + cmd_config.c MAN= hammer.8 CFLAGS+= -I${.CURDIR}/../../sys -DALIST_NO_DEBUG diff --git a/sbin/hammer/cmd_cleanup.c b/sbin/hammer/cmd_cleanup.c index 48b2bcafd5..842f5457a9 100644 --- a/sbin/hammer/cmd_cleanup.c +++ b/sbin/hammer/cmd_cleanup.c @@ -63,27 +63,32 @@ struct didpfs { }; static void do_cleanup(const char *path); +static void config_init(const char *path, struct hammer_ioc_config *config); +static void migrate_config(FILE *fp, struct hammer_ioc_config *config); +static void migrate_snapshots(int fd, const char *snapshots_path); +static void migrate_one_snapshot(int fd, const char *fpath, + struct hammer_ioc_snapshot *snapshot); static int strtosecs(char *ptr); static const char *dividing_slash(const char *path); static int check_period(const char *snapshots_path, const char *cmd, int arg1, time_t *savep); static void save_period(const char *snapshots_path, const char *cmd, time_t savet); -static int check_softlinks(const char *snapshots_path); -static void cleanup_softlinks(const char *path, const char *snapshots_path, - int arg2, char *arg3); +static int check_softlinks(int fd, int new_config, const char *snapshots_path); +static void cleanup_softlinks(int fd, int new_config, + const char *snapshots_path, int arg2, char *arg3); static int check_expired(const char *fpath, int arg2); -static int create_snapshot(const char *path, const char *snapshots_path, - int arg1, int arg2); +static int create_snapshot(int new_config, const char *path, + const char *snapshots_path); static int cleanup_rebalance(const char *path, const char *snapshots_path, - int arg1, int arg2); + int arg1, int arg2); static int cleanup_prune(const char *path, const char *snapshots_path, - int arg1, int arg2, int snapshots_disabled); + int arg1, int arg2, int snapshots_disabled); static int cleanup_reblock(const char *path, const char *snapshots_path, - int arg1, int arg2); + int arg1, int arg2); static int cleanup_recopy(const char *path, const char *snapshots_path, - int arg1, int arg2); + int arg1, int arg2); static void runcmd(int *resp, const char *ctl, ...); @@ -135,6 +140,8 @@ void do_cleanup(const char *path) { struct hammer_ioc_pseudofs_rw pfs; + struct hammer_ioc_config config; + struct hammer_ioc_version version; union hammer_ioc_mrecord_any mrec_tmp; char *snapshots_path; char *config_path; @@ -146,10 +153,13 @@ do_cleanup(const char *path) char *arg3; time_t savet; char buf[256]; + char *cbase; + char *cptr; FILE *fp; struct didpfs *didpfs; int snapshots_disabled = 0; int prune_warning = 0; + int new_config = 0; int fd; int r; int found_rebal = 0; @@ -166,16 +176,31 @@ do_cleanup(const char *path) printf(" unable to access directory: %s\n", strerror(errno)); return; } - if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) { + if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) { printf(" not a HAMMER filesystem: %s\n", strerror(errno)); + close(fd); return; } - close(fd); if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) { printf(" unrecognized HAMMER version\n"); + close(fd); + return; + } + bzero(&version, sizeof(version)); + if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) { + printf(" HAMMER filesystem but couldn't retrieve version!\n"); + close(fd); return; } + bzero(&config, sizeof(config)); + if (version.cur_version >= 3) { + if (ioctl(fd, HAMMERIOC_GET_CONFIG, &config) == 0 && + config.head.error == 0) { + new_config = 1; + } + } + /* * Make sure we have not already handled this PFS. Several nullfs * mounts might alias the same PFS. @@ -183,6 +208,7 @@ do_cleanup(const char *path) for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) { if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) { printf(" PFS #%d already handled\n", pfs.pfs_id); + close(fd); return; } } @@ -198,12 +224,14 @@ do_cleanup(const char *path) asprintf(&snapshots_path, "%s", mrec_tmp.pfs.pfsd.snapshots); } else if (mrec_tmp.pfs.pfsd.snapshots[0]) { printf(" WARNING: pfs-slave's snapshots dir is not absolute\n"); + close(fd); return; } else if (mrec_tmp.pfs.pfsd.mirror_flags & HAMMER_PFSD_SLAVE) { printf(" WARNING: must configure snapshot dir for PFS slave\n"); printf("\tWe suggest /var/slaves/ where " " is the base HAMMER fs\n"); printf("\tcontaining the slave\n"); + close(fd); return; } else { asprintf(&snapshots_path, @@ -213,50 +241,72 @@ do_cleanup(const char *path) /* * Create a snapshot directory if necessary, and a config file if * necessary. + * + * If the filesystem is running version >= 3 migrate the config + * file to meta-data. */ if (stat(snapshots_path, &st) < 0) { if (mkdir(snapshots_path, 0755) != 0) { free(snapshots_path); printf(" unable to create snapshot dir \"%s\": %s\n", snapshots_path, strerror(errno)); + close(fd); return; } } asprintf(&config_path, "%s/config", snapshots_path); - if ((fp = fopen(config_path, "r")) == NULL) { - fp = fopen(config_path, "w"); - if (fp == NULL) { - printf(" cannot create %s: %s\n", - config_path, strerror(errno)); - return; + fp = fopen(config_path, "r"); + + /* + * Handle upgrades to hammer version 3, move the config + * file into meta-data. + * + * For the old config read the file into the config structure, + * we will parse it out of the config structure regardless. + */ + if (version.cur_version >= 3) { + if (fp) { + printf("(migrating) "); + fflush(stdout); + migrate_config(fp, &config); + migrate_snapshots(fd, snapshots_path); + fclose(fp); + if (ioctl(fd, HAMMERIOC_SET_CONFIG, &config) < 0) { + printf(" cannot init meta-data config!\n"); + close(fd); + return; + } + remove(config_path); + } else if (new_config == 0) { + config_init(path, &config); + if (ioctl(fd, HAMMERIOC_SET_CONFIG, &config) < 0) { + printf(" cannot init meta-data config!\n"); + close(fd); + return; + } } - if (strcmp(path, "/tmp") == 0 || - strcmp(path, "/var/tmp") == 0 || - strcmp(path, "/usr/obj") == 0) { - fprintf(fp, "snapshots 0d 0d\n"); + new_config = 1; + } else { + if (fp == NULL) { + config_init(path, &config); + fp = fopen(config_path, "w"); + if (fp) { + fwrite(config.config.text, 1, + strlen(config.config.text), fp); + fclose(fp); + } } else { - fprintf(fp, "snapshots 1d 60d\n"); + migrate_config(fp, &config); + fclose(fp); } - fprintf(fp, - "prune 1d 5m\n" - "rebalance 1d 5m\n" - "reblock 1d 5m\n" - "recopy 30d 10m\n"); - fclose(fp); - fp = fopen(config_path, "r"); - } - if (fp == NULL) { - printf(" cannot access %s: %s\n", - config_path, strerror(errno)); - return; } - if (flock(fileno(fp), LOCK_EX|LOCK_NB) == -1) { + if (flock(fd, LOCK_EX|LOCK_NB) == -1) { if (errno == EWOULDBLOCK) printf(" PFS #%d locked by other process\n", pfs.pfs_id); else printf(" can not lock %s: %s\n", config_path, strerror(errno)); - fclose(fp); + close(fd); return; } @@ -265,7 +315,13 @@ do_cleanup(const char *path) /* * Process the config file */ - while (fgets(buf, sizeof(buf), fp) != NULL) { + cbase = config.config.text; + + while ((cptr = strchr(cbase, '\n')) != NULL) { + bcopy(cbase, buf, cptr - cbase); + buf[cptr - cbase] = 0; + cbase = cptr + 1; + cmd = strtok(buf, WS); arg1 = 0; arg2 = 0; @@ -284,10 +340,13 @@ do_cleanup(const char *path) r = 1; if (strcmp(cmd, "snapshots") == 0) { if (arg1 == 0) { - if (arg2 && check_softlinks(snapshots_path)) { + if (arg2 && + check_softlinks(fd, new_config, + snapshots_path)) { printf("only removing old snapshots\n"); prune_warning = 1; - cleanup_softlinks(path, snapshots_path, + cleanup_softlinks(fd, new_config, + snapshots_path, arg2, arg3); } else { printf("disabled\n"); @@ -296,10 +355,11 @@ do_cleanup(const char *path) } else if (check_period(snapshots_path, cmd, arg1, &savet)) { printf("run\n"); - cleanup_softlinks(path, snapshots_path, + cleanup_softlinks(fd, new_config, + snapshots_path, arg2, arg3); - r = create_snapshot(path, snapshots_path, - arg1, arg2); + r = create_snapshot(new_config, + path, snapshots_path); } else { printf("skip\n"); } @@ -365,21 +425,179 @@ do_cleanup(const char *path) if (r == 0) save_period(snapshots_path, cmd, savet); } - fclose(fp); /* - * Add new rebalance feature if the config doesn't have it + * Add new rebalance feature if the config doesn't have it. + * (old style config only) */ - if (found_rebal == 0) { + if (new_config == 0 && found_rebal == 0) { if ((fp = fopen(config_path, "r+")) != NULL) { fseek(fp, 0L, 2); fprintf(fp, "rebalance 1d 5m\n"); fclose(fp); } } + + /* + * Cleanup, and delay a little + */ + close(fd); usleep(1000); } +/* + * Initialize new config data (new or old style) + */ +static void +config_init(const char *path, struct hammer_ioc_config *config) +{ + const char *snapshots; + + if (strcmp(path, "/tmp") == 0 || + strcmp(path, "/var/tmp") == 0 || + strcmp(path, "/usr/obj") == 0) { + snapshots = "snapshots 0d 0d\n"; + } else { + snapshots = "snapshots 1d 60d\n"; + } + bzero(config->config.text, sizeof(config->config.text)); + snprintf(config->config.text, sizeof(config->config.text) - 1, "%s%s", + snapshots, + "prune 1d 5m\n" + "rebalance 1d 5m\n" + "reblock 1d 5m\n" + "recopy 30d 10m\n" + "rebalance 1d 5m\n"); +} + +/* + * Migrate configuration data from the old snapshots/config + * file to the new mata-data format. + */ +static void +migrate_config(FILE *fp, struct hammer_ioc_config *config) +{ + int n; + + n = fread(config->config.text, 1, sizeof(config->config.text) - 1, fp); + if (n >= 0) + bzero(config->config.text + n, sizeof(config->config.text) - n); +} + +/* + * Migrate snapshot softlinks in the snapshots directory to the + * new meta-data format. The softlinks are left intact, but + * this way the pruning code won't lose track of them if you + * happen to blow away the snapshots directory. + */ +static void +migrate_snapshots(int fd, const char *snapshots_path) +{ + struct hammer_ioc_snapshot snapshot; + struct dirent *den; + struct stat st; + DIR *dir; + char *fpath; + + bzero(&snapshot, sizeof(snapshot)); + + if ((dir = opendir(snapshots_path)) != NULL) { + while ((den = readdir(dir)) != NULL) { + if (den->d_name[0] == '.') + continue; + asprintf(&fpath, "%s/%s", snapshots_path, den->d_name); + if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode)) { + migrate_one_snapshot(fd, fpath, &snapshot); + } + free(fpath); + } + closedir(dir); + } + migrate_one_snapshot(fd, NULL, &snapshot); + +} + +/* + * Migrate a single snapshot. If fpath is NULL the ioctl is flushed, + * otherwise it is flushed when it fills up. + */ +static void +migrate_one_snapshot(int fd, const char *fpath, + struct hammer_ioc_snapshot *snapshot) +{ + if (fpath) { + struct hammer_snapshot_data *snap; + struct tm tm; + time_t t; + int year; + int month; + int day = 0; + int hour = 0; + int minute = 0; + int r; + char linkbuf[1024]; + const char *ptr; + hammer_tid_t tid; + + t = (time_t)-1; + tid = (hammer_tid_t)(int64_t)-1; + + ptr = fpath; + while (*ptr && *ptr != '-' && *ptr != '.') + ++ptr; + if (*ptr) + ++ptr; + r = sscanf(ptr, "%4d%2d%2d-%2d%2d", + &year, &month, &day, &hour, &minute); + + if (r >= 3) { + bzero(&tm, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_min = minute; + tm.tm_hour = hour; + tm.tm_mday = day; + tm.tm_mon = month - 1; + tm.tm_year = year - 1900; + t = mktime(&tm); + } + bzero(linkbuf, sizeof(linkbuf)); + if (readlink(fpath, linkbuf, sizeof(linkbuf) - 1) > 0 && + (ptr = strrchr(linkbuf, '@')) != NULL && + ptr > linkbuf && ptr[-1] == '@') { + tid = strtoull(ptr + 1, NULL, 16); + } + if (t != (time_t)-1 && tid != (hammer_tid_t)(int64_t)-1) { + snap = &snapshot->snaps[snapshot->count]; + bzero(snap, sizeof(*snap)); + snap->tid = tid; + snap->ts = (u_int64_t)t * 1000000ULL; + snprintf(snap->label, sizeof(snap->label), + "migrated"); + ++snapshot->count; + } + } + + if ((fpath == NULL && snapshot->count) || + snapshot->count == HAMMER_SNAPS_PER_IOCTL) { + printf(" (%d snapshots)", snapshot->count); +again: + if (ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, snapshot) < 0) { + printf(" Ioctl to migrate snapshots failed: %s\n", + strerror(errno)); + } else if (snapshot->head.error == EALREADY) { + ++snapshot->index; + goto again; + } else if (snapshot->head.error) { + printf(" Ioctl to delete snapshots failed: %s\n", + strerror(snapshot->head.error)); + } + printf("index %d\n", snapshot->index); + snapshot->index = 0; + snapshot->count = 0; + snapshot->head.error = 0; + } +} + static int strtosecs(char *ptr) @@ -516,7 +734,7 @@ save_period(const char *snapshots_path, const char *cmd, * Simply count the number of softlinks in the snapshots dir */ static int -check_softlinks(const char *snapshots_path) +check_softlinks(int fd, int new_config, const char *snapshots_path) { struct dirent *den; struct stat st; @@ -524,6 +742,9 @@ check_softlinks(const char *snapshots_path) char *fpath; int res = 0; + /* + * Old-style softlink-based snapshots + */ if ((dir = opendir(snapshots_path)) != NULL) { while ((den = readdir(dir)) != NULL) { if (den->d_name[0] == '.') @@ -535,15 +756,33 @@ check_softlinks(const char *snapshots_path) } closedir(dir); } - return(res); + + /* + * New-style snapshots are stored as filesystem meta-data, + * count those too. + */ + if (new_config) { + struct hammer_ioc_snapshot snapshot; + + bzero(&snapshot, sizeof(snapshot)); + do { + if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { + err(2, "hammer cleanup: check_softlink " + "snapshot error"); + /* not reached */ + } + res += snapshot.count; + } while (snapshot.head.error == 0 && snapshot.count); + } + return (res); } /* * Clean up expired softlinks in the snapshots dir */ static void -cleanup_softlinks(const char *path __unused, const char *snapshots_path, - int arg2, char *arg3) +cleanup_softlinks(int fd, int new_config, + const char *snapshots_path, int arg2, char *arg3) { struct dirent *den; struct stat st; @@ -574,6 +813,66 @@ cleanup_softlinks(const char *path __unused, const char *snapshots_path, } closedir(dir); } + + /* + * New-style snapshots are stored as filesystem meta-data, + * count those too. + */ + if (new_config) { + struct hammer_ioc_snapshot snapshot; + struct hammer_ioc_snapshot dsnapshot; + struct hammer_snapshot_data *snap; + struct tm *tp; + time_t t; + char snapts[32]; + u_int32_t i; + + bzero(&snapshot, sizeof(snapshot)); + bzero(&dsnapshot, sizeof(dsnapshot)); + do { + if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { + err(2, "hammer cleanup: check_softlink " + "snapshot error"); + /* not reached */ + } + for (i = 0; i < snapshot.count; ++i) { + snap = &snapshot.snaps[i]; + t = time(NULL) - snap->ts / 1000000ULL; + if ((int)t > arg2) { + dsnapshot.snaps[dsnapshot.count++] = + *snap; + } + if ((int)t > arg2 && VerboseOpt) { + tp = localtime(&t); + strftime(snapts, sizeof(snapts), + "%Y-%m-%d %H:%M:%S %Z", tp); + printf(" expire 0x%016jx %s %s\n", + (uintmax_t)snap->tid, + snapts, + snap->label); + } + if (dsnapshot.count == HAMMER_SNAPS_PER_IOCTL) { + if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, &dsnapshot) < 0) { + printf(" Ioctl to delete snapshots failed: %s\n", strerror(errno)); + } else if (dsnapshot.head.error) { + printf(" Ioctl to delete snapshots failed: %s\n", strerror(dsnapshot.head.error)); + } + dsnapshot.count = 0; + dsnapshot.head.error = 0; + } + } + } while (snapshot.head.error == 0 && snapshot.count); + + if (dsnapshot.count) { + if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, &dsnapshot) < 0) { + printf(" Ioctl to delete snapshots failed: %s\n", strerror(errno)); + } else if (dsnapshot.head.error) { + printf(" Ioctl to delete snapshots failed: %s\n", strerror(dsnapshot.head.error)); + } + dsnapshot.count = 0; + dsnapshot.head.error = 0; + } + } } /* @@ -623,12 +922,21 @@ check_expired(const char *fpath, int arg2) * Issue a snapshot. */ static int -create_snapshot(const char *path __unused, const char *snapshots_path, - int arg1 __unused, int arg2 __unused) +create_snapshot(int new_config, const char *path, const char *snapshots_path) { int r; - runcmd(&r, "hammer snapshot %s %s", path, snapshots_path); + if (new_config) { + /* + * New-style snapshot >= version 3. + */ + runcmd(&r, "hammer snapfs %s cleanup", snapshots_path); + } else { + /* + * Old-style snapshot prior to version 3 + */ + runcmd(&r, "hammer snapshot %s %s", path, snapshots_path); + } return(r); } diff --git a/sbin/hammer/cmd_config.c b/sbin/hammer/cmd_config.c new file mode 100644 index 0000000000..c87d0e914f --- /dev/null +++ b/sbin/hammer/cmd_config.c @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2009 The DragonFly Project. All rights reserved. + * + * This code is derived from software contributed to The DragonFly Project + * by Matthew Dillon + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of The DragonFly Project nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific, prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "hammer.h" +#include +#include +#include +#include +#include +#include +#include + +static void config_get(const char *dirpath, struct hammer_ioc_config *config); +static void config_set(const char *dirpath, struct hammer_ioc_config *config); +static void config_remove_path(void); + +char *ConfigPath; + +/* + * hammer config [ [configfile]] + * + * Prints out the hammer cleanup configuration for the specified HAMMER + * filesystem(s) or the current filesystem. + */ +void +hammer_cmd_config(char **av, int ac) +{ + struct hammer_ioc_config config; + char *dirpath; + ssize_t n; + int fd; + + bzero(&config, sizeof(config)); + if (ac == 0) { + config_get(".", &config); + if (config.head.error == 0) { + printf("%s", config.config.text); + } else { + errx(2, "hammer config: no configuration found"); + /* not reached */ + } + return; + } + dirpath = av[0]; + if (ac == 1) { + config_get(dirpath, &config); + if (config.head.error == 0) { + printf("%s", config.config.text); + } else { + errx(2, "hammer config: no configuration found"); + /* not reached */ + } + return; + } + config_get(dirpath, &config); /* ignore errors */ + config.head.error = 0; + + fd = open(av[1], O_RDONLY); + n = read(fd, config.config.text, sizeof(config.config.text) - 1); + if (n == sizeof(config.config.text) - 1) { + err(2, "hammer config: config file too big, limit %d bytes", + sizeof(config.config.text) - 1); + /* not reached */ + } + bzero(config.config.text + n, sizeof(config.config.text) - n); + config_set(dirpath, &config); + close(fd); +} + +/* + * hammer viconfig [] + */ +void +hammer_cmd_viconfig(char **av, int ac) +{ + struct hammer_ioc_config config; + struct timeval times[2]; + const char *dirpath; + struct stat st; + char *runcmd; + char path[32]; + ssize_t n; + int fd; + + if (ac > 1) { + errx(1, "hammer viconfig: 0 or 1 argument () only"); + /* not reached */ + } + if (ac == 0) + dirpath = "."; + else + dirpath = av[0]; + config_get(dirpath, &config); + if (config.head.error == ENOENT) { + snprintf(config.config.text, sizeof(config.config.text), + "%s", + "# No configuration present, here are some defaults\n" + "# you can uncomment. Also remove these instructions\n" + "#\n" + "#snapshots 1d 60d\n" + "#prune 1d 5m\n" + "#rebalance 1d 5m\n" + "#reblock 1d 5m\n" + "#recopy 30d 10m\n" + "#rebalance 1d 5m\n"); + config.head.error = 0; + } + if (config.head.error) { + errx(2, "hammer viconfig: read config failed error: %s", + strerror(config.head.error)); + /* not reached */ + } + + /* + * Edit a temporary file and write back if it was modified. + * Adjust the mtime back one second so a quick edit is not + * improperly detected as not having been modified. + */ + snprintf(path, sizeof(path), "/tmp/configXXXXXXXXXX"); + mkstemp(path); + ConfigPath = path; + atexit(config_remove_path); + + fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0600); + if (fd < 0) + err(2, "hammer viconfig: creating temporary file %s", path); + write(fd, config.config.text, strlen(config.config.text)); + if (fstat(fd, &st) < 0) + err(2, "hammer viconfig"); + times[0].tv_sec = st.st_mtime - 1; + times[0].tv_usec = 0; + times[1] = times[0]; + close(fd); + utimes(path, times); + + asprintf(&runcmd, "vi %s", path); + system(runcmd); + + if (stat(path, &st) < 0) + err(2, "hammer viconfig: unable to stat file after vi"); + if (times[0].tv_sec == st.st_mtime) { + printf("hammer viconfig: no changes were made\n"); + remove(path); + return; + } + fd = open(path, O_RDONLY); + if (fd < 0) + err(2, "hammer viconfig: unable to read %s", path); + remove(path); + n = read(fd, config.config.text, sizeof(config.config.text) - 1); + if (n < 0) + err(2, "hammer viconfig: unable to read %s", path); + if (n == sizeof(config.config.text) - 1) { + err(2, "hammer config: config file too big, limit %d bytes", + sizeof(config.config.text) - 1); + /* not reached */ + } + bzero(config.config.text + n, sizeof(config.config.text) - n); + config_set(dirpath, &config); +} + +static void +config_get(const char *dirpath, struct hammer_ioc_config *config) +{ + struct hammer_ioc_version version; + int fd; + + bzero(&version, sizeof(version)); + if ((fd = open(dirpath, O_RDONLY)) < 0) + err(2, "hammer config: unable to open directory %s", dirpath); + if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) + errx(2, "hammer config: not a HAMMER filesystem!"); + + if (ioctl(fd, HAMMERIOC_GET_CONFIG, config) < 0) + errx(2, "hammer config: config_get"); + close(fd); +} + +static void +config_set(const char *dirpath, struct hammer_ioc_config *config) +{ + struct hammer_ioc_version version; + int fd; + + bzero(&version, sizeof(version)); + if ((fd = open(dirpath, O_RDONLY)) < 0) + errx(2, "hammer config: unable to open directory %s", dirpath); + if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) + errx(2, "hammer config: not a HAMMER filesystem!"); + if (ioctl(fd, HAMMERIOC_SET_CONFIG, config) < 0) + errx(2, "hammer config: config_set"); + close(fd); +} + +static void +config_remove_path(void) +{ + remove(ConfigPath); +} diff --git a/sbin/hammer/cmd_snapshot.c b/sbin/hammer/cmd_snapshot.c index f43b13af71..1fe9fd452c 100644 --- a/sbin/hammer/cmd_snapshot.c +++ b/sbin/hammer/cmd_snapshot.c @@ -46,6 +46,213 @@ #define DEFAULT_SNAPSHOT_NAME "snap-%Y%m%d-%H%M" static void snapshot_usage(int exit_code); +static void snapshot_add(int fd, const char *fsym, const char *tsym, + const char *label, hammer_tid_t tid); +static void snapshot_ls(const char *path); +static void snapshot_del(int fsfd, hammer_tid_t tid); +static char *dirpart(const char *path); + +/* + * hammer snap path ["note"] + * + * Path may be a directory, softlink, or non-existant (a softlink will be + * created). + */ +void +hammer_cmd_snap(char **av, int ac, int tostdout, int fsbase) +{ + struct hammer_ioc_synctid synctid; + struct hammer_ioc_version version; + char *dirpath; + char *fsym; + char *tsym; + struct stat st; + char note[64]; + int fsfd; + + if (ac == 0 || ac > 2) { + snapshot_usage(1); + /* not reached */ + exit(1); + } + + if (ac == 2) + snprintf(note, sizeof(note), "%s", av[1]); + else + note[0] = 0; + + /* + * Figure out the softlink path and directory path + */ + if (stat(av[0], &st) < 0) { + dirpath = dirpart(av[0]); + tsym = av[0]; + } else if (S_ISLNK(st.st_mode)) { + dirpath = dirpart(av[0]); + tsym = av[0]; + } else if (S_ISDIR(st.st_mode)) { + time_t t = time(NULL); + struct tm *tp; + char extbuf[64]; + + tp = localtime(&t); + strftime(extbuf, sizeof(extbuf), DEFAULT_SNAPSHOT_NAME, tp); + + dirpath = strdup(av[0]); + asprintf(&tsym, "%s/%s", dirpath, extbuf); + } else { + err(2, "hammer snap: File %s exists and is not a softlink\n", + av[0]); + /* not reached */ + } + + /* + * Get a handle on some directory in the filesystem for the + * ioctl (so it is stored in the correct PFS). + */ + fsfd = open(dirpath, O_RDONLY); + if (fsfd < 0) { + err(2, "hammder snap: Cannot open directory %s\n", dirpath); + /* not reached */ + } + + /* + * Must be at least version 3 to use this command. + */ + bzero(&version, sizeof(version)); + + if (ioctl(fsfd, HAMMERIOC_GET_VERSION, &version) < 0) { + err(2, "Unable to create snapshot"); + /* not reached */ + } else if (version.cur_version < 3) { + errx(2, "Unable to create snapshot: This directive requires " + "you to upgrade the filesystem\n" + "to version 3. Use 'hammer snapshot' for legacy " + "operation"); + /* not reached */ + } + + /* + * Synctid to get a transaction id for the snapshot. + */ + bzero(&synctid, sizeof(synctid)); + synctid.op = HAMMER_SYNCTID_SYNC2; + if (ioctl(fsfd, HAMMERIOC_SYNCTID, &synctid) < 0) { + err(2, "hammer snap: Synctid %s failed", + dirpath); + } + if (tostdout) { + printf("%s/@@%016jx\n", dirpath, (uintmax_t)synctid.tid); + fsym = NULL; + tsym = NULL; + } + + /* + * Contents of the symlink being created. + */ + if (fsbase) { + struct statfs buf; + + if (statfs(dirpath, &buf) < 0) { + err(2, "hammer snapfs: Cannot determine mount for %s", + dirpath); + } + asprintf(&fsym, "%s/@@0x%016jx", + buf.f_mntonname, (uintmax_t)synctid.tid); + } else { + asprintf(&fsym, "%s/@@0x%016jx", + dirpath, (uintmax_t)synctid.tid); + } + + /* + * Create the snapshot. + */ + snapshot_add(fsfd, fsym, tsym, note, synctid.tid); + free(dirpath); +} + +/* + * hammer snapls [path]* + * + * If no arguments are specified snapshots for the PFS containing the + * current directory are listed. + */ +void +hammer_cmd_snapls(char **av, int ac) +{ + int i; + + for (i = 0; i < ac; ++i) + snapshot_ls(av[i]); + if (ac == 0) + snapshot_ls("."); +} + +/* + * hammer snaprm [fsdir] [path/transid]* + */ +void +hammer_cmd_snaprm(char **av, int ac) +{ + struct stat st; + char linkbuf[1024]; + intmax_t tid; + int fsfd = -1; + int i; + char *dirpath; + char *ptr; + + for (i = 0; i < ac; ++i) { + if (lstat(av[i], &st) < 0) { + tid = strtoull(av[i], &ptr, 16); + if (tid == 0 || *ptr) { + err(2, "hammer snaprm: not a file or tid: %s", + av[i]); + /* not reached */ + } + snapshot_del(fsfd, tid); + } else if (S_ISDIR(st.st_mode)) { + if (fsfd >= 0) + close(fsfd); + fsfd = open(av[i], O_RDONLY); + if (fsfd < 0) { + err(2, "hammer snaprm: cannot open dir %s", + av[i]); + /* not reached */ + } + } else if (S_ISLNK(st.st_mode)) { + dirpath = dirpart(av[i]); + if (fsfd >= 0) + close(fsfd); + fsfd = open(dirpath, O_RDONLY); + if (fsfd < 0) { + err(2, "hammer snaprm: cannot open dir %s", + dirpath); + /* not reached */ + } + + bzero(linkbuf, sizeof(linkbuf)); + if (readlink(av[i], linkbuf, sizeof(linkbuf) - 1) < 0) { + err(2, "hammer snaprm: cannot read softlink: " + "%s", av[i]); + /* not reached */ + } + if ((ptr = strrchr(linkbuf, '@')) && + ptr > linkbuf && ptr[-1] == '@') { + tid = strtoull(ptr + 1, NULL, 16); + snapshot_del(fsfd, tid); + } + remove(av[i]); + free(dirpath); + } else { + err(2, "hammer snaprm: not directory or snapshot " + "softlink: %s", av[i]); + /* not reached */ + } + } + if (fsfd >= 0) + close(fsfd); +} /* * snapshot @@ -71,6 +278,9 @@ hammer_cmd_snapshot(char **av, int ac) softlink_dir = av[1]; } else { snapshot_usage(1); + /* not reached */ + softlink_dir = NULL; + filesystem = NULL; } if (stat(softlink_dir, &st) == 0) { @@ -129,12 +339,12 @@ hammer_cmd_snapshot(char **av, int ac) */ bzero(&synctid, sizeof(synctid)); synctid.op = HAMMER_SYNCTID_SYNC2; + int fd = open(filesystem, O_RDONLY); if (fd < 0) err(2, "Unable to open %s", filesystem); if (ioctl(fd, HAMMERIOC_SYNCTID, &synctid) < 0) err(2, "Synctid %s failed", filesystem); - close(fd); asprintf(&from, "%s/@@0x%016jx", filesystem, (uintmax_t)synctid.tid); if (from == NULL) @@ -148,10 +358,12 @@ hammer_cmd_snapshot(char **av, int ac) time_t t = time(NULL); if (strftime(to, sz, softlink_fmt, localtime(&t)) == 0) err(2, "String buffer too small"); - - if (symlink(from, to) != 0) - err(2, "Unable to symlink %s to %s", from, to); + asprintf(&from, "%s/@@0x%016jx", filesystem, (uintmax_t)synctid.tid); + + snapshot_add(fd, from, to, NULL, synctid.tid); + + close(fd); printf("%s\n", to); free(softlink_fmt); @@ -159,11 +371,175 @@ hammer_cmd_snapshot(char **av, int ac) free(to); } +static +void +snapshot_add(int fd, const char *fsym, const char *tsym, const char *label, + hammer_tid_t tid) +{ + struct hammer_ioc_version version; + struct hammer_ioc_snapshot snapshot; + + bzero(&version, sizeof(version)); + bzero(&snapshot, sizeof(snapshot)); + + if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) == 0 && + version.cur_version >= 3) { + snapshot.index = 0; + snapshot.count = 1; + snapshot.snaps[0].tid = tid; + snapshot.snaps[0].ts = time(NULL) * 1000000ULL; + if (label) { + snprintf(snapshot.snaps[0].label, + sizeof(snapshot.snaps[0].label), + "%s", + label); + } + if (ioctl(fd, HAMMERIOC_ADD_SNAPSHOT, &snapshot) < 0) { + err(2, "Unable to create snapshot"); + } else if (snapshot.head.error && + snapshot.head.error != EEXIST) { + errx(2, "Unable to create snapshot: %s\n", + strerror(snapshot.head.error)); + } + } + if (fsym && tsym) { + remove(tsym); + if (symlink(fsym, tsym) < 0) { + err(2, "Unable to create symlink %s", tsym); + } + } +} + +static +void +snapshot_ls(const char *path) +{ + /*struct hammer_ioc_version version;*/ + struct hammer_ioc_snapshot snapshot; + struct hammer_ioc_pseudofs_rw pfs; + struct hammer_snapshot_data *snap; + struct tm *tp; + time_t t; + u_int32_t i; + int fd; + char snapts[64]; + + fd = open(path, O_RDONLY); + if (fd < 0) { + err(2, "hammer snapls: cannot open %s", path); + /* not reached */ + } + + bzero(&pfs, sizeof(pfs)); + pfs.pfs_id = -1; + pfs.bytes = sizeof(struct hammer_pseudofs_data); + if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) { + err(2, "hammer snapls: cannot retrieve PFS info on %s", path); + /* not reached */ + } + + printf("snapshots on pfs %d\n", pfs.pfs_id); + + bzero(&snapshot, sizeof(snapshot)); + do { + if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { + err(2, "hammer snapls: %s: not HAMMER fs or " + "version < 3", path); + /* not reached */ + } + for (i = 0; i < snapshot.count; ++i) { + snap = &snapshot.snaps[i]; + + t = snap->ts / 1000000ULL; + tp = localtime(&t); + strftime(snapts, sizeof(snapts), + "%Y-%m-%d %H:%M:%S %Z", tp); + printf("0x%016jx\t%s\t%s\n", + (uintmax_t)snap->tid, snapts, snap->label); + } + } while (snapshot.head.error == 0 && snapshot.count); +} + +static +void +snapshot_del(int fsfd, hammer_tid_t tid) +{ + struct hammer_ioc_snapshot snapshot; + struct hammer_ioc_version version; + + bzero(&version, sizeof(version)); + + if (ioctl(fsfd, HAMMERIOC_GET_VERSION, &version) < 0) { + err(2, "hammer snaprm 0x%016jx", (uintmax_t)tid); + } + if (version.cur_version < 3) { + errx(2, "hammer snaprm 0x%016jx: You must upgrade to version " + " 3 to use this directive", (uintmax_t)tid); + } + + bzero(&snapshot, sizeof(snapshot)); + snapshot.count = 1; + snapshot.snaps[0].tid = tid; + + if (ioctl(fsfd, HAMMERIOC_DEL_SNAPSHOT, &snapshot) < 0) { + err(2, "hammer snaprm 0x%016jx", (uintmax_t)tid); + } else if (snapshot.head.error) { + fprintf(stderr, "hammer snaprm 0x%016jx: %s\n", + (uintmax_t)tid, strerror(snapshot.head.error)); + } +} + static void snapshot_usage(int exit_code) { - fprintf(stderr, "hammer snapshot \n"); - fprintf(stderr, "hammer snapshot \n"); + fprintf(stderr, + "hammer snap path [\"note\"]\t- create snapshot & link, " + "points to base of PFS mount\n" + "hammer snaplo path [\"note\"]\t- create snapshot & link, " + "points to target dir\n" + "hammer snapq path [\"note\"]\t- create snapshot, output path to stdout\n" + "hammer snapls []\t\t- list available snapshots.\n" + "hammer snaprm [] [path/transid]*\t- delete snapshots.\n" + "\n" + "NOTE: Snapshots are created in filesystem meta-data, any directory\n" + " in a HAMMER filesystem or PFS may be specified. If the path\n" + " specified does not exist this function will also create a\n" + " softlink.\n" + "\n" + " When deleting snapshots transaction ids may be directly specified\n" + " or file paths to snapshoft softlinks may be specified. If a\n" + " softlink is specified the softlink will also be deleted\n" + "\n" + "NOTE: The old 'hammer snapshot [] ' form\n" + " is still accepted but is a deprecated form. This form will\n" + " work for older hammer versions. The new forms only work for\n" + " HAMMER version 3 or later filesystems. HAMMER can be upgraded\n" + " to version 3 in-place\n" + ); exit(exit_code); } + +static +char * +dirpart(const char *path) +{ + const char *ptr; + char *res; + + ptr = strrchr(path, '/'); + if (ptr) { + while (ptr > path && ptr[-1] == '/') + --ptr; + if (ptr == path) + ptr = NULL; + } + if (ptr == NULL) { + path = "."; + ptr = path + 1; + } + res = malloc(ptr - path + 1); + bcopy(path, res, ptr - path); + res[ptr - path] = 0; + return(res); +} diff --git a/sbin/hammer/cmd_softprune.c b/sbin/hammer/cmd_softprune.c index 91a16f223b..4e63f583d2 100644 --- a/sbin/hammer/cmd_softprune.c +++ b/sbin/hammer/cmd_softprune.c @@ -47,13 +47,18 @@ struct softprune { static void softprune_usage(int code); static void hammer_softprune_scandir(struct softprune **basep, - struct hammer_ioc_prune *template, - const char *dirname); + struct hammer_ioc_prune *template, + const char *dirname); +static int hammer_softprune_scanmeta(int fd, struct softprune *scan, + int delete_all); +static void hammer_meta_flushdelete(int fd, struct hammer_ioc_snapshot *dsnap); static struct softprune *hammer_softprune_addentry(struct softprune **basep, - struct hammer_ioc_prune *template, - const char *dirpath, const char *denname, - struct stat *st, - const char *linkbuf, const char *tidptr); + struct hammer_ioc_prune *template, + const char *dirpath, const char *denname, + struct stat *st, + const char *linkbuf, const char *tidptr); +static void hammer_softprune_addelm(struct softprune *scan, hammer_tid_t tid, + time_t ct, time_t mt); static void hammer_softprune_finalize(struct softprune *scan); /* @@ -155,6 +160,18 @@ hammer_cmd_softprune(char **av, int ac, int everything_opt) } scan->prune_min = pfs.ondisk->prune_min; + /* + * Incorporate meta-data snapshots into the pruning regimen. + * If pruning everything we delete the meta-data snapshots. + */ + if (hammer_softprune_scanmeta(fd, scan, everything_opt) < 0) { + warn("Filesystem %s could not scan meta-data snaps", + scan->filesystem); + rcode = 1; + close(fd); + continue; + } + /* * Finalize operations */ @@ -222,6 +239,10 @@ hammer_cmd_softprune(char **av, int ac, int everything_opt) /* * Scan a directory for softlinks representing snapshots and build * associated softprune structures. + * + * NOTE: Once a filesystem is completely converted to the meta-data + * snapshot mechanic we don't have to scan softlinks any more + * and can just use the meta-data. But for now we do both. */ static void hammer_softprune_scandir(struct softprune **basep, @@ -268,6 +289,84 @@ hammer_softprune_scandir(struct softprune **basep, free(path); } +/* + * Scan the metadata snapshots for the filesystem and either delete them + * or add them to the pruning list. + */ +static +int +hammer_softprune_scanmeta(int fd, struct softprune *scan, int delete_all) +{ + struct hammer_ioc_version version; + struct hammer_ioc_snapshot snapshot; + struct hammer_ioc_snapshot dsnapshot; + struct hammer_snapshot_data *snap; + time_t ct; + + /* + * Stop if we can't get the version. Meta-data snapshots only + * exist for HAMMER version 3 or greater. + */ + bzero(&version, sizeof(version)); + if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) + return(-1); + if (version.cur_version < 3) + return(0); + + bzero(&snapshot, sizeof(snapshot)); + bzero(&dsnapshot, sizeof(dsnapshot)); + + /* + * Scan meta-data snapshots, either add them to the prune list or + * delete them. When deleting, just skip any entries which cannot + * be deleted. + */ + for (;;) { + if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) { + printf("hammer prune: Unable to access " + "meta-data snaps: %s\n", strerror(errno)); + return(-1); + } + while (snapshot.index < snapshot.count) { + snap = &snapshot.snaps[snapshot.index]; + if (delete_all) { + dsnapshot.snaps[dsnapshot.count++] = *snap; + if (dsnapshot.count == HAMMER_SNAPS_PER_IOCTL) + hammer_meta_flushdelete(fd, &dsnapshot); + } else { + ct = snap->ts / 1000000ULL; + hammer_softprune_addelm(scan, snap->tid, + ct, ct); + } + ++snapshot.index; + } + if (snapshot.head.flags & HAMMER_IOC_SNAPSHOT_EOF) + break; + snapshot.index = 0; + } + if (delete_all) + hammer_meta_flushdelete(fd, &dsnapshot); + return(0); +} + +/* + * Flush any entries built up in the deletion snapshot ioctl structure. + * Used during a prune-everything. + */ +static void +hammer_meta_flushdelete(int fd, struct hammer_ioc_snapshot *dsnap) +{ + while (dsnap->index < dsnap->count) { + if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, dsnap) < 0) + break; + if (dsnap->head.error == 0) + break; + ++dsnap->index; + } + dsnap->index = 0; + dsnap->count = 0; +} + /* * Add the softlink to the appropriate softprune structure, creating a new * one if necessary. @@ -280,7 +379,6 @@ hammer_softprune_addentry(struct softprune **basep, struct stat *st, const char *linkbuf, const char *tidptr) { - struct hammer_ioc_prune_elm *elm; struct softprune *scan; struct statfs fs; char *fspath; @@ -324,19 +422,32 @@ hammer_softprune_addentry(struct softprune **basep, scan->filesystem = fspath; scan->prune = *template; scan->maxelms = 32; - scan->prune.elms = malloc(sizeof(*elm) * scan->maxelms); + scan->prune.elms = malloc(sizeof(struct hammer_ioc_prune_elm) * + scan->maxelms); scan->next = *basep; *basep = scan; } else { free(fspath); } + hammer_softprune_addelm(scan, + (hammer_tid_t)strtoull(tidptr + 2, NULL, 0), + (st ? st->st_ctime : 0), + (st ? st->st_mtime : 0)); + return(scan); +} + +/* + * Add the entry (unsorted). Just set the beg_tid, we will sort + * and set the remaining entries later. + * + * Always leave one entry free for our terminator. + */ +static void +hammer_softprune_addelm(struct softprune *scan, hammer_tid_t tid, + time_t ct, time_t mt) +{ + struct hammer_ioc_prune_elm *elm; - /* - * Add the entry (unsorted). Just set the beg_tid, we will sort - * and set the remaining entries later. - * - * Always leave one entry free for our terminator. - */ if (scan->prune.nelms >= scan->maxelms - 1) { scan->maxelms = (scan->maxelms * 3 / 2); scan->prune.elms = realloc(scan->prune.elms, @@ -348,17 +459,14 @@ hammer_softprune_addentry(struct softprune **basep, * This will be cleaned up in the finalization phase. */ elm = &scan->prune.elms[scan->prune.nelms]; - elm->beg_tid = strtoull(tidptr + 2, NULL, 0); + elm->beg_tid = tid; elm->end_tid = 0; elm->mod_tid = 0; - if (st) { - if (st->st_ctime < st->st_mtime) - elm->mod_tid = st->st_ctime; - else - elm->mod_tid = st->st_mtime; - } + if (ct < mt) + elm->mod_tid = ct; + else + elm->mod_tid = mt; ++scan->prune.nelms; - return(scan); } /* diff --git a/sbin/hammer/cmd_version.c b/sbin/hammer/cmd_version.c index eab08db0c5..ff9a11eb51 100644 --- a/sbin/hammer/cmd_version.c +++ b/sbin/hammer/cmd_version.c @@ -100,6 +100,8 @@ hammer_cmd_set_version(char **av, int ac) { struct hammer_ioc_version version; int fd; + int overs; + int nvers; if (ac < 2 || ac > 3 || (ac == 3 && strcmp(av[2], "force") != 0)) { fprintf(stderr, @@ -115,7 +117,14 @@ hammer_cmd_set_version(char **av, int ac) } bzero(&version, sizeof(version)); + if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) { + fprintf(stderr, "hammer ioctl: %s\n", strerror(errno)); + exit(1); + } + overs = version.cur_version; + version.cur_version = strtol(av[1], NULL, 0); + nvers = version.cur_version; if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0) { fprintf(stderr, "hammer ioctl: %s\n", strerror(errno)); @@ -127,7 +136,6 @@ hammer_cmd_set_version(char **av, int ac) " and requires the 'force' directive\n"); exit(1); } - if (ioctl(fd, HAMMERIOC_SET_VERSION, &version) < 0) { fprintf(stderr, "hammer version-upgrade ioctl: %s\n", strerror(errno)); @@ -138,6 +146,15 @@ hammer_cmd_set_version(char **av, int ac) strerror(version.head.error)); exit(1); } + printf("hammer version-upgrade: succeeded\n"); + if (overs < 3 && nvers >= 3) { + printf("NOTE! Please run hammer cleanup to convert the\n" + "/snapshots directory to the new meta-data\n" + "format. Once converted configuration data will\n" + "no longer resides in /snapshots and you can\n" + "even rm -rf it entirely if you want.\n"); + } + close(fd); } diff --git a/sbin/hammer/hammer.8 b/sbin/hammer/hammer.8 index 2b343e3250..2c6c627bf3 100644 --- a/sbin/hammer/hammer.8 +++ b/sbin/hammer/hammer.8 @@ -410,6 +410,23 @@ Work on this command is still in progress. Expected additions: An ability to remove snapshots dynamically as the file system becomes full. +.\" ==== config ==== +.It Cm config Ar filesystem Op configfile +(HAMMER VERSION 3+) +If one argument is specified this function dumps the current configuration +file to stdout. This configuration file is stored in filesystem meta-data. +If two arguments are specified this function installs a new config file. +.Pp +In +.Nm HAMMER +versions less then 3 the configuration file is stored in +.Pa /snapshots/config , +but in all later versions the configuration file is stored in filesystem +meta-data. +.It Cm viconfig Ar filesystem +(HAMMER VERSION 3+) +Edit the configuration file and reinstall into filesystem meta-data when +done. .\" ==== expand ==== .It Cm expand Ar filesystem Ar device This command will format @@ -435,6 +452,7 @@ together with any other device that make the filesystem, colon-separated to .Xr mount_hammer 8 . .\" ==== snapshot ==== .It Cm snapshot Oo Ar filesystem Oc Ar snapshot-dir +(HAMMER VERSIONS LESS THEN 3) Takes a snapshot of the file system either explicitly given by .Ar filesystem or implicitly derived from the @@ -480,6 +498,32 @@ would create symlinks similar to: /mysnapshots/obj-2008-06-27 -> /obj@@0x10d2cd05b7270d16 .Ed +.\" ==== snap* ==== +.It Cm snap Ar path Op "note" +(HAMMER VERSION 3+) +Create the named snapshot softlink. If the path specified is a +directory a standard snapshot softlink will be created in the directory. +The snapshot softlink points to the base of the mounted PFS. +.It Cm snaplo Ar path Op "note" +(HAMMER VERSION 3+) +Create the named snapshot softlink. If the path specified is a +directory a standard snapshot softlink will be created in the directory. +The snapshot softlink points into the directory it is contained in. +.It Cm snapq Ar dir Op "note" +(HAMMER VERSION 3+) +Create a snapshot for the PFS containing the specified directory but do +not create a softlink. Instead output a path which can be used to access +the directory via the snapshot. +.It Cm snaprm Op fs Ar path/transid +(HAMMER VERSION 3+) +Remove a snapshot given its softlink. If specifying a transaction id +the snapshot is removed from filesystem meta-data but you are responsible +for removing any related softlinks. +.It Cm snapls +(HAMMER VERSION 3+) +Dump the snapshot meta-data in the filesystem, listing all available +snapshots and their notes. This is the definitive list of snapshots +for the filesystem. .\" ==== prune ==== .It Cm prune Ar softlink-dir Prune the file system based on previously created snapshot softlinks. @@ -558,10 +602,19 @@ softlink and rerun the .Cm prune command, history for modifications pertaining to that snapshot would be destroyed. +.Pp +In +.Nm HAMMER +filesystem versions 3+ this command also scans the snapshots stored +in the filesystem meta-data and includes them in the prune. .\" ==== prune-everything ==== .It Cm prune-everything Ar filesystem This command will remove all historical records from the file system. This directive is not normally used on a production system. +.Pp +This command does not remove snapshot softlinks but will delete all +snapshots recorded in filesystem meta-data (for filesystem version 3+). +The user is responsible for deleting any softlinks. .\" ==== rebalance ==== .It Cm rebalance Ar filesystem Op Ar saturation_level This command will rebalance the B-tree, nodes with small number of diff --git a/sbin/hammer/hammer.c b/sbin/hammer/hammer.c index fa10812462..e02bbc86b5 100644 --- a/sbin/hammer/hammer.c +++ b/sbin/hammer/hammer.c @@ -290,6 +290,14 @@ main(int ac, char **av) hammer_cmd_softprune(av + 1, ac - 1, 0); exit(0); } + if (strcmp(av[0], "config") == 0) { + hammer_cmd_config(av + 1, ac - 1); + exit(0); + } + if (strcmp(av[0], "viconfig") == 0) { + hammer_cmd_viconfig(av + 1, ac - 1); + exit(0); + } if (strcmp(av[0], "cleanup") == 0) { hammer_cmd_cleanup(av + 1, ac - 1); exit(0); @@ -302,6 +310,26 @@ main(int ac, char **av) hammer_cmd_softprune(av + 1, ac - 1, 1); exit(0); } + if (strcmp(av[0], "snap") == 0) { + hammer_cmd_snap(av + 1, ac - 1, 0, 1); + exit(0); + } + if (strcmp(av[0], "snaplo") == 0) { + hammer_cmd_snap(av + 1, ac - 1, 0, 0); + exit(0); + } + if (strcmp(av[0], "snapq") == 0) { + hammer_cmd_snap(av + 1, ac - 1, 1, 0); + exit(0); + } + if (strcmp(av[0], "snapls") == 0) { + hammer_cmd_snapls(av + 1, ac - 1); + exit(0); + } + if (strcmp(av[0], "snaprm") == 0) { + hammer_cmd_snaprm(av + 1, ac - 1); + exit(0); + } if (strcmp(av[0], "snapshot") == 0) { hammer_cmd_snapshot(av + 1, ac - 1); exit(0); @@ -489,6 +517,21 @@ usage(int exit_code) "hammer version-upgrade [force]\n" "hammer expand \n" ); + + fprintf(stderr, "\nHAMMER utility version 3+ commands:\n\n"); + + fprintf(stderr, + "hammer config [ []]\n" + "hammer viconfig []\n" + "hammer snap [\"note\"]\t\tas above but points to base of PFS\n" + "hammer snaplo [\"note\"]\t\tcreate in dir or softlink to create\n" + " \t\t\tpoints to target directory\n" + "hammer snapq [\"note\"]\t\tsnapshot path is output to stdout\n" + "hammer snaprm *\t\t\tpaths to softlinks\n" + "hammer snaprm *\t\tspecific transaction ids\n" + "hammer snapls \t\t\tlist all hard snapshots in PFS\n" + ); + exit(exit_code); } diff --git a/sbin/hammer/hammer.h b/sbin/hammer/hammer.h index b0c6bdd870..e27c751c93 100644 --- a/sbin/hammer/hammer.h +++ b/sbin/hammer/hammer.h @@ -99,7 +99,12 @@ void hammer_cmd_pseudofs_upgrade(char **av, int ac); void hammer_cmd_pseudofs_downgrade(char **av, int ac); void hammer_cmd_status(char **av, int ac); void hammer_cmd_snapshot(char **av, int ac); +void hammer_cmd_snap(char **av, int ac, int tostdout, int fsbase); +void hammer_cmd_snapls(char **av, int ac); +void hammer_cmd_snaprm(char **av, int ac); void hammer_cmd_cleanup(char **av, int ac); +void hammer_cmd_config(char **av, int ac); +void hammer_cmd_viconfig(char **av, int ac); void hammer_cmd_info(void); void hammer_cmd_get_version(char **av, int ac); void hammer_cmd_set_version(char **av, int ac); diff --git a/sys/vfs/hammer/hammer.h b/sys/vfs/hammer/hammer.h index 6492eb2db9..65ca1f79fc 100644 --- a/sys/vfs/hammer/hammer.h +++ b/sys/vfs/hammer/hammer.h @@ -444,8 +444,11 @@ typedef struct hammer_record *hammer_record_t; #define HAMMER_RECF_DIRECT_INVAL 0x0800 /* buffer alias invalidation */ /* - * hammer_delete_at_cursor() flags + * hammer_create_at_cursor() and hammer_delete_at_cursor() flags. */ +#define HAMMER_CREATE_MODE_UMIRROR 0x0001 +#define HAMMER_CREATE_MODE_SYS 0x0002 + #define HAMMER_DELETE_ADJUST 0x0001 #define HAMMER_DELETE_DESTROY 0x0002 @@ -776,6 +779,7 @@ struct hammer_mount { struct hammer_lock free_lock; struct hammer_lock undo_lock; struct hammer_lock blkmap_lock; + struct hammer_lock snapshot_lock; struct hammer_blockmap blockmap[HAMMER_MAX_ZONES]; struct hammer_undo undos[HAMMER_MAX_UNDOS]; int undo_alloc; @@ -912,6 +916,8 @@ int hammer_ip_next(hammer_cursor_t cursor); int hammer_ip_resolve_data(hammer_cursor_t cursor); int hammer_ip_delete_record(hammer_cursor_t cursor, hammer_inode_t ip, hammer_tid_t tid); +int hammer_create_at_cursor(hammer_cursor_t cursor, + hammer_btree_leaf_elm_t leaf, void *udata, int mode); int hammer_delete_at_cursor(hammer_cursor_t cursor, int delete_flags, hammer_tid_t delete_tid, u_int32_t delete_ts, int track, int64_t *stat_bytes); @@ -1124,6 +1130,7 @@ void hammer_simple_transaction(struct hammer_transaction *trans, void hammer_start_transaction_fls(struct hammer_transaction *trans, struct hammer_mount *hmp); void hammer_done_transaction(struct hammer_transaction *trans); +hammer_tid_t hammer_alloc_tid(hammer_mount_t hmp, int count); void hammer_modify_inode(hammer_inode_t ip, int flags); void hammer_flush_inode(hammer_inode_t ip, int flags); diff --git a/sys/vfs/hammer/hammer_disk.h b/sys/vfs/hammer/hammer_disk.h index 262cbac48f..5a6f3d77e7 100644 --- a/sys/vfs/hammer/hammer_disk.h +++ b/sys/vfs/hammer/hammer_disk.h @@ -545,10 +545,12 @@ typedef struct hammer_volume_ondisk *hammer_volume_ondisk_t; #define HAMMER_VOL_VERSION_MIN 1 /* minimum supported version */ #define HAMMER_VOL_VERSION_DEFAULT 1 /* newfs default version */ #define HAMMER_VOL_VERSION_WIP 3 /* version >= this is WIP */ -#define HAMMER_VOL_VERSION_MAX 2 /* maximum supported version */ +#define HAMMER_VOL_VERSION_MAX 3 /* maximum supported version */ #define HAMMER_VOL_VERSION_ONE 1 #define HAMMER_VOL_VERSION_TWO 2 /* new dirent layout (2.3+) */ +#define HAMMER_VOL_VERSION_THREE 3 /* new snapshot layout (2.5+) */ + /* * Record types are fairly straightforward. The B-Tree includes the record * type in its index sort. @@ -564,6 +566,8 @@ typedef struct hammer_volume_ondisk *hammer_volume_ondisk_t; #define HAMMER_RECTYPE_EXT 0x0013 /* ext attributes */ #define HAMMER_RECTYPE_FIX 0x0014 /* fixed attribute */ #define HAMMER_RECTYPE_PFS 0x0015 /* PFS management */ +#define HAMMER_RECTYPE_SNAPSHOT 0x0016 /* Snapshot management */ +#define HAMMER_RECTYPE_CONFIG 0x0017 /* hammer cleanup config */ #define HAMMER_RECTYPE_MOVED 0x8000 /* special recovery flag */ #define HAMMER_RECTYPE_MAX 0xFFFF @@ -736,6 +740,36 @@ typedef struct hammer_pseudofs_data *hammer_pseudofs_data_t; #define HAMMER_PFSD_SLAVE 0x00000001 #define HAMMER_PFSD_DELETED 0x80000000 +/* + * Snapshot meta-data { Objid = HAMMER_OBJID_ROOT, Key = tid, rectype = SNAPSHOT }. + * + * Snapshot records replace the old /snapshots/ methodology. Snapshot + * records are mirrored but may be independantly managed once they are laid down on + * a slave. + * + * NOTE: The b-tree key is signed, the tid is not, so callers must still sort the + * results. + * + * NOTE: Reserved fields must be zero (as usual) + */ +struct hammer_snapshot_data { + hammer_tid_t tid; /* the snapshot TID itself (== key) */ + u_int64_t ts; /* real-time when snapshot was made */ + u_int64_t reserved01; + u_int64_t reserved02; + char label[64]; /* user-supplied description */ + u_int64_t reserved03[4]; +}; + +/* + * Config meta-data { ObjId = HAMMER_OBJID_ROOT, Key = 0, rectype = CONFIG }. + * + * Used to store the hammer cleanup config. This data is not mirrored. + */ +struct hammer_config_data { + char text[1024]; +}; + /* * Rollup various structures embedded as record data */ @@ -744,6 +778,8 @@ union hammer_data_ondisk { struct hammer_inode_data inode; struct hammer_symlink_data symlink; struct hammer_pseudofs_data pfsd; + struct hammer_snapshot_data snap; + struct hammer_config_data config; }; typedef union hammer_data_ondisk *hammer_data_ondisk_t; diff --git a/sys/vfs/hammer/hammer_ioctl.c b/sys/vfs/hammer/hammer_ioctl.c index b0979abfd7..5ed70bed99 100644 --- a/sys/vfs/hammer/hammer_ioctl.c +++ b/sys/vfs/hammer/hammer_ioctl.c @@ -48,8 +48,16 @@ static int hammer_ioc_set_version(hammer_transaction_t trans, struct hammer_ioc_version *ver); static int hammer_ioc_get_info(hammer_transaction_t trans, struct hammer_ioc_info *info); - - +static int hammer_ioc_add_snapshot(hammer_transaction_t trans, hammer_inode_t ip, + struct hammer_ioc_snapshot *snap); +static int hammer_ioc_del_snapshot(hammer_transaction_t trans, hammer_inode_t ip, + struct hammer_ioc_snapshot *snap); +static int hammer_ioc_get_snapshot(hammer_transaction_t trans, hammer_inode_t ip, + struct hammer_ioc_snapshot *snap); +static int hammer_ioc_get_config(hammer_transaction_t trans, hammer_inode_t ip, + struct hammer_ioc_config *snap); +static int hammer_ioc_set_config(hammer_transaction_t trans, hammer_inode_t ip, + struct hammer_ioc_config *snap); int hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag, @@ -157,7 +165,32 @@ hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag, (struct hammer_ioc_expand *)data); } break; - + case HAMMERIOC_ADD_SNAPSHOT: + if (error == 0) { + error = hammer_ioc_add_snapshot( + &trans, ip, (struct hammer_ioc_snapshot *)data); + } + break; + case HAMMERIOC_DEL_SNAPSHOT: + if (error == 0) { + error = hammer_ioc_del_snapshot( + &trans, ip, (struct hammer_ioc_snapshot *)data); + } + break; + case HAMMERIOC_GET_SNAPSHOT: + error = hammer_ioc_get_snapshot( + &trans, ip, (struct hammer_ioc_snapshot *)data); + break; + case HAMMERIOC_GET_CONFIG: + error = hammer_ioc_get_config( + &trans, ip, (struct hammer_ioc_config *)data); + break; + case HAMMERIOC_SET_CONFIG: + if (error == 0) { + error = hammer_ioc_set_config( + &trans, ip, (struct hammer_ioc_config *)data); + } + break; default: error = EOPNOTSUPP; break; @@ -458,11 +491,15 @@ hammer_ioc_get_version(hammer_transaction_t trans, hammer_inode_t ip, switch(ver->cur_version) { case 1: ksnprintf(ver->description, sizeof(ver->description), - "2.0 - First HAMMER release"); + "First HAMMER release (DragonFly 2.0+)"); break; case 2: ksnprintf(ver->description, sizeof(ver->description), - "2.3 - New directory entry layout"); + "New directory entry layout (DragonFly 2.3+)"); + break; + case 3: + ksnprintf(ver->description, sizeof(ver->description), + "New snapshot management (DragonFly 2.5+)"); break; default: ksnprintf(ver->description, sizeof(ver->description), @@ -545,3 +582,330 @@ hammer_ioc_get_info(hammer_transaction_t trans, struct hammer_ioc_info *info) { return 0; } +/* + * Add a snapshot transction id(s) to the list of snapshots. + * + * NOTE: Records are created with an allocated TID. If a flush cycle + * is in progress the record may be synced in the current flush + * cycle and the volume header will reflect the allocation of the + * TID, but the synchronization point may not catch up to the + * TID until the next flush cycle. + */ +static +int +hammer_ioc_add_snapshot(hammer_transaction_t trans, hammer_inode_t ip, + struct hammer_ioc_snapshot *snap) +{ + hammer_mount_t hmp = ip->hmp; + struct hammer_btree_leaf_elm leaf; + struct hammer_cursor cursor; + int error; + + /* + * Validate structure + */ + if (snap->count > HAMMER_SNAPS_PER_IOCTL) + return (EINVAL); + if (snap->index > snap->count) + return (EINVAL); + + hammer_lock_ex(&hmp->snapshot_lock); +again: + /* + * Look for keys starting after the previous iteration, or at + * the beginning if snap->count is 0. + */ + error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL); + if (error) { + hammer_done_cursor(&cursor); + return(error); + } + + cursor.asof = HAMMER_MAX_TID; + cursor.flags |= HAMMER_CURSOR_BACKEND | HAMMER_CURSOR_ASOF; + + bzero(&leaf, sizeof(leaf)); + leaf.base.obj_id = HAMMER_OBJID_ROOT; + leaf.base.rec_type = HAMMER_RECTYPE_SNAPSHOT; + leaf.base.create_tid = hammer_alloc_tid(hmp, 1); + leaf.base.btype = HAMMER_BTREE_TYPE_RECORD; + leaf.base.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE; + leaf.data_len = sizeof(struct hammer_snapshot_data); + + while (snap->index < snap->count) { + leaf.base.key = (int64_t)snap->snaps[snap->index].tid; + cursor.key_beg = leaf.base; + error = hammer_btree_lookup(&cursor); + if (error == 0) { + error = EEXIST; + break; + } + + cursor.flags &= ~HAMMER_CURSOR_ASOF; + error = hammer_create_at_cursor(&cursor, &leaf, + &snap->snaps[snap->index], + HAMMER_CREATE_MODE_SYS); + if (error == EDEADLK) { + hammer_done_cursor(&cursor); + goto again; + } + cursor.flags |= HAMMER_CURSOR_ASOF; + if (error) + break; + ++snap->index; + } + snap->head.error = error; + hammer_done_cursor(&cursor); + hammer_unlock(&hmp->snapshot_lock); + return(0); +} + +/* + * Delete snapshot transaction id(s) from the list of snapshots. + */ +static +int +hammer_ioc_del_snapshot(hammer_transaction_t trans, hammer_inode_t ip, + struct hammer_ioc_snapshot *snap) +{ + hammer_mount_t hmp = ip->hmp; + struct hammer_cursor cursor; + int error; + + /* + * Validate structure + */ + if (snap->count > HAMMER_SNAPS_PER_IOCTL) + return (EINVAL); + if (snap->index > snap->count) + return (EINVAL); + + hammer_lock_ex(&hmp->snapshot_lock); +again: + /* + * Look for keys starting after the previous iteration, or at + * the beginning if snap->count is 0. + */ + error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL); + if (error) { + hammer_done_cursor(&cursor); + return(error); + } + + cursor.key_beg.obj_id = HAMMER_OBJID_ROOT; + cursor.key_beg.create_tid = 0; + cursor.key_beg.delete_tid = 0; + cursor.key_beg.obj_type = 0; + cursor.key_beg.rec_type = HAMMER_RECTYPE_SNAPSHOT; + cursor.key_beg.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE; + cursor.asof = HAMMER_MAX_TID; + cursor.flags |= HAMMER_CURSOR_ASOF; + + while (snap->index < snap->count) { + cursor.key_beg.key = (int64_t)snap->snaps[snap->index].tid; + error = hammer_btree_lookup(&cursor); + if (error) + break; + error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF); + if (error) + break; + error = hammer_delete_at_cursor(&cursor, HAMMER_DELETE_DESTROY, + 0, 0, 0, NULL); + if (error == EDEADLK) { + hammer_done_cursor(&cursor); + goto again; + } + if (error) + break; + ++snap->index; + } + snap->head.error = error; + hammer_done_cursor(&cursor); + hammer_unlock(&hmp->snapshot_lock); + return(0); +} + +/* + * Retrieve as many snapshot ids as possible or until the array is + * full, starting after the last transction id passed in. If count + * is 0 we retrieve starting at the beginning. + * + * NOTE: Because the b-tree key field is signed but transaction ids + * are unsigned the returned list will be signed-sorted instead + * of unsigned sorted. The Caller must still sort the aggregate + * results. + */ +static +int +hammer_ioc_get_snapshot(hammer_transaction_t trans, hammer_inode_t ip, + struct hammer_ioc_snapshot *snap) +{ + struct hammer_cursor cursor; + int error; + + /* + * Validate structure + */ + if (snap->index != 0) + return (EINVAL); + if (snap->count > HAMMER_SNAPS_PER_IOCTL) + return (EINVAL); + + /* + * Look for keys starting after the previous iteration, or at + * the beginning if snap->count is 0. + */ + error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL); + if (error) { + hammer_done_cursor(&cursor); + return(error); + } + + cursor.key_beg.obj_id = HAMMER_OBJID_ROOT; + cursor.key_beg.create_tid = 0; + cursor.key_beg.delete_tid = 0; + cursor.key_beg.obj_type = 0; + cursor.key_beg.rec_type = HAMMER_RECTYPE_SNAPSHOT; + cursor.key_beg.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE; + if (snap->count == 0) + cursor.key_beg.key = HAMMER_MIN_KEY; + else + cursor.key_beg.key = (int64_t)snap->snaps[snap->count - 1].tid + 1; + + cursor.key_end = cursor.key_beg; + cursor.key_end.key = HAMMER_MAX_KEY; + cursor.asof = HAMMER_MAX_TID; + cursor.flags |= HAMMER_CURSOR_END_EXCLUSIVE | HAMMER_CURSOR_ASOF; + + snap->count = 0; + + error = hammer_btree_first(&cursor); + while (error == 0 && snap->count < HAMMER_SNAPS_PER_IOCTL) { + error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF); + if (error) + break; + if (cursor.leaf->base.rec_type == HAMMER_RECTYPE_SNAPSHOT) { + error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF | + HAMMER_CURSOR_GET_DATA); + snap->snaps[snap->count] = cursor.data->snap; + ++snap->count; + } + error = hammer_btree_iterate(&cursor); + } + + if (error == ENOENT) { + snap->head.flags |= HAMMER_IOC_SNAPSHOT_EOF; + error = 0; + } + snap->head.error = error; + hammer_done_cursor(&cursor); + return(0); +} + +/* + * Retrieve the PFS hammer cleanup utility config record. This is + * different (newer than) the PFS config. + */ +static +int +hammer_ioc_get_config(hammer_transaction_t trans, hammer_inode_t ip, + struct hammer_ioc_config *config) +{ + struct hammer_cursor cursor; + int error; + + error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL); + if (error) { + hammer_done_cursor(&cursor); + return(error); + } + + cursor.key_beg.obj_id = HAMMER_OBJID_ROOT; + cursor.key_beg.create_tid = 0; + cursor.key_beg.delete_tid = 0; + cursor.key_beg.obj_type = 0; + cursor.key_beg.rec_type = HAMMER_RECTYPE_CONFIG; + cursor.key_beg.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE; + cursor.key_beg.key = 0; /* config space page 0 */ + + cursor.asof = HAMMER_MAX_TID; + cursor.flags |= HAMMER_CURSOR_ASOF; + + error = hammer_btree_lookup(&cursor); + if (error == 0) { + error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF | + HAMMER_CURSOR_GET_DATA); + if (error == 0) + config->config = cursor.data->config; + } + /* error can be ENOENT */ + config->head.error = error; + hammer_done_cursor(&cursor); + return(0); +} + +/* + * Retrieve the PFS hammer cleanup utility config record. This is + * different (newer than) the PFS config. + * + * This is kinda a hack. + */ +static +int +hammer_ioc_set_config(hammer_transaction_t trans, hammer_inode_t ip, + struct hammer_ioc_config *config) +{ + struct hammer_btree_leaf_elm leaf; + struct hammer_cursor cursor; + hammer_mount_t hmp = ip->hmp; + int error; + +again: + error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL); + if (error) { + hammer_done_cursor(&cursor); + return(error); + } + + bzero(&leaf, sizeof(leaf)); + leaf.base.obj_id = HAMMER_OBJID_ROOT; + leaf.base.rec_type = HAMMER_RECTYPE_CONFIG; + leaf.base.create_tid = hammer_alloc_tid(hmp, 1); + leaf.base.btype = HAMMER_BTREE_TYPE_RECORD; + leaf.base.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE; + leaf.base.key = 0; /* page 0 */ + leaf.data_len = sizeof(struct hammer_config_data); + + cursor.key_beg = leaf.base; + + cursor.asof = HAMMER_MAX_TID; + cursor.flags |= HAMMER_CURSOR_BACKEND | HAMMER_CURSOR_ASOF; + + error = hammer_btree_lookup(&cursor); + if (error == 0) { + error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF | + HAMMER_CURSOR_GET_DATA); + error = hammer_delete_at_cursor(&cursor, HAMMER_DELETE_DESTROY, + 0, 0, 0, NULL); + if (error == EDEADLK) { + hammer_done_cursor(&cursor); + goto again; + } + } + if (error == ENOENT) + error = 0; + if (error == 0) { + cursor.flags &= ~HAMMER_CURSOR_ASOF; + cursor.key_beg = leaf.base; + error = hammer_create_at_cursor(&cursor, &leaf, + &config->config, + HAMMER_CREATE_MODE_SYS); + if (error == EDEADLK) { + hammer_done_cursor(&cursor); + goto again; + } + } + config->head.error = error; + hammer_done_cursor(&cursor); + return(0); +} diff --git a/sys/vfs/hammer/hammer_ioctl.h b/sys/vfs/hammer/hammer_ioctl.h index 770e61e211..f4ea80a17d 100644 --- a/sys/vfs/hammer/hammer_ioctl.h +++ b/sys/vfs/hammer/hammer_ioctl.h @@ -389,9 +389,52 @@ typedef union hammer_ioc_mrecord_any *hammer_ioc_mrecord_any_t; #define HAMMER_IOC_MIRROR_SIGNATURE_REV 0x7272d94dU /* - * Ioctl cmd ids + * HAMMERIOC_ADD_SNAPSHOT - Add snapshot tid(s). + * HAMMERIOC_DEL_SNAPSHOT - Delete snapshot tids. + * HAMMERIOC_GET_SNAPSHOT - Get/continue retrieving snapshot tids. + * (finds restart point based on last snaps[] entry) + * + * These are per-PFS operations. + * + * NOTE: There is no limit on the number of snapshots, but there is a limit + * on how many can be set or returned in each ioctl. + * + * NOTE: ADD and DEL start at snap->index. If an error occurs the index will + * point at the errored record. snap->index must be set to 0 for GET. + */ +#define HAMMER_SNAPS_PER_IOCTL 16 + +#define HAMMER_IOC_SNAPSHOT_EOF 0x0008 /* no more keys */ + +struct hammer_ioc_snapshot { + struct hammer_ioc_head head; + int unused01; + u_int32_t index; + u_int32_t count; + struct hammer_snapshot_data snaps[HAMMER_SNAPS_PER_IOCTL]; +}; + +/* + * HAMMERIOC_GET_CONFIG + * HAMMERIOC_SET_CONFIG + * + * The configuration space is a freeform nul-terminated string, typically + * a text file. It is per-PFS and used by the 'hammer cleanup' utility. + * + * The configuration space is NOT mirrored. mirror-write will ignore + * configuration space records. */ +struct hammer_ioc_config { + struct hammer_ioc_head head; + u_int32_t reserved01; + u_int32_t reserved02; + u_int64_t reserved03[4]; + struct hammer_config_data config; +}; +/* + * Ioctl cmd ids + */ #define HAMMERIOC_PRUNE _IOWR('h',1,struct hammer_ioc_prune) #define HAMMERIOC_GETHISTORY _IOWR('h',2,struct hammer_ioc_history) #define HAMMERIOC_REBLOCK _IOWR('h',3,struct hammer_ioc_reblock) @@ -409,6 +452,11 @@ typedef union hammer_ioc_mrecord_any *hammer_ioc_mrecord_any_t; #define HAMMERIOC_REBALANCE _IOWR('h',15,struct hammer_ioc_rebalance) #define HAMMERIOC_GET_INFO _IOR('h',16,struct hammer_ioc_info) #define HAMMERIOC_EXPAND _IOWR('h',17,struct hammer_ioc_expand) +#define HAMMERIOC_ADD_SNAPSHOT _IOWR('h',18,struct hammer_ioc_snapshot) +#define HAMMERIOC_DEL_SNAPSHOT _IOWR('h',19,struct hammer_ioc_snapshot) +#define HAMMERIOC_GET_SNAPSHOT _IOWR('h',20,struct hammer_ioc_snapshot) +#define HAMMERIOC_GET_CONFIG _IOWR('h',21,struct hammer_ioc_config) +#define HAMMERIOC_SET_CONFIG _IOWR('h',22,struct hammer_ioc_config) #endif diff --git a/sys/vfs/hammer/hammer_mirror.c b/sys/vfs/hammer/hammer_mirror.c index 84295070e3..574b74dcac 100644 --- a/sys/vfs/hammer/hammer_mirror.c +++ b/sys/vfs/hammer/hammer_mirror.c @@ -44,9 +44,6 @@ static int hammer_mirror_check(hammer_cursor_t cursor, struct hammer_ioc_mrecord_rec *mrec); static int hammer_mirror_update(hammer_cursor_t cursor, struct hammer_ioc_mrecord_rec *mrec); -static int hammer_mirror_write(hammer_cursor_t cursor, - struct hammer_ioc_mrecord_rec *mrec, - char *udata); static int hammer_ioc_mirror_write_rec(hammer_cursor_t cursor, struct hammer_ioc_mrecord_rec *mrec, struct hammer_ioc_mirror_rw *mirror, @@ -62,8 +59,7 @@ static int hammer_ioc_mirror_write_skip(hammer_cursor_t cursor, u_int32_t localization); static int hammer_mirror_delete_to(hammer_cursor_t cursor, struct hammer_ioc_mirror_rw *mirror); -static int hammer_mirror_localize_data(hammer_data_ondisk_t data, - hammer_btree_leaf_elm_t leaf); +static int hammer_mirror_nomirror(struct hammer_base_elm *base); /* * All B-Tree records within the specified key range which also conform @@ -596,6 +592,12 @@ hammer_ioc_mirror_write_rec(hammer_cursor_t cursor, cursor->flags |= HAMMER_CURSOR_BACKEND; error = hammer_mirror_delete_to(cursor, mirror); + /* + * Certain records are not part of the mirroring operation + */ + if (hammer_mirror_nomirror(&mrec->leaf.base)) + return(0); + /* * Locate the record. * @@ -619,10 +621,13 @@ hammer_ioc_mirror_write_rec(hammer_cursor_t cursor, if (error == 0 && hammer_mirror_check(cursor, mrec)) { error = hammer_mirror_update(cursor, mrec); } else if (error == ENOENT) { - if (mrec->leaf.base.create_tid >= mirror->tid_beg) - error = hammer_mirror_write(cursor, mrec, uptr); - else + if (mrec->leaf.base.create_tid >= mirror->tid_beg) { + error = hammer_create_at_cursor( + cursor, &mrec->leaf, + uptr, HAMMER_CREATE_MODE_UMIRROR); + } else { error = 0; + } } if (error == 0 || error == EALREADY) mirror->key_cur = mrec->leaf.base; @@ -671,6 +676,12 @@ hammer_ioc_mirror_write_pass(hammer_cursor_t cursor, cursor->flags |= HAMMER_CURSOR_BACKEND; error = hammer_mirror_delete_to(cursor, mirror); + /* + * Certain records are not part of the mirroring operation + */ + if (hammer_mirror_nomirror(&mrec->leaf.base)) + return(0); + /* * Locate the record and get past it by setting ATEDISK. Perform * any necessary deletions. We have no data payload and cannot @@ -727,6 +738,14 @@ hammer_mirror_delete_to(hammer_cursor_t cursor, KKASSERT(elm->base.btype == HAMMER_BTREE_TYPE_RECORD); cursor->flags |= HAMMER_CURSOR_ATEDISK; + /* + * Certain records are not part of the mirroring operation + */ + if (hammer_mirror_nomirror(&elm->base)) { + error = hammer_btree_iterate(cursor); + continue; + } + /* * Note: Must still delete records with create_tid < tid_beg, * as record may have been pruned-away on source. @@ -767,6 +786,26 @@ hammer_mirror_check(hammer_cursor_t cursor, struct hammer_ioc_mrecord_rec *mrec) return(0); } +/* + * Filter out records which are never mirrored, such as configuration space + * records (for hammer cleanup). + * + * NOTE: We currently allow HAMMER_RECTYPE_SNAPSHOT records to be mirrored. + */ +static +int +hammer_mirror_nomirror(struct hammer_base_elm *base) +{ + /* + * Certain types of records are never updated when mirroring. + * Slaves have their own configuration space. + */ + if (base->rec_type == HAMMER_RECTYPE_CONFIG) + return(1); + return(0); +} + + /* * Update a record in-place. Only the delete_tid can change, and * only from zero to non-zero. @@ -795,6 +834,14 @@ hammer_mirror_update(hammer_cursor_t cursor, return(error); } +#if 0 +/* + * MOVED TO HAMMER_OBJECT.C: hammer_create_at_cursor() + */ + +static int hammer_mirror_localize_data(hammer_data_ondisk_t data, + hammer_btree_leaf_elm_t leaf); + /* * Write out a new record. */ @@ -940,3 +987,4 @@ hammer_mirror_localize_data(hammer_data_ondisk_t data, return(0); } +#endif diff --git a/sys/vfs/hammer/hammer_object.c b/sys/vfs/hammer/hammer_object.c index 530713bd73..8ba2d534a7 100644 --- a/sys/vfs/hammer/hammer_object.c +++ b/sys/vfs/hammer/hammer_object.c @@ -43,7 +43,9 @@ static int hammer_frontend_trunc_callback(hammer_record_t record, static int hammer_bulk_scan_callback(hammer_record_t record, void *data); static int hammer_record_needs_overwrite_delete(hammer_record_t record); static int hammer_delete_general(hammer_cursor_t cursor, hammer_inode_t ip, - hammer_btree_leaf_elm_t leaf); + hammer_btree_leaf_elm_t leaf); +static int hammer_cursor_localize_data(hammer_data_ondisk_t data, + hammer_btree_leaf_elm_t leaf); struct rec_trunc_info { u_int16_t rec_type; @@ -2209,6 +2211,168 @@ hammer_ip_delete_record(hammer_cursor_t cursor, hammer_inode_t ip, return(error); } +/* + * Used to write a generic record w/optional data to the media b-tree + * when no inode context is available. Used by the mirroring and + * snapshot code. + * + * Caller must set cursor->key_beg to leaf->base. The cursor must be + * flagged for backend operation and not flagged ASOF (since we are + * doing an insertion). + * + * This function will acquire the appropriate sync lock and will set + * the cursor insertion flag for the operation, do the btree lookup, + * and the insertion, and clear the insertion flag and sync lock before + * returning. The cursor state will be such that the caller can continue + * scanning (used by the mirroring code). + * + * mode: HAMMER_CREATE_MODE_UMIRROR copyin data, check crc + * HAMMER_CREATE_MODE_SYS bcopy data, generate crc + * + * NOTE: EDEADLK can be returned. The caller must do deadlock handling and + * retry. + * + * EALREADY can be returned if the record already exists (WARNING, + * because ASOF cannot be used no check is made for illegal + * duplicates). + * + * NOTE: Do not use the function for normal inode-related records as this + * functions goes directly to the media and is not integrated with + * in-memory records. + */ +int +hammer_create_at_cursor(hammer_cursor_t cursor, hammer_btree_leaf_elm_t leaf, + void *udata, int mode) +{ + hammer_transaction_t trans; + hammer_buffer_t data_buffer; + hammer_off_t ndata_offset; + hammer_tid_t high_tid; + void *ndata; + int error; + int doprop; + + trans = cursor->trans; + data_buffer = NULL; + ndata_offset = 0; + doprop = 0; + + KKASSERT((cursor->flags & + (HAMMER_CURSOR_BACKEND | HAMMER_CURSOR_ASOF)) == + (HAMMER_CURSOR_BACKEND)); + + hammer_sync_lock_sh(trans); + + if (leaf->data_len) { + ndata = hammer_alloc_data(trans, leaf->data_len, + leaf->base.rec_type, + &ndata_offset, &data_buffer, + 0, &error); + if (ndata == NULL) { + hammer_sync_unlock(trans); + return (error); + } + leaf->data_offset = ndata_offset; + hammer_modify_buffer(trans, data_buffer, NULL, 0); + + switch(mode) { + case HAMMER_CREATE_MODE_UMIRROR: + error = copyin(udata, ndata, leaf->data_len); + if (error == 0) { + if (hammer_crc_test_leaf(ndata, leaf) == 0) { + kprintf("data crc mismatch on pipe\n"); + error = EINVAL; + } else { + error = hammer_cursor_localize_data( + ndata, leaf); + } + } + break; + case HAMMER_CREATE_MODE_SYS: + bcopy(udata, ndata, leaf->data_len); + error = 0; + hammer_crc_set_leaf(ndata, leaf); + break; + default: + panic("hammer: hammer_create_at_cursor: bad mode %d", + mode); + break; /* NOT REACHED */ + } + hammer_modify_buffer_done(data_buffer); + } else { + leaf->data_offset = 0; + error = 0; + ndata = NULL; + } + if (error) + goto failed; + + /* + * Do the insertion. This can fail with a EDEADLK or EALREADY + */ + cursor->flags |= HAMMER_CURSOR_INSERT; + error = hammer_btree_lookup(cursor); + if (error != ENOENT) { + if (error == 0) + error = EALREADY; + goto failed; + } + error = hammer_btree_insert(cursor, leaf, &doprop); + + /* + * Cursor is left on current element, we want to skip it now. + * (in case the caller is scanning) + */ + cursor->flags |= HAMMER_CURSOR_ATEDISK; + cursor->flags &= ~HAMMER_CURSOR_INSERT; + + /* + * If the insertion happens to be creating (and not just replacing) + * an inode we have to track it. + */ + if (error == 0 && + leaf->base.rec_type == HAMMER_RECTYPE_INODE && + leaf->base.delete_tid == 0) { + hammer_modify_volume_field(trans, trans->rootvol, + vol0_stat_inodes); + ++trans->hmp->rootvol->ondisk->vol0_stat_inodes; + hammer_modify_volume_done(trans->rootvol); + } + + /* + * vol0_next_tid must track the highest TID stored in the filesystem. + * We do not need to generate undo for this update. + */ + high_tid = leaf->base.create_tid; + if (high_tid < leaf->base.delete_tid) + high_tid = leaf->base.delete_tid; + if (trans->rootvol->ondisk->vol0_next_tid < high_tid) { + hammer_modify_volume(trans, trans->rootvol, NULL, 0); + trans->rootvol->ondisk->vol0_next_tid = high_tid; + hammer_modify_volume_done(trans->rootvol); + } + + /* + * WARNING! cursor's leaf pointer may have changed after + * do_propagation returns. + */ + if (error == 0 && doprop) + hammer_btree_do_propagation(cursor, NULL, leaf); + +failed: + /* + * Cleanup + */ + if (error && leaf->data_offset) { + hammer_blockmap_free(trans, leaf->data_offset, leaf->data_len); + + } + hammer_sync_unlock(trans); + if (data_buffer) + hammer_rel_buffer(data_buffer, 0); + return (error); +} + /* * Delete the B-Tree element at the current cursor and do any necessary * mirror propagation. @@ -2435,3 +2599,24 @@ hammer_ip_check_directory_empty(hammer_transaction_t trans, hammer_inode_t ip) return(error); } +/* + * Localize the data payload. Directory entries may need their + * localization adjusted. + */ +static +int +hammer_cursor_localize_data(hammer_data_ondisk_t data, + hammer_btree_leaf_elm_t leaf) +{ + u_int32_t localization; + + if (leaf->base.rec_type == HAMMER_RECTYPE_DIRENTRY) { + localization = leaf->base.localization & + HAMMER_LOCALIZE_PSEUDOFS_MASK; + if (data->entry.localization != localization) { + data->entry.localization = localization; + hammer_crc_set_leaf(data, leaf); + } + } + return(0); +} diff --git a/sys/vfs/hammer/hammer_ondisk.c b/sys/vfs/hammer/hammer_ondisk.c index 1cb9bd1b57..32fd7ee21d 100644 --- a/sys/vfs/hammer/hammer_ondisk.c +++ b/sys/vfs/hammer/hammer_ondisk.c @@ -1472,6 +1472,8 @@ hammer_alloc_data(hammer_transaction_t trans, int32_t data_len, case HAMMER_RECTYPE_EXT: case HAMMER_RECTYPE_FIX: case HAMMER_RECTYPE_PFS: + case HAMMER_RECTYPE_SNAPSHOT: + case HAMMER_RECTYPE_CONFIG: zone = HAMMER_ZONE_META_INDEX; break; case HAMMER_RECTYPE_DATA: diff --git a/sys/vfs/hammer/hammer_reblock.c b/sys/vfs/hammer/hammer_reblock.c index 23cdba87b5..5acae819df 100644 --- a/sys/vfs/hammer/hammer_reblock.c +++ b/sys/vfs/hammer/hammer_reblock.c @@ -260,10 +260,12 @@ hammer_reblock_helper(struct hammer_ioc_reblock *reblock, /* * NOTE: Localization restrictions may also have been set-up, we can't - * just set the match flags willy-nilly here. + * just set the match flags willy-nilly here. */ switch(elm->leaf.base.rec_type) { case HAMMER_RECTYPE_INODE: + case HAMMER_RECTYPE_SNAPSHOT: + case HAMMER_RECTYPE_CONFIG: iocflags = HAMMER_IOC_DO_INODES; break; case HAMMER_RECTYPE_EXT: diff --git a/sys/vfs/hammer/hammer_transaction.c b/sys/vfs/hammer/hammer_transaction.c index 33a6280cd3..a3fb181293 100644 --- a/sys/vfs/hammer/hammer_transaction.c +++ b/sys/vfs/hammer/hammer_transaction.c @@ -36,7 +36,6 @@ #include "hammer.h" -static hammer_tid_t hammer_alloc_tid(hammer_mount_t hmp, int count); static u_int32_t ocp_allocbit(hammer_objid_cache_t ocp, u_int32_t n); @@ -146,9 +145,13 @@ hammer_done_transaction(struct hammer_transaction *trans) * Directories may pre-allocate a large number of object ids (100,000). * * NOTE: There is no longer a requirement that successive transaction - * ids be 2 apart for separator generation. + * ids be 2 apart for separator generation. + * + * NOTE: When called by pseudo-backends such as ioctls the allocated + * TID will be larger then the current flush TID, if a flush is running, + * so any mirroring will pick the records up on a later flush. */ -static hammer_tid_t +hammer_tid_t hammer_alloc_tid(hammer_mount_t hmp, int count) { hammer_tid_t tid; diff --git a/sys/vfs/hammer/hammer_vfsops.c b/sys/vfs/hammer/hammer_vfsops.c index 0b6139e26c..55d16b9e83 100644 --- a/sys/vfs/hammer/hammer_vfsops.c +++ b/sys/vfs/hammer/hammer_vfsops.c @@ -404,6 +404,7 @@ hammer_vfs_mount(struct mount *mp, char *mntpt, caddr_t data, hmp->free_lock.refs = 1; hmp->undo_lock.refs = 1; hmp->blkmap_lock.refs = 1; + hmp->snapshot_lock.refs = 1; TAILQ_INIT(&hmp->delay_list); TAILQ_INIT(&hmp->flush_group_list);