1 /* $NetBSD: walk.c,v 1.6 2001/12/05 11:08:53 lukem Exp $ */ 2 3 /* 4 * Copyright (c) 2001 Wasabi Systems, Inc. 5 * All rights reserved. 6 * 7 * Written by Luke Mewburn for Wasabi Systems, Inc. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. All advertising materials mentioning features or use of this software 18 * must display the following acknowledgement: 19 * This product includes software developed for the NetBSD Project by 20 * Wasabi Systems, Inc. 21 * 4. The name of Wasabi Systems, Inc. may not be used to endorse 22 * or promote products derived from this software without specific prior 23 * written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC 29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 * POSSIBILITY OF SUCH DAMAGE. 36 */ 37 38 /* 39 * The function link_check() was inspired from NetBSD's usr.bin/du/du.c, 40 * which has the following copyright notice: 41 * 42 * 43 * Copyright (c) 1989, 1993, 1994 44 * The Regents of the University of California. All rights reserved. 45 * 46 * This code is derived from software contributed to Berkeley by 47 * Chris Newcomb. 48 * 49 * Redistribution and use in source and binary forms, with or without 50 * modification, are permitted provided that the following conditions 51 * are met: 52 * 1. Redistributions of source code must retain the above copyright 53 * notice, this list of conditions and the following disclaimer. 54 * 2. Redistributions in binary form must reproduce the above copyright 55 * notice, this list of conditions and the following disclaimer in the 56 * documentation and/or other materials provided with the distribution. 57 * 3. All advertising materials mentioning features or use of this software 58 * must display the following acknowledgement: 59 * This product includes software developed by the University of 60 * California, Berkeley and its contributors. 61 * 4. Neither the name of the University nor the names of its contributors 62 * may be used to endorse or promote products derived from this software 63 * without specific prior written permission. 64 * 65 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 66 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 67 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 68 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 69 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 70 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 71 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 72 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 73 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 74 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 75 * SUCH DAMAGE. 76 */ 77 78 #include <sys/cdefs.h> 79 #ifndef __lint 80 __RCSID("$NetBSD: walk.c,v 1.6 2001/12/05 11:08:53 lukem Exp $"); 81 #endif /* !__lint */ 82 83 #include <sys/param.h> 84 85 #include <assert.h> 86 #include <err.h> 87 #include <errno.h> 88 #include <fcntl.h> 89 #include <stdio.h> 90 #include <dirent.h> 91 #include <stdlib.h> 92 #include <string.h> 93 #include <unistd.h> 94 95 #include "makefs.h" 96 #include "mtree.h" 97 98 static void apply_specdir(const char *, NODE *, fsnode *); 99 static void apply_specentry(const char *, NODE *, fsnode *); 100 static fsnode *create_fsnode(const char *, struct stat *); 101 static fsinode *create_fsinode(struct stat *); 102 static fsinode *link_check(fsinode *); 103 104 105 /* 106 * walk_dir -- 107 * build a tree of fsnodes from `dir', with a parent fsnode of `parent' 108 * (which may be NULL for the root of the tree). 109 * each "level" is a directory, with the "." entry guaranteed to be 110 * at the start of the list, and without ".." entries. 111 */ 112 fsnode * 113 walk_dir(const char *dir, fsnode *parent) 114 { 115 fsnode *first, *cur, *prev; 116 DIR *dirp; 117 struct dirent *dent; 118 char path[MAXPATHLEN + 1]; 119 struct stat stbuf; 120 121 assert(dir != NULL); 122 123 if (debug & DEBUG_WALK_DIR) 124 printf("walk_dir: %s %p\n", dir, parent); 125 if ((dirp = opendir(dir)) == NULL) 126 err(1, "Can't opendir `%s'", dir); 127 first = prev = NULL; 128 while ((dent = readdir(dirp)) != NULL) { 129 if (strcmp(dent->d_name, "..") == 0) 130 continue; 131 if (debug & DEBUG_WALK_DIR_NODE) 132 printf("scanning %s/%s\n", dir, dent->d_name); 133 if (snprintf(path, sizeof(path), "%s/%s", dir, dent->d_name) 134 >= sizeof(path)) 135 errx(1, "Pathname too long."); 136 if (lstat(path, &stbuf) == -1) 137 err(1, "Can't lstat `%s'", path); 138 if (S_ISSOCK(stbuf.st_mode & S_IFMT)) { 139 if (debug & DEBUG_WALK_DIR_NODE) 140 printf(" skipping socket %s\n", path); 141 continue; 142 } 143 144 cur = create_fsnode(dent->d_name, &stbuf); 145 cur->inode = create_fsinode(&stbuf); 146 cur->parent = parent; 147 if (strcmp(dent->d_name, ".") == 0) { 148 /* ensure "." is at the start of the list */ 149 cur->next = first; 150 first = cur; 151 if (! prev) 152 prev = cur; 153 } else { /* not "." */ 154 if (prev) 155 prev->next = cur; 156 prev = cur; 157 if (!first) 158 first = cur; 159 if (S_ISDIR(cur->type)) { 160 cur->child = walk_dir(path, cur); 161 continue; 162 } 163 } 164 if (stbuf.st_nlink > 1) { 165 fsinode *curino; 166 167 curino = link_check(cur->inode); 168 if (curino != NULL) { 169 free(cur->inode); 170 cur->inode = curino; 171 cur->inode->nlink++; 172 } 173 } 174 if (S_ISLNK(cur->type)) { 175 char slink[PATH_MAX+1]; 176 int llen; 177 178 llen = readlink(path, slink, PATH_MAX); 179 if (llen == -1) 180 err(1, "Readlink `%s'", path); 181 slink[llen] = '\0'; 182 if ((cur->symlink = strdup(slink)) == NULL) 183 err(1, "Memory allocation error"); 184 } 185 } 186 for (cur = first; cur != NULL; cur = cur->next) 187 cur->first = first; 188 if (closedir(dirp) == -1) 189 err(1, "Can't closedir `%s'", dir); 190 return (first); 191 } 192 193 static fsnode * 194 create_fsnode(const char *name, struct stat *stbuf) 195 { 196 fsnode *cur; 197 198 if ((cur = calloc(1, sizeof(fsnode))) == NULL || 199 (cur->name = strdup(name)) == NULL) 200 err(1, "Memory allocation error"); 201 cur->type = stbuf->st_mode & S_IFMT; 202 return (cur); 203 } 204 205 static fsinode * 206 create_fsinode(struct stat *statbuf) 207 { 208 fsinode *cur; 209 210 if ((cur = calloc(1, sizeof(fsinode))) == NULL) 211 err(1, "Memory allocation error"); 212 cur->nlink = 1; 213 cur->st = *statbuf; 214 return (cur); 215 } 216 217 /* 218 * apply_specfile -- 219 * read in the mtree(8) specfile, and apply it to the tree 220 * at dir,parent. parameters in parent on equivalent types 221 * will be changed to those found in specfile, and missing 222 * entries will be added. 223 */ 224 void 225 apply_specfile(const char *specfile, const char *dir, fsnode *parent) 226 { 227 struct timeval start; 228 FILE *fp; 229 NODE *root; 230 231 assert(specfile != NULL); 232 assert(parent != NULL); 233 234 if (debug & DEBUG_APPLY_SPECFILE) 235 printf("apply_specfile: %s, %s %p\n", specfile, dir, parent); 236 237 /* read in the specfile */ 238 if ((fp = fopen(specfile, "r")) == NULL) 239 err(1, "Can't open `%s'", specfile); 240 TIMER_START(start); 241 root = spec(fp); 242 TIMER_RESULTS(start, "spec"); 243 if (fclose(fp) == EOF) 244 err(1, "Can't close `%s'", specfile); 245 246 /* perform some sanity checks */ 247 if (root == NULL) 248 errx(1, "Specfile `%s' did not contain a tree", specfile); 249 assert(strcmp(root->name, ".") == 0); 250 assert(root->type == F_DIR); 251 252 /* merge in the changes */ 253 apply_specdir(dir, root, parent); 254 } 255 256 static void 257 apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode) 258 { 259 char path[MAXPATHLEN + 1]; 260 NODE *curnode; 261 fsnode *curfsnode; 262 263 assert(specnode != NULL); 264 assert(dirnode != NULL); 265 266 if (debug & DEBUG_APPLY_SPECFILE) 267 printf("apply_specdir: %s %p %p\n", dir, specnode, dirnode); 268 269 if (specnode->type != F_DIR) 270 errx(1, "Specfile node `%s/%s' is not a directory", 271 dir, specnode->name); 272 if (dirnode->type != S_IFDIR) 273 errx(1, "Directory node `%s/%s' is not a directory", 274 dir, dirnode->name); 275 276 apply_specentry(dir, specnode, dirnode); 277 278 /* now walk specnode->child matching up with dirnode */ 279 for (curnode = specnode->child; curnode != NULL; 280 curnode = curnode->next) { 281 if (debug & DEBUG_APPLY_SPECENTRY) 282 printf("apply_specdir: spec %s\n", 283 curnode->name); 284 for (curfsnode = dirnode->next; curfsnode != NULL; 285 curfsnode = curfsnode->next) { 286 #if 0 /* too verbose for now */ 287 if (debug & DEBUG_APPLY_SPECENTRY) 288 printf("apply_specdir: dirent %s\n", 289 curfsnode->name); 290 #endif 291 if (strcmp(curnode->name, curfsnode->name) == 0) 292 break; 293 } 294 if (curfsnode == NULL) { /* need new entry */ 295 struct stat stbuf; 296 297 /* check that enough info is provided */ 298 #define NODETEST(t, m) \ 299 if (!(t)) \ 300 errx(1, "`%s/%s': %s not provided", \ 301 dir, curnode->name, m) 302 NODETEST(curnode->flags & F_TYPE, "type"); 303 NODETEST(curnode->flags & F_MODE, "mode"); 304 /* XXX: require F_TIME ? */ 305 NODETEST(curnode->flags & F_GID || 306 curnode->flags & F_GNAME, "group"); 307 NODETEST(curnode->flags & F_UID || 308 curnode->flags & F_UNAME, "user"); 309 if (curnode->type == F_BLOCK || curnode->type == F_CHAR) 310 NODETEST(curnode->flags & F_DEV, 311 "device number"); 312 #undef NODETEST 313 314 if (debug & DEBUG_APPLY_SPECFILE) 315 printf("apply_specdir: adding %s\n", 316 curnode->name); 317 /* build minimal fsnode */ 318 memset(&stbuf, 0, sizeof(stbuf)); 319 stbuf.st_mode = nodetoino(curnode->type); 320 stbuf.st_nlink = 1; 321 stbuf.st_mtime = stbuf.st_atime = 322 stbuf.st_ctime = start_time.tv_sec; 323 stbuf.st_mtimensec = stbuf.st_atimensec = 324 stbuf.st_ctimensec = start_time.tv_nsec; 325 curfsnode = create_fsnode(curnode->name, &stbuf); 326 curfsnode->parent = dirnode->parent; 327 curfsnode->first = dirnode; 328 curfsnode->next = dirnode->next; 329 dirnode->next = curfsnode; 330 if (curfsnode->type == S_IFDIR) { 331 /* for dirs, make "." entry as well */ 332 curfsnode->child = create_fsnode(".", &stbuf); 333 curfsnode->child->parent = curfsnode; 334 curfsnode->child->first = curfsnode->child; 335 } 336 if (curfsnode->type == S_IFLNK) { 337 assert(curnode->slink != NULL); 338 /* for symlinks, copy the target */ 339 if ((curfsnode->symlink = 340 strdup(curnode->slink)) == NULL) 341 err(1, "Memory allocation error"); 342 } 343 } 344 apply_specentry(dir, curnode, curfsnode); 345 if (curnode->type == F_DIR) { 346 if (curfsnode->type != S_IFDIR) 347 errx(1, "`%s/%s' is not a directory", 348 dir, curfsnode->name); 349 assert (curfsnode->child != NULL); 350 if (snprintf(path, sizeof(path), "%s/%s", 351 dir, curnode->name) >= sizeof(path)) 352 errx(1, "Pathname too long."); 353 apply_specdir(path, curnode, curfsnode->child); 354 } 355 } 356 } 357 358 static void 359 apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode) 360 { 361 362 assert(specnode != NULL); 363 assert(dirnode != NULL); 364 365 if (nodetoino(specnode->type) != dirnode->type) 366 errx(1, "`%s/%s' type mismatch: specfile %s, tree %s", 367 dir, specnode->name, inode_type(nodetoino(specnode->type)), 368 inode_type(dirnode->type)); 369 370 if (debug & DEBUG_APPLY_SPECENTRY) 371 printf("apply_specentry: %s/%s\n", dir, dirnode->name); 372 373 #define ASEPRINT(t, b, o, n) \ 374 if (debug & DEBUG_APPLY_SPECENTRY) \ 375 printf("\t\t\tchanging %s from " b " to " b "\n", \ 376 t, o, n) 377 378 if (specnode->flags & (F_GID | F_GNAME)) { 379 ASEPRINT("gid", "%d", 380 dirnode->inode->st.st_gid, specnode->st_gid); 381 dirnode->inode->st.st_gid = specnode->st_gid; 382 } 383 if (specnode->flags & F_MODE) { 384 ASEPRINT("mode", "%#o", 385 dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode); 386 dirnode->inode->st.st_mode &= ~ALLPERMS; 387 dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS); 388 } 389 /* XXX: ignoring F_NLINK for now */ 390 if (specnode->flags & F_SIZE) { 391 ASEPRINT("size", "%lld", 392 (long long)dirnode->inode->st.st_size, 393 (long long)specnode->st_size); 394 dirnode->inode->st.st_size = specnode->st_size; 395 } 396 if (specnode->flags & F_SLINK) { 397 assert(dirnode->symlink != NULL); 398 assert(specnode->slink != NULL); 399 ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink); 400 free(dirnode->symlink); 401 if ((dirnode->symlink = strdup(specnode->slink)) == NULL) 402 err(1, "Memory allocation error"); 403 } 404 if (specnode->flags & F_TIME) { 405 ASEPRINT("time", "%ld", 406 (long)dirnode->inode->st.st_mtime, 407 (long)specnode->st_mtime); 408 dirnode->inode->st.st_mtime = specnode->st_mtime; 409 dirnode->inode->st.st_mtimensec = specnode->st_mtimensec; 410 dirnode->inode->st.st_atime = specnode->st_mtime; 411 dirnode->inode->st.st_atimensec = specnode->st_mtimensec; 412 dirnode->inode->st.st_ctime = start_time.tv_sec; 413 dirnode->inode->st.st_ctimensec = start_time.tv_nsec; 414 } 415 if (specnode->flags & (F_UID | F_UNAME)) { 416 ASEPRINT("uid", "%d", 417 dirnode->inode->st.st_uid, specnode->st_uid); 418 dirnode->inode->st.st_uid = specnode->st_uid; 419 } 420 if (specnode->flags & F_FLAGS) { 421 ASEPRINT("flags", "%#lX", 422 (ulong)dirnode->inode->st.st_flags, 423 (ulong)specnode->st_flags); 424 dirnode->inode->st.st_flags = specnode->st_flags; 425 } 426 if (specnode->flags & F_DEV) { 427 ASEPRINT("rdev", "%#x", 428 dirnode->inode->st.st_rdev, specnode->st_rdev); 429 dirnode->inode->st.st_rdev = specnode->st_rdev; 430 } 431 #undef ASEPRINT 432 } 433 434 435 /* 436 * dump_fsnodes -- 437 * dump the fsnodes from `cur', based in the directory `dir' 438 */ 439 void 440 dump_fsnodes(const char *dir, fsnode *root) 441 { 442 fsnode *cur; 443 char path[MAXPATHLEN + 1]; 444 445 assert (dir != NULL); 446 printf("dump_fsnodes: %s %p\n", dir, root); 447 for (cur = root; cur != NULL; cur = cur->next) { 448 if (snprintf(path, sizeof(path), "%s/%s", dir, cur->name) 449 >= sizeof(path)) 450 errx(1, "Pathname too long."); 451 452 if (debug & DEBUG_DUMP_FSNODES_VERBOSE) 453 printf("cur=%8p parent=%8p first=%8p ", 454 cur, cur->parent, cur->first); 455 printf("%7s: %s", inode_type(cur->type), path); 456 if (S_ISLNK(cur->type)) { 457 assert(cur->symlink != NULL); 458 printf(" -> %s", cur->symlink); 459 } else { 460 assert (cur->symlink == NULL); 461 } 462 if (cur->inode->nlink > 1) 463 printf(", nlinks=%d", cur->inode->nlink); 464 putchar('\n'); 465 466 if (cur->child) { 467 assert (cur->type == S_IFDIR); 468 dump_fsnodes(path, cur->child); 469 } 470 } 471 printf("dump_fsnodes: finished %s\n", dir); 472 } 473 474 475 /* 476 * inode_type -- 477 * for a given inode type `mode', return a descriptive string. 478 * for most cases, uses inotype() from mtree/misc.c 479 */ 480 const char * 481 inode_type(mode_t mode) 482 { 483 484 if (S_ISLNK(mode)) 485 return ("symlink"); /* inotype() returns "link"... */ 486 return (inotype(mode)); 487 } 488 489 490 /* 491 * link_check -- 492 * return pointer to fsnode matching `entry's st_ino & st_dev if it exists, 493 * otherwise add `entry' to table and return NULL 494 */ 495 static fsinode * 496 link_check(fsinode *entry) 497 { 498 static struct dupnode { 499 uint32_t dev; 500 uint32_t ino; 501 fsinode *dup; 502 } *dups; 503 static int ndups, maxdups; 504 505 int i; 506 507 assert (entry != NULL); 508 509 /* XXX; maybe traverse in reverse for speed? */ 510 for (i = 0; i < ndups; i++) { 511 if (dups[i].dev == entry->st.st_dev && 512 dups[i].ino == entry->st.st_ino) { 513 if (debug & DEBUG_WALK_DIR_LINKCHECK) 514 printf("link_check: found [%d,%d]\n", 515 entry->st.st_dev, entry->st.st_ino); 516 return (dups[i].dup); 517 } 518 } 519 520 if (debug & DEBUG_WALK_DIR_LINKCHECK) 521 printf("link_check: no match for [%d, %d]\n", 522 entry->st.st_dev, entry->st.st_ino); 523 if (ndups == maxdups) { 524 maxdups += 128; 525 if ((dups = realloc(dups, sizeof(struct dupnode) * maxdups)) 526 == NULL) 527 err(1, "Memory allocation error"); 528 } 529 dups[ndups].dev = entry->st.st_dev; 530 dups[ndups].ino = entry->st.st_ino; 531 dups[ndups].dup = entry; 532 ndups++; 533 534 return (NULL); 535 } 536