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