* Fix a bug in runcmd() - the argv[] list was not NULL terminated.
[dragonfly.git] / sbin / hammer / cmd_cleanup.c
blobbe9789857db640f3525316db8c956bf55a604e38
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:
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.
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.
34 * $DragonFly: src/sbin/hammer/cmd_cleanup.c,v 1.3 2008/09/20 07:08:06 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
46 * defaults:
48 * snapshots 1d 60d (0d 60d for /tmp, /var/tmp, /usr/obj)
49 * prune 1d 5m
50 * reblock 1d 5m
51 * recopy 30d 5m
53 * All hammer commands create and maintain cycle files in the snapshots
54 * directory.
57 #include "hammer.h"
59 struct didpfs {
60 struct didpfs *next;
61 uuid_t uuid;
64 static void do_cleanup(const char *path);
65 static int strtosecs(char *ptr);
66 static const char *dividing_slash(const char *path);
67 static int check_period(const char *snapshots_path, const char *cmd, int arg1,
68 time_t *savep);
69 static void save_period(const char *snapshots_path, const char *cmd,
70 time_t savet);
71 static int check_softlinks(const char *snapshots_path);
73 static int cleanup_snapshots(const char *path, const char *snapshots_path,
74 int arg1, int arg2);
75 static int cleanup_prune(const char *path, const char *snapshots_path,
76 int arg1, int arg2, int snapshots_disabled);
77 static int cleanup_reblock(const char *path, const char *snapshots_path,
78 int arg1, int arg2);
79 static int cleanup_recopy(const char *path, const char *snapshots_path,
80 int arg1, int arg2);
82 static void runcmd(int *resp, const char *ctl, ...);
84 #define WS " \t\r\n"
86 struct didpfs *FirstPFS;
88 void
89 hammer_cmd_cleanup(char **av, int ac)
91 FILE *fp;
92 char *ptr;
93 char *path;
94 char buf[256];
96 tzset();
97 if (ac == 0) {
98 fp = popen("df -t hammer,null", "r");
99 if (fp == NULL)
100 errx(1, "hammer cleanup: 'df' failed");
101 while (fgets(buf, sizeof(buf), fp) != NULL) {
102 ptr = strtok(buf, WS);
103 if (ptr && strcmp(ptr, "Filesystem") == 0)
104 continue;
105 if (ptr)
106 ptr = strtok(NULL, WS);
107 if (ptr)
108 ptr = strtok(NULL, WS);
109 if (ptr)
110 ptr = strtok(NULL, WS);
111 if (ptr)
112 ptr = strtok(NULL, WS);
113 if (ptr) {
114 path = strtok(NULL, WS);
115 if (path)
116 do_cleanup(path);
119 fclose(fp);
120 } else {
121 while (ac) {
122 do_cleanup(*av);
123 --ac;
124 ++av;
129 static
130 void
131 do_cleanup(const char *path)
133 struct hammer_ioc_pseudofs_rw pfs;
134 union hammer_ioc_mrecord_any mrec_tmp;
135 char *snapshots_path;
136 char *config_path;
137 struct stat st;
138 char *cmd;
139 char *ptr;
140 int arg1;
141 int arg2;
142 time_t savet;
143 char buf[256];
144 FILE *fp;
145 struct didpfs *didpfs;
146 int snapshots_disabled = 0;
147 int prune_warning = 0;
148 int fd;
149 int r;
151 bzero(&pfs, sizeof(pfs));
152 bzero(&mrec_tmp, sizeof(mrec_tmp));
153 pfs.ondisk = &mrec_tmp.pfs.pfsd;
154 pfs.bytes = sizeof(mrec_tmp.pfs.pfsd);
155 pfs.pfs_id = -1;
157 printf("cleanup %-20s -", path);
158 fd = open(path, O_RDONLY);
159 if (fd < 0) {
160 printf(" unable to access directory: %s\n", strerror(errno));
161 return;
163 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
164 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
165 return;
167 close(fd);
168 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
169 printf(" unrecognized HAMMER version\n");
170 return;
174 * Make sure we have not already handled this PFS. Several nullfs
175 * mounts might alias the same PFS.
177 for (didpfs = FirstPFS; didpfs; didpfs = didpfs->next) {
178 if (bcmp(&didpfs->uuid, &mrec_tmp.pfs.pfsd.unique_uuid, sizeof(uuid_t)) == 0) {
179 printf(" pfs_id %d already handled\n", pfs.pfs_id);
180 return;
183 didpfs = malloc(sizeof(*didpfs));
184 didpfs->next = FirstPFS;
185 FirstPFS = didpfs;
186 didpfs->uuid = mrec_tmp.pfs.pfsd.unique_uuid;
189 * Create a snapshot directory if necessary, and a config file if
190 * necessary.
192 asprintf(&snapshots_path, "%s%ssnapshots", path, dividing_slash(path));
193 if (stat(snapshots_path, &st) < 0) {
194 if (mkdir(snapshots_path, 0755) != 0) {
195 free(snapshots_path);
196 printf(" unable to create snapshot dir: %s\n",
197 strerror(errno));
198 return;
201 asprintf(&config_path, "%s/config", snapshots_path);
202 if ((fp = fopen(config_path, "r")) == NULL) {
203 fp = fopen(config_path, "w");
204 if (fp == NULL) {
205 printf(" cannot create %s: %s\n",
206 config_path, strerror(errno));
207 return;
209 if (strcmp(path, "/tmp") == 0 ||
210 strcmp(path, "/var/tmp") == 0 ||
211 strcmp(path, "/usr/obj") == 0) {
212 fprintf(fp, "snapshots 0d 60d\n");
213 } else {
214 fprintf(fp, "snapshots 1d 60d\n");
216 fprintf(fp,
217 "prune 1d 5m\n"
218 "reblock 1d 5m\n"
219 "recopy 30d 10m\n");
220 fclose(fp);
221 fp = fopen(config_path, "r");
223 if (fp == NULL) {
224 printf(" cannot access %s: %s\n",
225 config_path, strerror(errno));
226 return;
229 printf(" processing PFS #%d\n", pfs.pfs_id);
232 * Process the config file
234 while (fgets(buf, sizeof(buf), fp) != NULL) {
235 cmd = strtok(buf, WS);
236 arg1 = 0;
237 arg2 = 0;
238 if ((ptr = strtok(NULL, WS)) != NULL) {
239 arg1 = strtosecs(ptr);
240 if ((ptr = strtok(NULL, WS)) != NULL)
241 arg2 = strtosecs(ptr);
244 printf("%20s - ", cmd);
245 fflush(stdout);
247 if (arg1 == 0) {
248 printf("disabled\n");
249 if (strcmp(cmd, "snapshots") == 0) {
250 if (check_softlinks(snapshots_path))
251 prune_warning = 1;
252 else
253 snapshots_disabled = 1;
255 continue;
258 r = 1;
259 if (strcmp(cmd, "snapshots") == 0) {
260 if (check_period(snapshots_path, cmd, arg1, &savet)) {
261 printf("run\n");
262 r = cleanup_snapshots(path, snapshots_path,
263 arg1, arg2);
264 } else {
265 printf("skip\n");
267 } else if (strcmp(cmd, "prune") == 0) {
268 if (check_period(snapshots_path, cmd, arg1, &savet)) {
269 if (prune_warning)
270 printf("run - WARNING snapshot softlinks present but snapshots disabled\n");
271 else
272 printf("run\n");
273 r = cleanup_prune(path, snapshots_path,
274 arg1, arg2, snapshots_disabled);
275 } else {
276 printf("skip\n");
278 } else if (strcmp(cmd, "reblock") == 0) {
279 if (check_period(snapshots_path, cmd, arg1, &savet)) {
280 printf("run");
281 fflush(stdout);
282 if (VerboseOpt)
283 printf("\n");
284 r = cleanup_reblock(path, snapshots_path,
285 arg1, arg2);
286 } else {
287 printf("skip\n");
289 } else if (strcmp(cmd, "recopy") == 0) {
290 if (check_period(snapshots_path, cmd, arg1, &savet)) {
291 printf("run");
292 fflush(stdout);
293 if (VerboseOpt)
294 printf("\n");
295 r = cleanup_recopy(path, snapshots_path,
296 arg1, arg2);
297 } else {
298 printf("skip\n");
300 } else {
301 printf("unknown directive\n");
302 r = 1;
304 if (r == 0)
305 save_period(snapshots_path, cmd, savet);
307 fclose(fp);
308 usleep(1000);
311 static
313 strtosecs(char *ptr)
315 int val;
317 val = strtol(ptr, &ptr, 0);
318 switch(*ptr) {
319 case 'd':
320 val *= 24;
321 /* fall through */
322 case 'h':
323 val *= 60;
324 /* fall through */
325 case 'm':
326 val *= 60;
327 /* fall through */
328 case 's':
329 break;
330 default:
331 errx(1, "illegal suffix converting %s\n", ptr);
332 break;
334 return(val);
337 static const char *
338 dividing_slash(const char *path)
340 int len = strlen(path);
341 if (len && path[len-1] == '/')
342 return("");
343 else
344 return("/");
348 * Check whether the desired period has elapsed since the last successful
349 * run. The run may take a while and cross a boundary so we remember the
350 * current time_t so we can save it later on.
352 * Periods in minutes, hours, or days are assumed to have been crossed
353 * if the local time crosses a minute, hour, or day boundary regardless
354 * of how close the last operation actually was.
356 static int
357 check_period(const char *snapshots_path, const char *cmd, int arg1,
358 time_t *savep)
360 char *check_path;
361 struct tm tp1;
362 struct tm tp2;
363 FILE *fp;
364 time_t baset, lastt;
365 char buf[256];
367 time(savep);
368 localtime_r(savep, &tp1);
371 * Retrieve the start time of the last successful operation.
373 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
374 fp = fopen(check_path, "r");
375 free(check_path);
376 if (fp == NULL)
377 return(1);
378 if (fgets(buf, sizeof(buf), fp) == NULL) {
379 fclose(fp);
380 return(1);
382 fclose(fp);
384 lastt = strtol(buf, NULL, 0);
385 localtime_r(&lastt, &tp2);
388 * Normalize the times. e.g. if asked to do something on a 1-day
389 * interval the operation will be performed as soon as the day
390 * turns over relative to the previous operation, even if the previous
391 * operation ran a few seconds ago just before midnight.
393 if (arg1 % 60 == 0) {
394 tp1.tm_sec = 0;
395 tp2.tm_sec = 0;
397 if (arg1 % (60 * 60) == 0) {
398 tp1.tm_min = 0;
399 tp2.tm_min = 0;
401 if (arg1 % (24 * 60 * 60) == 0) {
402 tp1.tm_hour = 0;
403 tp2.tm_hour = 0;
406 baset = mktime(&tp1);
407 lastt = mktime(&tp2);
409 #if 0
410 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
411 #endif
413 if ((int)(baset - lastt) >= arg1)
414 return(1);
415 return(0);
419 * Store the start time of the last successful operation.
421 static void
422 save_period(const char *snapshots_path, const char *cmd,
423 time_t savet)
425 char *ocheck_path;
426 char *ncheck_path;
427 FILE *fp;
429 asprintf(&ocheck_path, "%s/.%s.period", snapshots_path, cmd);
430 asprintf(&ncheck_path, "%s/.%s.period.new", snapshots_path, cmd);
431 fp = fopen(ncheck_path, "w");
432 fprintf(fp, "0x%08llx\n", (long long)savet);
433 if (fclose(fp) == 0)
434 rename(ncheck_path, ocheck_path);
435 remove(ncheck_path);
438 static int
439 check_softlinks(const char *snapshots_path)
441 struct dirent *den;
442 struct stat st;
443 DIR *dir;
444 char *fpath;
445 int res = 0;
448 * Force snapshots_disabled to 0 if the snapshots directory
449 * contains softlinks.
451 if ((dir = opendir(snapshots_path)) != NULL) {
452 while ((den = readdir(dir)) != NULL) {
453 if (den->d_name[0] == '.')
454 continue;
455 asprintf(&fpath, "%s/%s", snapshots_path, den->d_name);
456 if (lstat(fpath, &st) == 0 && S_ISLNK(st.st_mode))
457 ++res;
458 free(fpath);
460 closedir(dir);
462 return(res);
466 * Issue a snapshot.
468 static int
469 cleanup_snapshots(const char *path __unused, const char *snapshots_path,
470 int arg1 __unused, int arg2 __unused)
472 int r;
474 runcmd(&r, "hammer snapshot %s", snapshots_path);
475 return(r);
478 static int
479 cleanup_prune(const char *path __unused, const char *snapshots_path,
480 int arg1 __unused, int arg2, int snapshots_disabled)
483 * If snapshots have been disabled run prune-everything instead
484 * of prune.
486 if (snapshots_disabled && arg2) {
487 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
488 snapshots_path, arg2, path);
489 } else if (snapshots_disabled) {
490 runcmd(NULL, "hammer prune-everything %s", path);
491 } else if (arg2) {
492 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
493 snapshots_path, arg2, snapshots_path);
494 } else {
495 runcmd(NULL, "hammer prune %s", snapshots_path);
497 return(0);
500 static int
501 cleanup_reblock(const char *path, const char *snapshots_path,
502 int arg1 __unused, int arg2)
504 if (VerboseOpt == 0) {
505 printf(".");
506 fflush(stdout);
508 runcmd(NULL,
509 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s 95",
510 snapshots_path, arg2, path);
511 if (VerboseOpt == 0) {
512 printf(".");
513 fflush(stdout);
515 runcmd(NULL,
516 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s 95",
517 snapshots_path, arg2, path);
518 if (VerboseOpt == 0) {
519 printf(".");
520 fflush(stdout);
522 runcmd(NULL,
523 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
524 snapshots_path, arg2, path);
525 if (VerboseOpt == 0)
526 printf("\n");
527 return(0);
530 static int
531 cleanup_recopy(const char *path, const char *snapshots_path,
532 int arg1 __unused, int arg2)
534 if (VerboseOpt == 0) {
535 printf(".");
536 fflush(stdout);
538 runcmd(NULL,
539 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
540 snapshots_path, arg2, path);
541 if (VerboseOpt == 0) {
542 printf(".");
543 fflush(stdout);
545 runcmd(NULL,
546 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
547 snapshots_path, arg2, path);
548 if (VerboseOpt == 0) {
549 printf(".");
550 fflush(stdout);
552 runcmd(NULL,
553 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
554 snapshots_path, arg2, path);
555 if (VerboseOpt == 0)
556 printf("\n");
557 return(0);
560 static
561 void
562 runcmd(int *resp, const char *ctl, ...)
564 va_list va;
565 char *cmd;
566 char *arg;
567 char **av;
568 int n;
569 int nmax;
570 int res;
571 pid_t pid;
574 * Generate the command
576 va_start(va, ctl);
577 vasprintf(&cmd, ctl, va);
578 va_end(va);
579 if (VerboseOpt)
580 printf(" %s\n", cmd);
583 * Break us down into arguments. We do not just use system() here
584 * because it blocks SIGINT and friends.
586 n = 0;
587 nmax = 16;
588 av = malloc(sizeof(char *) * nmax);
590 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
591 if (n == nmax - 1) {
592 nmax += 16;
593 av = realloc(av, sizeof(char *) * nmax);
595 av[n++] = arg;
597 av[n++] = NULL;
600 * Run the command.
602 if ((pid = fork()) == 0) {
603 if (VerboseOpt < 2) {
604 int fd = open("/dev/null", O_RDWR);
605 dup2(fd, 1);
606 close(fd);
608 execvp(av[0], av);
609 _exit(127);
610 } else if (pid < 0) {
611 res = 127;
612 } else {
613 int status;
614 while (waitpid(pid, &status, 0) != pid)
616 res = WEXITSTATUS(status);
619 free(cmd);
620 free(av);
621 if (resp)
622 *resp = res;