If snapshots are disabled and the snapshots directory contains no
[dragonfly.git] / sbin / hammer / cmd_cleanup.c
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.2 2008/09/20 06:46:22 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
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,
63                         time_t *savep);
64 static void save_period(const char *snapshots_path, const char *cmd,
65                         time_t savet);
66 static int check_softlinks(const char *snapshots_path);
67
68 static int cleanup_snapshots(const char *path, const char *snapshots_path,
69                               int arg1, int arg2);
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,
73                               int arg1, int arg2);
74 static int cleanup_recopy(const char *path, const char *snapshots_path,
75                               int arg1, int arg2);
76
77 static 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
83 static int DidPFS[MAXPFS/DIDBITS];
84
85 void
86 hammer_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
126 static
127 void
128 do_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;
142         int snapshots_disabled = 0;
143         int prune_warning = 0;
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");
245                         if (strcmp(cmd, "snapshots") == 0) {
246                                 if (check_softlinks(snapshots_path))
247                                         prune_warning = 1;
248                                 else
249                                         snapshots_disabled = 1;
250                         }
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)) {
265                                 if (prune_warning)
266                                         printf("run - WARNING snapshot softlinks present but snapshots disabled\n");
267                                 else
268                                         printf("run\n");
269                                 r = cleanup_prune(path, snapshots_path,
270                                               arg1, arg2, snapshots_disabled);
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
307 static
308 int
309 strtosecs(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
333 static const char *
334 dividing_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  */
352 static int
353 check_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  */
417 static void
418 save_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
434 static int
435 check_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
461 /*
462  * Issue a snapshot.
463  */
464 static int
465 cleanup_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
474 static int
475 cleanup_prune(const char *path __unused, const char *snapshots_path,
476                   int arg1 __unused, int arg2, int snapshots_disabled)
477 {
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) {
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
496 static int
497 cleanup_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
526 static int
527 cleanup_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
556 static
557 void
558 runcmd(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