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