2 * Copyright (c) 2008 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
17 * 3. Neither the name of The DragonFly Project nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific, prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * $DragonFly: src/sbin/hammer/cmd_cleanup.c,v 1.2 2008/09/20 06:46:22 dillon Exp $
37 * Clean up a specific HAMMER filesystem or all HAMMER filesystems.
39 * Each filesystem is expected to have a <mount>/snapshots directory.
40 * No cleanup will be performed on any filesystem that does not. If
41 * no filesystems are specified the 'df' program is run and any HAMMER
42 * or null-mounted hammer PFS's are extracted.
44 * The snapshots directory may contain a config file called 'config'. If
45 * no config file is present one will be created with the following
48 * snapshots 1d 60d (0d 60d for /tmp, /var/tmp, /usr/obj)
53 * All hammer commands create and maintain cycle files in the snapshots
59 static void do_cleanup(const char *path);
60 static int strtosecs(char *ptr);
61 static const char *dividing_slash(const char *path);
62 static int check_period(const char *snapshots_path, const char *cmd, int arg1,
64 static void save_period(const char *snapshots_path, const char *cmd,
66 static int check_softlinks(const char *snapshots_path);
68 static int cleanup_snapshots(const char *path, const char *snapshots_path,
70 static int cleanup_prune(const char *path, const char *snapshots_path,
71 int arg1, int arg2, int snapshots_disabled);
72 static int cleanup_reblock(const char *path, const char *snapshots_path,
74 static int cleanup_recopy(const char *path, const char *snapshots_path,
77 static void runcmd(int *resp, const char *ctl, ...);
81 #define DIDBITS (sizeof(int) * 8)
83 static int DidPFS[MAXPFS/DIDBITS];
86 hammer_cmd_cleanup(char **av, int ac)
95 fp = popen("df -t hammer,null", "r");
97 errx(1, "hammer cleanup: 'df' failed");
98 while (fgets(buf, sizeof(buf), fp) != NULL) {
99 ptr = strtok(buf, WS);
100 if (ptr && strcmp(ptr, "Filesystem") == 0)
103 ptr = strtok(NULL, WS);
105 ptr = strtok(NULL, WS);
107 ptr = strtok(NULL, WS);
109 ptr = strtok(NULL, WS);
111 path = strtok(NULL, WS);
128 do_cleanup(const char *path)
130 struct hammer_ioc_pseudofs_rw pfs;
131 union hammer_ioc_mrecord_any mrec_tmp;
132 char *snapshots_path;
142 int snapshots_disabled = 0;
143 int prune_warning = 0;
147 bzero(&pfs, sizeof(pfs));
148 bzero(&mrec_tmp, sizeof(mrec_tmp));
149 pfs.ondisk = &mrec_tmp.pfs.pfsd;
150 pfs.bytes = sizeof(mrec_tmp.pfs.pfsd);
153 printf("cleanup %-20s -", path);
154 fd = open(path, O_RDONLY);
156 printf(" unable to access directory: %s\n", strerror(errno));
159 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
160 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
164 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
165 printf(" unrecognized HAMMER version\n");
170 * Make sure we have not already handled this PFS. Several nullfs
171 * mounts might alias the same PFS.
173 if (pfs.pfs_id < 0 || pfs.pfs_id >= MAXPFS) {
174 printf(" pfs_id %d illegal\n", pfs.pfs_id);
178 if (DidPFS[pfs.pfs_id / DIDBITS] & (1 << (pfs.pfs_id % DIDBITS))) {
179 printf(" pfs_id %d already handled\n", pfs.pfs_id);
182 DidPFS[pfs.pfs_id / DIDBITS] |= (1 << (pfs.pfs_id % DIDBITS));
185 * Create a snapshot directory if necessary, and a config file if
188 asprintf(&snapshots_path, "%s%ssnapshots", path, dividing_slash(path));
189 if (stat(snapshots_path, &st) < 0) {
190 if (mkdir(snapshots_path, 0755) != 0) {
191 free(snapshots_path);
192 printf(" unable to create snapshot dir: %s\n",
197 asprintf(&config_path, "%s/config", snapshots_path);
198 if ((fp = fopen(config_path, "r")) == NULL) {
199 fp = fopen(config_path, "w");
201 printf(" cannot create %s: %s\n",
202 config_path, strerror(errno));
205 if (strcmp(path, "/tmp") == 0 ||
206 strcmp(path, "/var/tmp") == 0 ||
207 strcmp(path, "/usr/obj") == 0) {
208 fprintf(fp, "snapshots 0d 60d\n");
210 fprintf(fp, "snapshots 1d 60d\n");
217 fp = fopen(config_path, "r");
220 printf(" cannot access %s: %s\n",
221 config_path, strerror(errno));
225 printf(" processing PFS #%d\n", pfs.pfs_id);
228 * Process the config file
230 while (fgets(buf, sizeof(buf), fp) != NULL) {
231 cmd = strtok(buf, WS);
234 if ((ptr = strtok(NULL, WS)) != NULL) {
235 arg1 = strtosecs(ptr);
236 if ((ptr = strtok(NULL, WS)) != NULL)
237 arg2 = strtosecs(ptr);
240 printf("%20s - ", cmd);
244 printf("disabled\n");
245 if (strcmp(cmd, "snapshots") == 0) {
246 if (check_softlinks(snapshots_path))
249 snapshots_disabled = 1;
255 if (strcmp(cmd, "snapshots") == 0) {
256 if (check_period(snapshots_path, cmd, arg1, &savet)) {
258 r = cleanup_snapshots(path, snapshots_path,
263 } else if (strcmp(cmd, "prune") == 0) {
264 if (check_period(snapshots_path, cmd, arg1, &savet)) {
266 printf("run - WARNING snapshot softlinks present but snapshots disabled\n");
269 r = cleanup_prune(path, snapshots_path,
270 arg1, arg2, snapshots_disabled);
274 } else if (strcmp(cmd, "reblock") == 0) {
275 if (check_period(snapshots_path, cmd, arg1, &savet)) {
280 r = cleanup_reblock(path, snapshots_path,
285 } else if (strcmp(cmd, "recopy") == 0) {
286 if (check_period(snapshots_path, cmd, arg1, &savet)) {
291 r = cleanup_recopy(path, snapshots_path,
297 printf("unknown directive\n");
301 save_period(snapshots_path, cmd, savet);
313 val = strtol(ptr, &ptr, 0);
327 errx(1, "illegal suffix converting %s\n", ptr);
334 dividing_slash(const char *path)
336 int len = strlen(path);
337 if (len && path[len-1] == '/')
344 * Check whether the desired period has elapsed since the last successful
345 * run. The run may take a while and cross a boundary so we remember the
346 * current time_t so we can save it later on.
348 * Periods in minutes, hours, or days are assumed to have been crossed
349 * if the local time crosses a minute, hour, or day boundary regardless
350 * of how close the last operation actually was.
353 check_period(const char *snapshots_path, const char *cmd, int arg1,
364 localtime_r(savep, &tp1);
367 * Retrieve the start time of the last successful operation.
369 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
370 fp = fopen(check_path, "r");
374 if (fgets(buf, sizeof(buf), fp) == NULL) {
380 lastt = strtol(buf, NULL, 0);
381 localtime_r(&lastt, &tp2);
384 * Normalize the times. e.g. if asked to do something on a 1-day
385 * interval the operation will be performed as soon as the day
386 * turns over relative to the previous operation, even if the previous
387 * operation ran a few seconds ago just before midnight.
389 if (arg1 % 60 == 0) {
393 if (arg1 % (60 * 60) == 0) {
397 if (arg1 % (24 * 60 * 60) == 0) {
402 baset = mktime(&tp1);
403 lastt = mktime(&tp2);
406 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
409 if ((int)(baset - lastt) >= arg1)
415 * Store the start time of the last successful operation.
418 save_period(const char *snapshots_path, const char *cmd,
425 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
426 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
427 fp = fopen(ncheck_path, "w");
428 fprintf(fp, "0x%08llx\n", (long long)savet);
430 rename(ncheck_path, ocheck_path);
435 check_softlinks(const char *snapshots_path)
444 * Force snapshots_disabled to 0 if the snapshots directory
445 * contains softlinks.
447 if ((dir = opendir(snapshots_path)) != NULL) {
448 while ((den = readdir(dir)) != NULL) {
449 if (den->d_name[0] == '.')
451 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
452 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
465 cleanup_snapshots(const char *path __unused, const char *snapshots_path,
466 int arg1 __unused, int arg2 __unused)
470 runcmd(&r, "hammer snapshot %s", snapshots_path);
475 cleanup_prune(const char *path __unused, const char *snapshots_path,
476 int arg1 __unused, int arg2, int snapshots_disabled)
479 * If snapshots have been disabled run prune-everything instead
482 if (snapshots_disabled && arg2) {
483 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
484 snapshots_path, arg2, path);
485 } else if (snapshots_disabled) {
486 runcmd(NULL, "hammer prune-everything %s", path);
488 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
489 snapshots_path, arg2, snapshots_path);
491 runcmd(NULL, "hammer prune %s", snapshots_path);
497 cleanup_reblock(const char *path, const char *snapshots_path,
498 int arg1 __unused, int arg2)
500 if (VerboseOpt == 0) {
505 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s 95",
506 snapshots_path, arg2, path);
507 if (VerboseOpt == 0) {
512 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s 95",
513 snapshots_path, arg2, path);
514 if (VerboseOpt == 0) {
519 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
520 snapshots_path, arg2, path);
527 cleanup_recopy(const char *path, const char *snapshots_path,
528 int arg1 __unused, int arg2)
530 if (VerboseOpt == 0) {
535 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
536 snapshots_path, arg2, path);
537 if (VerboseOpt == 0) {
542 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
543 snapshots_path, arg2, path);
544 if (VerboseOpt == 0) {
549 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
550 snapshots_path, arg2, path);
558 runcmd(int *resp, const char *ctl, ...)
570 * Generate the command
573 vasprintf(&cmd, ctl, va);
576 printf(" %s\n", cmd);
579 * Break us down into arguments. We do not just use system() here
580 * because it blocks SIGINT and friends.
584 av = malloc(sizeof(char *) * nmax);
586 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
589 av = realloc(av, sizeof(char *) * nmax);
597 if ((pid = fork()) == 0) {
598 if (VerboseOpt < 2) {
599 int fd = open("/dev/null", O_RDWR);
605 } else if (pid < 0) {
609 while (waitpid(pid, &status, 0) != pid)
611 res = WEXITSTATUS(status);