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.1 2008/09/20 04:23:21 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,
67 static int cleanup_snapshots(const char *path, const char *snapshots_path,
69 static int cleanup_prune(const char *path, const char *snapshots_path,
71 static int cleanup_reblock(const char *path, const char *snapshots_path,
73 static int cleanup_recopy(const char *path, const char *snapshots_path,
76 static void runcmd(int *resp, const char *ctl, ...);
80 #define DIDBITS (sizeof(int) * 8)
82 static int DidPFS[MAXPFS/DIDBITS];
85 hammer_cmd_cleanup(char **av, int ac)
94 fp = popen("df -t hammer,null", "r");
96 errx(1, "hammer cleanup: 'df' failed");
97 while (fgets(buf, sizeof(buf), fp) != NULL) {
98 ptr = strtok(buf, WS);
99 if (ptr && strcmp(ptr, "Filesystem") == 0)
102 ptr = strtok(NULL, WS);
104 ptr = strtok(NULL, WS);
106 ptr = strtok(NULL, WS);
108 ptr = strtok(NULL, WS);
110 path = strtok(NULL, WS);
127 do_cleanup(const char *path)
129 struct hammer_ioc_pseudofs_rw pfs;
130 union hammer_ioc_mrecord_any mrec_tmp;
131 char *snapshots_path;
144 bzero(&pfs, sizeof(pfs));
145 bzero(&mrec_tmp, sizeof(mrec_tmp));
146 pfs.ondisk = &mrec_tmp.pfs.pfsd;
147 pfs.bytes = sizeof(mrec_tmp.pfs.pfsd);
150 printf("cleanup %-20s -", path);
151 fd = open(path, O_RDONLY);
153 printf(" unable to access directory: %s\n", strerror(errno));
156 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
157 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
161 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
162 printf(" unrecognized HAMMER version\n");
167 * Make sure we have not already handled this PFS. Several nullfs
168 * mounts might alias the same PFS.
170 if (pfs.pfs_id < 0 || pfs.pfs_id >= MAXPFS) {
171 printf(" pfs_id %d illegal\n", pfs.pfs_id);
175 if (DidPFS[pfs.pfs_id / DIDBITS] & (1 << (pfs.pfs_id % DIDBITS))) {
176 printf(" pfs_id %d already handled\n", pfs.pfs_id);
179 DidPFS[pfs.pfs_id / DIDBITS] |= (1 << (pfs.pfs_id % DIDBITS));
182 * Create a snapshot directory if necessary, and a config file if
185 asprintf(&snapshots_path, "%s%ssnapshots", path, dividing_slash(path));
186 if (stat(snapshots_path, &st) < 0) {
187 if (mkdir(snapshots_path, 0755) != 0) {
188 free(snapshots_path);
189 printf(" unable to create snapshot dir: %s\n",
194 asprintf(&config_path, "%s/config", snapshots_path);
195 if ((fp = fopen(config_path, "r")) == NULL) {
196 fp = fopen(config_path, "w");
198 printf(" cannot create %s: %s\n",
199 config_path, strerror(errno));
202 if (strcmp(path, "/tmp") == 0 ||
203 strcmp(path, "/var/tmp") == 0 ||
204 strcmp(path, "/usr/obj") == 0) {
205 fprintf(fp, "snapshots 0d 60d\n");
207 fprintf(fp, "snapshots 1d 60d\n");
214 fp = fopen(config_path, "r");
217 printf(" cannot access %s: %s\n",
218 config_path, strerror(errno));
222 printf(" processing PFS #%d\n", pfs.pfs_id);
225 * Process the config file
227 while (fgets(buf, sizeof(buf), fp) != NULL) {
228 cmd = strtok(buf, WS);
231 if ((ptr = strtok(NULL, WS)) != NULL) {
232 arg1 = strtosecs(ptr);
233 if ((ptr = strtok(NULL, WS)) != NULL)
234 arg2 = strtosecs(ptr);
237 printf("%20s - ", cmd);
241 printf("disabled\n");
246 if (strcmp(cmd, "snapshots") == 0) {
247 if (check_period(snapshots_path, cmd, arg1, &savet)) {
249 r = cleanup_snapshots(path, snapshots_path,
254 } else if (strcmp(cmd, "prune") == 0) {
255 if (check_period(snapshots_path, cmd, arg1, &savet)) {
256 r = cleanup_prune(path, snapshots_path,
261 } else if (strcmp(cmd, "reblock") == 0) {
262 if (check_period(snapshots_path, cmd, arg1, &savet)) {
267 r = cleanup_reblock(path, snapshots_path,
272 } else if (strcmp(cmd, "recopy") == 0) {
273 if (check_period(snapshots_path, cmd, arg1, &savet)) {
278 r = cleanup_recopy(path, snapshots_path,
284 printf("unknown directive\n");
288 save_period(snapshots_path, cmd, savet);
300 val = strtol(ptr, &ptr, 0);
314 errx(1, "illegal suffix converting %s\n", ptr);
321 dividing_slash(const char *path)
323 int len = strlen(path);
324 if (len && path[len-1] == '/')
331 * Check whether the desired period has elapsed since the last successful
332 * run. The run may take a while and cross a boundary so we remember the
333 * current time_t so we can save it later on.
335 * Periods in minutes, hours, or days are assumed to have been crossed
336 * if the local time crosses a minute, hour, or day boundary regardless
337 * of how close the last operation actually was.
340 check_period(const char *snapshots_path, const char *cmd, int arg1,
351 localtime_r(savep, &tp1);
354 * Retrieve the start time of the last successful operation.
356 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
357 fp = fopen(check_path, "r");
361 if (fgets(buf, sizeof(buf), fp) == NULL) {
367 lastt = strtol(buf, NULL, 0);
368 localtime_r(&lastt, &tp2);
371 * Normalize the times. e.g. if asked to do something on a 1-day
372 * interval the operation will be performed as soon as the day
373 * turns over relative to the previous operation, even if the previous
374 * operation ran a few seconds ago just before midnight.
376 if (arg1 % 60 == 0) {
380 if (arg1 % (60 * 60) == 0) {
384 if (arg1 % (24 * 60 * 60) == 0) {
389 baset = mktime(&tp1);
390 lastt = mktime(&tp2);
393 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
396 if ((int)(baset - lastt) >= arg1)
402 * Store the start time of the last successful operation.
405 save_period(const char *snapshots_path, const char *cmd,
412 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
413 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
414 fp = fopen(ncheck_path, "w");
415 fprintf(fp, "0x%08llx\n", (long long)savet);
417 rename(ncheck_path, ocheck_path);
425 cleanup_snapshots(const char *path __unused, const char *snapshots_path,
426 int arg1 __unused, int arg2 __unused)
430 runcmd(&r, "hammer snapshot %s", snapshots_path);
435 cleanup_prune(const char *path __unused, const char *snapshots_path,
436 int arg1 __unused, int arg2)
439 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
440 snapshots_path, arg2, snapshots_path);
442 runcmd(NULL, "hammer prune %s", snapshots_path);
448 cleanup_reblock(const char *path, const char *snapshots_path,
449 int arg1 __unused, int arg2)
451 if (VerboseOpt == 0) {
456 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s 95",
457 snapshots_path, arg2, path);
458 if (VerboseOpt == 0) {
463 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s 95",
464 snapshots_path, arg2, path);
465 if (VerboseOpt == 0) {
470 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
471 snapshots_path, arg2, path);
478 cleanup_recopy(const char *path, const char *snapshots_path,
479 int arg1 __unused, int arg2)
481 if (VerboseOpt == 0) {
486 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
487 snapshots_path, arg2, path);
488 if (VerboseOpt == 0) {
493 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
494 snapshots_path, arg2, path);
495 if (VerboseOpt == 0) {
500 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
501 snapshots_path, arg2, path);
509 runcmd(int *resp, const char *ctl, ...)
521 * Generate the command
524 vasprintf(&cmd, ctl, va);
527 printf(" %s\n", cmd);
530 * Break us down into arguments. We do not just use system() here
531 * because it blocks SIGINT and friends.
535 av = malloc(sizeof(char *) * nmax);
537 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
540 av = realloc(av, sizeof(char *) * nmax);
548 if ((pid = fork()) == 0) {
549 if (VerboseOpt < 2) {
550 int fd = open("/dev/null", O_RDWR);
556 } else if (pid < 0) {
560 while (waitpid(pid, &status, 0) != pid)
562 res = WEXITSTATUS(status);