xref: /dragonfly/sbin/hammer/cmd_softprune.c (revision e2f5ccfb)
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.6 2008/07/07 00:27:22 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 	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 %016llx\n",
149 		       scan->prune.key_beg.obj_id,
150 		       scan->prune.key_end.obj_id);
151 
152 		if (ioctl(fd, HAMMERIOC_PRUNE, &scan->prune) < 0) {
153 			printf("Prune %s failed: %s\n",
154 			       scan->filesystem, strerror(errno));
155 			rcode = 2;
156 		} else if (scan->prune.head.flags & HAMMER_IOC_HEAD_INTR) {
157 			printf("Prune %s interrupted by timer at "
158 			       "%016llx %04x\n",
159 			       scan->filesystem,
160 			       scan->prune.key_cur.obj_id,
161 			       scan->prune.key_cur.localization);
162 			if (CyclePath)
163 				hammer_set_cycle(&scan->prune.key_cur, 0);
164 			rcode = 0;
165 		} else {
166 			if (CyclePath)
167 				hammer_reset_cycle();
168 			printf("Prune %s succeeded\n", scan->filesystem);
169 		}
170 		printf("Pruned %lld/%lld records (%lld directory entries) "
171 		       "and %lld bytes\n",
172 			scan->prune.stat_rawrecords,
173 			scan->prune.stat_scanrecords,
174 			scan->prune.stat_dirrecords,
175 			scan->prune.stat_bytes
176 		);
177 		close(fd);
178 	}
179 	if (rcode)
180 		exit(rcode);
181 }
182 
183 /*
184  * Scan a directory for softlinks representing snapshots and build
185  * associated softprune structures.
186  */
187 static void
188 hammer_softprune_scandir(struct softprune **basep,
189 			 struct hammer_ioc_prune *template,
190 			 const char *dirname)
191 {
192 	struct stat st;
193 	struct dirent *den;
194 	DIR *dir;
195 	char *path;
196 	int len;
197 	char *linkbuf;
198 	char *ptr;
199 
200 	path = NULL;
201 	linkbuf = malloc(MAXPATHLEN);
202 
203 	if ((dir = opendir(dirname)) == NULL)
204 		err(1, "Cannot open directory %s", dirname);
205 	while ((den = readdir(dir)) != NULL) {
206 		if (strcmp(den->d_name, ".") == 0)
207 			continue;
208 		if (strcmp(den->d_name, "..") == 0)
209 			continue;
210 		if (path)
211 			free(path);
212 		asprintf(&path, "%s/%s", dirname, den->d_name);
213 		if (lstat(path, &st) < 0)
214 			continue;
215 		if (!S_ISLNK(st.st_mode))
216 			continue;
217 		if ((len = readlink(path, linkbuf, MAXPATHLEN - 1)) < 0)
218 			continue;
219 		linkbuf[len] = 0;
220 		if ((ptr = strrchr(linkbuf, '@')) &&
221 		    ptr > linkbuf && ptr[-1] == '@') {
222 			hammer_softprune_addentry(basep, template,
223 						  dirname, linkbuf, ptr - 1);
224 		}
225 	}
226 	free(linkbuf);
227 	if (path)
228 		free(path);
229 }
230 
231 /*
232  * Add the softlink to the appropriate softprune structure, creating a new
233  * if necessary.
234  */
235 static
236 struct softprune *
237 hammer_softprune_addentry(struct softprune **basep,
238 			 struct hammer_ioc_prune *template,
239 			 const char *dirpath,
240 			 const char *linkbuf, const char *tidptr)
241 {
242 	struct hammer_ioc_prune_elm *elm;
243 	struct softprune *scan;
244 	struct statfs fs;
245 	char *fspath;
246 
247 	if (linkbuf[0] == '/') {
248 		asprintf(&fspath, "%*.*s",
249 			 (tidptr - linkbuf), (tidptr - linkbuf), linkbuf);
250 	} else {
251 		asprintf(&fspath, "%s/%*.*s", dirpath,
252 			 (tidptr - linkbuf), (tidptr - linkbuf), linkbuf);
253 	}
254 	if (statfs(fspath, &fs) < 0) {
255 		free(fspath);
256 		return(NULL);
257 	}
258 
259 	/*
260 	 * Locate the filesystem in an existing softprune structure
261 	 */
262 	for (scan = *basep; scan; scan = scan->next) {
263 		if (bcmp(&fs.f_fsid, &scan->fs.f_fsid, sizeof(fs.f_fsid)) != 0)
264 			continue;
265 		if (strcmp(fs.f_mntonname, scan->fs.f_mntonname) != 0)
266 			continue;
267 		break;
268 	}
269 
270 	/*
271 	 * Create a new softprune structure if necessasry
272 	 */
273 	if (scan == NULL) {
274 		scan = malloc(sizeof(*scan));
275 		bzero(scan, sizeof(*scan));
276 
277 		scan->fs = fs;
278 		scan->filesystem = fspath;
279 		scan->prune = *template;
280 		scan->maxelms = 32;
281 		scan->prune.elms = malloc(sizeof(*elm) * scan->maxelms);
282 		scan->next = *basep;
283 		*basep = scan;
284 	} else {
285 		free(fspath);
286 	}
287 
288 	/*
289 	 * Add the entry (unsorted).  Just set the beg_tid, we will sort
290 	 * and set the remaining entries later.
291 	 *
292 	 * Always leave one entry free for our terminator.
293 	 */
294 	if (scan->prune.nelms >= scan->maxelms - 1) {
295 		scan->maxelms = (scan->maxelms * 3 / 2);
296 		scan->prune.elms = realloc(scan->prune.elms,
297 					   sizeof(*elm) * scan->maxelms);
298 	}
299 	elm = &scan->prune.elms[scan->prune.nelms];
300 	elm->beg_tid = strtoull(tidptr + 2, NULL, 0);
301 	elm->end_tid = 0;
302 	elm->mod_tid = 0;
303 	++scan->prune.nelms;
304 	return(scan);
305 }
306 
307 /*
308  * Finalize a softprune structure after scanning in its softlinks.
309  * Sort the elements, remove duplicates, and then fill in end_tid and
310  * mod_tid.
311  *
312  * The array must end up in descending order.
313  */
314 static int
315 hammer_softprune_qsort_cmp(const void *arg1, const void *arg2)
316 {
317 	const struct hammer_ioc_prune_elm *elm1 = arg1;
318 	const struct hammer_ioc_prune_elm *elm2 = arg2;
319 
320 	if (elm1->beg_tid < elm2->beg_tid)
321 		return(1);
322 	if (elm1->beg_tid > elm2->beg_tid)
323 		return(-1);
324 	return(0);
325 }
326 
327 static void
328 hammer_softprune_finalize(struct softprune *scan)
329 {
330 	struct hammer_ioc_prune_elm *elm;
331 	int i;
332 
333 	/*
334 	 * Don't do anything if there are no elements.
335 	 */
336 	if (scan->prune.nelms == 0)
337 		return;
338 
339 	/*
340 	 * Sort the elements in descending order, remove duplicates, and
341 	 * fill in any missing bits.
342 	 */
343 	qsort(scan->prune.elms, scan->prune.nelms, sizeof(*elm),
344 	      hammer_softprune_qsort_cmp);
345 
346 	for (i = 0; i < scan->prune.nelms; ++i) {
347 		elm = &scan->prune.elms[i];
348 		if (i == 0) {
349 			/*
350 			 * First (highest TID) (also last if only one element)
351 			 */
352 			elm->end_tid = HAMMER_MAX_TID;
353 		} else if (elm[0].beg_tid == elm[-1].beg_tid) {
354 			/*
355 			 * Remove duplicate
356 			 */
357 			--scan->prune.nelms;
358 			if (i != scan->prune.nelms) {
359 				bcopy(elm + 1, elm,
360 				      (scan->prune.nelms - i) * sizeof(*elm));
361 			}
362 			--i;
363 			continue;
364 		} else {
365 			/*
366 			 * Middle or last.
367 			 */
368 			elm->end_tid = elm[-1].beg_tid;
369 		}
370 		elm->mod_tid = elm->end_tid - elm->beg_tid;
371 	}
372 
373 	/*
374 	 * Add a final element to prune everything from transaction id
375 	 * 0 to the lowest transaction id (aka last so far).
376 	 */
377 	assert(scan->prune.nelms < scan->maxelms);
378 	elm = &scan->prune.elms[scan->prune.nelms++];
379 	elm->beg_tid = 1;
380 	elm->end_tid = elm[-1].beg_tid;
381 	elm->mod_tid = elm->end_tid - elm->beg_tid;
382 }
383 
384 static
385 void
386 softprune_usage(int code)
387 {
388 	fprintf(stderr, "Badly formed prune command, use:\n");
389 	fprintf(stderr, "hammer prune            <dir-holding-softlinks>\n");
390 	fprintf(stderr, "hammer prune-everything <filesystem>\n");
391 	exit(code);
392 }
393 
394 
395