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