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