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