Add the 'hammer cleanup' command. This is a meta-command which will
[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.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
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
67 static int cleanup_snapshots(const char *path, const char *snapshots_path,
68                               int arg1, int arg2);
69 static int cleanup_prune(const char *path, const char *snapshots_path,
70                               int arg1, int arg2);
71 static int cleanup_reblock(const char *path, const char *snapshots_path,
72                               int arg1, int arg2);
73 static int cleanup_recopy(const char *path, const char *snapshots_path,
74                               int arg1, int arg2);
75
76 static 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
82 static int DidPFS[MAXPFS/DIDBITS];
83
84 void
85 hammer_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
125 static
126 void
127 do_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
294 static
295 int
296 strtosecs(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
320 static const char *
321 dividing_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  */
339 static int
340 check_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  */
404 static void
405 save_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  */
424 static int
425 cleanup_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
434 static int
435 cleanup_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
447 static int
448 cleanup_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
477 static int
478 cleanup_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
507 static
508 void
509 runcmd(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