2 * Copyright (c) 2008 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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
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
34 * $DragonFly: src/sbin/hammer/cmd_cleanup.c,v 1.6 2008/10/07 22:28:41 thomas 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
48 * snapshots 1d 60d (0d 0d for /tmp, /var/tmp, /usr/obj)
53 * All hammer commands create and maintain cycle files in the snapshots
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
,
69 static void save_period(const char *snapshots_path
, const char *cmd
,
71 static int check_softlinks(const char *snapshots_path
);
72 static void cleanup_softlinks(const char *path
, const char *snapshots_path
,
73 int arg2
, char *arg3
);
74 static int check_expired(const char *fpath
, int arg2
);
76 static int create_snapshot(const char *path
, const char *snapshots_path
,
78 static int cleanup_prune(const char *path
, const char *snapshots_path
,
79 int arg1
, int arg2
, int snapshots_disabled
);
80 static int cleanup_reblock(const char *path
, const char *snapshots_path
,
82 static int cleanup_recopy(const char *path
, const char *snapshots_path
,
85 static void runcmd(int *resp
, const char *ctl
, ...);
89 struct didpfs
*FirstPFS
;
92 hammer_cmd_cleanup(char **av
, int ac
)
95 char *fs
, *ptr
, *path
;
100 fp
= popen("/sbin/mount -t hammer,null", "r");
102 errx(1, "hammer cleanup: 'mount' failed");
103 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
104 fs
= strtok(buf
, WS
);
107 ptr
= strtok(NULL
, WS
);
110 path
= strtok(NULL
, WS
);
113 ptr
= strtok(NULL
, WS
);
116 if ((strncmp(ptr
, "(hammer,", 8) == 0) ||
117 ((strncmp(ptr
, "(null,", 6) == 0) &&
118 (strstr(fs
, "/@@0x") != NULL
||
119 strstr(fs
, "/@@-1") != NULL
))) {
135 do_cleanup(const char *path
)
137 struct hammer_ioc_pseudofs_rw pfs
;
138 union hammer_ioc_mrecord_any mrec_tmp
;
139 char *snapshots_path
;
150 struct didpfs
*didpfs
;
151 int snapshots_disabled
= 0;
152 int prune_warning
= 0;
156 bzero(&pfs
, sizeof(pfs
));
157 bzero(&mrec_tmp
, sizeof(mrec_tmp
));
158 pfs
.ondisk
= &mrec_tmp
.pfs
.pfsd
;
159 pfs
.bytes
= sizeof(mrec_tmp
.pfs
.pfsd
);
162 printf("cleanup %-20s -", path
);
163 fd
= open(path
, O_RDONLY
);
165 printf(" unable to access directory: %s\n", strerror(errno
));
168 if (ioctl(fd
, HAMMERIOC_GET_PSEUDOFS
, &pfs
) != 0) {
169 printf(" not a HAMMER filesystem: %s\n", strerror(errno
));
173 if (pfs
.version
!= HAMMER_IOC_PSEUDOFS_VERSION
) {
174 printf(" unrecognized HAMMER version\n");
179 * Make sure we have not already handled this PFS. Several nullfs
180 * mounts might alias the same PFS.
182 for (didpfs
= FirstPFS
; didpfs
; didpfs
= didpfs
->next
) {
183 if (bcmp(&didpfs
->uuid
, &mrec_tmp
.pfs
.pfsd
.unique_uuid
, sizeof(uuid_t
)) == 0) {
184 printf(" PFS #%d already handled\n", pfs
.pfs_id
);
188 didpfs
= malloc(sizeof(*didpfs
));
189 didpfs
->next
= FirstPFS
;
191 didpfs
->uuid
= mrec_tmp
.pfs
.pfsd
.unique_uuid
;
194 * Figure out where the snapshot directory is.
196 if (mrec_tmp
.pfs
.pfsd
.snapshots
[0] == '/') {
197 asprintf(&snapshots_path
, "%s", mrec_tmp
.pfs
.pfsd
.snapshots
);
198 } else if (mrec_tmp
.pfs
.pfsd
.snapshots
[0]) {
199 printf(" WARNING: pfs-slave's snapshots dir is not absolute\n");
201 } else if (mrec_tmp
.pfs
.pfsd
.mirror_flags
& HAMMER_PFSD_SLAVE
) {
202 printf(" WARNING: must configure snapshot dir for PFS slave\n");
203 printf("\tWe suggest <fs>/var/slaves/<name> where "
204 "<fs> is the base HAMMER fs\n");
205 printf("\tcontaining the slave\n");
208 asprintf(&snapshots_path
,
209 "%s%ssnapshots", path
, dividing_slash(path
));
213 * Create a snapshot directory if necessary, and a config file if
216 if (stat(snapshots_path
, &st
) < 0) {
217 if (mkdir(snapshots_path
, 0755) != 0) {
218 free(snapshots_path
);
219 printf(" unable to create snapshot dir \"%s\": %s\n",
220 snapshots_path
, strerror(errno
));
224 asprintf(&config_path
, "%s/config", snapshots_path
);
225 if ((fp
= fopen(config_path
, "r")) == NULL
) {
226 fp
= fopen(config_path
, "w");
228 printf(" cannot create %s: %s\n",
229 config_path
, strerror(errno
));
232 if (strcmp(path
, "/tmp") == 0 ||
233 strcmp(path
, "/var/tmp") == 0 ||
234 strcmp(path
, "/usr/obj") == 0) {
235 fprintf(fp
, "snapshots 0d 0d\n");
237 fprintf(fp
, "snapshots 1d 60d\n");
244 fp
= fopen(config_path
, "r");
247 printf(" cannot access %s: %s\n",
248 config_path
, strerror(errno
));
252 if (flock(fileno(fp
), LOCK_EX
|LOCK_NB
) == -1) {
253 if (errno
== EWOULDBLOCK
)
254 printf(" PFS #%d locked by other process\n", pfs
.pfs_id
);
256 printf(" can not lock %s: %s\n", config_path
, strerror(errno
));
261 printf(" handle PFS #%d using %s\n", pfs
.pfs_id
, snapshots_path
);
264 * Process the config file
266 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
267 cmd
= strtok(buf
, WS
);
271 if ((ptr
= strtok(NULL
, WS
)) != NULL
) {
272 arg1
= strtosecs(ptr
);
273 if ((ptr
= strtok(NULL
, WS
)) != NULL
) {
274 arg2
= strtosecs(ptr
);
275 arg3
= strtok(NULL
, WS
);
279 printf("%20s - ", cmd
);
283 if (strcmp(cmd
, "snapshots") == 0) {
285 if (arg2
&& check_softlinks(snapshots_path
)) {
286 printf("only removing old snapshots\n");
288 cleanup_softlinks(path
, snapshots_path
,
291 printf("disabled\n");
292 snapshots_disabled
= 1;
295 if (check_period(snapshots_path
, cmd
, arg1
, &savet
)) {
297 cleanup_softlinks(path
, snapshots_path
,
299 r
= create_snapshot(path
, snapshots_path
,
304 } else if (arg1
== 0) {
306 * The commands following this check can't handle
307 * a period of 0, so call the feature disabled and
308 * ignore the directive.
310 printf("disabled\n");
311 } else if (strcmp(cmd
, "prune") == 0) {
312 if (check_period(snapshots_path
, cmd
, arg1
, &savet
)) {
314 printf("run - WARNING snapshot "
316 "but snapshots disabled\n");
320 r
= cleanup_prune(path
, snapshots_path
,
321 arg1
, arg2
, snapshots_disabled
);
325 } else if (strcmp(cmd
, "reblock") == 0) {
326 if (check_period(snapshots_path
, cmd
, arg1
, &savet
)) {
331 r
= cleanup_reblock(path
, snapshots_path
,
336 } else if (strcmp(cmd
, "recopy") == 0) {
337 if (check_period(snapshots_path
, cmd
, arg1
, &savet
)) {
342 r
= cleanup_recopy(path
, snapshots_path
,
348 printf("unknown directive\n");
352 save_period(snapshots_path
, cmd
, savet
);
364 val
= strtol(ptr
, &ptr
, 0);
378 errx(1, "illegal suffix converting %s\n", ptr
);
385 dividing_slash(const char *path
)
387 int len
= strlen(path
);
388 if (len
&& path
[len
-1] == '/')
395 * Check whether the desired period has elapsed since the last successful
396 * run. The run may take a while and cross a boundary so we remember the
397 * current time_t so we can save it later on.
399 * Periods in minutes, hours, or days are assumed to have been crossed
400 * if the local time crosses a minute, hour, or day boundary regardless
401 * of how close the last operation actually was.
404 check_period(const char *snapshots_path
, const char *cmd
, int arg1
,
415 localtime_r(savep
, &tp1
);
418 * Retrieve the start time of the last successful operation.
420 asprintf(&check_path
, "%s/.%s.period", snapshots_path
, cmd
);
421 fp
= fopen(check_path
, "r");
425 if (fgets(buf
, sizeof(buf
), fp
) == NULL
) {
431 lastt
= strtol(buf
, NULL
, 0);
432 localtime_r(&lastt
, &tp2
);
435 * Normalize the times. e.g. if asked to do something on a 1-day
436 * interval the operation will be performed as soon as the day
437 * turns over relative to the previous operation, even if the previous
438 * operation ran a few seconds ago just before midnight.
440 if (arg1
% 60 == 0) {
444 if (arg1
% (60 * 60) == 0) {
448 if (arg1
% (24 * 60 * 60) == 0) {
453 baset
= mktime(&tp1
);
454 lastt
= mktime(&tp2
);
457 printf("%lld vs %lld\n", (long long)(baset
- lastt
), (long long)arg1
);
460 if ((int)(baset
- lastt
) >= arg1
)
466 * Store the start time of the last successful operation.
469 save_period(const char *snapshots_path
, const char *cmd
,
476 asprintf(&ocheck_path
, "%s/.%s.period", snapshots_path
, cmd
);
477 asprintf(&ncheck_path
, "%s/.%s.period.new", snapshots_path
, cmd
);
478 fp
= fopen(ncheck_path
, "w");
480 fprintf(fp
, "0x%08llx\n", (long long)savet
);
482 rename(ncheck_path
, ocheck_path
);
485 fprintf(stderr
, "hammer: Unable to create period-file %s: %s\n",
486 ncheck_path
, strerror(errno
));
491 * Simply count the number of softlinks in the snapshots dir
494 check_softlinks(const char *snapshots_path
)
502 if ((dir
= opendir(snapshots_path
)) != NULL
) {
503 while ((den
= readdir(dir
)) != NULL
) {
504 if (den
->d_name
[0] == '.')
506 asprintf(&fpath
, "%s/%s", snapshots_path
, den
->d_name
);
507 if (lstat(fpath
, &st
) == 0 && S_ISLNK(st
.st_mode
))
517 * Clean up expired softlinks in the snapshots dir
520 cleanup_softlinks(const char *path __unused
, const char *snapshots_path
,
521 int arg2
, char *arg3
)
529 if (arg3
!= NULL
&& strstr(arg3
, "any") != NULL
)
532 if ((dir
= opendir(snapshots_path
)) != NULL
) {
533 while ((den
= readdir(dir
)) != NULL
) {
534 if (den
->d_name
[0] == '.')
536 asprintf(&fpath
, "%s/%s", snapshots_path
, den
->d_name
);
537 if (lstat(fpath
, &st
) == 0 && S_ISLNK(st
.st_mode
) &&
538 (anylink
|| strncmp(den
->d_name
, "snap-", 5) == 0)
540 if (check_expired(den
->d_name
, arg2
)) {
542 printf(" expire %s\n",
555 * Take a softlink path in the form snap-yyyymmdd-hhmm and the
556 * expiration in seconds (arg2) and return non-zero if the softlink
560 check_expired(const char *fpath
, int arg2
)
571 while (*fpath
&& *fpath
!= '-' && *fpath
!= '.')
576 r
= sscanf(fpath
, "%4d%2d%2d-%2d%2d",
577 &year
, &month
, &day
, &hour
, &minute
);
580 bzero(&tm
, sizeof(tm
));
585 tm
.tm_mon
= month
- 1;
586 tm
.tm_year
= year
- 1900;
601 create_snapshot(const char *path __unused
, const char *snapshots_path
,
602 int arg1 __unused
, int arg2 __unused
)
606 runcmd(&r
, "hammer snapshot %s %s", path
, snapshots_path
);
611 cleanup_prune(const char *path __unused
, const char *snapshots_path
,
612 int arg1 __unused
, int arg2
, int snapshots_disabled
)
615 * If snapshots have been disabled run prune-everything instead
618 if (snapshots_disabled
&& arg2
) {
619 runcmd(NULL
, "hammer -c %s/.prune.cycle -t %d prune-everything %s",
620 snapshots_path
, arg2
, path
);
621 } else if (snapshots_disabled
) {
622 runcmd(NULL
, "hammer prune-everything %s", path
);
624 runcmd(NULL
, "hammer -c %s/.prune.cycle -t %d prune %s",
625 snapshots_path
, arg2
, snapshots_path
);
627 runcmd(NULL
, "hammer prune %s", snapshots_path
);
633 cleanup_reblock(const char *path
, const char *snapshots_path
,
634 int arg1 __unused
, int arg2
)
636 if (VerboseOpt
== 0) {
642 * When reblocking the B-Tree always reblock everything in normal
646 "hammer -c %s/.reblock-1.cycle -t %d reblock-btree %s",
647 snapshots_path
, arg2
, path
);
648 if (VerboseOpt
== 0) {
654 * When reblocking the inodes always reblock everything in normal
658 "hammer -c %s/.reblock-2.cycle -t %d reblock-inodes %s",
659 snapshots_path
, arg2
, path
);
660 if (VerboseOpt
== 0) {
666 * When reblocking the directories always reblock everything in normal
670 "hammer -c %s/.reblock-4.cycle -t %d reblock-dirs %s",
671 snapshots_path
, arg2
, path
);
672 if (VerboseOpt
== 0) {
678 * Do not reblock all the data in normal mode.
681 "hammer -c %s/.reblock-3.cycle -t %d reblock-data %s 95",
682 snapshots_path
, arg2
, path
);
689 cleanup_recopy(const char *path
, const char *snapshots_path
,
690 int arg1 __unused
, int arg2
)
692 if (VerboseOpt
== 0) {
697 "hammer -c %s/.recopy-1.cycle -t %d reblock-btree %s",
698 snapshots_path
, arg2
, path
);
699 if (VerboseOpt
== 0) {
704 "hammer -c %s/.recopy-2.cycle -t %d reblock-inodes %s",
705 snapshots_path
, arg2
, path
);
706 if (VerboseOpt
== 0) {
711 "hammer -c %s/.recopy-4.cycle -t %d reblock-dirs %s",
712 snapshots_path
, arg2
, path
);
713 if (VerboseOpt
== 0) {
718 "hammer -c %s/.recopy-3.cycle -t %d reblock-data %s",
719 snapshots_path
, arg2
, path
);
727 runcmd(int *resp
, const char *ctl
, ...)
739 * Generate the command
742 vasprintf(&cmd
, ctl
, va
);
745 printf(" %s\n", cmd
);
748 * Break us down into arguments. We do not just use system() here
749 * because it blocks SIGINT and friends.
753 av
= malloc(sizeof(char *) * nmax
);
755 for (arg
= strtok(cmd
, WS
); arg
; arg
= strtok(NULL
, WS
)) {
758 av
= realloc(av
, sizeof(char *) * nmax
);
768 if ((pid
= fork()) == 0) {
769 if (VerboseOpt
< 2) {
770 int fd
= open("/dev/null", O_RDWR
);
776 } else if (pid
< 0) {
781 while (waitpid(pid
, &status
, 0) != pid
)
783 res
= WEXITSTATUS(status
);