HAMMER - Add version 3 meta-data features
authorMatthew Dillon <dillon@apollo.backplane.com>
Wed, 14 Oct 2009 18:44:59 +0000 (11:44 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Wed, 14 Oct 2009 18:44:59 +0000 (11:44 -0700)
* 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.

19 files changed:
sbin/hammer/Makefile
sbin/hammer/cmd_cleanup.c
sbin/hammer/cmd_config.c [new file with mode: 0644]
sbin/hammer/cmd_snapshot.c
sbin/hammer/cmd_softprune.c
sbin/hammer/cmd_version.c
sbin/hammer/hammer.8
sbin/hammer/hammer.c
sbin/hammer/hammer.h
sys/vfs/hammer/hammer.h
sys/vfs/hammer/hammer_disk.h
sys/vfs/hammer/hammer_ioctl.c
sys/vfs/hammer/hammer_ioctl.h
sys/vfs/hammer/hammer_mirror.c
sys/vfs/hammer/hammer_object.c
sys/vfs/hammer/hammer_ondisk.c
sys/vfs/hammer/hammer_reblock.c
sys/vfs/hammer/hammer_transaction.c
sys/vfs/hammer/hammer_vfsops.c

index 32ae468..ee17982 100644 (file)
@@ -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
index 48b2bca..842f545 100644 (file)
@@ -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 <fs>/var/slaves/<name> where "
                       "<fs> 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 (file)
index 0000000..c87d0e9
--- /dev/null
@@ -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 <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);
+}
index f43b13a..1fe9fd4 100644 (file)
 #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>
@@ -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);
@@ -161,9 +373,173 @@ hammer_cmd_snapshot(char **av, int ac)
 
 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);
+}
index 91a16f2..4e63f58 100644 (file)
@@ -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);
 
 /*
@@ -156,6 +161,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
                 */
                hammer_softprune_finalize(scan);
@@ -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,
@@ -269,6 +290,84 @@ 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.
  */
@@ -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);
 }
 
 /*
index eab08db..ff9a11e 100644 (file)
@@ -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"
+                      "<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);
 }
 
index 2b343e3..2c6c627 100644 (file)
@@ -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 <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
@@ -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
index fa10812..e02bbc8 100644 (file)
@@ -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 <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);
 }
 
index b0c6bdd..e27c751 100644 (file)
@@ -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);
index 6492eb2..65ca1f7 100644 (file)
@@ -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);
index 262cbac..5a6f3d7 100644 (file)
@@ -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
 
@@ -737,6 +741,36 @@ typedef struct hammer_pseudofs_data *hammer_pseudofs_data_t;
 #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 {
@@ -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;
index b0979ab..5ed70be 100644 (file)
@@ -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);
+}
index 770e61e..f4ea80a 100644 (file)
@@ -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
 
index 8429507..574b74d 100644 (file)
@@ -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
@@ -597,6 +593,12 @@ hammer_ioc_mirror_write_rec(hammer_cursor_t cursor,
        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.
@@ -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;
@@ -672,6 +677,12 @@ hammer_ioc_mirror_write_pass(hammer_cursor_t cursor,
        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.
@@ -728,6 +739,14 @@ hammer_mirror_delete_to(hammer_cursor_t cursor,
                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.
                 */
@@ -768,6 +787,26 @@ hammer_mirror_check(hammer_cursor_t cursor, struct hammer_ioc_mrecord_rec *mrec)
 }
 
 /*
+ * 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
index 530713b..8ba2d53 100644 (file)
@@ -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;
@@ -2210,6 +2212,168 @@ hammer_ip_delete_record(hammer_cursor_t cursor, hammer_inode_t ip,
 }
 
 /*
+ * 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);
+}
index 1cb9bd1..32fd7ee 100644 (file)
@@ -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:
index 23cdba8..5acae81 100644 (file)
@@ -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:
index 33a6280..a3fb181 100644 (file)
@@ -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;
index 0b6139e..55d16b9 100644 (file)
@@ -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);