* 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
<fs>/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 <fs>/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 <fs>/snapshots/config
to the new meta-data config and deletes <fs>/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.
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
};
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, ...);
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;
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;
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.
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;
}
}
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 <fs>/var/slaves/<name> where "
"<fs> is the base HAMMER fs\n");
printf("\tcontaining the slave\n");
+ close(fd);
return;
} else {
asprintf(&snapshots_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;
}
/*
* 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;
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");
} 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");
}
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)
* 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;
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] == '.')
}
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;
}
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;
+ }
+ }
}
/*
* 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);
}
--- /dev/null
+/*
+ * Copyright (c) 2009 The DragonFly Project. All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ *
+ * 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 <sys/param.h>
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+
+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 [<fs> [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 [<fs>]
+ */
+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 (<fs>) 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);
+}
#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 <softlink-dir-in-filesystem>
softlink_dir = av[1];
} else {
snapshot_usage(1);
+ /* not reached */
+ softlink_dir = NULL;
+ filesystem = NULL;
}
if (stat(softlink_dir, &st) == 0) {
*/
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)
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);
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 <snapshot-dir-in-filesystem>\n");
- fprintf(stderr, "hammer snapshot <filesystem> <snapshot-dir>\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 [<fs>]\t\t- list available snapshots.\n"
+ "hammer snaprm [<fs>] [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 [<filesystem>] <snapshot-dir>' 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);
+}
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);
/*
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
*/
hammer_softprune_finalize(scan);
/*
* 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,
}
/*
+ * 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.
*/
struct stat *st,
const char *linkbuf, const char *tidptr)
{
- struct hammer_ioc_prune_elm *elm;
struct softprune *scan;
struct statfs fs;
char *fspath;
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,
* 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);
}
/*
{
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,
}
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));
" 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));
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"
+ "<fs>/snapshots directory to the new meta-data\n"
+ "format. Once converted configuration data will\n"
+ "no longer resides in <fs>/snapshots and you can\n"
+ "even rm -rf it entirely if you want.\n");
+ }
+
close(fd);
}
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 <fs>/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
.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
/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.
.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
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);
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);
"hammer version-upgrade <filesystem> <version> [force]\n"
"hammer expand <filesystem> <device>\n"
);
+
+ fprintf(stderr, "\nHAMMER utility version 3+ commands:\n\n");
+
+ fprintf(stderr,
+ "hammer config [<filesystem> [<configfile>]]\n"
+ "hammer viconfig [<filesystem>]\n"
+ "hammer snap <path> [\"note\"]\t\tas above but points to base of PFS\n"
+ "hammer snaplo <path> [\"note\"]\t\tcreate in dir or softlink to create\n"
+ " \t\t\tpoints to target directory\n"
+ "hammer snapq <dir> [\"note\"]\t\tsnapshot path is output to stdout\n"
+ "hammer snaprm <path>*\t\t\tpaths to softlinks\n"
+ "hammer snaprm <fs> <transid>*\t\tspecific transaction ids\n"
+ "hammer snapls <fs>\t\t\tlist all hard snapshots in PFS\n"
+ );
+
exit(exit_code);
}
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);
#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
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;
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);
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);
#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.
#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
#define HAMMER_PFSD_DELETED 0x80000000
/*
+ * Snapshot meta-data { Objid = HAMMER_OBJID_ROOT, Key = tid, rectype = SNAPSHOT }.
+ *
+ * Snapshot records replace the old <fs>/snapshots/<softlink> 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
*/
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;
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,
(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;
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),
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);
+}
#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)
#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
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,
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
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.
*
* If the record exists only the delete_tid may be updated.
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;
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
* create a new 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.
*/
}
/*
+ * 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.
*/
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.
*/
return(0);
}
+#endif
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;
}
/*
+ * 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.
*
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);
+}
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:
/*
* 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:
#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);
* 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;
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);