1*b79065bdSchristos /* $NetBSD: walk.c,v 1.41 2024/10/18 23:28:03 christos Exp $ */ 26325773eSlukem 36325773eSlukem /* 41226af2bSlukem * Copyright (c) 2001 Wasabi Systems, Inc. 56325773eSlukem * All rights reserved. 66325773eSlukem * 76325773eSlukem * Written by Luke Mewburn for Wasabi Systems, Inc. 86325773eSlukem * 96325773eSlukem * Redistribution and use in source and binary forms, with or without 106325773eSlukem * modification, are permitted provided that the following conditions 116325773eSlukem * are met: 126325773eSlukem * 1. Redistributions of source code must retain the above copyright 136325773eSlukem * notice, this list of conditions and the following disclaimer. 146325773eSlukem * 2. Redistributions in binary form must reproduce the above copyright 156325773eSlukem * notice, this list of conditions and the following disclaimer in the 166325773eSlukem * documentation and/or other materials provided with the distribution. 176325773eSlukem * 3. All advertising materials mentioning features or use of this software 186325773eSlukem * must display the following acknowledgement: 196325773eSlukem * This product includes software developed for the NetBSD Project by 206325773eSlukem * Wasabi Systems, Inc. 216325773eSlukem * 4. The name of Wasabi Systems, Inc. may not be used to endorse 226325773eSlukem * or promote products derived from this software without specific prior 236325773eSlukem * written permission. 246325773eSlukem * 256325773eSlukem * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND 266325773eSlukem * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 276325773eSlukem * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 286325773eSlukem * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC 296325773eSlukem * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 306325773eSlukem * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 316325773eSlukem * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 326325773eSlukem * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 336325773eSlukem * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 346325773eSlukem * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 356325773eSlukem * POSSIBILITY OF SUCH DAMAGE. 366325773eSlukem */ 376325773eSlukem 38b2f78261Sjmc #if HAVE_NBTOOL_CONFIG_H 39b2f78261Sjmc #include "nbtool_config.h" 40b2f78261Sjmc #endif 41b2f78261Sjmc 42cafb53fcSlukem #include <sys/cdefs.h> 439fbd8888Stv #if defined(__RCSID) && !defined(__lint) 44*b79065bdSchristos __RCSID("$NetBSD: walk.c,v 1.41 2024/10/18 23:28:03 christos Exp $"); 45cafb53fcSlukem #endif /* !__lint */ 46cafb53fcSlukem 476325773eSlukem #include <sys/param.h> 48e4989541Schristos #include <sys/stat.h> 496325773eSlukem 506325773eSlukem #include <assert.h> 516325773eSlukem #include <errno.h> 526325773eSlukem #include <fcntl.h> 536325773eSlukem #include <stdio.h> 546325773eSlukem #include <dirent.h> 556325773eSlukem #include <stdlib.h> 566325773eSlukem #include <string.h> 576325773eSlukem #include <unistd.h> 58e4989541Schristos #include <util.h> 596325773eSlukem 606325773eSlukem #include "makefs.h" 616325773eSlukem #include "mtree.h" 626325773eSlukem 63d0c4ff45Sdbj static void apply_specdir(const char *, NODE *, fsnode *, int); 646325773eSlukem static void apply_specentry(const char *, NODE *, fsnode *); 65f1cc0951Schristos static fsnode *create_fsnode(const char *, const char *, const char *, 66f1cc0951Schristos struct stat *); 67ebbf3dddSlukem static fsinode *link_check(fsinode *); 680fe82aa4Schristos static size_t missing = 0; 696325773eSlukem 70c50dd23eSchristos /* 71c50dd23eSchristos * fsnode_cmp -- 72c50dd23eSchristos * This function is used by `qsort` so sort one directory's 73c50dd23eSchristos * entries. `.` is always first, sollowed by anything else 74c50dd23eSchristos * as compared by `strcmp()`. 75c50dd23eSchristos */ 76c50dd23eSchristos static int 77a5d2beeeSchristos fsnode_cmp(const void *vleft, const void *vright) 78c50dd23eSchristos { 79a5d2beeeSchristos const fsnode * const *left = vleft; 80a5d2beeeSchristos const fsnode * const *right = vright; 81a5d2beeeSchristos const char *lname = (*left)->name, *rname = (*right)->name; 82c50dd23eSchristos 83a5d2beeeSchristos if (strcmp(lname, ".") == 0) 84c50dd23eSchristos return -1; 85a5d2beeeSchristos if (strcmp(rname, ".") == 0) 86c50dd23eSchristos return 1; 87a5d2beeeSchristos return strcmp(lname, rname); 88c50dd23eSchristos } 896325773eSlukem 90c7e1aa68Schristos static fsnode * 91c7e1aa68Schristos fsnode_sort(fsnode *first, const char *root, const char *dir) 92c7e1aa68Schristos { 93c7e1aa68Schristos fsnode **list, **listptr; 94c7e1aa68Schristos size_t num = 0; 95c7e1aa68Schristos 96c7e1aa68Schristos for (fsnode *tmp = first; tmp; tmp = tmp->next, num++) { 97c7e1aa68Schristos if (debug & DEBUG_DUMP_FSNODES_VERBOSE) 98514871bdSchristos printf("%s: pre sort: %s %s %s\n", 99514871bdSchristos __func__, root, dir, tmp->name); 100c7e1aa68Schristos } 101c7e1aa68Schristos 102c7e1aa68Schristos list = listptr = ecalloc(num, sizeof(*list)); 103c7e1aa68Schristos for (fsnode *tmp = first; tmp; tmp = tmp->next) 104c7e1aa68Schristos *listptr++ = tmp; 105c7e1aa68Schristos 106c7e1aa68Schristos qsort(list, num, sizeof(*list), fsnode_cmp); 107c7e1aa68Schristos 108c7e1aa68Schristos for (size_t i = 0; i < num - 1; ++i) 109c7e1aa68Schristos list[i]->next = list[i + 1]; 110c7e1aa68Schristos list[num - 1]->next = NULL; 111c7e1aa68Schristos first = list[0]; 112c7e1aa68Schristos assert(strcmp(first->name, ".") == 0); 113c7e1aa68Schristos free(list); 114c7e1aa68Schristos if (debug & DEBUG_DUMP_FSNODES_VERBOSE) 115c7e1aa68Schristos for (fsnode *tmp = first; tmp; tmp = tmp->next) 116514871bdSchristos printf("%s: post sort: %s %s %s\n", 117514871bdSchristos __func__, root, dir, tmp->name); 118c7e1aa68Schristos 119c7e1aa68Schristos return first; 120c7e1aa68Schristos } 121c7e1aa68Schristos 1226325773eSlukem /* 123*b79065bdSchristos * join current entry with the list. Return the current entry to replace 124*b79065bdSchristos * in cur, and 1 if it is a directory and we need to add or 0 if we need 125*b79065bdSchristos * to replace it. 126*b79065bdSchristos */ 127*b79065bdSchristos static int 128*b79065bdSchristos fsnode_join(fsnode **curp, fsnode *join, fsnode *last, const char *path, 129*b79065bdSchristos const char *name, const struct stat *st, int replace) 130*b79065bdSchristos { 131*b79065bdSchristos fsnode *cur; 132*b79065bdSchristos 133*b79065bdSchristos /* Look for the entry to replace by name */ 134*b79065bdSchristos cur = join->next; 135*b79065bdSchristos for (;;) { 136*b79065bdSchristos if (cur == NULL || strcmp(cur->name, name) == 0) 137*b79065bdSchristos break; 138*b79065bdSchristos if (cur == last) { 139*b79065bdSchristos cur = NULL; 140*b79065bdSchristos break; 141*b79065bdSchristos } 142*b79065bdSchristos cur = cur->next; 143*b79065bdSchristos } 144*b79065bdSchristos if (cur == NULL) { 145*b79065bdSchristos /* Not found */ 146*b79065bdSchristos *curp = NULL; 147*b79065bdSchristos return 0; 148*b79065bdSchristos } 149*b79065bdSchristos if (S_ISDIR(cur->type) && S_ISDIR(st->st_mode)) { 150*b79065bdSchristos /* 151*b79065bdSchristos * both the entry to join and this entry are directories 152*b79065bdSchristos * need to merge the two directories 153*b79065bdSchristos */ 154*b79065bdSchristos if (debug & DEBUG_WALK_DIR_NODE) 155*b79065bdSchristos printf("%s: merging %s with %p\n", 156*b79065bdSchristos __func__, path, cur->child); 157*b79065bdSchristos *curp = cur; 158*b79065bdSchristos return 1; 159*b79065bdSchristos } 160*b79065bdSchristos if (!replace) { 161*b79065bdSchristos /* 162*b79065bdSchristos * if they are not both directories and replace is not 163*b79065bdSchristos * specified, bail out 164*b79065bdSchristos */ 165*b79065bdSchristos errx(EXIT_FAILURE, "Can't merge %s `%s' with existing %s", 166*b79065bdSchristos inode_type(st->st_mode), path, inode_type(cur->type)); 167*b79065bdSchristos } 168*b79065bdSchristos 169*b79065bdSchristos if (debug & DEBUG_WALK_DIR_NODE) 170*b79065bdSchristos printf("%s: replacing %s %s\n", 171*b79065bdSchristos __func__, inode_type(st->st_mode), path); 172*b79065bdSchristos 173*b79065bdSchristos /* merge the join list */ 174*b79065bdSchristos if (cur == join->next) 175*b79065bdSchristos join->next = cur->next; 176*b79065bdSchristos else { 177*b79065bdSchristos fsnode *p; 178*b79065bdSchristos for (p = join->next; 179*b79065bdSchristos p->next != cur; p = p->next) 180*b79065bdSchristos continue; 181*b79065bdSchristos p->next = cur->next; 182*b79065bdSchristos } 183*b79065bdSchristos /* return the entry to be replaced */ 184*b79065bdSchristos *curp = cur; 185*b79065bdSchristos return 0; 186*b79065bdSchristos } 187*b79065bdSchristos 188*b79065bdSchristos /* 1896325773eSlukem * walk_dir -- 190f1cc0951Schristos * build a tree of fsnodes from `root' and `dir', with a parent 191f1cc0951Schristos * fsnode of `parent' (which may be NULL for the root of the tree). 192f1cc0951Schristos * append the tree to a fsnode of `join' if it is not NULL. 1936325773eSlukem * each "level" is a directory, with the "." entry guaranteed to be 1946325773eSlukem * at the start of the list, and without ".." entries. 1956325773eSlukem */ 1966325773eSlukem fsnode * 197c06c93b2Schristos walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join, 198ba42c78dSsimonb int replace, int follow) 1996325773eSlukem { 200f1cc0951Schristos fsnode *first, *cur, *prev, *last; 2016325773eSlukem DIR *dirp; 2026325773eSlukem struct dirent *dent; 2036325773eSlukem char path[MAXPATHLEN + 1]; 2046325773eSlukem struct stat stbuf; 205f1cc0951Schristos char *name, *rp; 206f1cc0951Schristos int dot, len; 2076325773eSlukem 208f1cc0951Schristos assert(root != NULL); 2096325773eSlukem assert(dir != NULL); 2106325773eSlukem 211f1cc0951Schristos len = snprintf(path, sizeof(path), "%s/%s", root, dir); 212c7e1aa68Schristos if ((size_t)len >= sizeof(path)) 213ec5d1d82Stsutsui errx(EXIT_FAILURE, "Pathname too long."); 2146325773eSlukem if (debug & DEBUG_WALK_DIR) 215514871bdSchristos printf("%s: %s %p\n", __func__, path, parent); 216f1cc0951Schristos if ((dirp = opendir(path)) == NULL) 217ec5d1d82Stsutsui err(EXIT_FAILURE, "Can't opendir `%s'", path); 218f1cc0951Schristos rp = path + strlen(root) + 1; 219f1cc0951Schristos if (join != NULL) { 220f1cc0951Schristos first = cur = join; 221f1cc0951Schristos while (cur->next != NULL) 222f1cc0951Schristos cur = cur->next; 223f1cc0951Schristos prev = last = cur; 224f1cc0951Schristos } else 225f1cc0951Schristos last = first = prev = NULL; 2266325773eSlukem while ((dent = readdir(dirp)) != NULL) { 227f1cc0951Schristos name = dent->d_name; 228f1cc0951Schristos dot = 0; 229f1cc0951Schristos if (name[0] == '.') 230f1cc0951Schristos switch (name[1]) { 231f1cc0951Schristos case '\0': /* "." */ 232f1cc0951Schristos if (join != NULL) 2336325773eSlukem continue; 234f1cc0951Schristos dot = 1; 235f1cc0951Schristos break; 236f1cc0951Schristos case '.': /* ".." */ 237f1cc0951Schristos if (name[2] == '\0') 238f1cc0951Schristos continue; 239f1cc0951Schristos /* FALLTHROUGH */ 240f1cc0951Schristos default: 241f1cc0951Schristos dot = 0; 242f1cc0951Schristos } 2436325773eSlukem if (debug & DEBUG_WALK_DIR_NODE) 244514871bdSchristos printf("%s: scanning %s/%s/%s\n", 245514871bdSchristos __func__, root, dir, name); 246f1cc0951Schristos if (snprintf(path + len, sizeof(path) - len, "/%s", name) >= 247f1cc0951Schristos (int)sizeof(path) - len) 248ec5d1d82Stsutsui errx(EXIT_FAILURE, "Pathname too long."); 249ba42c78dSsimonb if (follow) { 250ba42c78dSsimonb if (stat(path, &stbuf) == -1) 251ec5d1d82Stsutsui err(EXIT_FAILURE, "Can't stat `%s'", path); 252ba42c78dSsimonb } else { 2536325773eSlukem if (lstat(path, &stbuf) == -1) 254ec5d1d82Stsutsui err(EXIT_FAILURE, "Can't lstat `%s'", path); 255c7e1aa68Schristos /* 256c7e1aa68Schristos * Symlink permission bits vary between filesystems/OSs 257c7e1aa68Schristos * (ie. 0755 on FFS/NetBSD, 0777 for ext[234]/Linux), 258c7e1aa68Schristos * force them to 0755. 259c7e1aa68Schristos */ 260c7526c4eSchristos if (S_ISLNK(stbuf.st_mode)) { 261c7526c4eSchristos stbuf.st_mode &= ~(S_IRWXU | S_IRWXG | S_IRWXO); 262c7526c4eSchristos stbuf.st_mode |= S_IRWXU 263c7526c4eSchristos | S_IRGRP | S_IXGRP 264c7526c4eSchristos | S_IROTH | S_IXOTH; 265c7526c4eSchristos } 266ba42c78dSsimonb } 267b2f78261Sjmc #ifdef S_ISSOCK 2686325773eSlukem if (S_ISSOCK(stbuf.st_mode & S_IFMT)) { 2696325773eSlukem if (debug & DEBUG_WALK_DIR_NODE) 270*b79065bdSchristos printf("%s: skipping socket %s\n", __func__, 271*b79065bdSchristos path); 2726325773eSlukem continue; 2736325773eSlukem } 274b2f78261Sjmc #endif 2756325773eSlukem 276f1cc0951Schristos if (join != NULL) { 277*b79065bdSchristos if (fsnode_join(&cur, join, last, path, name, &stbuf, 278*b79065bdSchristos replace)) { 279f1cc0951Schristos cur->child = walk_dir(root, rp, cur, 280ba42c78dSsimonb cur->child, replace, follow); 281f1cc0951Schristos continue; 282*b79065bdSchristos } else if (cur) { 283*b79065bdSchristos if (prev == cur) { 284*b79065bdSchristos fsnode *p = join; 285*b79065bdSchristos while (p->next != NULL) 286*b79065bdSchristos p = p->next; 287*b79065bdSchristos prev = p; 288f1cc0951Schristos } 289*b79065bdSchristos free(cur->name); 290*b79065bdSchristos free(cur->path); 291c06c93b2Schristos free(cur); 292c06c93b2Schristos } 293f1cc0951Schristos } 294f1cc0951Schristos 295f1cc0951Schristos cur = create_fsnode(root, dir, name, &stbuf); 2966325773eSlukem cur->parent = parent; 297f1cc0951Schristos if (dot) { 2986325773eSlukem /* ensure "." is at the start of the list */ 2996325773eSlukem cur->next = first; 3006325773eSlukem first = cur; 3016325773eSlukem if (! prev) 3026325773eSlukem prev = cur; 303f1cc0951Schristos cur->first = first; 3046325773eSlukem } else { /* not "." */ 3056325773eSlukem if (prev) 3066325773eSlukem prev->next = cur; 3076325773eSlukem prev = cur; 3086325773eSlukem if (!first) 3096325773eSlukem first = cur; 310f1cc0951Schristos cur->first = first; 3116325773eSlukem if (S_ISDIR(cur->type)) { 312c06c93b2Schristos cur->child = walk_dir(root, rp, cur, NULL, 313ba42c78dSsimonb replace, follow); 3146325773eSlukem continue; 3156325773eSlukem } 3166325773eSlukem } 317ebbf3dddSlukem if (stbuf.st_nlink > 1) { 318ebbf3dddSlukem fsinode *curino; 319ebbf3dddSlukem 320ebbf3dddSlukem curino = link_check(cur->inode); 321ebbf3dddSlukem if (curino != NULL) { 322ebbf3dddSlukem free(cur->inode); 323ebbf3dddSlukem cur->inode = curino; 324ebbf3dddSlukem cur->inode->nlink++; 3251ca1523dSdbj if (debug & DEBUG_WALK_DIR_LINKCHECK) 326514871bdSchristos printf("%s: link check found [%ju, %ju]\n", 327514871bdSchristos __func__, 328a5d2beeeSchristos (uintmax_t)curino->st.st_dev, 329a5d2beeeSchristos (uintmax_t)curino->st.st_ino); 330ebbf3dddSlukem } 3316325773eSlukem } 3326325773eSlukem if (S_ISLNK(cur->type)) { 3336325773eSlukem char slink[PATH_MAX+1]; 334a5d2beeeSchristos ssize_t llen; 3356325773eSlukem 3362ab2b66eSitojun llen = readlink(path, slink, sizeof(slink) - 1); 3376325773eSlukem if (llen == -1) 338ec5d1d82Stsutsui err(EXIT_FAILURE, "Readlink `%s'", path); 3396325773eSlukem slink[llen] = '\0'; 340e4989541Schristos cur->symlink = estrdup(slink); 3416325773eSlukem } 3426325773eSlukem } 343f1cc0951Schristos assert(first != NULL); 344f1cc0951Schristos if (join == NULL) 345f1cc0951Schristos for (cur = first->next; cur != NULL; cur = cur->next) 3466325773eSlukem cur->first = first; 3476325773eSlukem if (closedir(dirp) == -1) 348ec5d1d82Stsutsui err(EXIT_FAILURE, "Can't closedir `%s/%s'", root, dir); 349c50dd23eSchristos 350c7e1aa68Schristos return fsnode_sort(first, root, dir); 3516325773eSlukem } 3526325773eSlukem 3536325773eSlukem static fsnode * 354f1cc0951Schristos create_fsnode(const char *root, const char *path, const char *name, 355f1cc0951Schristos struct stat *stbuf) 3566325773eSlukem { 3576325773eSlukem fsnode *cur; 3586325773eSlukem 359e4989541Schristos cur = ecalloc(1, sizeof(*cur)); 360e4989541Schristos cur->path = estrdup(path); 361e4989541Schristos cur->name = estrdup(name); 362e4989541Schristos cur->inode = ecalloc(1, sizeof(*cur->inode)); 363f1cc0951Schristos cur->root = root; 364ebbf3dddSlukem cur->type = stbuf->st_mode & S_IFMT; 3655647b7dfSlukem cur->inode->nlink = 1; 3665647b7dfSlukem cur->inode->st = *stbuf; 367799916c0Schristos if (stampst.st_ino) { 368799916c0Schristos cur->inode->st.st_atime = stampst.st_atime; 369799916c0Schristos cur->inode->st.st_mtime = stampst.st_mtime; 370799916c0Schristos cur->inode->st.st_ctime = stampst.st_ctime; 371799916c0Schristos #if HAVE_STRUCT_STAT_ST_MTIMENSEC 372799916c0Schristos cur->inode->st.st_atimensec = stampst.st_atimensec; 373799916c0Schristos cur->inode->st.st_mtimensec = stampst.st_mtimensec; 374799916c0Schristos cur->inode->st.st_ctimensec = stampst.st_ctimensec; 375799916c0Schristos #endif 376799916c0Schristos #if HAVE_STRUCT_STAT_BIRTHTIME 377799916c0Schristos cur->inode->st.st_birthtime = stampst.st_birthtime; 378799916c0Schristos cur->inode->st.st_birthtimensec = stampst.st_birthtimensec; 379799916c0Schristos #endif 380799916c0Schristos } 381ebbf3dddSlukem return (cur); 382ebbf3dddSlukem } 3836325773eSlukem 3846325773eSlukem /* 3850e392af9Sdbj * free_fsnodes -- 3860e392af9Sdbj * Removes node from tree and frees it and all of 387374cb9beSwiz * its descendents. 3880e392af9Sdbj */ 3890e392af9Sdbj void 3900e392af9Sdbj free_fsnodes(fsnode *node) 3910e392af9Sdbj { 3920e392af9Sdbj fsnode *cur, *next; 3930e392af9Sdbj 3940e392af9Sdbj assert(node != NULL); 3950e392af9Sdbj 3960e392af9Sdbj /* for ".", start with actual parent node */ 3970e392af9Sdbj if (node->first == node) { 3980e392af9Sdbj assert(node->name[0] == '.' && node->name[1] == '\0'); 3990e392af9Sdbj if (node->parent) { 4000e392af9Sdbj assert(node->parent->child == node); 4010e392af9Sdbj node = node->parent; 4020e392af9Sdbj } 4030e392af9Sdbj } 4040e392af9Sdbj 4050e392af9Sdbj /* Find ourselves in our sibling list and unlink */ 4060e392af9Sdbj if (node->first != node) { 4070e392af9Sdbj for (cur = node->first; cur->next; cur = cur->next) { 4080e392af9Sdbj if (cur->next == node) { 4090e392af9Sdbj cur->next = node->next; 4100e392af9Sdbj node->next = NULL; 4110e392af9Sdbj break; 4120e392af9Sdbj } 4130e392af9Sdbj } 4140e392af9Sdbj } 4150e392af9Sdbj 4160e392af9Sdbj for (cur = node; cur != NULL; cur = next) { 4170e392af9Sdbj next = cur->next; 4180e392af9Sdbj if (cur->child) { 4190e392af9Sdbj cur->child->parent = NULL; 4200e392af9Sdbj free_fsnodes(cur->child); 4210e392af9Sdbj } 4220e392af9Sdbj if (cur->inode->nlink-- == 1) 4230e392af9Sdbj free(cur->inode); 4240e392af9Sdbj if (cur->symlink) 4250e392af9Sdbj free(cur->symlink); 426f1cc0951Schristos free(cur->path); 4270e392af9Sdbj free(cur->name); 4280e392af9Sdbj free(cur); 4290e392af9Sdbj } 4300e392af9Sdbj } 4310e392af9Sdbj 4320e392af9Sdbj /* 4336325773eSlukem * apply_specfile -- 4346325773eSlukem * read in the mtree(8) specfile, and apply it to the tree 4356325773eSlukem * at dir,parent. parameters in parent on equivalent types 4366325773eSlukem * will be changed to those found in specfile, and missing 4376325773eSlukem * entries will be added. 4386325773eSlukem */ 4396325773eSlukem void 440d0c4ff45Sdbj apply_specfile(const char *specfile, const char *dir, fsnode *parent, int speconly) 4416325773eSlukem { 4426325773eSlukem struct timeval start; 4436325773eSlukem FILE *fp; 4446325773eSlukem NODE *root; 4456325773eSlukem 4466325773eSlukem assert(specfile != NULL); 4476325773eSlukem assert(parent != NULL); 4486325773eSlukem 4496325773eSlukem if (debug & DEBUG_APPLY_SPECFILE) 450514871bdSchristos printf("%s: %s, %s %p\n", __func__, specfile, dir, parent); 4516325773eSlukem 4526325773eSlukem /* read in the specfile */ 4536325773eSlukem if ((fp = fopen(specfile, "r")) == NULL) 454ec5d1d82Stsutsui err(EXIT_FAILURE, "Can't open `%s'", specfile); 4556325773eSlukem TIMER_START(start); 4566325773eSlukem root = spec(fp); 4576325773eSlukem TIMER_RESULTS(start, "spec"); 4586325773eSlukem if (fclose(fp) == EOF) 459ec5d1d82Stsutsui err(EXIT_FAILURE, "Can't close `%s'", specfile); 4606325773eSlukem 4616325773eSlukem /* perform some sanity checks */ 4626325773eSlukem if (root == NULL) 463ec5d1d82Stsutsui errx(EXIT_FAILURE, 464ec5d1d82Stsutsui "Specfile `%s' did not contain a tree", specfile); 4656325773eSlukem assert(strcmp(root->name, ".") == 0); 4666325773eSlukem assert(root->type == F_DIR); 4676325773eSlukem 4686325773eSlukem /* merge in the changes */ 469d0c4ff45Sdbj apply_specdir(dir, root, parent, speconly); 4702d7375ceSdbj 4712d7375ceSdbj free_nodes(root); 4720fe82aa4Schristos if (missing) 4730fe82aa4Schristos errx(EXIT_FAILURE, "Add %zu missing entries in `%s'", 4740fe82aa4Schristos missing, specfile); 4756325773eSlukem } 4766325773eSlukem 4776325773eSlukem static void 478d0c4ff45Sdbj apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode, int speconly) 4796325773eSlukem { 4806325773eSlukem char path[MAXPATHLEN + 1]; 4816325773eSlukem NODE *curnode; 4826325773eSlukem fsnode *curfsnode; 4836325773eSlukem 4846325773eSlukem assert(specnode != NULL); 4856325773eSlukem assert(dirnode != NULL); 4866325773eSlukem 4876325773eSlukem if (debug & DEBUG_APPLY_SPECFILE) 488514871bdSchristos printf("%s: %s %p %p\n", __func__, dir, specnode, dirnode); 4896325773eSlukem 4906325773eSlukem if (specnode->type != F_DIR) 491ec5d1d82Stsutsui errx(EXIT_FAILURE, "Specfile node `%s/%s' is not a directory", 4926325773eSlukem dir, specnode->name); 4936325773eSlukem if (dirnode->type != S_IFDIR) 494ec5d1d82Stsutsui errx(EXIT_FAILURE, "Directory node `%s/%s' is not a directory", 4956325773eSlukem dir, dirnode->name); 4966325773eSlukem 4976325773eSlukem apply_specentry(dir, specnode, dirnode); 4986325773eSlukem 499d0c4ff45Sdbj /* Remove any filesystem nodes not found in specfile */ 500d0c4ff45Sdbj /* XXX inefficient. This is O^2 in each dir and it would 501d0c4ff45Sdbj * have been better never to have walked this part of the tree 502d0c4ff45Sdbj * to begin with 503d0c4ff45Sdbj */ 504d0c4ff45Sdbj if (speconly) { 505d0c4ff45Sdbj fsnode *next; 506d0c4ff45Sdbj assert(dirnode->name[0] == '.' && dirnode->name[1] == '\0'); 507d0c4ff45Sdbj for (curfsnode = dirnode->next; curfsnode != NULL; curfsnode = next) { 508d0c4ff45Sdbj next = curfsnode->next; 509d0c4ff45Sdbj for (curnode = specnode->child; curnode != NULL; 510d0c4ff45Sdbj curnode = curnode->next) { 511d0c4ff45Sdbj if (strcmp(curnode->name, curfsnode->name) == 0) 512d0c4ff45Sdbj break; 513d0c4ff45Sdbj } 514d0c4ff45Sdbj if (curnode == NULL) { 5150fe82aa4Schristos if (speconly > 1) { 5160fe82aa4Schristos warnx("missing specfile entry for %s/%s", 5170fe82aa4Schristos dir, curfsnode->name); 5180fe82aa4Schristos missing++; 5190fe82aa4Schristos } 520d0c4ff45Sdbj if (debug & DEBUG_APPLY_SPECONLY) { 521514871bdSchristos printf("%s: trimming %s/%s %p\n", 522514871bdSchristos __func__, dir, curfsnode->name, 523514871bdSchristos curfsnode); 524d0c4ff45Sdbj } 525d0c4ff45Sdbj free_fsnodes(curfsnode); 526d0c4ff45Sdbj } 527d0c4ff45Sdbj } 528d0c4ff45Sdbj } 529d0c4ff45Sdbj 5306325773eSlukem /* now walk specnode->child matching up with dirnode */ 5316325773eSlukem for (curnode = specnode->child; curnode != NULL; 5326325773eSlukem curnode = curnode->next) { 5336325773eSlukem if (debug & DEBUG_APPLY_SPECENTRY) 534514871bdSchristos printf("%s: spec %s\n", __func__, curnode->name); 5356325773eSlukem for (curfsnode = dirnode->next; curfsnode != NULL; 5366325773eSlukem curfsnode = curfsnode->next) { 537d1270f6dSlukem #if 0 /* too verbose for now */ 5386325773eSlukem if (debug & DEBUG_APPLY_SPECENTRY) 539514871bdSchristos printf("%s: dirent %s\n", __func__, 5406325773eSlukem curfsnode->name); 541d1270f6dSlukem #endif 5426325773eSlukem if (strcmp(curnode->name, curfsnode->name) == 0) 5436325773eSlukem break; 5446325773eSlukem } 545b825b96bSchristos if ((size_t)snprintf(path, sizeof(path), "%s/%s", 5462afe4e83Slukem dir, curnode->name) >= sizeof(path)) 547ec5d1d82Stsutsui errx(EXIT_FAILURE, "Pathname too long."); 5486325773eSlukem if (curfsnode == NULL) { /* need new entry */ 5496325773eSlukem struct stat stbuf; 5506325773eSlukem 5512afe4e83Slukem /* 5522afe4e83Slukem * don't add optional spec entries 5532afe4e83Slukem * that lack an existing fs entry 5542afe4e83Slukem */ 5552afe4e83Slukem if ((curnode->flags & F_OPT) && 5562afe4e83Slukem lstat(path, &stbuf) == -1) 5572afe4e83Slukem continue; 5582afe4e83Slukem 5596325773eSlukem /* check that enough info is provided */ 5606325773eSlukem #define NODETEST(t, m) \ 5616325773eSlukem if (!(t)) \ 562ec5d1d82Stsutsui errx(EXIT_FAILURE, \ 563ec5d1d82Stsutsui "`%s': %s not provided", path, m) 5646325773eSlukem NODETEST(curnode->flags & F_TYPE, "type"); 5656325773eSlukem NODETEST(curnode->flags & F_MODE, "mode"); 5666325773eSlukem /* XXX: require F_TIME ? */ 5676325773eSlukem NODETEST(curnode->flags & F_GID || 5686325773eSlukem curnode->flags & F_GNAME, "group"); 5696325773eSlukem NODETEST(curnode->flags & F_UID || 5706325773eSlukem curnode->flags & F_UNAME, "user"); 5716325773eSlukem if (curnode->type == F_BLOCK || curnode->type == F_CHAR) 5726325773eSlukem NODETEST(curnode->flags & F_DEV, 5736325773eSlukem "device number"); 5746325773eSlukem #undef NODETEST 5756325773eSlukem 5766325773eSlukem if (debug & DEBUG_APPLY_SPECFILE) 577514871bdSchristos printf("%s: adding %s\n", __func__, curnode->name); 5786325773eSlukem /* build minimal fsnode */ 5796325773eSlukem memset(&stbuf, 0, sizeof(stbuf)); 5806325773eSlukem stbuf.st_mode = nodetoino(curnode->type); 581ebbf3dddSlukem stbuf.st_nlink = 1; 5826325773eSlukem stbuf.st_mtime = stbuf.st_atime = 5836325773eSlukem stbuf.st_ctime = start_time.tv_sec; 5849fbd8888Stv #if HAVE_STRUCT_STAT_ST_MTIMENSEC 5856325773eSlukem stbuf.st_mtimensec = stbuf.st_atimensec = 5866325773eSlukem stbuf.st_ctimensec = start_time.tv_nsec; 5879fbd8888Stv #endif 588f1cc0951Schristos curfsnode = create_fsnode(".", ".", curnode->name, 589f1cc0951Schristos &stbuf); 5906325773eSlukem curfsnode->parent = dirnode->parent; 5916325773eSlukem curfsnode->first = dirnode; 5926325773eSlukem curfsnode->next = dirnode->next; 5936325773eSlukem dirnode->next = curfsnode; 5946325773eSlukem if (curfsnode->type == S_IFDIR) { 5956325773eSlukem /* for dirs, make "." entry as well */ 596f1cc0951Schristos curfsnode->child = create_fsnode(".", ".", ".", 597f1cc0951Schristos &stbuf); 5986325773eSlukem curfsnode->child->parent = curfsnode; 5996325773eSlukem curfsnode->child->first = curfsnode->child; 6006325773eSlukem } 6016325773eSlukem if (curfsnode->type == S_IFLNK) { 602d1270f6dSlukem assert(curnode->slink != NULL); 6036325773eSlukem /* for symlinks, copy the target */ 604e4989541Schristos curfsnode->symlink = estrdup(curnode->slink); 6056325773eSlukem } 6066325773eSlukem } 6076325773eSlukem apply_specentry(dir, curnode, curfsnode); 6086325773eSlukem if (curnode->type == F_DIR) { 6096325773eSlukem if (curfsnode->type != S_IFDIR) 610ec5d1d82Stsutsui errx(EXIT_FAILURE, 611ec5d1d82Stsutsui "`%s' is not a directory", path); 6126325773eSlukem assert(curfsnode->child != NULL); 613d0c4ff45Sdbj apply_specdir(path, curnode, curfsnode->child, speconly); 6146325773eSlukem } 6156325773eSlukem } 6166325773eSlukem } 6176325773eSlukem 6186325773eSlukem static void 6196325773eSlukem apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode) 6206325773eSlukem { 6216325773eSlukem 6226325773eSlukem assert(specnode != NULL); 6236325773eSlukem assert(dirnode != NULL); 6246325773eSlukem 6256325773eSlukem if (nodetoino(specnode->type) != dirnode->type) 626ec5d1d82Stsutsui errx(EXIT_FAILURE, 627ec5d1d82Stsutsui "`%s/%s' type mismatch: specfile %s, tree %s", 6286325773eSlukem dir, specnode->name, inode_type(nodetoino(specnode->type)), 6296325773eSlukem inode_type(dirnode->type)); 6306325773eSlukem 6316325773eSlukem if (debug & DEBUG_APPLY_SPECENTRY) 632514871bdSchristos printf("%s: %s/%s\n", dir, __func__, dirnode->name); 6336325773eSlukem 6346325773eSlukem #define ASEPRINT(t, b, o, n) \ 6356325773eSlukem if (debug & DEBUG_APPLY_SPECENTRY) \ 6366325773eSlukem printf("\t\t\tchanging %s from " b " to " b "\n", \ 6376325773eSlukem t, o, n) 6386325773eSlukem 6396325773eSlukem if (specnode->flags & (F_GID | F_GNAME)) { 6406325773eSlukem ASEPRINT("gid", "%d", 641ebbf3dddSlukem dirnode->inode->st.st_gid, specnode->st_gid); 642ebbf3dddSlukem dirnode->inode->st.st_gid = specnode->st_gid; 6436325773eSlukem } 6446325773eSlukem if (specnode->flags & F_MODE) { 6456325773eSlukem ASEPRINT("mode", "%#o", 646ebbf3dddSlukem dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode); 647ebbf3dddSlukem dirnode->inode->st.st_mode &= ~ALLPERMS; 648ebbf3dddSlukem dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS); 6496325773eSlukem } 6506325773eSlukem /* XXX: ignoring F_NLINK for now */ 6516325773eSlukem if (specnode->flags & F_SIZE) { 652a5d2beeeSchristos ASEPRINT("size", "%jd", 653a5d2beeeSchristos (intmax_t)dirnode->inode->st.st_size, 654a5d2beeeSchristos (intmax_t)specnode->st_size); 655ebbf3dddSlukem dirnode->inode->st.st_size = specnode->st_size; 6566325773eSlukem } 6576325773eSlukem if (specnode->flags & F_SLINK) { 6586325773eSlukem assert(dirnode->symlink != NULL); 6596325773eSlukem assert(specnode->slink != NULL); 6606325773eSlukem ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink); 6616325773eSlukem free(dirnode->symlink); 662e4989541Schristos dirnode->symlink = estrdup(specnode->slink); 6636325773eSlukem } 6646325773eSlukem if (specnode->flags & F_TIME) { 6656325773eSlukem ASEPRINT("time", "%ld", 666ebbf3dddSlukem (long)dirnode->inode->st.st_mtime, 6679fbd8888Stv (long)specnode->st_mtimespec.tv_sec); 6689fbd8888Stv dirnode->inode->st.st_mtime = specnode->st_mtimespec.tv_sec; 6699fbd8888Stv dirnode->inode->st.st_atime = specnode->st_mtimespec.tv_sec; 670ebbf3dddSlukem dirnode->inode->st.st_ctime = start_time.tv_sec; 6719fbd8888Stv #if HAVE_STRUCT_STAT_ST_MTIMENSEC 672b2f78261Sjmc dirnode->inode->st.st_mtimensec = specnode->st_mtimespec.tv_nsec; 673b2f78261Sjmc dirnode->inode->st.st_atimensec = specnode->st_mtimespec.tv_nsec; 674ebbf3dddSlukem dirnode->inode->st.st_ctimensec = start_time.tv_nsec; 6759fbd8888Stv #endif 6766325773eSlukem } 6776325773eSlukem if (specnode->flags & (F_UID | F_UNAME)) { 6786325773eSlukem ASEPRINT("uid", "%d", 679ebbf3dddSlukem dirnode->inode->st.st_uid, specnode->st_uid); 680ebbf3dddSlukem dirnode->inode->st.st_uid = specnode->st_uid; 6816325773eSlukem } 6829fbd8888Stv #if HAVE_STRUCT_STAT_ST_FLAGS 6836325773eSlukem if (specnode->flags & F_FLAGS) { 6846325773eSlukem ASEPRINT("flags", "%#lX", 68585b406edSuwe (unsigned long)dirnode->inode->st.st_flags, 68685b406edSuwe (unsigned long)specnode->st_flags); 687a5d2beeeSchristos dirnode->inode->st.st_flags = (unsigned int)specnode->st_flags; 6886325773eSlukem } 6899fbd8888Stv #endif 6906325773eSlukem if (specnode->flags & F_DEV) { 691a5d2beeeSchristos ASEPRINT("rdev", "%#jx", 692a5d2beeeSchristos (uintmax_t)dirnode->inode->st.st_rdev, 693a5d2beeeSchristos (uintmax_t)specnode->st_rdev); 694ebbf3dddSlukem dirnode->inode->st.st_rdev = specnode->st_rdev; 6956325773eSlukem } 6966325773eSlukem #undef ASEPRINT 6971bcb9d76Sthorpej 6981bcb9d76Sthorpej dirnode->flags |= FSNODE_F_HASSPEC; 6996325773eSlukem } 7006325773eSlukem 7016325773eSlukem 7026325773eSlukem /* 7036325773eSlukem * dump_fsnodes -- 704f1cc0951Schristos * dump the fsnodes from `cur' 7056325773eSlukem */ 7066325773eSlukem void 707f1cc0951Schristos dump_fsnodes(fsnode *root) 7086325773eSlukem { 7096325773eSlukem fsnode *cur; 7106325773eSlukem char path[MAXPATHLEN + 1]; 7116325773eSlukem 712514871bdSchristos printf("%s: %s %p\n", __func__, root->path, root); 7136325773eSlukem for (cur = root; cur != NULL; cur = cur->next) { 714f1cc0951Schristos if (snprintf(path, sizeof(path), "%s/%s", cur->path, 715f1cc0951Schristos cur->name) >= (int)sizeof(path)) 716ec5d1d82Stsutsui errx(EXIT_FAILURE, "Pathname too long."); 7176325773eSlukem 7186325773eSlukem if (debug & DEBUG_DUMP_FSNODES_VERBOSE) 719514871bdSchristos printf("%s: cur=%8p parent=%8p first=%8p ", __func__, 7206325773eSlukem cur, cur->parent, cur->first); 7216325773eSlukem printf("%7s: %s", inode_type(cur->type), path); 7226325773eSlukem if (S_ISLNK(cur->type)) { 7236325773eSlukem assert(cur->symlink != NULL); 7246325773eSlukem printf(" -> %s", cur->symlink); 7256325773eSlukem } else { 7266325773eSlukem assert(cur->symlink == NULL); 7276325773eSlukem } 728ebbf3dddSlukem if (cur->inode->nlink > 1) 729ebbf3dddSlukem printf(", nlinks=%d", cur->inode->nlink); 7306325773eSlukem putchar('\n'); 7316325773eSlukem 7326325773eSlukem if (cur->child) { 7336325773eSlukem assert(cur->type == S_IFDIR); 734f1cc0951Schristos dump_fsnodes(cur->child); 7356325773eSlukem } 7366325773eSlukem } 737514871bdSchristos printf("%s: finished %s/%s\n", __func__, root->path, root->name); 7386325773eSlukem } 7396325773eSlukem 7406325773eSlukem 7416325773eSlukem /* 7426325773eSlukem * inode_type -- 7436325773eSlukem * for a given inode type `mode', return a descriptive string. 7446325773eSlukem * for most cases, uses inotype() from mtree/misc.c 7456325773eSlukem */ 7466325773eSlukem const char * 7476325773eSlukem inode_type(mode_t mode) 7486325773eSlukem { 7496325773eSlukem 750d1270f6dSlukem if (S_ISLNK(mode)) 7516325773eSlukem return ("symlink"); /* inotype() returns "link"... */ 7526325773eSlukem return (inotype(mode)); 7536325773eSlukem } 7546325773eSlukem 7556325773eSlukem 7566325773eSlukem /* 7576325773eSlukem * link_check -- 7581ca1523dSdbj * return pointer to fsinode matching `entry's st_ino & st_dev if it exists, 7596325773eSlukem * otherwise add `entry' to table and return NULL 7606325773eSlukem */ 7611ca1523dSdbj /* This was borrowed from du.c and tweaked to keep an fsnode 7621ca1523dSdbj * pointer instead. -- dbj@netbsd.org 7631ca1523dSdbj */ 764ebbf3dddSlukem static fsinode * 765ebbf3dddSlukem link_check(fsinode *entry) 7666325773eSlukem { 7671ca1523dSdbj static struct entry { 7681ca1523dSdbj fsinode *data; 7691ca1523dSdbj } *htable; 770a5d2beeeSchristos static size_t htshift; /* log(allocated size) */ 771a5d2beeeSchristos static size_t htmask; /* allocated size - 1 */ 772a5d2beeeSchristos static size_t htused; /* 2*number of insertions */ 773a5d2beeeSchristos size_t h, h2; 7741ca1523dSdbj uint64_t tmp; 7751ca1523dSdbj /* this constant is (1<<64)/((1+sqrt(5))/2) 7761ca1523dSdbj * aka (word size)/(golden ratio) 7771ca1523dSdbj */ 7781ca1523dSdbj const uint64_t HTCONST = 11400714819323198485ULL; 779a5d2beeeSchristos const size_t HTBITS = 64; 7806325773eSlukem 7811ca1523dSdbj /* Never store zero in hashtable */ 7821ca1523dSdbj assert(entry); 7836325773eSlukem 7841ca1523dSdbj /* Extend hash table if necessary, keep load under 0.5 */ 7851ca1523dSdbj if (htused<<1 >= htmask) { 7861ca1523dSdbj struct entry *ohtable; 7876325773eSlukem 7881ca1523dSdbj if (!htable) 7891ca1523dSdbj htshift = 10; /* starting hashtable size */ 7901ca1523dSdbj else 7911ca1523dSdbj htshift++; /* exponential hashtable growth */ 7926325773eSlukem 7931ca1523dSdbj htmask = (1 << htshift) - 1; 7941ca1523dSdbj htused = 0; 7951ca1523dSdbj 7961ca1523dSdbj ohtable = htable; 797e4989541Schristos htable = ecalloc(htmask+1, sizeof(*htable)); 7981ca1523dSdbj /* populate newly allocated hashtable */ 7991ca1523dSdbj if (ohtable) { 800a5d2beeeSchristos for (size_t i = 0; i <= htmask>>1; i++) 8011ca1523dSdbj if (ohtable[i].data) 8021ca1523dSdbj link_check(ohtable[i].data); 8031ca1523dSdbj free(ohtable); 8041ca1523dSdbj } 8051ca1523dSdbj } 8061ca1523dSdbj 8071ca1523dSdbj /* multiplicative hashing */ 8081ca1523dSdbj tmp = entry->st.st_dev; 8091ca1523dSdbj tmp <<= HTBITS>>1; 8101ca1523dSdbj tmp |= entry->st.st_ino; 8111ca1523dSdbj tmp *= HTCONST; 8121ca1523dSdbj h = tmp >> (HTBITS - htshift); 8131ca1523dSdbj h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */ 8141ca1523dSdbj 8151ca1523dSdbj /* open address hashtable search with double hash probing */ 8161ca1523dSdbj while (htable[h].data) { 8171ca1523dSdbj if ((htable[h].data->st.st_ino == entry->st.st_ino) && 8181ca1523dSdbj (htable[h].data->st.st_dev == entry->st.st_dev)) { 8191ca1523dSdbj return htable[h].data; 8201ca1523dSdbj } 8211ca1523dSdbj h = (h + h2) & htmask; 8221ca1523dSdbj } 8231ca1523dSdbj 8241ca1523dSdbj /* Insert the current entry into hashtable */ 8251ca1523dSdbj htable[h].data = entry; 8261ca1523dSdbj htused++; 8271ca1523dSdbj return NULL; 8286325773eSlukem } 829