Add PFIL_MPSAFE flag to give hint to pfil(9) that the underlying firewall
[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 *
34 * $DragonFly: src/sbin/hammer/cmd_cleanup.c,v 1.1 2008/09/20 04:23:21 dillon Exp $
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);
66
67static int cleanup_snapshots(const char *path, const char *snapshots_path,
68 int arg1, int arg2);
69static int cleanup_prune(const char *path, const char *snapshots_path,
70 int arg1, int arg2);
71static int cleanup_reblock(const char *path, const char *snapshots_path,
72 int arg1, int arg2);
73static int cleanup_recopy(const char *path, const char *snapshots_path,
74 int arg1, int arg2);
75
76static void runcmd(int *resp, const char *ctl, ...);
77
78#define WS " \t\r\n"
79#define MAXPFS 65536
80#define DIDBITS (sizeof(int) * 8)
81
82static int DidPFS[MAXPFS/DIDBITS];
83
84void
85hammer_cmd_cleanup(char **av, int ac)
86{
87 FILE *fp;
88 char *ptr;
89 char *path;
90 char buf[256];
91
92 tzset();
93 if (ac == 0) {
94 fp = popen("df -t hammer,null", "r");
95 if (fp == NULL)
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)
100 continue;
101 if (ptr)
102 ptr = strtok(NULL, WS);
103 if (ptr)
104 ptr = strtok(NULL, WS);
105 if (ptr)
106 ptr = strtok(NULL, WS);
107 if (ptr)
108 ptr = strtok(NULL, WS);
109 if (ptr) {
110 path = strtok(NULL, WS);
111 if (path)
112 do_cleanup(path);
113 }
114 }
115 fclose(fp);
116 } else {
117 while (ac) {
118 do_cleanup(*av);
119 --ac;
120 ++av;
121 }
122 }
123}
124
125static
126void
127do_cleanup(const char *path)
128{
129 struct hammer_ioc_pseudofs_rw pfs;
130 union hammer_ioc_mrecord_any mrec_tmp;
131 char *snapshots_path;
132 char *config_path;
133 struct stat st;
134 char *cmd;
135 char *ptr;
136 int arg1;
137 int arg2;
138 time_t savet;
139 char buf[256];
140 FILE *fp;
141 int fd;
142 int r;
143
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);
148 pfs.pfs_id = -1;
149
150 printf("cleanup %-20s -", path);
151 fd = open(path, O_RDONLY);
152 if (fd < 0) {
153 printf(" unable to access directory: %s\n", strerror(errno));
154 return;
155 }
156 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) != 0) {
157 printf(" not a HAMMER filesystem: %s\n", strerror(errno));
158 return;
159 }
160 close(fd);
161 if (pfs.version != HAMMER_IOC_PSEUDOFS_VERSION) {
162 printf(" unrecognized HAMMER version\n");
163 return;
164 }
165
166 /*
167 * Make sure we have not already handled this PFS. Several nullfs
168 * mounts might alias the same PFS.
169 */
170 if (pfs.pfs_id < 0 || pfs.pfs_id >= MAXPFS) {
171 printf(" pfs_id %d illegal\n", pfs.pfs_id);
172 return;
173 }
174
175 if (DidPFS[pfs.pfs_id / DIDBITS] & (1 << (pfs.pfs_id % DIDBITS))) {
176 printf(" pfs_id %d already handled\n", pfs.pfs_id);
177 return;
178 }
179 DidPFS[pfs.pfs_id / DIDBITS] |= (1 << (pfs.pfs_id % DIDBITS));
180
181 /*
182 * Create a snapshot directory if necessary, and a config file if
183 * necessary.
184 */
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",
190 strerror(errno));
191 return;
192 }
193 }
194 asprintf(&config_path, "%s/config", snapshots_path);
195 if ((fp = fopen(config_path, "r")) == NULL) {
196 fp = fopen(config_path, "w");
197 if (fp == NULL) {
198 printf(" cannot create %s: %s\n",
199 config_path, strerror(errno));
200 return;
201 }
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");
206 } else {
207 fprintf(fp, "snapshots 1d 60d\n");
208 }
209 fprintf(fp,
210 "prune 1d 5m\n"
211 "reblock 1d 5m\n"
212 "recopy 30d 10m\n");
213 fclose(fp);
214 fp = fopen(config_path, "r");
215 }
216 if (fp == NULL) {
217 printf(" cannot access %s: %s\n",
218 config_path, strerror(errno));
219 return;
220 }
221
222 printf(" processing PFS #%d\n", pfs.pfs_id);
223
224 /*
225 * Process the config file
226 */
227 while (fgets(buf, sizeof(buf), fp) != NULL) {
228 cmd = strtok(buf, WS);
229 arg1 = 0;
230 arg2 = 0;
231 if ((ptr = strtok(NULL, WS)) != NULL) {
232 arg1 = strtosecs(ptr);
233 if ((ptr = strtok(NULL, WS)) != NULL)
234 arg2 = strtosecs(ptr);
235 }
236
237 printf("%20s - ", cmd);
238 fflush(stdout);
239
240 if (arg1 == 0) {
241 printf("disabled\n");
242 continue;
243 }
244
245 r = 1;
246 if (strcmp(cmd, "snapshots") == 0) {
247 if (check_period(snapshots_path, cmd, arg1, &savet)) {
248 printf("run\n");
249 r = cleanup_snapshots(path, snapshots_path,
250 arg1, arg2);
251 } else {
252 printf("skip\n");
253 }
254 } else if (strcmp(cmd, "prune") == 0) {
255 if (check_period(snapshots_path, cmd, arg1, &savet)) {
256 r = cleanup_prune(path, snapshots_path,
257 arg1, arg2);
258 } else {
259 printf("skip\n");
260 }
261 } else if (strcmp(cmd, "reblock") == 0) {
262 if (check_period(snapshots_path, cmd, arg1, &savet)) {
263 printf("run");
264 fflush(stdout);
265 if (VerboseOpt)
266 printf("\n");
267 r = cleanup_reblock(path, snapshots_path,
268 arg1, arg2);
269 } else {
270 printf("skip\n");
271 }
272 } else if (strcmp(cmd, "recopy") == 0) {
273 if (check_period(snapshots_path, cmd, arg1, &savet)) {
274 printf("run");
275 fflush(stdout);
276 if (VerboseOpt)
277 printf("\n");
278 r = cleanup_recopy(path, snapshots_path,
279 arg1, arg2);
280 } else {
281 printf("skip\n");
282 }
283 } else {
284 printf("unknown directive\n");
285 r = 1;
286 }
287 if (r == 0)
288 save_period(snapshots_path, cmd, savet);
289 }
290 fclose(fp);
291 usleep(1000);
292}
293
294static
295int
296strtosecs(char *ptr)
297{
298 int val;
299
300 val = strtol(ptr, &ptr, 0);
301 switch(*ptr) {
302 case 'd':
303 val *= 24;
304 /* fall through */
305 case 'h':
306 val *= 60;
307 /* fall through */
308 case 'm':
309 val *= 60;
310 /* fall through */
311 case 's':
312 break;
313 default:
314 errx(1, "illegal suffix converting %s\n", ptr);
315 break;
316 }
317 return(val);
318}
319
320static const char *
321dividing_slash(const char *path)
322{
323 int len = strlen(path);
324 if (len && path[len-1] == '/')
325 return("");
326 else
327 return("/");
328}
329
330/*
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.
334 *
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.
338 */
339static int
340check_period(const char *snapshots_path, const char *cmd, int arg1,
341 time_t *savep)
342{
343 char *check_path;
344 struct tm tp1;
345 struct tm tp2;
346 FILE *fp;
347 time_t baset, lastt;
348 char buf[256];
349
350 time(savep);
351 localtime_r(savep, &tp1);
352
353 /*
354 * Retrieve the start time of the last successful operation.
355 */
356 asprintf(&check_path, "%s/.%s.period", snapshots_path, cmd);
357 fp = fopen(check_path, "r");
358 free(check_path);
359 if (fp == NULL)
360 return(1);
361 if (fgets(buf, sizeof(buf), fp) == NULL) {
362 fclose(fp);
363 return(1);
364 }
365 fclose(fp);
366
367 lastt = strtol(buf, NULL, 0);
368 localtime_r(&lastt, &tp2);
369
370 /*
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.
375 */
376 if (arg1 % 60 == 0) {
377 tp1.tm_sec = 0;
378 tp2.tm_sec = 0;
379 }
380 if (arg1 % (60 * 60) == 0) {
381 tp1.tm_min = 0;
382 tp2.tm_min = 0;
383 }
384 if (arg1 % (24 * 60 * 60) == 0) {
385 tp1.tm_hour = 0;
386 tp2.tm_hour = 0;
387 }
388
389 baset = mktime(&tp1);
390 lastt = mktime(&tp2);
391
392#if 0
393 printf("%lld vs %lld\n", (long long)(baset - lastt), (long long)arg1);
394#endif
395
396 if ((int)(baset - lastt) >= arg1)
397 return(1);
398 return(0);
399}
400
401/*
402 * Store the start time of the last successful operation.
403 */
404static void
405save_period(const char *snapshots_path, const char *cmd,
406 time_t savet)
407{
408 char *ocheck_path;
409 char *ncheck_path;
410 FILE *fp;
411
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);
416 if (fclose(fp) == 0)
417 rename(ncheck_path, ocheck_path);
418 remove(ncheck_path);
419}
420
421/*
422 * Issue a snapshot.
423 */
424static int
425cleanup_snapshots(const char *path __unused, const char *snapshots_path,
426 int arg1 __unused, int arg2 __unused)
427{
428 int r;
429
430 runcmd(&r, "hammer snapshot %s", snapshots_path);
431 return(r);
432}
433
434static int
435cleanup_prune(const char *path __unused, const char *snapshots_path,
436 int arg1 __unused, int arg2)
437{
438 if (arg2) {
439 runcmd(NULL, "hammer -c %s/.prune.cycle -t %d prune %s",
440 snapshots_path, arg2, snapshots_path);
441 } else {
442 runcmd(NULL, "hammer prune %s", snapshots_path);
443 }
444 return(0);
445}
446
447static int
448cleanup_reblock(const char *path, const char *snapshots_path,
449 int arg1 __unused, int arg2)
450{
451 if (VerboseOpt == 0) {
452 printf(".");
453 fflush(stdout);
454 }
455 runcmd(NULL,
456 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s 95",
457 snapshots_path, arg2, path);
458 if (VerboseOpt == 0) {
459 printf(".");
460 fflush(stdout);
461 }
462 runcmd(NULL,
463 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s 95",
464 snapshots_path, arg2, path);
465 if (VerboseOpt == 0) {
466 printf(".");
467 fflush(stdout);
468 }
469 runcmd(NULL,
470 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
471 snapshots_path, arg2, path);
472 if (VerboseOpt == 0)
473 printf("\n");
474 return(0);
475}
476
477static int
478cleanup_recopy(const char *path, const char *snapshots_path,
479 int arg1 __unused, int arg2)
480{
481 if (VerboseOpt == 0) {
482 printf(".");
483 fflush(stdout);
484 }
485 runcmd(NULL,
486 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
487 snapshots_path, arg2, path);
488 if (VerboseOpt == 0) {
489 printf(".");
490 fflush(stdout);
491 }
492 runcmd(NULL,
493 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
494 snapshots_path, arg2, path);
495 if (VerboseOpt == 0) {
496 printf(".");
497 fflush(stdout);
498 }
499 runcmd(NULL,
500 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
501 snapshots_path, arg2, path);
502 if (VerboseOpt == 0)
503 printf("\n");
504 return(0);
505}
506
507static
508void
509runcmd(int *resp, const char *ctl, ...)
510{
511 va_list va;
512 char *cmd;
513 char *arg;
514 char **av;
515 int n;
516 int nmax;
517 int res;
518 pid_t pid;
519
520 /*
521 * Generate the command
522 */
523 va_start(va, ctl);
524 vasprintf(&cmd, ctl, va);
525 va_end(va);
526 if (VerboseOpt)
527 printf(" %s\n", cmd);
528
529 /*
530 * Break us down into arguments. We do not just use system() here
531 * because it blocks SIGINT and friends.
532 */
533 n = 0;
534 nmax = 16;
535 av = malloc(sizeof(char *) * nmax);
536
537 for (arg = strtok(cmd, WS); arg; arg = strtok(NULL, WS)) {
538 if (n == nmax) {
539 nmax += 16;
540 av = realloc(av, sizeof(char *) * nmax);
541 }
542 av[n++] = arg;
543 }
544
545 /*
546 * Run the command.
547 */
548 if ((pid = fork()) == 0) {
549 if (VerboseOpt < 2) {
550 int fd = open("/dev/null", O_RDWR);
551 dup2(fd, 1);
552 close(fd);
553 }
554 execvp(av[0], av);
555 _exit(127);
556 } else if (pid < 0) {
557 res = 127;
558 } else {
559 int status;
560 while (waitpid(pid, &status, 0) != pid)
561 ;
562 res = WEXITSTATUS(status);
563 }
564
565 free(cmd);
566 free(av);
567 if (resp)
568 *resp = res;
569}
570
571