From d4e847c61dc3ddfcfe15942bc3a8463da5ae8dca Mon Sep 17 00:00:00 2001 From: Tomohiro Kusumi Date: Fri, 23 Jan 2015 04:17:02 +0900 Subject: [PATCH RFC] sbin/hammer: add hammer volume-erase command This RFC patch adds a new hammer command "volume-erase". It erases the hammer volume signature (0xC8414D4DC5523031) or the entire header without using dd. It aims to do the following. Currently hammer doesn't allow you to volume-add a volume with the signature, even if the volume is not currently used (mounted). When adding a volume, ioctl reads the volume header sector and see if the signature exists. If it does exist then the ioctl tells you to erase it using dd on dmesg. This is right behavior as it protects users from possible operation error, however forcing users to manually do dd is too low level IMO. Filesystem should provide a way to do that with its userspace utility and it's not difficult to implement it with some safety measures. There isn't anything new in terms of implementation because hammer's userspace already has all the functionalities to do this. It simply finds existing hammer filesystems using getmntinfo(3) and then check each volume to make sure it's currently not used. If it's ok to erase it overwrites the signature or the entire header with 0 after 5 seconds of grace periods. Specifying "header" keyword makes this command erase whole volume header instead of just signature. Note that hammer volume-del command also erases the signature when removing a device from filesystem and it does that in kernel space. volume-erase command erases the signature by simply memset(3)ing 0 to ondisk structure of the volume header and then write(2) it because it's possible to do it that way with less code change (no need to add another ioctl like HAMMERIOC_ERASE_VOLUME only to expose hammer_clear_volume_header() functionality to userspace). Also note that this patch moves some code from sbin/hammer/cmd_pseudofs.c to sbin/hammer/misc.c as independent functions that are available from hammer userspace, instead of being static functions, as they are general enough for other hammer commands to use. volume-erase command uses them to do user interaction before erasing. --- sbin/hammer/cmd_pseudofs.c | 34 +----- sbin/hammer/cmd_volume.c | 229 +++++++++++++++++++++++++++++++++++++++++ sbin/hammer/hammer.8 | 33 +++++- sbin/hammer/hammer.c | 5 + sbin/hammer/hammer.h | 1 + sbin/hammer/hammer_util.h | 2 + sbin/hammer/misc.c | 32 ++++++ sys/vfs/hammer/hammer_volume.c | 3 +- 8 files changed, 306 insertions(+), 33 deletions(-) diff --git a/sbin/hammer/cmd_pseudofs.c b/sbin/hammer/cmd_pseudofs.c index 9c01d44..544af33 100644 --- a/sbin/hammer/cmd_pseudofs.c +++ b/sbin/hammer/cmd_pseudofs.c @@ -40,7 +40,6 @@ static void parse_pfsd_options(char **av, int ac, hammer_pseudofs_data_t pfsd); static void init_pfsd(hammer_pseudofs_data_t pfsd, int is_slave); static void pseudofs_usage(int code); static char *strtrl(char **path, int len); -static int getyn(void); static int timetosecs(char *str); /* @@ -297,7 +296,6 @@ hammer_cmd_pseudofs_destroy(char **av, int ac) struct hammer_ioc_pseudofs_rw pfs; struct stat st; int fd; - int i; if (ac == 0) pseudofs_usage(1); @@ -313,7 +311,7 @@ hammer_cmd_pseudofs_destroy(char **av, int ac) printf("This will irrevocably destroy all data on this PFS!!!!!\n"); printf("Do you really want to do this? "); fflush(stdout); - if (getyn() == 0) { + if (get_yes() == 0) { fprintf(stderr, "No action taken on PFS#%d\n", pfs.pfs_id); exit(1); } @@ -322,7 +320,7 @@ hammer_cmd_pseudofs_destroy(char **av, int ac) printf("This PFS is currently setup as a MASTER!\n"); printf("Are you absolutely sure you want to destroy it? "); fflush(stdout); - if (getyn() == 0) { + if (get_yes() == 0) { fprintf(stderr, "No action taken on PFS#%d\n", pfs.pfs_id); exit(1); @@ -330,11 +328,7 @@ hammer_cmd_pseudofs_destroy(char **av, int ac) } printf("Destroying PFS #%d (%s) in ", pfs.pfs_id, pfs.ondisk->label); - for (i = 5; i; --i) { - printf(" %d", i); - fflush(stdout); - sleep(1); - } + wait_grace_period(5); printf(".. starting destruction pass\n"); fflush(stdout); @@ -659,28 +653,6 @@ pseudofs_usage(int code) exit(code); } -static -int -getyn(void) -{ - char buf[256]; - int len; - - if (fgets(buf, sizeof(buf), stdin) == NULL) - return(0); - len = strlen(buf); - while (len && (buf[len-1] == '\n' || buf[len-1] == '\r')) - --len; - buf[len] = 0; - if (strcmp(buf, "y") == 0 || - strcmp(buf, "yes") == 0 || - strcmp(buf, "Y") == 0 || - strcmp(buf, "YES") == 0) { - return(1); - } - return(0); -} - /* * Convert time in the form [Nd/]hh[:mm[:ss]] to seconds. * diff --git a/sbin/hammer/cmd_volume.c b/sbin/hammer/cmd_volume.c index f184469..fb637cc 100644 --- a/sbin/hammer/cmd_volume.c +++ b/sbin/hammer/cmd_volume.c @@ -38,6 +38,7 @@ * * - volume-add: Add new volume to HAMMER filesystem * - volume-del: Remove volume from HAMMER filesystem + * - volume-erase: Erase signature or header from HAMMER filesystem volume * - volume-list: List volumes making up a HAMMER filesystem */ @@ -45,6 +46,9 @@ #include #include +static void erase_volume(struct volume_info *vol, int full); +static void dump_volume(struct volume_info *vol, int full); +static int find_volume(const char *vol_name, const char *mnt_name); static uint64_t check_volume(const char *vol_name); /* @@ -130,6 +134,160 @@ hammer_cmd_volume_del(char **av, int ac) } /* + * volume-erase [signature|header] + */ +void +hammer_cmd_volume_erase(char **av, int ac) +{ + int i, mntsize, hdr, ret = 0; + char *device; + const char *what; + struct statfs *stfsbuf; + struct volume_info *vol; + + if (ac == 0 || ac > 2) { + fprintf(stderr, + "hammer volume-erase [signature|header]\n"); + exit(1); + } + + device = av[0]; + if (ac == 1) + hdr = 0; /* signature only */ + else if (strcmp(av[1], "signature") == 0) + hdr = 0; + else if (strcmp(av[1], "header") == 0) + hdr = 1; + else { + fprintf(stderr, + "hammer volume-erase: Unknown keyword %s\n", av[1]); + exit(1); + } + what = hdr ? "header" : "signature"; + + /* + * Check if it's currently mounted as HAMMER filesystem + */ + mntsize = getmntinfo(&stfsbuf, MNT_NOWAIT); + for (i = 0; i < mntsize; i++) { + struct statfs *stfs = &stfsbuf[i]; + if (strcmp(stfs->f_fstypename, "hammer") == 0) { + int result = find_volume(device, stfs->f_mntonname); + if (result == 0) { + fprintf(stderr, + "hammer volume-erase: %s is used by %s\n", + device, stfs->f_mntonname); + exit(1); /* existing filesystem */ + } else if (result < 0) { + exit(1); /* error */ + } + } + } + + /* + * Setup volume with HAMMER_MAX_VOLUMES as we don't know id + */ + vol = setup_volume(HAMMER_MAX_VOLUMES, device, 0, O_RDWR); + assert(vol && vol->ondisk); + + if (vol->ondisk->vol_no < 0 || + vol->ondisk->vol_no >= HAMMER_MAX_VOLUMES) { + fprintf(stderr, "hammer volume-erase: %s has invalid id %d\n", + device, vol->ondisk->vol_no); + ret = 1; + goto fail_free_volume; + } + + vol = get_volume(vol->ondisk->vol_no); + assert(vol && vol->ondisk); + if (DebugOpt) + dump_volume(vol, hdr); + + /* + * Assume little endian (x86_64) + */ + if (vol->ondisk->vol_signature != HAMMER_FSBUF_VOLUME) { + fprintf(stderr, "hammer volume-erase: %s is already erased\n", + device); + ret = 1; + goto fail_rel_volume; + } + + /* + * Taken from sbin/hammer/cmd_pseudofs.c + */ + printf("You have requested that volume %s of %s be erased\n", + what, device); + printf("Do you really want to do this? "); + fflush(stdout); + if (get_yes() == 0) { + fprintf(stderr, "No action taken\n"); + ret = 1; + goto fail_rel_volume; + } + + printf("Erasing volume %s of %s", what, device); + wait_grace_period(5); + printf("\n"); + + erase_volume(vol, hdr); + printf("Erased\n"); + if (DebugOpt) + dump_volume(vol, hdr); + +fail_rel_volume: + rel_volume(vol); + +fail_free_volume: + if (vol->name) + free(vol->name); + if (vol->ondisk) + free(vol->ondisk); + if (vol) + free(vol); + if (ret) + exit(ret); +} + +static void +erase_volume(struct volume_info *vol, int full) +{ + int n; + char *p; + + if (full) { + p = (char *)vol->ondisk; + n = (int)sizeof(struct hammer_volume_ondisk); + } else { + p = (char *)&vol->ondisk->vol_signature; + n = (int)sizeof(u_int64_t); + } + + memset(p, 0, n); + vol->cache.modified = 1; + flush_volume(vol); +} + +static void +dump_volume(struct volume_info *vol, int full) +{ + int i, n; + char *p; + + if (full) { + p = (char *)vol->ondisk; + n = (int)sizeof(struct hammer_volume_ondisk); + } else { + p = (char *)&vol->ondisk->vol_signature; + n = (int)sizeof(u_int64_t); + } + + for (i = 0; i < n; i++) + printf("%02X ", p[i] & 0xFF); + printf("\n"); +} + +/* * volume-list */ void @@ -225,3 +383,74 @@ check_volume(const char *vol_name) close(fd); return pinfo.media_size; } + +/* + * Check if vol_name is a part of currently mounted HAMMER filesystem + * + * Returns either of the followings: + * 0 - vol_name found in mnt_name + * 1 - vol_name NOT found in mnt_name + * < 0 - error + */ +static int +find_volume(const char *vol_name, const char *mnt_name) +{ + int i, fd = -1, ret = -1; + struct stat st; + struct hammer_ioc_volume_list ioc; + + fd = open(mnt_name, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Unable to access %s: %s\n", + mnt_name, strerror(errno)); + ret = -1; + goto end; + } + + /* + * volume-list ioctl + */ + bzero(&ioc, sizeof(ioc)); + ioc.vols = malloc(HAMMER_MAX_VOLUMES * + sizeof(struct hammer_ioc_volume)); + if (ioc.vols == NULL) { + fprintf(stderr, "Unable to allocate memory: %s\n", + strerror(errno)); + ret = -1; + goto end; + } + ioc.nvols = HAMMER_MAX_VOLUMES; + + if ((ret = ioctl(fd, HAMMERIOC_LIST_VOLUMES, &ioc)) < 0) { + fprintf(stderr, "ioctl: %s\n", strerror(errno)); + goto end; + } + + if ((ret = stat(vol_name, &st)) < 0) { + fprintf(stderr, "Unable to stat %s: %s\n", + vol_name, strerror(errno)); + goto end; + } + + for (i = 0; i < ioc.nvols; i++) { + char *path = ioc.vols[i].device_name; + struct stat _st; + if ((ret = stat(path, &_st)) < 0) { + fprintf(stderr, "Unable to stat %s: %s\n", + path, strerror(errno)); + goto end; + } + if ((st.st_dev == _st.st_dev) && (st.st_ino == _st.st_ino)) { + ret = 0; /* found */ + goto end; + } + } + + ret = 1; /* not found */ +end: + if (ioc.vols) + free(ioc.vols); + if (fd > -1) + close(fd); + return ret; +} diff --git a/sbin/hammer/hammer.8 b/sbin/hammer/hammer.8 index 59aef5a..6dc1cfa 100644 --- a/sbin/hammer/hammer.8 +++ b/sbin/hammer/hammer.8 @@ -693,7 +693,9 @@ contains a valid file system, formatting will be denied. You can overcome this sanity check by using .Xr dd 1 -to erase the beginning sectors of the device. +to erase the beginning sectors of the device or by using +.Nm volume-erase +command. .Pp Remember that you have to specify .Ar device , @@ -735,6 +737,35 @@ in .Pa /boot/loader.conf , see .Xr loader 8 . +.\" ==== volume-erase ==== +.It Cm volume-erase Ar device [signature|header] +Erase +.Nm HAMMER +filesystem signature or the entire volume header from +the volume +.Ar device . +.Pp +This command is safer than using +.Xr dd 1 +as it has some safety measures. This command overwrites +.Nm HAMMER +filesystem signature or the entire volume header with 0 +after making sure you are going to erase the volume +.Ar device +.Pp +If the +.Nm header +keyword is specified this command erases the entire volume +header instead of just signature. If the +.Nm signature +keyword is specified this command only erases signature. Using +no keyword works the same as specifying +.Nm signature +keyword. +.Pp +As safety measure the +.Fl y +flag have no effect on this directive. .\" ==== volume-list ==== .It Cm volume-list Ar filesystem List the volumes that make up diff --git a/sbin/hammer/hammer.c b/sbin/hammer/hammer.c index 48bb9db..629267c 100644 --- a/sbin/hammer/hammer.c +++ b/sbin/hammer/hammer.c @@ -509,6 +509,10 @@ main(int ac, char **av) hammer_cmd_volume_del(av + 1, ac - 1); exit(0); } + if (strcmp(av[0], "volume-erase") == 0) { + hammer_cmd_volume_erase(av + 1, ac - 1); + exit(0); + } if (strcmp(av[0], "volume-list") == 0) { hammer_cmd_volume_list(av + 1, ac - 1); exit(0); @@ -651,6 +655,7 @@ usage(int exit_code) "hammer version-upgrade [force]\n" "hammer volume-add \n" "hammer volume-del \n" + "hammer volume-erase [signature|header]\n" "hammer volume-list \n" ); diff --git a/sbin/hammer/hammer.h b/sbin/hammer/hammer.h index d66fd4b..f041fd9 100644 --- a/sbin/hammer/hammer.h +++ b/sbin/hammer/hammer.h @@ -129,6 +129,7 @@ void hammer_cmd_get_version(char **av, int ac); void hammer_cmd_set_version(char **av, int ac); void hammer_cmd_volume_add(char **av, int ac); void hammer_cmd_volume_del(char **av, int ac); +void hammer_cmd_volume_erase(char **av, int ac); void hammer_cmd_volume_list(char **av, int ac); void hammer_cmd_dedup_simulate(char **av, int ac); void hammer_cmd_dedup(char **av, int ac); diff --git a/sbin/hammer/hammer_util.h b/sbin/hammer/hammer_util.h index c3987c3..e14c5db 100644 --- a/sbin/hammer/hammer_util.h +++ b/sbin/hammer/hammer_util.h @@ -163,3 +163,5 @@ void score_printf(size_t i, size_t w, const char *ctl, ...) __printflike(3, 4); void panic(const char *ctl, ...) __printflike(1, 2); +int get_yes(void); +void wait_grace_period(int); diff --git a/sbin/hammer/misc.c b/sbin/hammer/misc.c index 414c281..2f5db28 100644 --- a/sbin/hammer/misc.c +++ b/sbin/hammer/misc.c @@ -196,3 +196,35 @@ hammer_check_restrict(const char *filesystem) ++rlen; } } + +int +get_yes(void) +{ + char buf[256]; + int len; + + if (fgets(buf, sizeof(buf), stdin) == NULL) + return(0); + len = strlen(buf); + while (len && (buf[len-1] == '\n' || buf[len-1] == '\r')) + --len; + buf[len] = 0; + if (strcmp(buf, "y") == 0 || + strcmp(buf, "yes") == 0 || + strcmp(buf, "Y") == 0 || + strcmp(buf, "YES") == 0) { + return(1); + } + return(0); +} + +void +wait_grace_period(int count) +{ + int i; + for (i = count; i; --i) { + printf(" %d", i); + fflush(stdout); + sleep(1); + } +} diff --git a/sys/vfs/hammer/hammer_volume.c b/sys/vfs/hammer/hammer_volume.c index dbd750b..3d7385f 100644 --- a/sys/vfs/hammer/hammer_volume.c +++ b/sys/vfs/hammer/hammer_volume.c @@ -827,7 +827,8 @@ hammer_format_volume_header(struct hammer_mount *hmp, struct vnode *devvp, */ if (ondisk->vol_signature == HAMMER_FSBUF_VOLUME) { kprintf("hammer_volume_add: Formatting of valid HAMMER volume " - "%s denied. Erase with dd!\n", vol_name); + "%s denied. Erase with dd or hammer volume-erase!\n", + vol_name); error = EFTYPE; goto late_failure; } -- 2.1.2