Merge branch 'vendor/TCPDUMP'
[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 };
46
47 static void softprune_usage(int code);
48 static void hammer_softprune_scandir(struct softprune **basep,
49                          struct hammer_ioc_prune *template,
50                          const char *dirname);
51 static struct softprune *hammer_softprune_addentry(struct softprune **basep,
52                          struct hammer_ioc_prune *template,
53                          const char *dirpath,
54                          const char *linkbuf, const char *tidptr);
55 static void hammer_softprune_finalize(struct softprune *scan);
56
57 /*
58  * prune <softlink-dir>
59  * prune-everything <filesystem>
60  */
61 void
62 hammer_cmd_softprune(char **av, int ac, int everything_opt)
63 {
64         struct hammer_ioc_prune template;
65         struct softprune *base, *scan;
66         int fd;
67         int rcode;
68
69         base = NULL;
70         rcode = 0;
71         if (TimeoutOpt > 0)
72                 alarm(TimeoutOpt);
73
74         /*
75          * NOTE: To restrict to a single file XXX we have to set
76          * the localization the same (not yet implemented).  Typically
77          * two passes would be needed, one using HAMMER_LOCALIZE_MISC
78          * and one using HAMMER_LOCALIZE_INODE.
79          */
80
81         bzero(&template, sizeof(template));
82         template.key_beg.localization = HAMMER_MIN_LOCALIZATION;
83         template.key_beg.obj_id = HAMMER_MIN_OBJID;
84         template.key_end.localization = HAMMER_MAX_LOCALIZATION;
85         template.key_end.obj_id = HAMMER_MAX_OBJID;
86         hammer_get_cycle(&template.key_end, NULL);
87         template.stat_oldest_tid = HAMMER_MAX_TID;
88
89         /*
90          * For now just allow one directory
91          */
92         if (ac == 0 || ac > 1)
93                 softprune_usage(1);
94
95         /*
96          * Scan the softlink directory.
97          */
98         if (everything_opt) {
99                 const char *dummylink = "";
100                 scan = hammer_softprune_addentry(&base, &template, *av,
101                                                  dummylink, dummylink);
102                 if (scan == NULL)
103                         softprune_usage(1);
104                 scan->prune.nelms = 0;
105                 scan->prune.head.flags |= HAMMER_IOC_PRUNE_ALL;
106
107         } else {
108                 hammer_softprune_scandir(&base, &template, *av);
109                 ++av;
110                 --ac;
111         }
112
113         /*
114          * XXX future (need to store separate cycles for each filesystem)
115          */
116         if (base == NULL) {
117                 fprintf(stderr, "No snapshot softlinks found\n");
118                 exit(1);
119         }
120         if (base->next) {
121                 fprintf(stderr, "Currently only one HAMMER filesystem may "
122                                 "be specified in the softlink scan\n");
123                 exit(1);
124         }
125
126         /*
127          * Issue the prunes
128          */
129         for (scan = base; scan; scan = scan->next) {
130                 hammer_softprune_finalize(scan);
131                 if (everything_opt) {
132                         printf("Prune %s: EVERYTHING\n",
133                                scan->filesystem);
134                 } else {
135                         printf("Prune %s: %d snapshots\n",
136                                scan->filesystem, scan->prune.nelms);
137                 }
138                 if (scan->prune.nelms == 0 &&
139                     (scan->prune.head.flags & HAMMER_IOC_PRUNE_ALL) == 0) {
140                         continue;
141                 }
142                 fd = open(scan->filesystem, O_RDONLY);
143                 if (fd < 0) {
144                         warn("Unable to open %s", scan->filesystem);
145                         rcode = 1;
146                         continue;
147                 }
148                 printf("objspace %016llx:%04x %016llx:%04x\n",
149                        scan->prune.key_beg.obj_id,
150                        scan->prune.key_beg.localization,
151                        scan->prune.key_end.obj_id,
152                        scan->prune.key_end.localization);
153
154                 RunningIoctl = 1;
155                 if (ioctl(fd, HAMMERIOC_PRUNE, &scan->prune) < 0) {
156                         printf("Prune %s failed: %s\n",
157                                scan->filesystem, strerror(errno));
158                         rcode = 2;
159                 } else if (scan->prune.head.flags & HAMMER_IOC_HEAD_INTR) {
160                         printf("Prune %s interrupted by timer at "
161                                "%016llx %04x\n",
162                                scan->filesystem,
163                                scan->prune.key_cur.obj_id,
164                                scan->prune.key_cur.localization);
165                         if (CyclePath)
166                                 hammer_set_cycle(&scan->prune.key_cur, 0);
167                         rcode = 0;
168                 } else {
169                         if (CyclePath)
170                                 hammer_reset_cycle();
171                         printf("Prune %s succeeded\n", scan->filesystem);
172                 }
173                 printf("Pruned %lld/%lld records (%lld directory entries) "
174                        "and %lld bytes\n",
175                         scan->prune.stat_rawrecords,
176                         scan->prune.stat_scanrecords,
177                         scan->prune.stat_dirrecords,
178                         scan->prune.stat_bytes
179                 );
180                 RunningIoctl = 0;
181                 close(fd);
182         }
183         if (rcode)
184                 exit(rcode);
185 }
186
187 /*
188  * Scan a directory for softlinks representing snapshots and build
189  * associated softprune structures.
190  */
191 static void
192 hammer_softprune_scandir(struct softprune **basep,
193                          struct hammer_ioc_prune *template,
194                          const char *dirname)
195 {
196         struct stat st;
197         struct dirent *den;
198         DIR *dir;
199         char *path;
200         int len;
201         char *linkbuf;
202         char *ptr;
203
204         path = NULL;
205         linkbuf = malloc(MAXPATHLEN);
206
207         if ((dir = opendir(dirname)) == NULL)
208                 err(1, "Cannot open directory %s", dirname);
209         while ((den = readdir(dir)) != NULL) {
210                 if (strcmp(den->d_name, ".") == 0)
211                         continue;
212                 if (strcmp(den->d_name, "..") == 0)
213                         continue;
214                 if (path)
215                         free(path);
216                 asprintf(&path, "%s/%s", dirname, den->d_name);
217                 if (lstat(path, &st) < 0)
218                         continue;
219                 if (!S_ISLNK(st.st_mode))
220                         continue;
221                 if ((len = readlink(path, linkbuf, MAXPATHLEN - 1)) < 0)
222                         continue;
223                 linkbuf[len] = 0;
224                 if ((ptr = strrchr(linkbuf, '@')) &&
225                     ptr > linkbuf && ptr[-1] == '@') {
226                         hammer_softprune_addentry(basep, template,
227                                                   dirname, linkbuf, ptr - 1);
228                 }
229         }
230         free(linkbuf);
231         if (path)
232                 free(path);
233 }
234
235 /*
236  * Add the softlink to the appropriate softprune structure, creating a new
237  * if necessary.
238  */
239 static
240 struct softprune *
241 hammer_softprune_addentry(struct softprune **basep,
242                          struct hammer_ioc_prune *template,
243                          const char *dirpath,
244                          const char *linkbuf, const char *tidptr)
245 {
246         struct hammer_ioc_prune_elm *elm;
247         struct softprune *scan;
248         struct statfs fs;
249         char *fspath;
250
251         if (linkbuf[0] == '/') {
252                 asprintf(&fspath, "%*.*s",
253                          (tidptr - linkbuf), (tidptr - linkbuf), linkbuf);
254         } else {
255                 asprintf(&fspath, "%s/%*.*s", dirpath,
256                          (tidptr - linkbuf), (tidptr - linkbuf), linkbuf);
257         }
258         if (statfs(fspath, &fs) < 0) {
259                 free(fspath);
260                 return(NULL);
261         }
262
263         /*
264          * Locate the filesystem in an existing softprune structure
265          */
266         for (scan = *basep; scan; scan = scan->next) {
267                 if (bcmp(&fs.f_fsid, &scan->fs.f_fsid, sizeof(fs.f_fsid)) != 0)
268                         continue;
269                 if (strcmp(fs.f_mntonname, scan->fs.f_mntonname) != 0)
270                         continue;
271                 break;
272         }
273
274         /*
275          * Create a new softprune structure if necessasry
276          */
277         if (scan == NULL) {
278                 scan = malloc(sizeof(*scan));
279                 bzero(scan, sizeof(*scan));
280
281                 scan->fs = fs;
282                 scan->filesystem = fspath;
283                 scan->prune = *template;
284                 scan->maxelms = 32;
285                 scan->prune.elms = malloc(sizeof(*elm) * scan->maxelms);
286                 scan->next = *basep;
287                 *basep = scan;
288         } else {
289                 free(fspath);
290         }
291
292         /*
293          * Add the entry (unsorted).  Just set the beg_tid, we will sort
294          * and set the remaining entries later.
295          *
296          * Always leave one entry free for our terminator.
297          */
298         if (scan->prune.nelms >= scan->maxelms - 1) {
299                 scan->maxelms = (scan->maxelms * 3 / 2);
300                 scan->prune.elms = realloc(scan->prune.elms,
301                                            sizeof(*elm) * scan->maxelms);
302         }
303         elm = &scan->prune.elms[scan->prune.nelms];
304         elm->beg_tid = strtoull(tidptr + 2, NULL, 0);
305         elm->end_tid = 0;
306         elm->mod_tid = 0;
307         ++scan->prune.nelms;
308         return(scan);
309 }
310
311 /*
312  * Finalize a softprune structure after scanning in its softlinks.
313  * Sort the elements, remove duplicates, and then fill in end_tid and
314  * mod_tid.
315  *
316  * The array must end up in descending order.
317  */
318 static int
319 hammer_softprune_qsort_cmp(const void *arg1, const void *arg2)
320 {
321         const struct hammer_ioc_prune_elm *elm1 = arg1;
322         const struct hammer_ioc_prune_elm *elm2 = arg2;
323
324         if (elm1->beg_tid < elm2->beg_tid)
325                 return(1);
326         if (elm1->beg_tid > elm2->beg_tid)
327                 return(-1);
328         return(0);
329 }
330
331 static void
332 hammer_softprune_finalize(struct softprune *scan)
333 {
334         struct hammer_ioc_prune_elm *elm;
335         int i;
336
337         /*
338          * Don't do anything if there are no elements.
339          */
340         if (scan->prune.nelms == 0)
341                 return;
342
343         /*
344          * Sort the elements in descending order, remove duplicates, and
345          * fill in any missing bits.
346          */
347         qsort(scan->prune.elms, scan->prune.nelms, sizeof(*elm), 
348               hammer_softprune_qsort_cmp);
349
350         for (i = 0; i < scan->prune.nelms; ++i) {
351                 elm = &scan->prune.elms[i];
352                 if (i == 0) {
353                         /*
354                          * First (highest TID) (also last if only one element)
355                          */
356                         elm->end_tid = HAMMER_MAX_TID;
357                 } else if (elm[0].beg_tid == elm[-1].beg_tid) {
358                         /*
359                          * Remove duplicate
360                          */
361                         --scan->prune.nelms;
362                         if (i != scan->prune.nelms) {
363                                 bcopy(elm + 1, elm,
364                                       (scan->prune.nelms - i) * sizeof(*elm));
365                         }
366                         --i;
367                         continue;
368                 } else {
369                         /*
370                          * Middle or last.
371                          */
372                         elm->end_tid = elm[-1].beg_tid;
373                 }
374                 elm->mod_tid = elm->end_tid - elm->beg_tid;
375         }
376
377         /*
378          * Add a final element to prune everything from transaction id
379          * 0 to the lowest transaction id (aka last so far).
380          */
381         assert(scan->prune.nelms < scan->maxelms);
382         elm = &scan->prune.elms[scan->prune.nelms++];
383         elm->beg_tid = 1;
384         elm->end_tid = elm[-1].beg_tid;
385         elm->mod_tid = elm->end_tid - elm->beg_tid;
386 }
387
388 static
389 void
390 softprune_usage(int code)
391 {
392         fprintf(stderr, "Badly formed prune command, use:\n");
393         fprintf(stderr, "hammer prune <softlink-dir>\n");
394         fprintf(stderr, "hammer prune-everything <filesystem>\n");
395         exit(code);
396 }
397
398