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