If snapshots are disabled and the snapshots directory contains no
[dragonfly.git] / sbin / hammer / cmd_cleanup.c
CommitLineData
6a6e350f
MD
1/*
2 * Copyright (c) 2008 The DragonFly Project. All rights reserved.
3 *
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
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
16 * distribution.
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.
20 *
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
32 * SUCH DAMAGE.
33 *
c6c298a7 34 * $DragonFly: src/sbin/hammer/cmd_cleanup.c,v 1.2 2008/09/20 06:46:22 dillon Exp $
6a6e350f
MD
35 */
36/*
37 * Clean up a specific HAMMER filesystem or all HAMMER filesystems.
38 *
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.
43 *
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
46 * defaults:
47 *
48 * snapshots 1d 60d (0d 60d for /tmp, /var/tmp, /usr/obj)
49 * prune 1d 5m
50 * reblock 1d 5m
51 * recopy 30d 5m
52 *
53 * All hammer commands create and maintain cycle files in the snapshots
54 * directory.
55 */
56
57#include "hammer.h"
58
59static void do_cleanup(const char *path);
60static int strtosecs(char *ptr);
61static const char *dividing_slash(const char *path);
62static int check_period(const char *snapshots_path, const char *cmd, int arg1,
63 time_t *savep);
64static void save_period(const char *snapshots_path, const char *cmd,
65 time_t savet);
c6c298a7 66static int check_softlinks(const char *snapshots_path);
6a6e350f
MD
67
68static int cleanup_snapshots(const char *path, const char *snapshots_path,
69 int arg1, int arg2);
70static int cleanup_prune(const char *path, const char *snapshots_path,
c6c298a7 71 int arg1, int arg2, int snapshots_disabled);
6a6e350f
MD
72static int cleanup_reblock(const char *path, const char *snapshots_path,
73 int arg1, int arg2);
74static int cleanup_recopy(const char *path, const char *snapshots_path,
75 int arg1, int arg2);
76
77static void runcmd(int *resp, const char *ctl, ...);
78
79#define WS " \t\r\n"
80#define MAXPFS 65536
81#define DIDBITS (sizeof(int) * 8)
82
83static int DidPFS[MAXPFS/DIDBITS];
84
85void
86hammer_cmd_cleanup(char **av, int ac)
87{
88 FILE *fp;
89 char *ptr;
90 char *path;
91 char buf[256];
92
93 tzset();
94 if (ac == 0) {
95 fp = popen("df -t hammer,null", "r");
96 if (fp == NULL)
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)
101 continue;
102 if (ptr)
103 ptr = strtok(NULL, WS);
104 if (ptr)
105 ptr = strtok(NULL, WS);
106 if (ptr)
107 ptr = strtok(NULL, WS);
108 if (ptr)
109 ptr = strtok(NULL, WS);
110 if (ptr) {
111 path = strtok(NULL, WS);
112 if (path)
113 do_cleanup(path);
114 }
115 }
116 fclose(fp);
117 } else {
118 while (ac) {
119 do_cleanup(*av);
120 --ac;
121 ++av;
122 }
123 }
124}
125
126static
127void
128do_cleanup(const char *path)
129{
130 struct hammer_ioc_pseudofs_rw pfs;
131 union hammer_ioc_mrecord_any mrec_tmp;
132 char *snapshots_path;
133 char *config_path;
134 struct stat st;
135 char *cmd;
136 char *ptr;
137 int arg1;
138 int arg2;
139 time_t savet;
140 char buf[256];
141 FILE *fp;
c6c298a7
MD
142 int snapshots_disabled = 0;
143 int prune_warning = 0;
6a6e350f
MD
144 int fd;
145 int r;
146
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);
151 pfs.pfs_id = -1;
152
153 printf("cleanup %-20s -", path);
154 fd = open(path, O_RDONLY);
155 if (fd < 0) {
156 printf(" unable to access directory: %s\n", strerror(errno));
157 return;
158 }
159 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
160 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
161 return;
162 }
163 close(fd);
164 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
165 printf(" unrecognized HAMMER version\n");
166 return;
167 }
168
169 /*
170 * Make sure we have not already handled this PFS. Several nullfs
171 * mounts might alias the same PFS.
172 */
173 if (pfs.pfs_id < 0 || pfs.pfs_id >= MAXPFS) {
174 printf(" pfs_id %d illegal\n", pfs.pfs_id);
175 return;
176 }
177
178 if (DidPFS[pfs.pfs_id / DIDBITS] & (1 << (pfs.pfs_id % DIDBITS))) {
179 printf(" pfs_id %d already handled\n", pfs.pfs_id);
180 return;
181 }
182 DidPFS[pfs.pfs_id / DIDBITS] |= (1 << (pfs.pfs_id % DIDBITS));
183
184 /*
185 * Create a snapshot directory if necessary, and a config file if
186 * necessary.
187 */
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",
193 strerror(errno));
194 return;
195 }
196 }
197 asprintf(&config_path, "%s/config", snapshots_path);
198 if ((fp = fopen(config_path, "r")) == NULL) {
199 fp = fopen(config_path, "w");
200 if (fp == NULL) {
201 printf(" cannot create %s: %s\n",
202 config_path, strerror(errno));
203 return;
204 }
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");
209 } else {
210 fprintf(fp, "snapshots 1d 60d\n");
211 }
212 fprintf(fp,
213 "prune 1d 5m\n"
214 "reblock 1d 5m\n"
215 "recopy 30d 10m\n");
216 fclose(fp);
217 fp = fopen(config_path, "r");
218 }
219 if (fp == NULL) {
220 printf(" cannot access %s: %s\n",
221 config_path, strerror(errno));
222 return;
223 }
224
225 printf(" processing PFS #%d\n", pfs.pfs_id);
226
227 /*
228 * Process the config file
229 */
230 while (fgets(buf, sizeof(buf), fp) != NULL) {
231 cmd = strtok(buf, WS);
232 arg1 = 0;
233 arg2 = 0;
234 if ((ptr = strtok(NULL, WS)) != NULL) {
235 arg1 = strtosecs(ptr);
236 if ((ptr = strtok(NULL, WS)) != NULL)
237 arg2 = strtosecs(ptr);
238 }
239
240 printf("%20s - ", cmd);
241 fflush(stdout);
242
243 if (arg1 == 0) {
244 printf("disabled\n");
c6c298a7
MD
245 if (strcmp(cmd, "snapshots") == 0) {
246 if (check_softlinks(snapshots_path))
247 prune_warning = 1;
248 else
249 snapshots_disabled = 1;
250 }
6a6e350f
MD
251 continue;
252 }
253
254 r = 1;
255 if (strcmp(cmd, "snapshots") == 0) {
256 if (check_period(snapshots_path, cmd, arg1, &savet)) {
257 printf("run\n");
258 r = cleanup_snapshots(path, snapshots_path,
259 arg1, arg2);
260 } else {
261 printf("skip\n");
262 }
263 } else if (strcmp(cmd, "prune") == 0) {
264 if (check_period(snapshots_path, cmd, arg1, &savet)) {
c6c298a7
MD
265 if (prune_warning)
266 printf("run - WARNING snapshot softlinks present but snapshots disabled\n");
267 else
268 printf("run\n");
6a6e350f 269 r = cleanup_prune(path, snapshots_path,
c6c298a7 270 arg1, arg2, snapshots_disabled);
6a6e350f
MD
271 } else {
272 printf("skip\n");
273 }
274 } else if (strcmp(cmd, "reblock") == 0) {
275 if (check_period(snapshots_path, cmd, arg1, &savet)) {
276 printf("run");
277 fflush(stdout);
278 if (VerboseOpt)
279 printf("\n");
280 r = cleanup_reblock(path, snapshots_path,
281 arg1, arg2);
282 } else {
283 printf("skip\n");
284 }
285 } else if (strcmp(cmd, "recopy") == 0) {
286 if (check_period(snapshots_path, cmd, arg1, &savet)) {
287 printf("run");
288 fflush(stdout);
289 if (VerboseOpt)
290 printf("\n");
291 r = cleanup_recopy(path, snapshots_path,
292 arg1, arg2);
293 } else {
294 printf("skip\n");
295 }
296 } else {
297 printf("unknown directive\n");
298 r = 1;
299 }
300 if (r == 0)
301 save_period(snapshots_path, cmd, savet);
302 }
303 fclose(fp);
304 usleep(1000);
305}
306
307static
308int
309strtosecs(char *ptr)
310{
311 int val;
312
313 val = strtol(ptr, &ptr, 0);
314 switch(*ptr) {
315 case 'd':
316 val *= 24;
317 /* fall through */
318 case 'h':
319 val *= 60;
320 /* fall through */
321 case 'm':
322 val *= 60;
323 /* fall through */
324 case 's':
325 break;
326 default:
327 errx(1, "illegal suffix converting %s\n", ptr);
328 break;
329 }
330 return(val);
331}
332
333static const char *
334dividing_slash(const char *path)
335{
336 int len = strlen(path);
337 if (len && path[len-1] == '/')
338 return("");
339 else
340 return("/");
341}
342
343/*
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.
347 *
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.
351 */
352static int
353check_period(const char *snapshots_path, const char *cmd, int arg1,
354 time_t *savep)
355{
356 char *check_path;
357 struct tm tp1;
358 struct tm tp2;
359 FILE *fp;
360 time_t baset, lastt;
361 char buf[256];
362
363 time(savep);
364 localtime_r(savep, &tp1);
365
366 /*
367 * Retrieve the start time of the last successful operation.
368 */
369 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
370 fp = fopen(check_path, "r");
371 free(check_path);
372 if (fp == NULL)
373 return(1);
374 if (fgets(buf, sizeof(buf), fp) == NULL) {
375 fclose(fp);
376 return(1);
377 }
378 fclose(fp);
379
380 lastt = strtol(buf, NULL, 0);
381 localtime_r(&lastt, &tp2);
382
383 /*
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.
388 */
389 if (arg1 % 60 == 0) {
390 tp1.tm_sec = 0;
391 tp2.tm_sec = 0;
392 }
393 if (arg1 % (60 * 60) == 0) {
394 tp1.tm_min = 0;
395 tp2.tm_min = 0;
396 }
397 if (arg1 % (24 * 60 * 60) == 0) {
398 tp1.tm_hour = 0;
399 tp2.tm_hour = 0;
400 }
401
402 baset = mktime(&tp1);
403 lastt = mktime(&tp2);
404
405#if 0
406 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
407#endif
408
409 if ((int)(baset - lastt) >= arg1)
410 return(1);
411 return(0);
412}
413
414/*
415 * Store the start time of the last successful operation.
416 */
417static void
418save_period(const char *snapshots_path, const char *cmd,
419 time_t savet)
420{
421 char *ocheck_path;
422 char *ncheck_path;
423 FILE *fp;
424
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);
429 if (fclose(fp) == 0)
430 rename(ncheck_path, ocheck_path);
431 remove(ncheck_path);
432}
433
c6c298a7
MD
434static int
435check_softlinks(const char *snapshots_path)
436{
437 struct dirent *den;
438 struct stat st;
439 DIR *dir;
440 char *fpath;
441 int res = 0;
442
443 /*
444 * Force snapshots_disabled to 0 if the snapshots directory
445 * contains softlinks.
446 */
447 if ((dir = opendir(snapshots_path)) != NULL) {
448 while ((den = readdir(dir)) != NULL) {
449 if (den->d_name[0] == '.')
450 continue;
451 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
452 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
453 ++res;
454 free(fpath);
455 }
456 closedir(dir);
457 }
458 return(res);
459}
460
6a6e350f
MD
461/*
462 * Issue a snapshot.
463 */
464static int
465cleanup_snapshots(const char *path __unused, const char *snapshots_path,
466 int arg1 __unused, int arg2 __unused)
467{
468 int r;
469
470 runcmd(&r, "hammer snapshot %s", snapshots_path);
471 return(r);
472}
473
474static int
475cleanup_prune(const char *path __unused, const char *snapshots_path,
c6c298a7 476 int arg1 __unused, int arg2, int snapshots_disabled)
6a6e350f 477{
c6c298a7
MD
478 /*
479 * If snapshots have been disabled run prune-everything instead
480 * of prune.
481 */
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);
487 } else if (arg2) {
6a6e350f
MD
488 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
489 snapshots_path, arg2, snapshots_path);
490 } else {
491 runcmd(NULL, "hammer prune %s", snapshots_path);
492 }
493 return(0);
494}
495
496static int
497cleanup_reblock(const char *path, const char *snapshots_path,
498 int arg1 __unused, int arg2)
499{
500 if (VerboseOpt == 0) {
501 printf(".");
502 fflush(stdout);
503 }
504 runcmd(NULL,
505 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s 95",
506 snapshots_path, arg2, path);
507 if (VerboseOpt == 0) {
508 printf(".");
509 fflush(stdout);
510 }
511 runcmd(NULL,
512 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s 95",
513 snapshots_path, arg2, path);
514 if (VerboseOpt == 0) {
515 printf(".");
516 fflush(stdout);
517 }
518 runcmd(NULL,
519 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
520 snapshots_path, arg2, path);
521 if (VerboseOpt == 0)
522 printf("\n");
523 return(0);
524}
525
526static int
527cleanup_recopy(const char *path, const char *snapshots_path,
528 int arg1 __unused, int arg2)
529{
530 if (VerboseOpt == 0) {
531 printf(".");
532 fflush(stdout);
533 }
534 runcmd(NULL,
535 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
536 snapshots_path, arg2, path);
537 if (VerboseOpt == 0) {
538 printf(".");
539 fflush(stdout);
540 }
541 runcmd(NULL,
542 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
543 snapshots_path, arg2, path);
544 if (VerboseOpt == 0) {
545 printf(".");
546 fflush(stdout);
547 }
548 runcmd(NULL,
549 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
550 snapshots_path, arg2, path);
551 if (VerboseOpt == 0)
552 printf("\n");
553 return(0);
554}
555
556static
557void
558runcmd(int *resp, const char *ctl, ...)
559{
560 va_list va;
561 char *cmd;
562 char *arg;
563 char **av;
564 int n;
565 int nmax;
566 int res;
567 pid_t pid;
568
569 /*
570 * Generate the command
571 */
572 va_start(va, ctl);
573 vasprintf(&cmd, ctl, va);
574 va_end(va);
575 if (VerboseOpt)
576 printf(" %s\n", cmd);
577
578 /*
579 * Break us down into arguments. We do not just use system() here
580 * because it blocks SIGINT and friends.
581 */
582 n = 0;
583 nmax = 16;
584 av = malloc(sizeof(char *) * nmax);
585
586 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
587 if (n == nmax) {
588 nmax += 16;
589 av = realloc(av, sizeof(char *) * nmax);
590 }
591 av[n++] = arg;
592 }
593
594 /*
595 * Run the command.
596 */
597 if ((pid = fork()) == 0) {
598 if (VerboseOpt < 2) {
599 int fd = open("/dev/null", O_RDWR);
600 dup2(fd, 1);
601 close(fd);
602 }
603 execvp(av[0], av);
604 _exit(127);
605 } else if (pid < 0) {
606 res = 127;
607 } else {
608 int status;
609 while (waitpid(pid, &status, 0) != pid)
610 ;
611 res = WEXITSTATUS(status);
612 }
613
614 free(cmd);
615 free(av);
616 if (resp)
617 *resp = res;
618}
619
620