1 1.43 andvar /* $NetBSD: walk.c,v 1.43 2025/05/01 14:14:36 andvar Exp $ */ 2 1.1 lukem 3 1.1 lukem /* 4 1.5 lukem * Copyright (c) 2001 Wasabi Systems, Inc. 5 1.1 lukem * All rights reserved. 6 1.1 lukem * 7 1.1 lukem * Written by Luke Mewburn for Wasabi Systems, Inc. 8 1.1 lukem * 9 1.1 lukem * Redistribution and use in source and binary forms, with or without 10 1.1 lukem * modification, are permitted provided that the following conditions 11 1.1 lukem * are met: 12 1.1 lukem * 1. Redistributions of source code must retain the above copyright 13 1.1 lukem * notice, this list of conditions and the following disclaimer. 14 1.1 lukem * 2. Redistributions in binary form must reproduce the above copyright 15 1.1 lukem * notice, this list of conditions and the following disclaimer in the 16 1.1 lukem * documentation and/or other materials provided with the distribution. 17 1.1 lukem * 3. All advertising materials mentioning features or use of this software 18 1.1 lukem * must display the following acknowledgement: 19 1.1 lukem * This product includes software developed for the NetBSD Project by 20 1.1 lukem * Wasabi Systems, Inc. 21 1.1 lukem * 4. The name of Wasabi Systems, Inc. may not be used to endorse 22 1.1 lukem * or promote products derived from this software without specific prior 23 1.1 lukem * written permission. 24 1.1 lukem * 25 1.1 lukem * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND 26 1.1 lukem * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 27 1.1 lukem * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 1.1 lukem * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC 29 1.1 lukem * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 1.1 lukem * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 1.1 lukem * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 1.1 lukem * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 1.1 lukem * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 1.1 lukem * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 1.1 lukem * POSSIBILITY OF SUCH DAMAGE. 36 1.1 lukem */ 37 1.1 lukem 38 1.17 jmc #if HAVE_NBTOOL_CONFIG_H 39 1.17 jmc #include "nbtool_config.h" 40 1.17 jmc #endif 41 1.17 jmc 42 1.2 lukem #include <sys/cdefs.h> 43 1.8 tv #if defined(__RCSID) && !defined(__lint) 44 1.43 andvar __RCSID("$NetBSD: walk.c,v 1.43 2025/05/01 14:14:36 andvar Exp $"); 45 1.2 lukem #endif /* !__lint */ 46 1.1 lukem 47 1.1 lukem #include <sys/param.h> 48 1.27 christos #include <sys/stat.h> 49 1.1 lukem 50 1.1 lukem #include <assert.h> 51 1.1 lukem #include <errno.h> 52 1.1 lukem #include <fcntl.h> 53 1.1 lukem #include <stdio.h> 54 1.1 lukem #include <dirent.h> 55 1.1 lukem #include <stdlib.h> 56 1.1 lukem #include <string.h> 57 1.1 lukem #include <unistd.h> 58 1.27 christos #include <util.h> 59 1.1 lukem 60 1.1 lukem #include "makefs.h" 61 1.1 lukem #include "mtree.h" 62 1.1 lukem 63 1.23 dbj static void apply_specdir(const char *, NODE *, fsnode *, int); 64 1.1 lukem static void apply_specentry(const char *, NODE *, fsnode *); 65 1.25 christos static fsnode *create_fsnode(const char *, const char *, const char *, 66 1.25 christos struct stat *); 67 1.6 lukem static fsinode *link_check(fsinode *); 68 1.40 christos static size_t missing = 0; 69 1.1 lukem 70 1.34 christos /* 71 1.34 christos * fsnode_cmp -- 72 1.34 christos * This function is used by `qsort` so sort one directory's 73 1.43 andvar * entries. `.` is always first, followed by anything else 74 1.34 christos * as compared by `strcmp()`. 75 1.34 christos */ 76 1.34 christos static int 77 1.36 christos fsnode_cmp(const void *vleft, const void *vright) 78 1.34 christos { 79 1.36 christos const fsnode * const *left = vleft; 80 1.36 christos const fsnode * const *right = vright; 81 1.36 christos const char *lname = (*left)->name, *rname = (*right)->name; 82 1.34 christos 83 1.36 christos if (strcmp(lname, ".") == 0) 84 1.34 christos return -1; 85 1.36 christos if (strcmp(rname, ".") == 0) 86 1.34 christos return 1; 87 1.36 christos return strcmp(lname, rname); 88 1.34 christos } 89 1.1 lukem 90 1.37 christos static fsnode * 91 1.37 christos fsnode_sort(fsnode *first, const char *root, const char *dir) 92 1.37 christos { 93 1.37 christos fsnode **list, **listptr; 94 1.37 christos size_t num = 0; 95 1.37 christos 96 1.37 christos for (fsnode *tmp = first; tmp; tmp = tmp->next, num++) { 97 1.37 christos if (debug & DEBUG_DUMP_FSNODES_VERBOSE) 98 1.38 christos printf("%s: pre sort: %s %s %s\n", 99 1.38 christos __func__, root, dir, tmp->name); 100 1.37 christos } 101 1.37 christos 102 1.37 christos list = listptr = ecalloc(num, sizeof(*list)); 103 1.37 christos for (fsnode *tmp = first; tmp; tmp = tmp->next) 104 1.37 christos *listptr++ = tmp; 105 1.37 christos 106 1.39 rillig qsort(list, num, sizeof(*list), fsnode_cmp); 107 1.37 christos 108 1.37 christos for (size_t i = 0; i < num - 1; ++i) 109 1.37 christos list[i]->next = list[i + 1]; 110 1.37 christos list[num - 1]->next = NULL; 111 1.37 christos first = list[0]; 112 1.37 christos assert(strcmp(first->name, ".") == 0); 113 1.37 christos free(list); 114 1.37 christos if (debug & DEBUG_DUMP_FSNODES_VERBOSE) 115 1.37 christos for (fsnode *tmp = first; tmp; tmp = tmp->next) 116 1.38 christos printf("%s: post sort: %s %s %s\n", 117 1.38 christos __func__, root, dir, tmp->name); 118 1.37 christos 119 1.37 christos return first; 120 1.37 christos } 121 1.37 christos 122 1.1 lukem /* 123 1.41 christos * join current entry with the list. Return the current entry to replace 124 1.41 christos * in cur, and 1 if it is a directory and we need to add or 0 if we need 125 1.41 christos * to replace it. 126 1.41 christos */ 127 1.41 christos static int 128 1.41 christos fsnode_join(fsnode **curp, fsnode *join, fsnode *last, const char *path, 129 1.41 christos const char *name, const struct stat *st, int replace) 130 1.41 christos { 131 1.41 christos fsnode *cur; 132 1.41 christos 133 1.41 christos /* Look for the entry to replace by name */ 134 1.41 christos cur = join->next; 135 1.41 christos for (;;) { 136 1.41 christos if (cur == NULL || strcmp(cur->name, name) == 0) 137 1.41 christos break; 138 1.41 christos if (cur == last) { 139 1.41 christos cur = NULL; 140 1.41 christos break; 141 1.41 christos } 142 1.41 christos cur = cur->next; 143 1.41 christos } 144 1.41 christos if (cur == NULL) { 145 1.41 christos /* Not found */ 146 1.41 christos *curp = NULL; 147 1.41 christos return 0; 148 1.41 christos } 149 1.41 christos if (S_ISDIR(cur->type) && S_ISDIR(st->st_mode)) { 150 1.41 christos /* 151 1.41 christos * both the entry to join and this entry are directories 152 1.41 christos * need to merge the two directories 153 1.41 christos */ 154 1.41 christos if (debug & DEBUG_WALK_DIR_NODE) 155 1.41 christos printf("%s: merging %s with %p\n", 156 1.41 christos __func__, path, cur->child); 157 1.41 christos *curp = cur; 158 1.41 christos return 1; 159 1.41 christos } 160 1.41 christos if (!replace) { 161 1.41 christos /* 162 1.41 christos * if they are not both directories and replace is not 163 1.41 christos * specified, bail out 164 1.41 christos */ 165 1.41 christos errx(EXIT_FAILURE, "Can't merge %s `%s' with existing %s", 166 1.41 christos inode_type(st->st_mode), path, inode_type(cur->type)); 167 1.41 christos } 168 1.41 christos 169 1.41 christos if (debug & DEBUG_WALK_DIR_NODE) 170 1.41 christos printf("%s: replacing %s %s\n", 171 1.41 christos __func__, inode_type(st->st_mode), path); 172 1.41 christos 173 1.41 christos /* merge the join list */ 174 1.41 christos if (cur == join->next) 175 1.41 christos join->next = cur->next; 176 1.41 christos else { 177 1.41 christos fsnode *p; 178 1.41 christos for (p = join->next; 179 1.41 christos p->next != cur; p = p->next) 180 1.41 christos continue; 181 1.41 christos p->next = cur->next; 182 1.41 christos } 183 1.41 christos /* return the entry to be replaced */ 184 1.41 christos *curp = cur; 185 1.41 christos return 0; 186 1.41 christos } 187 1.41 christos 188 1.41 christos /* 189 1.1 lukem * walk_dir -- 190 1.25 christos * build a tree of fsnodes from `root' and `dir', with a parent 191 1.25 christos * fsnode of `parent' (which may be NULL for the root of the tree). 192 1.25 christos * append the tree to a fsnode of `join' if it is not NULL. 193 1.1 lukem * each "level" is a directory, with the "." entry guaranteed to be 194 1.1 lukem * at the start of the list, and without ".." entries. 195 1.1 lukem */ 196 1.1 lukem fsnode * 197 1.28 christos walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join, 198 1.30 simonb int replace, int follow) 199 1.1 lukem { 200 1.25 christos fsnode *first, *cur, *prev, *last; 201 1.1 lukem DIR *dirp; 202 1.1 lukem struct dirent *dent; 203 1.1 lukem char path[MAXPATHLEN + 1]; 204 1.1 lukem struct stat stbuf; 205 1.25 christos char *name, *rp; 206 1.25 christos int dot, len; 207 1.1 lukem 208 1.25 christos assert(root != NULL); 209 1.1 lukem assert(dir != NULL); 210 1.1 lukem 211 1.25 christos len = snprintf(path, sizeof(path), "%s/%s", root, dir); 212 1.37 christos if ((size_t)len >= sizeof(path)) 213 1.33 tsutsui errx(EXIT_FAILURE, "Pathname too long."); 214 1.1 lukem if (debug & DEBUG_WALK_DIR) 215 1.38 christos printf("%s: %s %p\n", __func__, path, parent); 216 1.25 christos if ((dirp = opendir(path)) == NULL) 217 1.33 tsutsui err(EXIT_FAILURE, "Can't opendir `%s'", path); 218 1.25 christos rp = path + strlen(root) + 1; 219 1.25 christos if (join != NULL) { 220 1.25 christos first = cur = join; 221 1.25 christos while (cur->next != NULL) 222 1.25 christos cur = cur->next; 223 1.25 christos prev = last = cur; 224 1.25 christos } else 225 1.25 christos last = first = prev = NULL; 226 1.1 lukem while ((dent = readdir(dirp)) != NULL) { 227 1.25 christos name = dent->d_name; 228 1.25 christos dot = 0; 229 1.25 christos if (name[0] == '.') 230 1.25 christos switch (name[1]) { 231 1.25 christos case '\0': /* "." */ 232 1.25 christos if (join != NULL) 233 1.25 christos continue; 234 1.25 christos dot = 1; 235 1.25 christos break; 236 1.25 christos case '.': /* ".." */ 237 1.25 christos if (name[2] == '\0') 238 1.25 christos continue; 239 1.25 christos /* FALLTHROUGH */ 240 1.25 christos default: 241 1.25 christos dot = 0; 242 1.25 christos } 243 1.1 lukem if (debug & DEBUG_WALK_DIR_NODE) 244 1.38 christos printf("%s: scanning %s/%s/%s\n", 245 1.38 christos __func__, root, dir, name); 246 1.25 christos if (snprintf(path + len, sizeof(path) - len, "/%s", name) >= 247 1.25 christos (int)sizeof(path) - len) 248 1.33 tsutsui errx(EXIT_FAILURE, "Pathname too long."); 249 1.30 simonb if (follow) { 250 1.30 simonb if (stat(path, &stbuf) == -1) 251 1.33 tsutsui err(EXIT_FAILURE, "Can't stat `%s'", path); 252 1.30 simonb } else { 253 1.30 simonb if (lstat(path, &stbuf) == -1) 254 1.33 tsutsui err(EXIT_FAILURE, "Can't lstat `%s'", path); 255 1.37 christos /* 256 1.37 christos * Symlink permission bits vary between filesystems/OSs 257 1.37 christos * (ie. 0755 on FFS/NetBSD, 0777 for ext[234]/Linux), 258 1.37 christos * force them to 0755. 259 1.37 christos */ 260 1.35 christos if (S_ISLNK(stbuf.st_mode)) { 261 1.35 christos stbuf.st_mode &= ~(S_IRWXU | S_IRWXG | S_IRWXO); 262 1.35 christos stbuf.st_mode |= S_IRWXU 263 1.37 christos | S_IRGRP | S_IXGRP 264 1.37 christos | S_IROTH | S_IXOTH; 265 1.35 christos } 266 1.30 simonb } 267 1.17 jmc #ifdef S_ISSOCK 268 1.1 lukem if (S_ISSOCK(stbuf.st_mode & S_IFMT)) { 269 1.1 lukem if (debug & DEBUG_WALK_DIR_NODE) 270 1.41 christos printf("%s: skipping socket %s\n", __func__, 271 1.41 christos path); 272 1.1 lukem continue; 273 1.1 lukem } 274 1.17 jmc #endif 275 1.1 lukem 276 1.25 christos if (join != NULL) { 277 1.41 christos if (fsnode_join(&cur, join, last, path, name, &stbuf, 278 1.41 christos replace)) { 279 1.41 christos cur->child = walk_dir(root, rp, cur, 280 1.41 christos cur->child, replace, follow); 281 1.41 christos continue; 282 1.41 christos } else if (cur) { 283 1.41 christos if (prev == cur) { 284 1.41 christos fsnode *p = join; 285 1.41 christos while (p->next != NULL) 286 1.41 christos p = p->next; 287 1.41 christos prev = p; 288 1.28 christos } 289 1.41 christos free(cur->name); 290 1.41 christos free(cur->path); 291 1.41 christos free(cur); 292 1.25 christos } 293 1.25 christos } 294 1.25 christos 295 1.25 christos cur = create_fsnode(root, dir, name, &stbuf); 296 1.1 lukem cur->parent = parent; 297 1.25 christos if (dot) { 298 1.1 lukem /* ensure "." is at the start of the list */ 299 1.1 lukem cur->next = first; 300 1.1 lukem first = cur; 301 1.1 lukem if (! prev) 302 1.1 lukem prev = cur; 303 1.25 christos cur->first = first; 304 1.1 lukem } else { /* not "." */ 305 1.1 lukem if (prev) 306 1.1 lukem prev->next = cur; 307 1.1 lukem prev = cur; 308 1.1 lukem if (!first) 309 1.1 lukem first = cur; 310 1.25 christos cur->first = first; 311 1.1 lukem if (S_ISDIR(cur->type)) { 312 1.28 christos cur->child = walk_dir(root, rp, cur, NULL, 313 1.30 simonb replace, follow); 314 1.1 lukem continue; 315 1.1 lukem } 316 1.1 lukem } 317 1.6 lukem if (stbuf.st_nlink > 1) { 318 1.6 lukem fsinode *curino; 319 1.6 lukem 320 1.6 lukem curino = link_check(cur->inode); 321 1.6 lukem if (curino != NULL) { 322 1.6 lukem free(cur->inode); 323 1.6 lukem cur->inode = curino; 324 1.6 lukem cur->inode->nlink++; 325 1.20 dbj if (debug & DEBUG_WALK_DIR_LINKCHECK) 326 1.38 christos printf("%s: link check found [%ju, %ju]\n", 327 1.38 christos __func__, 328 1.36 christos (uintmax_t)curino->st.st_dev, 329 1.36 christos (uintmax_t)curino->st.st_ino); 330 1.6 lukem } 331 1.1 lukem } 332 1.1 lukem if (S_ISLNK(cur->type)) { 333 1.1 lukem char slink[PATH_MAX+1]; 334 1.36 christos ssize_t llen; 335 1.1 lukem 336 1.13 itojun llen = readlink(path, slink, sizeof(slink) - 1); 337 1.1 lukem if (llen == -1) 338 1.33 tsutsui err(EXIT_FAILURE, "Readlink `%s'", path); 339 1.1 lukem slink[llen] = '\0'; 340 1.27 christos cur->symlink = estrdup(slink); 341 1.1 lukem } 342 1.1 lukem } 343 1.25 christos assert(first != NULL); 344 1.25 christos if (join == NULL) 345 1.25 christos for (cur = first->next; cur != NULL; cur = cur->next) 346 1.25 christos cur->first = first; 347 1.1 lukem if (closedir(dirp) == -1) 348 1.33 tsutsui err(EXIT_FAILURE, "Can't closedir `%s/%s'", root, dir); 349 1.34 christos 350 1.37 christos return fsnode_sort(first, root, dir); 351 1.1 lukem } 352 1.1 lukem 353 1.1 lukem static fsnode * 354 1.25 christos create_fsnode(const char *root, const char *path, const char *name, 355 1.25 christos struct stat *stbuf) 356 1.1 lukem { 357 1.1 lukem fsnode *cur; 358 1.1 lukem 359 1.27 christos cur = ecalloc(1, sizeof(*cur)); 360 1.27 christos cur->path = estrdup(path); 361 1.27 christos cur->name = estrdup(name); 362 1.27 christos cur->inode = ecalloc(1, sizeof(*cur->inode)); 363 1.25 christos cur->root = root; 364 1.6 lukem cur->type = stbuf->st_mode & S_IFMT; 365 1.7 lukem cur->inode->nlink = 1; 366 1.7 lukem cur->inode->st = *stbuf; 367 1.29 christos if (stampst.st_ino) { 368 1.29 christos cur->inode->st.st_atime = stampst.st_atime; 369 1.29 christos cur->inode->st.st_mtime = stampst.st_mtime; 370 1.29 christos cur->inode->st.st_ctime = stampst.st_ctime; 371 1.29 christos #if HAVE_STRUCT_STAT_ST_MTIMENSEC 372 1.29 christos cur->inode->st.st_atimensec = stampst.st_atimensec; 373 1.29 christos cur->inode->st.st_mtimensec = stampst.st_mtimensec; 374 1.29 christos cur->inode->st.st_ctimensec = stampst.st_ctimensec; 375 1.29 christos #endif 376 1.32 riastrad #if HAVE_STRUCT_STAT_BIRTHTIME 377 1.29 christos cur->inode->st.st_birthtime = stampst.st_birthtime; 378 1.29 christos cur->inode->st.st_birthtimensec = stampst.st_birthtimensec; 379 1.29 christos #endif 380 1.29 christos } 381 1.1 lukem return (cur); 382 1.1 lukem } 383 1.1 lukem 384 1.1 lukem /* 385 1.22 dbj * free_fsnodes -- 386 1.22 dbj * Removes node from tree and frees it and all of 387 1.31 wiz * its descendents. 388 1.22 dbj */ 389 1.22 dbj void 390 1.22 dbj free_fsnodes(fsnode *node) 391 1.22 dbj { 392 1.22 dbj fsnode *cur, *next; 393 1.22 dbj 394 1.22 dbj assert(node != NULL); 395 1.22 dbj 396 1.22 dbj /* for ".", start with actual parent node */ 397 1.22 dbj if (node->first == node) { 398 1.22 dbj assert(node->name[0] == '.' && node->name[1] == '\0'); 399 1.22 dbj if (node->parent) { 400 1.22 dbj assert(node->parent->child == node); 401 1.22 dbj node = node->parent; 402 1.22 dbj } 403 1.22 dbj } 404 1.22 dbj 405 1.22 dbj /* Find ourselves in our sibling list and unlink */ 406 1.22 dbj if (node->first != node) { 407 1.22 dbj for (cur = node->first; cur->next; cur = cur->next) { 408 1.22 dbj if (cur->next == node) { 409 1.22 dbj cur->next = node->next; 410 1.22 dbj node->next = NULL; 411 1.22 dbj break; 412 1.22 dbj } 413 1.22 dbj } 414 1.22 dbj } 415 1.22 dbj 416 1.22 dbj for (cur = node; cur != NULL; cur = next) { 417 1.22 dbj next = cur->next; 418 1.22 dbj if (cur->child) { 419 1.22 dbj cur->child->parent = NULL; 420 1.22 dbj free_fsnodes(cur->child); 421 1.22 dbj } 422 1.22 dbj if (cur->inode->nlink-- == 1) 423 1.22 dbj free(cur->inode); 424 1.22 dbj if (cur->symlink) 425 1.22 dbj free(cur->symlink); 426 1.25 christos free(cur->path); 427 1.22 dbj free(cur->name); 428 1.22 dbj free(cur); 429 1.22 dbj } 430 1.22 dbj } 431 1.22 dbj 432 1.22 dbj /* 433 1.1 lukem * apply_specfile -- 434 1.1 lukem * read in the mtree(8) specfile, and apply it to the tree 435 1.1 lukem * at dir,parent. parameters in parent on equivalent types 436 1.1 lukem * will be changed to those found in specfile, and missing 437 1.1 lukem * entries will be added. 438 1.1 lukem */ 439 1.1 lukem void 440 1.23 dbj apply_specfile(const char *specfile, const char *dir, fsnode *parent, int speconly) 441 1.1 lukem { 442 1.1 lukem struct timeval start; 443 1.1 lukem FILE *fp; 444 1.1 lukem NODE *root; 445 1.1 lukem 446 1.1 lukem assert(specfile != NULL); 447 1.1 lukem assert(parent != NULL); 448 1.1 lukem 449 1.1 lukem if (debug & DEBUG_APPLY_SPECFILE) 450 1.38 christos printf("%s: %s, %s %p\n", __func__, specfile, dir, parent); 451 1.1 lukem 452 1.1 lukem /* read in the specfile */ 453 1.1 lukem if ((fp = fopen(specfile, "r")) == NULL) 454 1.33 tsutsui err(EXIT_FAILURE, "Can't open `%s'", specfile); 455 1.1 lukem TIMER_START(start); 456 1.1 lukem root = spec(fp); 457 1.1 lukem TIMER_RESULTS(start, "spec"); 458 1.1 lukem if (fclose(fp) == EOF) 459 1.33 tsutsui err(EXIT_FAILURE, "Can't close `%s'", specfile); 460 1.1 lukem 461 1.1 lukem /* perform some sanity checks */ 462 1.1 lukem if (root == NULL) 463 1.33 tsutsui errx(EXIT_FAILURE, 464 1.33 tsutsui "Specfile `%s' did not contain a tree", specfile); 465 1.1 lukem assert(strcmp(root->name, ".") == 0); 466 1.1 lukem assert(root->type == F_DIR); 467 1.1 lukem 468 1.1 lukem /* merge in the changes */ 469 1.23 dbj apply_specdir(dir, root, parent, speconly); 470 1.21 dbj 471 1.21 dbj free_nodes(root); 472 1.40 christos if (missing) 473 1.40 christos errx(EXIT_FAILURE, "Add %zu missing entries in `%s'", 474 1.40 christos missing, specfile); 475 1.1 lukem } 476 1.1 lukem 477 1.1 lukem static void 478 1.23 dbj apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode, int speconly) 479 1.1 lukem { 480 1.1 lukem char path[MAXPATHLEN + 1]; 481 1.1 lukem NODE *curnode; 482 1.1 lukem fsnode *curfsnode; 483 1.1 lukem 484 1.1 lukem assert(specnode != NULL); 485 1.1 lukem assert(dirnode != NULL); 486 1.1 lukem 487 1.1 lukem if (debug & DEBUG_APPLY_SPECFILE) 488 1.38 christos printf("%s: %s %p %p\n", __func__, dir, specnode, dirnode); 489 1.1 lukem 490 1.1 lukem if (specnode->type != F_DIR) 491 1.33 tsutsui errx(EXIT_FAILURE, "Specfile node `%s/%s' is not a directory", 492 1.1 lukem dir, specnode->name); 493 1.1 lukem if (dirnode->type != S_IFDIR) 494 1.33 tsutsui errx(EXIT_FAILURE, "Directory node `%s/%s' is not a directory", 495 1.1 lukem dir, dirnode->name); 496 1.1 lukem 497 1.1 lukem apply_specentry(dir, specnode, dirnode); 498 1.1 lukem 499 1.23 dbj /* Remove any filesystem nodes not found in specfile */ 500 1.23 dbj /* XXX inefficient. This is O^2 in each dir and it would 501 1.23 dbj * have been better never to have walked this part of the tree 502 1.23 dbj * to begin with 503 1.23 dbj */ 504 1.23 dbj if (speconly) { 505 1.23 dbj fsnode *next; 506 1.23 dbj assert(dirnode->name[0] == '.' && dirnode->name[1] == '\0'); 507 1.23 dbj for (curfsnode = dirnode->next; curfsnode != NULL; curfsnode = next) { 508 1.23 dbj next = curfsnode->next; 509 1.23 dbj for (curnode = specnode->child; curnode != NULL; 510 1.23 dbj curnode = curnode->next) { 511 1.23 dbj if (strcmp(curnode->name, curfsnode->name) == 0) 512 1.23 dbj break; 513 1.23 dbj } 514 1.23 dbj if (curnode == NULL) { 515 1.40 christos if (speconly > 1) { 516 1.40 christos warnx("missing specfile entry for %s/%s", 517 1.40 christos dir, curfsnode->name); 518 1.40 christos missing++; 519 1.40 christos } 520 1.23 dbj if (debug & DEBUG_APPLY_SPECONLY) { 521 1.38 christos printf("%s: trimming %s/%s %p\n", 522 1.38 christos __func__, dir, curfsnode->name, 523 1.38 christos curfsnode); 524 1.23 dbj } 525 1.23 dbj free_fsnodes(curfsnode); 526 1.23 dbj } 527 1.23 dbj } 528 1.23 dbj } 529 1.23 dbj 530 1.1 lukem /* now walk specnode->child matching up with dirnode */ 531 1.1 lukem for (curnode = specnode->child; curnode != NULL; 532 1.1 lukem curnode = curnode->next) { 533 1.1 lukem if (debug & DEBUG_APPLY_SPECENTRY) 534 1.38 christos printf("%s: spec %s\n", __func__, curnode->name); 535 1.1 lukem for (curfsnode = dirnode->next; curfsnode != NULL; 536 1.1 lukem curfsnode = curfsnode->next) { 537 1.3 lukem #if 0 /* too verbose for now */ 538 1.1 lukem if (debug & DEBUG_APPLY_SPECENTRY) 539 1.38 christos printf("%s: dirent %s\n", __func__, 540 1.1 lukem curfsnode->name); 541 1.3 lukem #endif 542 1.1 lukem if (strcmp(curnode->name, curfsnode->name) == 0) 543 1.1 lukem break; 544 1.1 lukem } 545 1.26 christos if ((size_t)snprintf(path, sizeof(path), "%s/%s", 546 1.9 lukem dir, curnode->name) >= sizeof(path)) 547 1.33 tsutsui errx(EXIT_FAILURE, "Pathname too long."); 548 1.1 lukem if (curfsnode == NULL) { /* need new entry */ 549 1.1 lukem struct stat stbuf; 550 1.1 lukem 551 1.9 lukem /* 552 1.9 lukem * don't add optional spec entries 553 1.9 lukem * that lack an existing fs entry 554 1.9 lukem */ 555 1.9 lukem if ((curnode->flags & F_OPT) && 556 1.9 lukem lstat(path, &stbuf) == -1) 557 1.9 lukem continue; 558 1.9 lukem 559 1.1 lukem /* check that enough info is provided */ 560 1.1 lukem #define NODETEST(t, m) \ 561 1.1 lukem if (!(t)) \ 562 1.33 tsutsui errx(EXIT_FAILURE, \ 563 1.33 tsutsui "`%s': %s not provided", path, m) 564 1.1 lukem NODETEST(curnode->flags & F_TYPE, "type"); 565 1.1 lukem NODETEST(curnode->flags & F_MODE, "mode"); 566 1.1 lukem /* XXX: require F_TIME ? */ 567 1.1 lukem NODETEST(curnode->flags & F_GID || 568 1.1 lukem curnode->flags & F_GNAME, "group"); 569 1.1 lukem NODETEST(curnode->flags & F_UID || 570 1.1 lukem curnode->flags & F_UNAME, "user"); 571 1.1 lukem if (curnode->type == F_BLOCK || curnode->type == F_CHAR) 572 1.1 lukem NODETEST(curnode->flags & F_DEV, 573 1.1 lukem "device number"); 574 1.1 lukem #undef NODETEST 575 1.1 lukem 576 1.1 lukem if (debug & DEBUG_APPLY_SPECFILE) 577 1.38 christos printf("%s: adding %s\n", __func__, curnode->name); 578 1.1 lukem /* build minimal fsnode */ 579 1.1 lukem memset(&stbuf, 0, sizeof(stbuf)); 580 1.1 lukem stbuf.st_mode = nodetoino(curnode->type); 581 1.6 lukem stbuf.st_nlink = 1; 582 1.1 lukem stbuf.st_mtime = stbuf.st_atime = 583 1.1 lukem stbuf.st_ctime = start_time.tv_sec; 584 1.8 tv #if HAVE_STRUCT_STAT_ST_MTIMENSEC 585 1.1 lukem stbuf.st_mtimensec = stbuf.st_atimensec = 586 1.1 lukem stbuf.st_ctimensec = start_time.tv_nsec; 587 1.8 tv #endif 588 1.25 christos curfsnode = create_fsnode(".", ".", curnode->name, 589 1.25 christos &stbuf); 590 1.1 lukem curfsnode->parent = dirnode->parent; 591 1.1 lukem curfsnode->first = dirnode; 592 1.1 lukem curfsnode->next = dirnode->next; 593 1.1 lukem dirnode->next = curfsnode; 594 1.1 lukem if (curfsnode->type == S_IFDIR) { 595 1.1 lukem /* for dirs, make "." entry as well */ 596 1.25 christos curfsnode->child = create_fsnode(".", ".", ".", 597 1.25 christos &stbuf); 598 1.1 lukem curfsnode->child->parent = curfsnode; 599 1.1 lukem curfsnode->child->first = curfsnode->child; 600 1.1 lukem } 601 1.1 lukem if (curfsnode->type == S_IFLNK) { 602 1.3 lukem assert(curnode->slink != NULL); 603 1.1 lukem /* for symlinks, copy the target */ 604 1.27 christos curfsnode->symlink = estrdup(curnode->slink); 605 1.1 lukem } 606 1.1 lukem } 607 1.1 lukem apply_specentry(dir, curnode, curfsnode); 608 1.1 lukem if (curnode->type == F_DIR) { 609 1.1 lukem if (curfsnode->type != S_IFDIR) 610 1.33 tsutsui errx(EXIT_FAILURE, 611 1.33 tsutsui "`%s' is not a directory", path); 612 1.39 rillig assert(curfsnode->child != NULL); 613 1.23 dbj apply_specdir(path, curnode, curfsnode->child, speconly); 614 1.1 lukem } 615 1.1 lukem } 616 1.1 lukem } 617 1.1 lukem 618 1.1 lukem static void 619 1.1 lukem apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode) 620 1.1 lukem { 621 1.1 lukem 622 1.1 lukem assert(specnode != NULL); 623 1.1 lukem assert(dirnode != NULL); 624 1.1 lukem 625 1.1 lukem if (nodetoino(specnode->type) != dirnode->type) 626 1.33 tsutsui errx(EXIT_FAILURE, 627 1.33 tsutsui "`%s/%s' type mismatch: specfile %s, tree %s", 628 1.1 lukem dir, specnode->name, inode_type(nodetoino(specnode->type)), 629 1.1 lukem inode_type(dirnode->type)); 630 1.1 lukem 631 1.1 lukem if (debug & DEBUG_APPLY_SPECENTRY) 632 1.38 christos printf("%s: %s/%s\n", dir, __func__, dirnode->name); 633 1.1 lukem 634 1.1 lukem #define ASEPRINT(t, b, o, n) \ 635 1.1 lukem if (debug & DEBUG_APPLY_SPECENTRY) \ 636 1.1 lukem printf("\t\t\tchanging %s from " b " to " b "\n", \ 637 1.1 lukem t, o, n) 638 1.1 lukem 639 1.1 lukem if (specnode->flags & (F_GID | F_GNAME)) { 640 1.1 lukem ASEPRINT("gid", "%d", 641 1.6 lukem dirnode->inode->st.st_gid, specnode->st_gid); 642 1.6 lukem dirnode->inode->st.st_gid = specnode->st_gid; 643 1.1 lukem } 644 1.1 lukem if (specnode->flags & F_MODE) { 645 1.1 lukem ASEPRINT("mode", "%#o", 646 1.6 lukem dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode); 647 1.6 lukem dirnode->inode->st.st_mode &= ~ALLPERMS; 648 1.6 lukem dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS); 649 1.1 lukem } 650 1.1 lukem /* XXX: ignoring F_NLINK for now */ 651 1.1 lukem if (specnode->flags & F_SIZE) { 652 1.36 christos ASEPRINT("size", "%jd", 653 1.36 christos (intmax_t)dirnode->inode->st.st_size, 654 1.36 christos (intmax_t)specnode->st_size); 655 1.6 lukem dirnode->inode->st.st_size = specnode->st_size; 656 1.1 lukem } 657 1.1 lukem if (specnode->flags & F_SLINK) { 658 1.1 lukem assert(dirnode->symlink != NULL); 659 1.1 lukem assert(specnode->slink != NULL); 660 1.1 lukem ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink); 661 1.1 lukem free(dirnode->symlink); 662 1.27 christos dirnode->symlink = estrdup(specnode->slink); 663 1.1 lukem } 664 1.1 lukem if (specnode->flags & F_TIME) { 665 1.1 lukem ASEPRINT("time", "%ld", 666 1.6 lukem (long)dirnode->inode->st.st_mtime, 667 1.8 tv (long)specnode->st_mtimespec.tv_sec); 668 1.8 tv dirnode->inode->st.st_mtime = specnode->st_mtimespec.tv_sec; 669 1.8 tv dirnode->inode->st.st_atime = specnode->st_mtimespec.tv_sec; 670 1.8 tv dirnode->inode->st.st_ctime = start_time.tv_sec; 671 1.8 tv #if HAVE_STRUCT_STAT_ST_MTIMENSEC 672 1.17 jmc dirnode->inode->st.st_mtimensec = specnode->st_mtimespec.tv_nsec; 673 1.17 jmc dirnode->inode->st.st_atimensec = specnode->st_mtimespec.tv_nsec; 674 1.6 lukem dirnode->inode->st.st_ctimensec = start_time.tv_nsec; 675 1.8 tv #endif 676 1.1 lukem } 677 1.1 lukem if (specnode->flags & (F_UID | F_UNAME)) { 678 1.1 lukem ASEPRINT("uid", "%d", 679 1.6 lukem dirnode->inode->st.st_uid, specnode->st_uid); 680 1.6 lukem dirnode->inode->st.st_uid = specnode->st_uid; 681 1.1 lukem } 682 1.1 lukem if (specnode->flags & F_FLAGS) { 683 1.1 lukem ASEPRINT("flags", "%#lX", 684 1.42 christos (unsigned long)FSINODE_ST_FLAGS(*dirnode->inode), 685 1.11 uwe (unsigned long)specnode->st_flags); 686 1.42 christos FSINODE_ST_FLAGS(*dirnode->inode) = specnode->st_flags; 687 1.1 lukem } 688 1.1 lukem if (specnode->flags & F_DEV) { 689 1.36 christos ASEPRINT("rdev", "%#jx", 690 1.36 christos (uintmax_t)dirnode->inode->st.st_rdev, 691 1.36 christos (uintmax_t)specnode->st_rdev); 692 1.6 lukem dirnode->inode->st.st_rdev = specnode->st_rdev; 693 1.1 lukem } 694 1.1 lukem #undef ASEPRINT 695 1.12 thorpej 696 1.12 thorpej dirnode->flags |= FSNODE_F_HASSPEC; 697 1.1 lukem } 698 1.1 lukem 699 1.1 lukem 700 1.1 lukem /* 701 1.1 lukem * dump_fsnodes -- 702 1.25 christos * dump the fsnodes from `cur' 703 1.1 lukem */ 704 1.1 lukem void 705 1.25 christos dump_fsnodes(fsnode *root) 706 1.1 lukem { 707 1.1 lukem fsnode *cur; 708 1.1 lukem char path[MAXPATHLEN + 1]; 709 1.1 lukem 710 1.38 christos printf("%s: %s %p\n", __func__, root->path, root); 711 1.1 lukem for (cur = root; cur != NULL; cur = cur->next) { 712 1.25 christos if (snprintf(path, sizeof(path), "%s/%s", cur->path, 713 1.25 christos cur->name) >= (int)sizeof(path)) 714 1.33 tsutsui errx(EXIT_FAILURE, "Pathname too long."); 715 1.1 lukem 716 1.1 lukem if (debug & DEBUG_DUMP_FSNODES_VERBOSE) 717 1.38 christos printf("%s: cur=%8p parent=%8p first=%8p ", __func__, 718 1.1 lukem cur, cur->parent, cur->first); 719 1.1 lukem printf("%7s: %s", inode_type(cur->type), path); 720 1.1 lukem if (S_ISLNK(cur->type)) { 721 1.1 lukem assert(cur->symlink != NULL); 722 1.1 lukem printf(" -> %s", cur->symlink); 723 1.1 lukem } else { 724 1.39 rillig assert(cur->symlink == NULL); 725 1.1 lukem } 726 1.6 lukem if (cur->inode->nlink > 1) 727 1.6 lukem printf(", nlinks=%d", cur->inode->nlink); 728 1.1 lukem putchar('\n'); 729 1.1 lukem 730 1.1 lukem if (cur->child) { 731 1.39 rillig assert(cur->type == S_IFDIR); 732 1.25 christos dump_fsnodes(cur->child); 733 1.1 lukem } 734 1.1 lukem } 735 1.38 christos printf("%s: finished %s/%s\n", __func__, root->path, root->name); 736 1.1 lukem } 737 1.1 lukem 738 1.1 lukem 739 1.1 lukem /* 740 1.1 lukem * inode_type -- 741 1.1 lukem * for a given inode type `mode', return a descriptive string. 742 1.1 lukem * for most cases, uses inotype() from mtree/misc.c 743 1.1 lukem */ 744 1.1 lukem const char * 745 1.1 lukem inode_type(mode_t mode) 746 1.1 lukem { 747 1.1 lukem 748 1.3 lukem if (S_ISLNK(mode)) 749 1.1 lukem return ("symlink"); /* inotype() returns "link"... */ 750 1.1 lukem return (inotype(mode)); 751 1.1 lukem } 752 1.1 lukem 753 1.1 lukem 754 1.1 lukem /* 755 1.1 lukem * link_check -- 756 1.20 dbj * return pointer to fsinode matching `entry's st_ino & st_dev if it exists, 757 1.1 lukem * otherwise add `entry' to table and return NULL 758 1.1 lukem */ 759 1.32 riastrad /* This was borrowed from du.c and tweaked to keep an fsnode 760 1.20 dbj * pointer instead. -- dbj (at) netbsd.org 761 1.20 dbj */ 762 1.6 lukem static fsinode * 763 1.6 lukem link_check(fsinode *entry) 764 1.1 lukem { 765 1.20 dbj static struct entry { 766 1.20 dbj fsinode *data; 767 1.20 dbj } *htable; 768 1.36 christos static size_t htshift; /* log(allocated size) */ 769 1.36 christos static size_t htmask; /* allocated size - 1 */ 770 1.36 christos static size_t htused; /* 2*number of insertions */ 771 1.36 christos size_t h, h2; 772 1.20 dbj uint64_t tmp; 773 1.20 dbj /* this constant is (1<<64)/((1+sqrt(5))/2) 774 1.20 dbj * aka (word size)/(golden ratio) 775 1.20 dbj */ 776 1.20 dbj const uint64_t HTCONST = 11400714819323198485ULL; 777 1.36 christos const size_t HTBITS = 64; 778 1.32 riastrad 779 1.20 dbj /* Never store zero in hashtable */ 780 1.20 dbj assert(entry); 781 1.20 dbj 782 1.20 dbj /* Extend hash table if necessary, keep load under 0.5 */ 783 1.20 dbj if (htused<<1 >= htmask) { 784 1.20 dbj struct entry *ohtable; 785 1.20 dbj 786 1.20 dbj if (!htable) 787 1.20 dbj htshift = 10; /* starting hashtable size */ 788 1.20 dbj else 789 1.20 dbj htshift++; /* exponential hashtable growth */ 790 1.20 dbj 791 1.20 dbj htmask = (1 << htshift) - 1; 792 1.20 dbj htused = 0; 793 1.20 dbj 794 1.20 dbj ohtable = htable; 795 1.27 christos htable = ecalloc(htmask+1, sizeof(*htable)); 796 1.20 dbj /* populate newly allocated hashtable */ 797 1.20 dbj if (ohtable) { 798 1.36 christos for (size_t i = 0; i <= htmask>>1; i++) 799 1.20 dbj if (ohtable[i].data) 800 1.20 dbj link_check(ohtable[i].data); 801 1.20 dbj free(ohtable); 802 1.1 lukem } 803 1.1 lukem } 804 1.1 lukem 805 1.20 dbj /* multiplicative hashing */ 806 1.20 dbj tmp = entry->st.st_dev; 807 1.20 dbj tmp <<= HTBITS>>1; 808 1.20 dbj tmp |= entry->st.st_ino; 809 1.20 dbj tmp *= HTCONST; 810 1.20 dbj h = tmp >> (HTBITS - htshift); 811 1.20 dbj h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */ 812 1.32 riastrad 813 1.20 dbj /* open address hashtable search with double hash probing */ 814 1.20 dbj while (htable[h].data) { 815 1.20 dbj if ((htable[h].data->st.st_ino == entry->st.st_ino) && 816 1.20 dbj (htable[h].data->st.st_dev == entry->st.st_dev)) { 817 1.20 dbj return htable[h].data; 818 1.20 dbj } 819 1.20 dbj h = (h + h2) & htmask; 820 1.1 lukem } 821 1.1 lukem 822 1.20 dbj /* Insert the current entry into hashtable */ 823 1.20 dbj htable[h].data = entry; 824 1.20 dbj htused++; 825 1.20 dbj return NULL; 826 1.1 lukem } 827