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