xref: /dragonfly/sbin/hammer/cmd_softprune.c (revision 9ddb8543)
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 struct softprune *hammer_softprune_addentry(struct softprune **basep,
53 			 struct hammer_ioc_prune *template,
54 			 const char *dirpath, const char *denname,
55 			 struct stat *st,
56 			 const char *linkbuf, const char *tidptr);
57 static void hammer_softprune_finalize(struct softprune *scan);
58 
59 /*
60  * prune <softlink-dir>
61  * prune-everything <filesystem>
62  */
63 void
64 hammer_cmd_softprune(char **av, int ac, int everything_opt)
65 {
66 	struct hammer_ioc_prune template;
67 	struct hammer_ioc_pseudofs_rw pfs;
68 	struct softprune *base, *scan;
69 	int fd;
70 	int rcode;
71 
72 	base = NULL;
73 	rcode = 0;
74 	if (TimeoutOpt > 0)
75 		alarm(TimeoutOpt);
76 
77 	bzero(&pfs, sizeof(pfs));
78 	pfs.bytes = sizeof(*pfs.ondisk);
79 	pfs.ondisk = malloc(pfs.bytes);
80 	bzero(pfs.ondisk, pfs.bytes);
81 	pfs.pfs_id = -1;
82 
83 	/*
84 	 * NOTE: To restrict to a single file XXX we have to set
85 	 * the localization the same (not yet implemented).  Typically
86 	 * two passes would be needed, one using HAMMER_LOCALIZE_MISC
87 	 * and one using HAMMER_LOCALIZE_INODE.
88 	 */
89 
90 	bzero(&template, sizeof(template));
91 	template.key_beg.localization = HAMMER_MIN_LOCALIZATION;
92 	template.key_beg.obj_id = HAMMER_MIN_OBJID;
93 	template.key_end.localization = HAMMER_MAX_LOCALIZATION;
94 	template.key_end.obj_id = HAMMER_MAX_OBJID;
95 	hammer_get_cycle(&template.key_end, NULL);
96 	template.stat_oldest_tid = HAMMER_MAX_TID;
97 
98 	/*
99 	 * For now just allow one directory
100 	 */
101 	if (ac == 0 || ac > 1)
102 		softprune_usage(1);
103 
104 	/*
105 	 * Scan the softlink directory.
106 	 */
107 	if (everything_opt) {
108 		const char *dummylink = "";
109 		scan = hammer_softprune_addentry(&base, &template,
110 						 *av, NULL, NULL,
111 						 dummylink, dummylink);
112 		if (scan == NULL)
113 			softprune_usage(1);
114 		scan->prune.nelms = 0;
115 		scan->prune.head.flags |= HAMMER_IOC_PRUNE_ALL;
116 	} else {
117 		hammer_softprune_scandir(&base, &template, *av);
118 		++av;
119 		--ac;
120 	}
121 
122 	/*
123 	 * XXX future (need to store separate cycles for each filesystem)
124 	 */
125 	if (base == NULL) {
126 		fprintf(stderr, "No snapshot softlinks found\n");
127 		exit(1);
128 	}
129 	if (base->next) {
130 		fprintf(stderr, "Currently only one HAMMER filesystem may "
131 				"be specified in the softlink scan\n");
132 		exit(1);
133 	}
134 
135 	/*
136 	 * Issue the prunes
137 	 */
138 	for (scan = base; scan; scan = scan->next) {
139 		/*
140 		 * Open the filesystem for ioctl calls and extract the
141 		 * PFS.
142 		 */
143 		fd = open(scan->filesystem, O_RDONLY);
144 		if (fd < 0) {
145 			warn("Unable to open %s", scan->filesystem);
146 			rcode = 1;
147 			continue;
148 		}
149 
150 		if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) {
151 			warn("Filesystem %s is not HAMMER", scan->filesystem);
152 			rcode = 1;
153 			close(fd);
154 			continue;
155 		}
156 		scan->prune_min = pfs.ondisk->prune_min;
157 
158 		/*
159 		 * Finalize operations
160 		 */
161 		hammer_softprune_finalize(scan);
162 		if (everything_opt) {
163 			printf("Prune %s: EVERYTHING\n",
164 			       scan->filesystem);
165 		} else {
166 			printf("Prune %s: %d snapshots\n",
167 			       scan->filesystem, scan->prune.nelms);
168 		}
169 		if (scan->prune.nelms == 0 &&
170 		    (scan->prune.head.flags & HAMMER_IOC_PRUNE_ALL) == 0) {
171 			continue;
172 		}
173 
174 		printf("Prune %s: objspace %016jx:%04x %016jx:%04x "
175 		       "pfs_id %d\n",
176 		       scan->filesystem,
177 		       (uintmax_t)scan->prune.key_beg.obj_id,
178 		       scan->prune.key_beg.localization,
179 		       (uintmax_t)scan->prune.key_end.obj_id,
180 		       scan->prune.key_end.localization,
181 		       pfs.pfs_id);
182 		printf("Prune %s: prune_min is %dd/%02d:%02d:%02d\n",
183 		       scan->filesystem,
184 			pfs.ondisk->prune_min / (24 * 60 * 60),
185 			pfs.ondisk->prune_min / 60 / 60 % 24,
186 			pfs.ondisk->prune_min / 60 % 60,
187 			pfs.ondisk->prune_min % 60);
188 
189 		RunningIoctl = 1;
190 		if (ioctl(fd, HAMMERIOC_PRUNE, &scan->prune) < 0) {
191 			printf("Prune %s failed: %s\n",
192 			       scan->filesystem, strerror(errno));
193 			rcode = 2;
194 		} else if (scan->prune.head.flags & HAMMER_IOC_HEAD_INTR) {
195 			printf("Prune %s interrupted by timer at "
196 			       "%016jx %04x\n",
197 			       scan->filesystem,
198 			       (uintmax_t)scan->prune.key_cur.obj_id,
199 			       scan->prune.key_cur.localization);
200 			if (CyclePath)
201 				hammer_set_cycle(&scan->prune.key_cur, 0);
202 			rcode = 0;
203 		} else {
204 			if (CyclePath)
205 				hammer_reset_cycle();
206 			printf("Prune %s succeeded\n", scan->filesystem);
207 		}
208 		printf("Pruned %jd/%jd records (%jd directory entries) "
209 		       "and %jd bytes\n",
210 			(uintmax_t)scan->prune.stat_rawrecords,
211 			(uintmax_t)scan->prune.stat_scanrecords,
212 			(uintmax_t)scan->prune.stat_dirrecords,
213 			(uintmax_t)scan->prune.stat_bytes
214 		);
215 		RunningIoctl = 0;
216 		close(fd);
217 	}
218 	if (rcode)
219 		exit(rcode);
220 }
221 
222 /*
223  * Scan a directory for softlinks representing snapshots and build
224  * associated softprune structures.
225  */
226 static void
227 hammer_softprune_scandir(struct softprune **basep,
228 			 struct hammer_ioc_prune *template,
229 			 const char *dirname)
230 {
231 	struct stat st;
232 	struct dirent *den;
233 	DIR *dir;
234 	char *path;
235 	int len;
236 	char *linkbuf;
237 	char *ptr;
238 
239 	path = NULL;
240 	linkbuf = malloc(MAXPATHLEN);
241 
242 	if ((dir = opendir(dirname)) == NULL)
243 		err(1, "Cannot open directory %s", dirname);
244 	while ((den = readdir(dir)) != NULL) {
245 		if (strcmp(den->d_name, ".") == 0)
246 			continue;
247 		if (strcmp(den->d_name, "..") == 0)
248 			continue;
249 		if (path)
250 			free(path);
251 		asprintf(&path, "%s/%s", dirname, den->d_name);
252 		if (lstat(path, &st) < 0)
253 			continue;
254 		if (!S_ISLNK(st.st_mode))
255 			continue;
256 		if ((len = readlink(path, linkbuf, MAXPATHLEN - 1)) < 0)
257 			continue;
258 		linkbuf[len] = 0;
259 		if ((ptr = strrchr(linkbuf, '@')) &&
260 		    ptr > linkbuf && ptr[-1] == '@') {
261 			hammer_softprune_addentry(basep, template,
262 						  dirname, den->d_name, &st,
263 						  linkbuf, ptr - 1);
264 		}
265 	}
266 	free(linkbuf);
267 	if (path)
268 		free(path);
269 }
270 
271 /*
272  * Add the softlink to the appropriate softprune structure, creating a new
273  * one if necessary.
274  */
275 static
276 struct softprune *
277 hammer_softprune_addentry(struct softprune **basep,
278 			 struct hammer_ioc_prune *template,
279 			 const char *dirpath, const char *denname __unused,
280 			 struct stat *st,
281 			 const char *linkbuf, const char *tidptr)
282 {
283 	struct hammer_ioc_prune_elm *elm;
284 	struct softprune *scan;
285 	struct statfs fs;
286 	char *fspath;
287 
288 	/*
289 	 * Calculate filesystem path.
290 	 */
291 	if (linkbuf[0] == '/') {
292 		asprintf(&fspath, "%*.*s",
293 			 (int)(tidptr - linkbuf), (int)(tidptr - linkbuf),
294 			 linkbuf);
295 	} else {
296 		asprintf(&fspath, "%s/%*.*s", dirpath,
297 			 (int)(tidptr - linkbuf), (int)(tidptr - linkbuf),
298 			 linkbuf);
299 	}
300 	if (statfs(fspath, &fs) < 0) {
301 		free(fspath);
302 		return(NULL);
303 	}
304 
305 	/*
306 	 * Locate the filesystem in an existing softprune structure
307 	 */
308 	for (scan = *basep; scan; scan = scan->next) {
309 		if (bcmp(&fs.f_fsid, &scan->fs.f_fsid, sizeof(fs.f_fsid)) != 0)
310 			continue;
311 		if (strcmp(fs.f_mntonname, scan->fs.f_mntonname) != 0)
312 			continue;
313 		break;
314 	}
315 
316 	/*
317 	 * Create a new softprune structure if necessasry
318 	 */
319 	if (scan == NULL) {
320 		scan = malloc(sizeof(*scan));
321 		bzero(scan, sizeof(*scan));
322 
323 		scan->fs = fs;
324 		scan->filesystem = fspath;
325 		scan->prune = *template;
326 		scan->maxelms = 32;
327 		scan->prune.elms = malloc(sizeof(*elm) * scan->maxelms);
328 		scan->next = *basep;
329 		*basep = scan;
330 	} else {
331 		free(fspath);
332 	}
333 
334 	/*
335 	 * Add the entry (unsorted).  Just set the beg_tid, we will sort
336 	 * and set the remaining entries later.
337 	 *
338 	 * Always leave one entry free for our terminator.
339 	 */
340 	if (scan->prune.nelms >= scan->maxelms - 1) {
341 		scan->maxelms = (scan->maxelms * 3 / 2);
342 		scan->prune.elms = realloc(scan->prune.elms,
343 					   sizeof(*elm) * scan->maxelms);
344 	}
345 
346 	/*
347 	 * NOTE: Temporarily store the snapshot timestamp in mod_tid.
348 	 *	 This will be cleaned up in the finalization phase.
349 	 */
350 	elm = &scan->prune.elms[scan->prune.nelms];
351 	elm->beg_tid = strtoull(tidptr + 2, NULL, 0);
352 	elm->end_tid = 0;
353 	elm->mod_tid = 0;
354 	if (st) {
355 		if (st->st_ctime < st->st_mtime)
356 			elm->mod_tid = st->st_ctime;
357 		else
358 			elm->mod_tid = st->st_mtime;
359 	}
360 	++scan->prune.nelms;
361 	return(scan);
362 }
363 
364 /*
365  * Finalize a softprune structure after scanning in its softlinks.
366  * Sort the elements, remove duplicates, and then fill in end_tid and
367  * mod_tid.
368  *
369  * The array must end up in descending order.
370  */
371 static int
372 hammer_softprune_qsort_cmp(const void *arg1, const void *arg2)
373 {
374 	const struct hammer_ioc_prune_elm *elm1 = arg1;
375 	const struct hammer_ioc_prune_elm *elm2 = arg2;
376 
377 	if (elm1->beg_tid < elm2->beg_tid)
378 		return(1);
379 	if (elm1->beg_tid > elm2->beg_tid)
380 		return(-1);
381 	return(0);
382 }
383 
384 static void
385 hammer_softprune_finalize(struct softprune *scan)
386 {
387 	struct hammer_ioc_prune_elm *elm;
388 	time_t t;
389 	long delta;
390 	int i;
391 
392 	/*
393 	 * Don't do anything if there are no elements.
394 	 */
395 	if (scan->prune.nelms == 0)
396 		return;
397 
398 	/*
399 	 * Sort the elements in descending order, remove duplicates, and
400 	 * fill in any missing bits.
401 	 */
402 	qsort(scan->prune.elms, scan->prune.nelms, sizeof(*elm),
403 	      hammer_softprune_qsort_cmp);
404 
405 	for (i = 0; i < scan->prune.nelms; ++i) {
406 		elm = &scan->prune.elms[i];
407 		if (i == 0) {
408 			/*
409 			 * First (highest TID) (also last if only one element)
410 			 */
411 			elm->end_tid = HAMMER_MAX_TID;
412 		} else if (elm[0].beg_tid == elm[-1].beg_tid) {
413 			/*
414 			 * Remove duplicate
415 			 */
416 			--scan->prune.nelms;
417 			if (i != scan->prune.nelms) {
418 				bcopy(elm + 1, elm,
419 				      (scan->prune.nelms - i) * sizeof(*elm));
420 			}
421 			--i;
422 			continue;
423 		} else {
424 			/*
425 			 * Middle or last.
426 			 */
427 			elm->end_tid = elm[-1].beg_tid;
428 		}
429 	}
430 
431 	/*
432 	 * If a minimum retention time (in seconds) is configured for the
433 	 * PFS, remove any snapshots from the pruning list that are within
434 	 * the period.
435 	 */
436 	if (scan->prune_min) {
437 		t = time(NULL);
438 		for (i = scan->prune.nelms - 1; i >= 0; --i) {
439 			elm = &scan->prune.elms[i];
440 			if (elm->mod_tid == 0)
441 				continue;
442 			delta = (long)(t - (time_t)elm->mod_tid);
443 			if (delta < scan->prune_min)
444 				break;
445 		}
446 		++i;
447 		if (i) {
448 			printf("Prune %s: prune_min: Will not clean between "
449 			       "the teeth of the first %d snapshots\n",
450 			       scan->filesystem, i);
451 			bcopy(&scan->prune.elms[i], &scan->prune.elms[0],
452 			      (scan->prune.nelms - i) * sizeof(scan->prune.elms[0]));
453 			scan->prune.elms[0].end_tid = HAMMER_MAX_TID;
454 			scan->prune.nelms -= i;
455 		}
456 	}
457 
458 	/*
459 	 * Remove the first entry.  This entry represents the prune from
460 	 * the most recent snapshot to current.  We wish to retain the
461 	 * fine-grained history for this region.
462 	 */
463 	if (scan->prune.nelms) {
464 		bcopy(&scan->prune.elms[1], &scan->prune.elms[0],
465 		      (scan->prune.nelms - 1) * sizeof(scan->prune.elms[0]));
466 		--scan->prune.nelms;
467 	}
468 
469 	/*
470 	 * Add a final element to prune everything from transaction id
471 	 * 0 to the lowest transaction id (aka last so far).
472 	 */
473 	if (scan->prune.nelms) {
474 		assert(scan->prune.nelms < scan->maxelms);
475 		elm = &scan->prune.elms[scan->prune.nelms];
476 		elm->beg_tid = 1;
477 		elm->end_tid = elm[-1].beg_tid;
478 		++scan->prune.nelms;
479 	}
480 
481 	/*
482 	 * Adjust mod_tid to what the ioctl() expects.
483 	 */
484 	for (i = 0; i < scan->prune.nelms; ++i) {
485 		elm = &scan->prune.elms[i];
486 		elm->mod_tid = elm->end_tid - elm->beg_tid;
487 		printf("TID %016jx - %016jx\n",
488 		       (uintmax_t)elm->beg_tid, (uintmax_t)elm->end_tid);
489 	}
490 }
491 
492 static
493 void
494 softprune_usage(int code)
495 {
496 	fprintf(stderr, "Badly formed prune command, use:\n");
497 	fprintf(stderr, "hammer prune <softlink-dir>\n");
498 	fprintf(stderr, "hammer prune-everything <filesystem>\n");
499 	exit(code);
500 }
501 
502 
503