amd64: Update some header files for the vkernel.
[dragonfly.git] / sbin / hammer / cmd_softprune.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_softprune.c,v 1.7 2008/08/21 23:28:43 thomas Exp $
35  */
36
37 #include "hammer.h"
38
39 struct softprune {
40         struct softprune *next;
41         struct statfs fs;
42         char *filesystem;
43         struct hammer_ioc_prune prune;
44         int maxelms;
45         int prune_min;
46 };
47
48 static void softprune_usage(int code);
49 static void hammer_softprune_scandir(struct softprune **basep,
50                         struct hammer_ioc_prune *template,
51                         const char *dirname);
52 static int hammer_softprune_scanmeta(int fd, struct softprune *scan,
53                         int delete_all);
54 static void hammer_meta_flushdelete(int fd, struct hammer_ioc_snapshot *dsnap);
55 static struct softprune *hammer_softprune_addentry(struct softprune **basep,
56                         struct hammer_ioc_prune *template,
57                         const char *dirpath, const char *denname,
58                         struct stat *st,
59                         const char *linkbuf, const char *tidptr);
60 static void hammer_softprune_addelm(struct softprune *scan, hammer_tid_t tid,
61                         time_t ct, time_t mt);
62 static void hammer_softprune_finalize(struct softprune *scan);
63
64 /*
65  * prune <softlink-dir>
66  * prune-everything <filesystem>
67  */
68 void
69 hammer_cmd_softprune(char **av, int ac, int everything_opt)
70 {
71         struct hammer_ioc_prune template;
72         struct hammer_ioc_pseudofs_rw pfs;
73         struct softprune *base, *scan;
74         int fd;
75         int rcode;
76
77         base = NULL;
78         rcode = 0;
79         if (TimeoutOpt > 0)
80                 alarm(TimeoutOpt);
81
82         bzero(&pfs, sizeof(pfs));
83         pfs.bytes = sizeof(*pfs.ondisk);
84         pfs.ondisk = malloc(pfs.bytes);
85         bzero(pfs.ondisk, pfs.bytes);
86         pfs.pfs_id = -1;
87
88         /*
89          * NOTE: To restrict to a single file XXX we have to set
90          * the localization the same (not yet implemented).  Typically
91          * two passes would be needed, one using HAMMER_LOCALIZE_MISC
92          * and one using HAMMER_LOCALIZE_INODE.
93          */
94
95         bzero(&template, sizeof(template));
96         template.key_beg.localization = HAMMER_MIN_LOCALIZATION;
97         template.key_beg.obj_id = HAMMER_MIN_OBJID;
98         template.key_end.localization = HAMMER_MAX_LOCALIZATION;
99         template.key_end.obj_id = HAMMER_MAX_OBJID;
100         hammer_get_cycle(&template.key_end, NULL);
101         template.stat_oldest_tid = HAMMER_MAX_TID;
102
103         /*
104          * For now just allow one directory
105          */
106         if (ac == 0 || ac > 1)
107                 softprune_usage(1);
108
109         /*
110          * Scan the softlink directory.
111          */
112         if (everything_opt) {
113                 const char *dummylink = "";
114                 scan = hammer_softprune_addentry(&base, &template,
115                                                  *av, NULL, NULL,
116                                                  dummylink, dummylink);
117                 if (scan == NULL)
118                         softprune_usage(1);
119                 scan->prune.nelms = 0;
120                 scan->prune.head.flags |= HAMMER_IOC_PRUNE_ALL;
121         } else {
122                 hammer_softprune_scandir(&base, &template, *av);
123                 ++av;
124                 --ac;
125         }
126
127         /*
128          * XXX future (need to store separate cycles for each filesystem)
129          */
130         if (base == NULL) {
131                 fprintf(stderr, "No snapshot softlinks found\n");
132                 exit(1);
133         }
134         if (base->next) {
135                 fprintf(stderr, "Currently only one HAMMER filesystem may "
136                                 "be specified in the softlink scan\n");
137                 exit(1);
138         }
139
140         /*
141          * Issue the prunes
142          */
143         for (scan = base; scan; scan = scan->next) {
144                 /*
145                  * Open the filesystem for ioctl calls and extract the
146                  * PFS.
147                  */
148                 fd = open(scan->filesystem, O_RDONLY);
149                 if (fd < 0) {
150                         warn("Unable to open %s", scan->filesystem);
151                         rcode = 1;
152                         continue;
153                 }
154
155                 if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) {
156                         warn("Filesystem %s is not HAMMER", scan->filesystem);
157                         rcode = 1;
158                         close(fd);
159                         continue;
160                 }
161                 scan->prune_min = pfs.ondisk->prune_min;
162
163                 /*
164                  * Incorporate meta-data snapshots into the pruning regimen.
165                  * If pruning everything we delete the meta-data snapshots.
166                  */
167                 if (hammer_softprune_scanmeta(fd, scan, everything_opt) < 0) {
168                         warn("Filesystem %s could not scan meta-data snaps",
169                              scan->filesystem);
170                         rcode = 1;
171                         close(fd);
172                         continue;
173                 }
174
175                 /*
176                  * Finalize operations
177                  */
178                 hammer_softprune_finalize(scan);
179                 if (everything_opt) {
180                         printf("Prune %s: EVERYTHING\n",
181                                scan->filesystem);
182                 } else {
183                         printf("Prune %s: %d snapshots\n",
184                                scan->filesystem, scan->prune.nelms);
185                 }
186                 if (scan->prune.nelms == 0 &&
187                     (scan->prune.head.flags & HAMMER_IOC_PRUNE_ALL) == 0) {
188                         continue;
189                 }
190
191                 printf("Prune %s: objspace %016jx:%04x %016jx:%04x "
192                        "pfs_id %d\n",
193                        scan->filesystem,
194                        (uintmax_t)scan->prune.key_beg.obj_id,
195                        scan->prune.key_beg.localization,
196                        (uintmax_t)scan->prune.key_end.obj_id,
197                        scan->prune.key_end.localization,
198                        pfs.pfs_id);
199                 printf("Prune %s: prune_min is %dd/%02d:%02d:%02d\n",
200                        scan->filesystem,
201                         pfs.ondisk->prune_min / (24 * 60 * 60),
202                         pfs.ondisk->prune_min / 60 / 60 % 24,
203                         pfs.ondisk->prune_min / 60 % 60,
204                         pfs.ondisk->prune_min % 60);
205
206                 RunningIoctl = 1;
207                 if (ioctl(fd, HAMMERIOC_PRUNE, &scan->prune) < 0) {
208                         printf("Prune %s failed: %s\n",
209                                scan->filesystem, strerror(errno));
210                         rcode = 2;
211                 } else if (scan->prune.head.flags & HAMMER_IOC_HEAD_INTR) {
212                         printf("Prune %s interrupted by timer at "
213                                "%016jx %04x\n",
214                                scan->filesystem,
215                                (uintmax_t)scan->prune.key_cur.obj_id,
216                                scan->prune.key_cur.localization);
217                         if (CyclePath)
218                                 hammer_set_cycle(&scan->prune.key_cur, 0);
219                         rcode = 0;
220                 } else {
221                         if (CyclePath)
222                                 hammer_reset_cycle();
223                         printf("Prune %s succeeded\n", scan->filesystem);
224                 }
225                 printf("Pruned %jd/%jd records (%jd directory entries) "
226                        "and %jd bytes\n",
227                         (uintmax_t)scan->prune.stat_rawrecords,
228                         (uintmax_t)scan->prune.stat_scanrecords,
229                         (uintmax_t)scan->prune.stat_dirrecords,
230                         (uintmax_t)scan->prune.stat_bytes
231                 );
232                 RunningIoctl = 0;
233                 close(fd);
234         }
235         if (rcode)
236                 exit(rcode);
237 }
238
239 /*
240  * Scan a directory for softlinks representing snapshots and build
241  * associated softprune structures.
242  *
243  * NOTE: Once a filesystem is completely converted to the meta-data
244  *       snapshot mechanic we don't have to scan softlinks any more
245  *       and can just use the meta-data.  But for now we do both.
246  */
247 static void
248 hammer_softprune_scandir(struct softprune **basep,
249                          struct hammer_ioc_prune *template,
250                          const char *dirname)
251 {
252         struct stat st;
253         struct dirent *den;
254         DIR *dir;
255         char *path;
256         int len;
257         char *linkbuf;
258         char *ptr;
259
260         path = NULL;
261         linkbuf = malloc(MAXPATHLEN);
262
263         if ((dir = opendir(dirname)) == NULL)
264                 err(1, "Cannot open directory %s", dirname);
265         while ((den = readdir(dir)) != NULL) {
266                 if (strcmp(den->d_name, ".") == 0)
267                         continue;
268                 if (strcmp(den->d_name, "..") == 0)
269                         continue;
270                 if (path)
271                         free(path);
272                 asprintf(&path, "%s/%s", dirname, den->d_name);
273                 if (lstat(path, &st) < 0)
274                         continue;
275                 if (!S_ISLNK(st.st_mode))
276                         continue;
277                 if ((len = readlink(path, linkbuf, MAXPATHLEN - 1)) < 0)
278                         continue;
279                 linkbuf[len] = 0;
280                 if ((ptr = strrchr(linkbuf, '@')) &&
281                     ptr > linkbuf && ptr[-1] == '@') {
282                         hammer_softprune_addentry(basep, template,
283                                                   dirname, den->d_name, &st,
284                                                   linkbuf, ptr - 1);
285                 }
286         }
287         free(linkbuf);
288         if (path)
289                 free(path);
290 }
291
292 /*
293  * Scan the metadata snapshots for the filesystem and either delete them
294  * or add them to the pruning list.
295  */
296 static
297 int
298 hammer_softprune_scanmeta(int fd, struct softprune *scan, int delete_all)
299 {
300         struct hammer_ioc_version       version;
301         struct hammer_ioc_snapshot      snapshot;
302         struct hammer_ioc_snapshot      dsnapshot;
303         struct hammer_snapshot_data     *snap;
304         time_t ct;
305
306         /*
307          * Stop if we can't get the version.  Meta-data snapshots only
308          * exist for HAMMER version 3 or greater.
309          */
310         bzero(&version, sizeof(version));
311         if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0)
312                 return(-1);
313         if (version.cur_version < 3)
314                 return(0);
315
316         bzero(&snapshot, sizeof(snapshot));
317         bzero(&dsnapshot, sizeof(dsnapshot));
318
319         /*
320          * Scan meta-data snapshots, either add them to the prune list or
321          * delete them.  When deleting, just skip any entries which cannot
322          * be deleted.
323          */
324         for (;;) {
325                 if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) {
326                         printf("hammer prune: Unable to access "
327                                "meta-data snaps: %s\n", strerror(errno));
328                         return(-1);
329                 }
330                 while (snapshot.index < snapshot.count) {
331                         snap = &snapshot.snaps[snapshot.index];
332                         if (delete_all) {
333                                 dsnapshot.snaps[dsnapshot.count++] = *snap;
334                                 if (dsnapshot.count == HAMMER_SNAPS_PER_IOCTL)
335                                         hammer_meta_flushdelete(fd, &dsnapshot);
336                         } else {
337                                 ct = snap->ts / 1000000ULL;
338                                 hammer_softprune_addelm(scan, snap->tid,
339                                                         ct, ct);
340                         }
341                         ++snapshot.index;
342                 }
343                 if (snapshot.head.flags & HAMMER_IOC_SNAPSHOT_EOF)
344                         break;
345                 snapshot.index = 0;
346         }
347         if (delete_all)
348                 hammer_meta_flushdelete(fd, &dsnapshot);
349         return(0);
350 }
351
352 /*
353  * Flush any entries built up in the deletion snapshot ioctl structure.
354  * Used during a prune-everything.
355  */
356 static void
357 hammer_meta_flushdelete(int fd, struct hammer_ioc_snapshot *dsnap)
358 {
359         while (dsnap->index < dsnap->count) {
360                 if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, dsnap) < 0)
361                         break;
362                 if (dsnap->head.error == 0)
363                         break;
364                 ++dsnap->index;
365         }
366         dsnap->index = 0;
367         dsnap->count = 0;
368 }
369
370 /*
371  * Add the softlink to the appropriate softprune structure, creating a new
372  * one if necessary.
373  */
374 static
375 struct softprune *
376 hammer_softprune_addentry(struct softprune **basep,
377                          struct hammer_ioc_prune *template,
378                          const char *dirpath, const char *denname __unused,
379                          struct stat *st,
380                          const char *linkbuf, const char *tidptr)
381 {
382         struct softprune *scan;
383         struct statfs fs;
384         char *fspath;
385
386         /*
387          * Calculate filesystem path.
388          */
389         if (linkbuf[0] == '/') {
390                 asprintf(&fspath, "%*.*s",
391                          (int)(tidptr - linkbuf), (int)(tidptr - linkbuf),
392                          linkbuf);
393         } else {
394                 asprintf(&fspath, "%s/%*.*s", dirpath,
395                          (int)(tidptr - linkbuf), (int)(tidptr - linkbuf),
396                          linkbuf);
397         }
398         if (statfs(fspath, &fs) < 0) {
399                 free(fspath);
400                 return(NULL);
401         }
402
403         /*
404          * Locate the filesystem in an existing softprune structure
405          */
406         for (scan = *basep; scan; scan = scan->next) {
407                 if (bcmp(&fs.f_fsid, &scan->fs.f_fsid, sizeof(fs.f_fsid)) != 0)
408                         continue;
409                 if (strcmp(fs.f_mntonname, scan->fs.f_mntonname) != 0)
410                         continue;
411                 break;
412         }
413
414         /*
415          * Create a new softprune structure if necessasry
416          */
417         if (scan == NULL) {
418                 scan = malloc(sizeof(*scan));
419                 bzero(scan, sizeof(*scan));
420
421                 scan->fs = fs;
422                 scan->filesystem = fspath;
423                 scan->prune = *template;
424                 scan->maxelms = 32;
425                 scan->prune.elms = malloc(sizeof(struct hammer_ioc_prune_elm) *
426                                           scan->maxelms);
427                 scan->next = *basep;
428                 *basep = scan;
429         } else {
430                 free(fspath);
431         }
432         hammer_softprune_addelm(scan,
433                                 (hammer_tid_t)strtoull(tidptr + 2, NULL, 0),
434                                 (st ? st->st_ctime : 0),
435                                 (st ? st->st_mtime : 0));
436         return(scan);
437 }
438
439 /*
440  * Add the entry (unsorted).  Just set the beg_tid, we will sort
441  * and set the remaining entries later.
442  *
443  * Always leave one entry free for our terminator.
444  */
445 static void
446 hammer_softprune_addelm(struct softprune *scan, hammer_tid_t tid,
447                         time_t ct, time_t mt)
448 {
449         struct hammer_ioc_prune_elm *elm;
450
451         if (scan->prune.nelms >= scan->maxelms - 1) {
452                 scan->maxelms = (scan->maxelms * 3 / 2);
453                 scan->prune.elms = realloc(scan->prune.elms,
454                                            sizeof(*elm) * scan->maxelms);
455         }
456
457         /*
458          * NOTE: Temporarily store the snapshot timestamp in mod_tid.
459          *       This will be cleaned up in the finalization phase.
460          */
461         elm = &scan->prune.elms[scan->prune.nelms];
462         elm->beg_tid = tid;
463         elm->end_tid = 0;
464         elm->mod_tid = 0;
465         if (ct < mt)
466                 elm->mod_tid = ct;
467         else
468                 elm->mod_tid = mt;
469         ++scan->prune.nelms;
470 }
471
472 /*
473  * Finalize a softprune structure after scanning in its softlinks.
474  * Sort the elements, remove duplicates, and then fill in end_tid and
475  * mod_tid.
476  *
477  * The array must end up in descending order.
478  */
479 static int
480 hammer_softprune_qsort_cmp(const void *arg1, const void *arg2)
481 {
482         const struct hammer_ioc_prune_elm *elm1 = arg1;
483         const struct hammer_ioc_prune_elm *elm2 = arg2;
484
485         if (elm1->beg_tid < elm2->beg_tid)
486                 return(1);
487         if (elm1->beg_tid > elm2->beg_tid)
488                 return(-1);
489         return(0);
490 }
491
492 static void
493 hammer_softprune_finalize(struct softprune *scan)
494 {
495         struct hammer_ioc_prune_elm *elm;
496         time_t t;
497         long delta;
498         int i;
499
500         /*
501          * Don't do anything if there are no elements.
502          */
503         if (scan->prune.nelms == 0)
504                 return;
505
506         /*
507          * Sort the elements in descending order, remove duplicates, and
508          * fill in any missing bits.
509          */
510         qsort(scan->prune.elms, scan->prune.nelms, sizeof(*elm), 
511               hammer_softprune_qsort_cmp);
512
513         for (i = 0; i < scan->prune.nelms; ++i) {
514                 elm = &scan->prune.elms[i];
515                 if (i == 0) {
516                         /*
517                          * First (highest TID) (also last if only one element)
518                          */
519                         elm->end_tid = HAMMER_MAX_TID;
520                 } else if (elm[0].beg_tid == elm[-1].beg_tid) {
521                         /*
522                          * Remove duplicate
523                          */
524                         --scan->prune.nelms;
525                         if (i != scan->prune.nelms) {
526                                 bcopy(elm + 1, elm,
527                                       (scan->prune.nelms - i) * sizeof(*elm));
528                         }
529                         --i;
530                         continue;
531                 } else {
532                         /*
533                          * Middle or last.
534                          */
535                         elm->end_tid = elm[-1].beg_tid;
536                 }
537         }
538
539         /*
540          * If a minimum retention time (in seconds) is configured for the
541          * PFS, remove any snapshots from the pruning list that are within
542          * the period.
543          */
544         if (scan->prune_min) {
545                 t = time(NULL);
546                 for (i = scan->prune.nelms - 1; i >= 0; --i) {
547                         elm = &scan->prune.elms[i];
548                         if (elm->mod_tid == 0)
549                                 continue;
550                         delta = (long)(t - (time_t)elm->mod_tid);
551                         if (delta < scan->prune_min)
552                                 break;
553                 }
554                 ++i;
555                 if (i) {
556                         printf("Prune %s: prune_min: Will not clean between "
557                                "the teeth of the first %d snapshots\n",
558                                scan->filesystem, i);
559                         bcopy(&scan->prune.elms[i], &scan->prune.elms[0],
560                               (scan->prune.nelms - i) * sizeof(scan->prune.elms[0]));
561                         scan->prune.elms[0].end_tid = HAMMER_MAX_TID;
562                         scan->prune.nelms -= i;
563                 }
564         }
565
566         /*
567          * Remove the first entry.  This entry represents the prune from
568          * the most recent snapshot to current.  We wish to retain the
569          * fine-grained history for this region.
570          */
571         if (scan->prune.nelms) {
572                 bcopy(&scan->prune.elms[1], &scan->prune.elms[0],
573                       (scan->prune.nelms - 1) * sizeof(scan->prune.elms[0]));
574                 --scan->prune.nelms;
575         }
576
577         /*
578          * Add a final element to prune everything from transaction id
579          * 0 to the lowest transaction id (aka last so far).
580          */
581         if (scan->prune.nelms) {
582                 assert(scan->prune.nelms < scan->maxelms);
583                 elm = &scan->prune.elms[scan->prune.nelms];
584                 elm->beg_tid = 1;
585                 elm->end_tid = elm[-1].beg_tid;
586                 ++scan->prune.nelms;
587         }
588
589         /*
590          * Adjust mod_tid to what the ioctl() expects.
591          */
592         for (i = 0; i < scan->prune.nelms; ++i) {
593                 elm = &scan->prune.elms[i];
594                 elm->mod_tid = elm->end_tid - elm->beg_tid;
595                 printf("TID %016jx - %016jx\n",
596                        (uintmax_t)elm->beg_tid, (uintmax_t)elm->end_tid);
597         }
598 }
599
600 static
601 void
602 softprune_usage(int code)
603 {
604         fprintf(stderr, "Badly formed prune command, use:\n");
605         fprintf(stderr, "hammer prune <softlink-dir>\n");
606         fprintf(stderr, "hammer prune-everything <filesystem>\n");
607         exit(code);
608 }
609
610