diff --git a/usr.sbin/makefs/ffs.c b/usr.sbin/makefs/ffs.c index aa5574c5201f..34a0f8a67497 100644 --- a/usr.sbin/makefs/ffs.c +++ b/usr.sbin/makefs/ffs.c @@ -1,1203 +1,1199 @@ /* $NetBSD: ffs.c,v 1.45 2011/10/09 22:49:26 christos Exp $ */ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 2001 Wasabi Systems, Inc. * All rights reserved. * * Written by Luke Mewburn for Wasabi Systems, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed for the NetBSD Project by * Wasabi Systems, Inc. * 4. The name of Wasabi Systems, Inc. may not be used to endorse * or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Copyright (c) 1982, 1986, 1989, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #if HAVE_NBTOOL_CONFIG_H #include "nbtool_config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS #include #endif #include #include #include #include "ffs/ufs_bswap.h" #include "ffs/ufs_inode.h" #include "ffs/newfs_extern.h" #include "ffs/ffs_extern.h" #undef clrbuf #include "makefs.h" #include "ffs.h" #undef DIP #define DIP(dp, field) \ ((ffs_opts->version == 1) ? \ (dp)->dp1.di_##field : (dp)->dp2.di_##field) /* * Various file system defaults (cribbed from newfs(8)). */ #define DFL_FRAGSIZE 4096 /* fragment size */ #define DFL_BLKSIZE 32768 /* block size */ #define DFL_SECSIZE 512 /* sector size */ #define DFL_CYLSPERGROUP 65536 /* cylinders per group */ #define DFL_FRAGSPERINODE 4 /* fragments per inode */ #define DFL_ROTDELAY 0 /* rotational delay */ #define DFL_NRPOS 1 /* rotational positions */ #define DFL_RPM 3600 /* rpm of disk */ #define DFL_NSECTORS 64 /* # of sectors */ #define DFL_NTRACKS 16 /* # of tracks */ typedef struct { u_char *buf; /* buf for directory */ doff_t size; /* full size of buf */ doff_t cur; /* offset of current entry */ } dirbuf_t; static int ffs_create_image(const char *, fsinfo_t *); static void ffs_dump_fsinfo(fsinfo_t *); static void ffs_dump_dirbuf(dirbuf_t *, const char *, int); static void ffs_make_dirbuf(dirbuf_t *, const char *, fsnode *, int); static int ffs_populate_dir(const char *, fsnode *, fsinfo_t *); static void ffs_size_dir(fsnode *, fsinfo_t *); static void ffs_validate(const char *, fsnode *, fsinfo_t *); static void ffs_write_file(union dinode *, uint32_t, void *, fsinfo_t *); static void ffs_write_inode(union dinode *, uint32_t, const fsinfo_t *); static void *ffs_build_dinode1(struct ufs1_dinode *, dirbuf_t *, fsnode *, fsnode *, fsinfo_t *); static void *ffs_build_dinode2(struct ufs2_dinode *, dirbuf_t *, fsnode *, fsnode *, fsinfo_t *); /* publicly visible functions */ void ffs_prep_opts(fsinfo_t *fsopts) { ffs_opt_t *ffs_opts = ecalloc(1, sizeof(*ffs_opts)); const option_t ffs_options[] = { { 'b', "bsize", &ffs_opts->bsize, OPT_INT32, 1, INT_MAX, "block size" }, { 'f', "fsize", &ffs_opts->fsize, OPT_INT32, 1, INT_MAX, "fragment size" }, { 'd', "density", &ffs_opts->density, OPT_INT32, 1, INT_MAX, "bytes per inode" }, { 'm', "minfree", &ffs_opts->minfree, OPT_INT32, 0, 99, "minfree" }, { 'M', "maxbpg", &ffs_opts->maxbpg, OPT_INT32, 1, INT_MAX, "max blocks per file in a cg" }, { 'a', "avgfilesize", &ffs_opts->avgfilesize, OPT_INT32, 1, INT_MAX, "expected average file size" }, { 'n', "avgfpdir", &ffs_opts->avgfpdir, OPT_INT32, 1, INT_MAX, "expected # of files per directory" }, { 'x', "extent", &ffs_opts->maxbsize, OPT_INT32, 1, INT_MAX, "maximum # extent size" }, { 'g', "maxbpcg", &ffs_opts->maxblkspercg, OPT_INT32, 1, INT_MAX, "max # of blocks per group" }, { 'v', "version", &ffs_opts->version, OPT_INT32, 1, 2, "UFS version" }, { 'o', "optimization", NULL, OPT_STRBUF, 0, 0, "Optimization (time|space)" }, { 'l', "label", ffs_opts->label, OPT_STRARRAY, 1, sizeof(ffs_opts->label), "UFS label" }, { 's', "softupdates", &ffs_opts->softupdates, OPT_INT32, 0, 1, "enable softupdates" }, { .name = NULL } }; ffs_opts->bsize= -1; ffs_opts->fsize= -1; ffs_opts->cpg= -1; ffs_opts->density= -1; ffs_opts->min_inodes= false; ffs_opts->minfree= -1; ffs_opts->optimization= -1; ffs_opts->maxcontig= -1; ffs_opts->maxbpg= -1; ffs_opts->avgfilesize= -1; ffs_opts->avgfpdir= -1; ffs_opts->version = 1; ffs_opts->softupdates = 0; fsopts->fs_specific = ffs_opts; fsopts->fs_options = copy_opts(ffs_options); } void ffs_cleanup_opts(fsinfo_t *fsopts) { free(fsopts->fs_specific); free(fsopts->fs_options); } int ffs_parse_opts(const char *option, fsinfo_t *fsopts) { ffs_opt_t *ffs_opts = fsopts->fs_specific; option_t *ffs_options = fsopts->fs_options; char buf[1024]; int rv; assert(option != NULL); assert(fsopts != NULL); assert(ffs_opts != NULL); if (debug & DEBUG_FS_PARSE_OPTS) printf("ffs_parse_opts: got `%s'\n", option); rv = set_option(ffs_options, option, buf, sizeof(buf)); if (rv == -1) return 0; if (ffs_options[rv].name == NULL) abort(); switch (ffs_options[rv].letter) { case 'o': if (strcmp(buf, "time") == 0) { ffs_opts->optimization = FS_OPTTIME; } else if (strcmp(buf, "space") == 0) { ffs_opts->optimization = FS_OPTSPACE; } else { warnx("Invalid optimization `%s'", buf); return 0; } break; default: break; } return 1; } void ffs_makefs(const char *image, const char *dir, fsnode *root, fsinfo_t *fsopts) { struct fs *superblock; struct timeval start; assert(image != NULL); assert(dir != NULL); assert(root != NULL); assert(fsopts != NULL); if (debug & DEBUG_FS_MAKEFS) printf("ffs_makefs: image %s directory %s root %p\n", image, dir, root); /* if user wants no free space, use minimum number of inodes */ if (fsopts->minsize == 0 && fsopts->freeblockpc == 0 && fsopts->freeblocks == 0) ((ffs_opt_t *)fsopts->fs_specific)->min_inodes = true; /* validate tree and options */ TIMER_START(start); ffs_validate(dir, root, fsopts); TIMER_RESULTS(start, "ffs_validate"); printf("Calculated size of `%s': %lld bytes, %lld inodes\n", image, (long long)fsopts->size, (long long)fsopts->inodes); /* create image */ TIMER_START(start); if (ffs_create_image(image, fsopts) == -1) errx(1, "Image file `%s' not created.", image); TIMER_RESULTS(start, "ffs_create_image"); fsopts->curinode = UFS_ROOTINO; if (debug & DEBUG_FS_MAKEFS) putchar('\n'); /* populate image */ printf("Populating `%s'\n", image); TIMER_START(start); if (! ffs_populate_dir(dir, root, fsopts)) errx(1, "Image file `%s' not populated.", image); TIMER_RESULTS(start, "ffs_populate_dir"); /* ensure no outstanding buffers remain */ if (debug & DEBUG_FS_MAKEFS) bcleanup(); /* update various superblock parameters */ superblock = fsopts->superblock; superblock->fs_fmod = 0; superblock->fs_old_cstotal.cs_ndir = superblock->fs_cstotal.cs_ndir; superblock->fs_old_cstotal.cs_nbfree = superblock->fs_cstotal.cs_nbfree; superblock->fs_old_cstotal.cs_nifree = superblock->fs_cstotal.cs_nifree; superblock->fs_old_cstotal.cs_nffree = superblock->fs_cstotal.cs_nffree; /* write out superblock; image is now complete */ ffs_write_superblock(fsopts->superblock, fsopts); if (close(fsopts->fd) == -1) err(1, "Closing `%s'", image); fsopts->fd = -1; printf("Image `%s' complete\n", image); } /* end of public functions */ static void ffs_validate(const char *dir, fsnode *root, fsinfo_t *fsopts) { #ifdef notyet int32_t spc, nspf, ncyl, fssize; #endif ffs_opt_t *ffs_opts = fsopts->fs_specific; assert(dir != NULL); assert(root != NULL); assert(fsopts != NULL); assert(ffs_opts != NULL); if (debug & DEBUG_FS_VALIDATE) { printf("ffs_validate: before defaults set:\n"); ffs_dump_fsinfo(fsopts); } /* set FFS defaults */ if (fsopts->sectorsize == -1) fsopts->sectorsize = DFL_SECSIZE; if (fsopts->sectorsize != DFL_SECSIZE) warnx("sectorsize %d may produce nonfunctional image", fsopts->sectorsize); if (ffs_opts->fsize == -1) ffs_opts->fsize = MAX(DFL_FRAGSIZE, fsopts->sectorsize); if (ffs_opts->bsize == -1) ffs_opts->bsize = MIN(DFL_BLKSIZE, 8 * ffs_opts->fsize); if (ffs_opts->cpg == -1) ffs_opts->cpg = DFL_CYLSPERGROUP; else ffs_opts->cpgflg = 1; /* fsopts->density is set below */ if (ffs_opts->nsectors == -1) ffs_opts->nsectors = DFL_NSECTORS; if (ffs_opts->minfree == -1) ffs_opts->minfree = MINFREE; if (ffs_opts->optimization == -1) ffs_opts->optimization = DEFAULTOPT; if (ffs_opts->maxcontig == -1) ffs_opts->maxcontig = MAX(1, MIN(MAXPHYS, FFS_MAXBSIZE) / ffs_opts->bsize); /* XXX ondisk32 */ if (ffs_opts->maxbpg == -1) ffs_opts->maxbpg = ffs_opts->bsize / sizeof(int32_t); if (ffs_opts->avgfilesize == -1) ffs_opts->avgfilesize = AVFILESIZ; if (ffs_opts->avgfpdir == -1) ffs_opts->avgfpdir = AFPDIR; if (fsopts->maxsize > 0 && roundup(fsopts->minsize, ffs_opts->bsize) > fsopts->maxsize) errx(1, "`%s' minsize of %lld rounded up to ffs bsize of %d " "exceeds maxsize %lld. Lower bsize, or round the minimum " "and maximum sizes to bsize.", dir, (long long)fsopts->minsize, ffs_opts->bsize, (long long)fsopts->maxsize); /* calculate size of tree */ ffs_size_dir(root, fsopts); fsopts->inodes += UFS_ROOTINO; /* include first two inodes */ if (debug & DEBUG_FS_VALIDATE) printf("ffs_validate: size of tree: %lld bytes, %lld inodes\n", (long long)fsopts->size, (long long)fsopts->inodes); /* add requested slop */ fsopts->size += fsopts->freeblocks; fsopts->inodes += fsopts->freefiles; if (fsopts->freefilepc > 0) fsopts->inodes = fsopts->inodes * (100 + fsopts->freefilepc) / 100; if (fsopts->freeblockpc > 0) fsopts->size = fsopts->size * (100 + fsopts->freeblockpc) / 100; /* * Add space needed for superblock, cylblock and to store inodes. * All of those segments are aligned to the block size. * XXX: This has to match calculations done in ffs_mkfs. */ if (ffs_opts->version == 1) { fsopts->size += roundup(SBLOCK_UFS1 + SBLOCKSIZE, ffs_opts->bsize); fsopts->size += roundup(SBLOCKSIZE, ffs_opts->bsize); fsopts->size += ffs_opts->bsize; fsopts->size += DINODE1_SIZE * roundup(fsopts->inodes, ffs_opts->bsize / DINODE1_SIZE); } else { fsopts->size += roundup(SBLOCK_UFS2 + SBLOCKSIZE, ffs_opts->bsize); fsopts->size += roundup(SBLOCKSIZE, ffs_opts->bsize); fsopts->size += ffs_opts->bsize; fsopts->size += DINODE2_SIZE * roundup(fsopts->inodes, ffs_opts->bsize / DINODE2_SIZE); } /* add minfree */ if (ffs_opts->minfree > 0) fsopts->size = fsopts->size * (100 + ffs_opts->minfree) / 100; /* * XXX any other fs slop to add, such as csum's, bitmaps, etc ?? */ if (fsopts->size < fsopts->minsize) /* ensure meets minimum size */ fsopts->size = fsopts->minsize; /* round up to the next block */ fsopts->size = roundup(fsopts->size, ffs_opts->bsize); /* round up to requested block size, if any */ if (fsopts->roundup > 0) fsopts->size = roundup(fsopts->size, fsopts->roundup); /* calculate density to just fit inodes if no free space */ if (ffs_opts->density == -1) ffs_opts->density = fsopts->size / fsopts->inodes + 1; if (debug & DEBUG_FS_VALIDATE) { printf("ffs_validate: after defaults set:\n"); ffs_dump_fsinfo(fsopts); printf("ffs_validate: dir %s; %lld bytes, %lld inodes\n", dir, (long long)fsopts->size, (long long)fsopts->inodes); } /* now check calculated sizes vs requested sizes */ if (fsopts->maxsize > 0 && fsopts->size > fsopts->maxsize) { errx(1, "`%s' size of %lld is larger than the maxsize of %lld.", dir, (long long)fsopts->size, (long long)fsopts->maxsize); } } static void ffs_dump_fsinfo(fsinfo_t *f) { ffs_opt_t *fs = f->fs_specific; printf("fsopts at %p\n", f); printf("\tsize %lld, inodes %lld, curinode %u\n", (long long)f->size, (long long)f->inodes, f->curinode); printf("\tminsize %lld, maxsize %lld\n", (long long)f->minsize, (long long)f->maxsize); printf("\tfree files %lld, freefile %% %d\n", (long long)f->freefiles, f->freefilepc); printf("\tfree blocks %lld, freeblock %% %d\n", (long long)f->freeblocks, f->freeblockpc); printf("\tneedswap %d, sectorsize %d\n", f->needswap, f->sectorsize); printf("\tbsize %d, fsize %d, cpg %d, density %d\n", fs->bsize, fs->fsize, fs->cpg, fs->density); printf("\tnsectors %d, rpm %d, minfree %d\n", fs->nsectors, fs->rpm, fs->minfree); printf("\tmaxcontig %d, maxbpg %d\n", fs->maxcontig, fs->maxbpg); printf("\toptimization %s\n", fs->optimization == FS_OPTSPACE ? "space" : "time"); } static int ffs_create_image(const char *image, fsinfo_t *fsopts) { #if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS struct statvfs sfs; #endif struct fs *fs; char *buf; int i, bufsize; off_t bufrem; int oflags = O_RDWR | O_CREAT; time_t tstamp; assert (image != NULL); assert (fsopts != NULL); /* create image */ if (fsopts->offset == 0) oflags |= O_TRUNC; if ((fsopts->fd = open(image, oflags, 0666)) == -1) { warn("Can't open `%s' for writing", image); return (-1); } /* zero image */ #if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS if (fstatvfs(fsopts->fd, &sfs) == -1) { #endif bufsize = 8192; #if HAVE_STRUCT_STATVFS_F_IOSIZE && HAVE_FSTATVFS warn("can't fstatvfs `%s', using default %d byte chunk", image, bufsize); } else bufsize = sfs.f_iosize; #endif bufrem = fsopts->size; if (fsopts->sparse) { if (ftruncate(fsopts->fd, bufrem) == -1) { warn("sparse option disabled."); fsopts->sparse = 0; } } if (fsopts->sparse) { /* File truncated at bufrem. Remaining is 0 */ bufrem = 0; buf = NULL; } else { if (debug & DEBUG_FS_CREATE_IMAGE) printf("zero-ing image `%s', %lld sectors, " "using %d byte chunks\n", image, (long long)bufrem, bufsize); buf = ecalloc(1, bufsize); } if (fsopts->offset != 0) if (lseek(fsopts->fd, fsopts->offset, SEEK_SET) == -1) { warn("can't seek"); free(buf); return -1; } while (bufrem > 0) { i = write(fsopts->fd, buf, MIN(bufsize, bufrem)); if (i == -1) { warn("zeroing image, %lld bytes to go", (long long)bufrem); free(buf); return (-1); } bufrem -= i; } if (buf) free(buf); /* make the file system */ if (debug & DEBUG_FS_CREATE_IMAGE) printf("calling mkfs(\"%s\", ...)\n", image); if (stampst.st_ino != 0) tstamp = stampst.st_ctime; else tstamp = start_time.tv_sec; srandom(tstamp); fs = ffs_mkfs(image, fsopts, tstamp); fsopts->superblock = (void *)fs; if (debug & DEBUG_FS_CREATE_IMAGE) { time_t t; t = (time_t)((struct fs *)fsopts->superblock)->fs_time; printf("mkfs returned %p; fs_time %s", fsopts->superblock, ctime(&t)); printf("fs totals: nbfree %lld, nffree %lld, nifree %lld, ndir %lld\n", (long long)fs->fs_cstotal.cs_nbfree, (long long)fs->fs_cstotal.cs_nffree, (long long)fs->fs_cstotal.cs_nifree, (long long)fs->fs_cstotal.cs_ndir); } if (fs->fs_cstotal.cs_nifree + (off_t)UFS_ROOTINO < fsopts->inodes) { warnx( "Image file `%s' has %lld free inodes; %lld are required.", image, (long long)(fs->fs_cstotal.cs_nifree + UFS_ROOTINO), (long long)fsopts->inodes); return (-1); } return (fsopts->fd); } static void ffs_size_dir(fsnode *root, fsinfo_t *fsopts) { struct direct tmpdir; fsnode * node; int curdirsize, this; ffs_opt_t *ffs_opts = fsopts->fs_specific; /* node may be NULL (empty directory) */ assert(fsopts != NULL); assert(ffs_opts != NULL); if (debug & DEBUG_FS_SIZE_DIR) printf("ffs_size_dir: entry: bytes %lld inodes %lld\n", (long long)fsopts->size, (long long)fsopts->inodes); #define ADDDIRENT(e) do { \ tmpdir.d_namlen = strlen((e)); \ this = DIRSIZ_SWAP(0, &tmpdir, 0); \ if (debug & DEBUG_FS_SIZE_DIR_ADD_DIRENT) \ printf("ADDDIRENT: was: %s (%d) this %d cur %d\n", \ e, tmpdir.d_namlen, this, curdirsize); \ if (this + curdirsize > roundup(curdirsize, DIRBLKSIZ)) \ curdirsize = roundup(curdirsize, DIRBLKSIZ); \ curdirsize += this; \ if (debug & DEBUG_FS_SIZE_DIR_ADD_DIRENT) \ printf("ADDDIRENT: now: %s (%d) this %d cur %d\n", \ e, tmpdir.d_namlen, this, curdirsize); \ } while (0); #define ADDSIZE(x) do { \ if ((size_t)(x) < UFS_NDADDR * (size_t)ffs_opts->bsize) { \ fsopts->size += roundup((x), ffs_opts->fsize); \ } else { \ /* Count space consumed by indirecttion blocks. */ \ fsopts->size += ffs_opts->bsize * \ (howmany((x), UFS_NDADDR * ffs_opts->bsize) - 1); \ /* \ * If the file is big enough to use indirect blocks, \ * we allocate bsize block for trailing data. \ */ \ fsopts->size += roundup((x), ffs_opts->bsize); \ } \ } while (0); curdirsize = 0; for (node = root; node != NULL; node = node->next) { ADDDIRENT(node->name); if (node == root) { /* we're at "." */ assert(strcmp(node->name, ".") == 0); ADDDIRENT(".."); } else if ((node->inode->flags & FI_SIZED) == 0) { /* don't count duplicate names */ node->inode->flags |= FI_SIZED; if (debug & DEBUG_FS_SIZE_DIR_NODE) printf("ffs_size_dir: `%s' size %lld\n", node->name, (long long)node->inode->st.st_size); fsopts->inodes++; if (node->type == S_IFREG) ADDSIZE(node->inode->st.st_size); if (node->type == S_IFLNK) { size_t slen; slen = strlen(node->symlink) + 1; if (slen >= (ffs_opts->version == 1 ? UFS1_MAXSYMLINKLEN : UFS2_MAXSYMLINKLEN)) ADDSIZE(slen); } } if (node->type == S_IFDIR) ffs_size_dir(node->child, fsopts); } ADDSIZE(curdirsize); if (debug & DEBUG_FS_SIZE_DIR) printf("ffs_size_dir: exit: size %lld inodes %lld\n", (long long)fsopts->size, (long long)fsopts->inodes); } static void * ffs_build_dinode1(struct ufs1_dinode *dinp, dirbuf_t *dbufp, fsnode *cur, fsnode *root, fsinfo_t *fsopts) { size_t slen; void *membuf; struct stat *st = stampst.st_ino != 0 ? &stampst : &cur->inode->st; memset(dinp, 0, sizeof(*dinp)); dinp->di_mode = cur->inode->st.st_mode; dinp->di_nlink = cur->inode->nlink; dinp->di_size = cur->inode->st.st_size; -#if HAVE_STRUCT_STAT_ST_FLAGS - dinp->di_flags = cur->inode->st.st_flags; -#endif + dinp->di_flags = FSINODE_ST_FLAGS(*cur->inode); dinp->di_gen = random(); dinp->di_uid = cur->inode->st.st_uid; dinp->di_gid = cur->inode->st.st_gid; dinp->di_atime = st->st_atime; dinp->di_mtime = st->st_mtime; dinp->di_ctime = st->st_ctime; #if HAVE_STRUCT_STAT_ST_MTIMENSEC dinp->di_atimensec = st->st_atimensec; dinp->di_mtimensec = st->st_mtimensec; dinp->di_ctimensec = st->st_ctimensec; #endif /* not set: di_db, di_ib, di_blocks, di_spare */ membuf = NULL; if (cur == root) { /* "."; write dirbuf */ membuf = dbufp->buf; dinp->di_size = dbufp->size; } else if (S_ISBLK(cur->type) || S_ISCHR(cur->type)) { dinp->di_size = 0; /* a device */ dinp->di_rdev = ufs_rw32(cur->inode->st.st_rdev, fsopts->needswap); } else if (S_ISLNK(cur->type)) { /* symlink */ slen = strlen(cur->symlink); if (slen < UFS1_MAXSYMLINKLEN) { /* short link */ memcpy(dinp->di_shortlink, cur->symlink, slen); } else membuf = cur->symlink; dinp->di_size = slen; } return membuf; } static void * ffs_build_dinode2(struct ufs2_dinode *dinp, dirbuf_t *dbufp, fsnode *cur, fsnode *root, fsinfo_t *fsopts) { size_t slen; void *membuf; struct stat *st = stampst.st_ino != 0 ? &stampst : &cur->inode->st; memset(dinp, 0, sizeof(*dinp)); dinp->di_mode = cur->inode->st.st_mode; dinp->di_nlink = cur->inode->nlink; dinp->di_size = cur->inode->st.st_size; -#if HAVE_STRUCT_STAT_ST_FLAGS - dinp->di_flags = cur->inode->st.st_flags; -#endif + dinp->di_flags = FSINODE_ST_FLAGS(*cur->inode); dinp->di_gen = random(); dinp->di_uid = cur->inode->st.st_uid; dinp->di_gid = cur->inode->st.st_gid; dinp->di_atime = st->st_atime; dinp->di_mtime = st->st_mtime; dinp->di_ctime = st->st_ctime; #if HAVE_STRUCT_STAT_BIRTHTIME dinp->di_birthtime = st->st_birthtime; #else dinp->di_birthtime = st->st_ctime; #endif #if HAVE_STRUCT_STAT_ST_MTIMENSEC dinp->di_atimensec = st->st_atimensec; dinp->di_mtimensec = st->st_mtimensec; dinp->di_ctimensec = st->st_ctimensec; #if HAVE_STRUCT_STAT_BIRTHTIME dinp->di_birthnsec = st->st_birthtimensec; #else dinp->di_birthnsec = st->st_ctimensec; #endif #endif /* not set: di_db, di_ib, di_blocks, di_spare */ membuf = NULL; if (cur == root) { /* "."; write dirbuf */ membuf = dbufp->buf; dinp->di_size = dbufp->size; } else if (S_ISBLK(cur->type) || S_ISCHR(cur->type)) { dinp->di_size = 0; /* a device */ dinp->di_rdev = ufs_rw64(cur->inode->st.st_rdev, fsopts->needswap); } else if (S_ISLNK(cur->type)) { /* symlink */ slen = strlen(cur->symlink); if (slen < UFS2_MAXSYMLINKLEN) { /* short link */ memcpy(dinp->di_shortlink, cur->symlink, slen); } else membuf = cur->symlink; dinp->di_size = slen; } return membuf; } static int ffs_populate_dir(const char *dir, fsnode *root, fsinfo_t *fsopts) { fsnode *cur; dirbuf_t dirbuf; union dinode din; void *membuf; char path[MAXPATHLEN + 1]; ffs_opt_t *ffs_opts = fsopts->fs_specific; assert(dir != NULL); assert(root != NULL); assert(fsopts != NULL); assert(ffs_opts != NULL); (void)memset(&dirbuf, 0, sizeof(dirbuf)); if (debug & DEBUG_FS_POPULATE) printf("ffs_populate_dir: PASS 1 dir %s node %p\n", dir, root); /* * pass 1: allocate inode numbers, build directory `file' */ for (cur = root; cur != NULL; cur = cur->next) { if ((cur->inode->flags & FI_ALLOCATED) == 0) { cur->inode->flags |= FI_ALLOCATED; if (cur == root && cur->parent != NULL) cur->inode->ino = cur->parent->inode->ino; else { cur->inode->ino = fsopts->curinode; fsopts->curinode++; } } ffs_make_dirbuf(&dirbuf, cur->name, cur, fsopts->needswap); if (cur == root) { /* we're at "."; add ".." */ ffs_make_dirbuf(&dirbuf, "..", cur->parent == NULL ? cur : cur->parent->first, fsopts->needswap); root->inode->nlink++; /* count my parent's link */ } else if (cur->child != NULL) root->inode->nlink++; /* count my child's link */ /* * XXX possibly write file and long symlinks here, * ensuring that blocks get written before inodes? * otoh, this isn't a real filesystem, so who * cares about ordering? :-) */ } if (debug & DEBUG_FS_POPULATE_DIRBUF) ffs_dump_dirbuf(&dirbuf, dir, fsopts->needswap); /* * pass 2: write out dirbuf, then non-directories at this level */ if (debug & DEBUG_FS_POPULATE) printf("ffs_populate_dir: PASS 2 dir %s\n", dir); for (cur = root; cur != NULL; cur = cur->next) { if (cur->inode->flags & FI_WRITTEN) continue; /* skip hard-linked entries */ cur->inode->flags |= FI_WRITTEN; if (cur->contents == NULL) { if (snprintf(path, sizeof(path), "%s/%s/%s", cur->root, cur->path, cur->name) >= (int)sizeof(path)) errx(1, "Pathname too long."); } if (cur->child != NULL) continue; /* child creates own inode */ /* build on-disk inode */ if (ffs_opts->version == 1) membuf = ffs_build_dinode1(&din.dp1, &dirbuf, cur, root, fsopts); else membuf = ffs_build_dinode2(&din.dp2, &dirbuf, cur, root, fsopts); if (debug & DEBUG_FS_POPULATE_NODE) { printf("ffs_populate_dir: writing ino %d, %s", cur->inode->ino, inode_type(cur->type)); if (cur->inode->nlink > 1) printf(", nlink %d", cur->inode->nlink); putchar('\n'); } if (membuf != NULL) { ffs_write_file(&din, cur->inode->ino, membuf, fsopts); } else if (S_ISREG(cur->type)) { ffs_write_file(&din, cur->inode->ino, (cur->contents) ? cur->contents : path, fsopts); } else { assert (! S_ISDIR(cur->type)); ffs_write_inode(&din, cur->inode->ino, fsopts); } } /* * pass 3: write out sub-directories */ if (debug & DEBUG_FS_POPULATE) printf("ffs_populate_dir: PASS 3 dir %s\n", dir); for (cur = root; cur != NULL; cur = cur->next) { if (cur->child == NULL) continue; if ((size_t)snprintf(path, sizeof(path), "%s/%s", dir, cur->name) >= sizeof(path)) errx(1, "Pathname too long."); if (! ffs_populate_dir(path, cur->child, fsopts)) return (0); } if (debug & DEBUG_FS_POPULATE) printf("ffs_populate_dir: DONE dir %s\n", dir); /* cleanup */ if (dirbuf.buf != NULL) free(dirbuf.buf); return (1); } static void ffs_write_file(union dinode *din, uint32_t ino, void *buf, fsinfo_t *fsopts) { int isfile, ffd; char *fbuf, *p; off_t bufleft, chunk, offset; ssize_t nread; struct inode in; struct m_buf * bp; ffs_opt_t *ffs_opts = fsopts->fs_specific; struct m_vnode vp = { fsopts, NULL }; assert (din != NULL); assert (buf != NULL); assert (fsopts != NULL); assert (ffs_opts != NULL); isfile = S_ISREG(DIP(din, mode)); fbuf = NULL; ffd = -1; p = NULL; in.i_fs = (struct fs *)fsopts->superblock; in.i_devvp = (void *)&vp; if (debug & DEBUG_FS_WRITE_FILE) { printf( "ffs_write_file: ino %u, din %p, isfile %d, %s, size %lld", ino, din, isfile, inode_type(DIP(din, mode) & S_IFMT), (long long)DIP(din, size)); if (isfile) printf(", file '%s'\n", (char *)buf); else printf(", buffer %p\n", buf); } in.i_number = ino; in.i_size = DIP(din, size); if (ffs_opts->version == 1) memcpy(&in.i_din.dp1, &din->dp1, sizeof(in.i_din.dp1)); else memcpy(&in.i_din.dp2, &din->dp2, sizeof(in.i_din.dp2)); if (DIP(din, size) == 0) goto write_inode_and_leave; /* mmm, cheating */ if (isfile) { fbuf = emalloc(ffs_opts->bsize); if ((ffd = open((char *)buf, O_RDONLY)) == -1) { err(EXIT_FAILURE, "Can't open `%s' for reading", (char *)buf); } } else { p = buf; } chunk = 0; for (bufleft = DIP(din, size); bufleft > 0; bufleft -= chunk) { chunk = MIN(bufleft, ffs_opts->bsize); if (!isfile) ; else if ((nread = read(ffd, fbuf, chunk)) == -1) err(EXIT_FAILURE, "Reading `%s', %lld bytes to go", (char *)buf, (long long)bufleft); else if (nread != chunk) errx(EXIT_FAILURE, "Reading `%s', %lld bytes to go, " "read %zd bytes, expected %ju bytes, does " "metalog size= attribute mismatch source size?", (char *)buf, (long long)bufleft, nread, (uintmax_t)chunk); else p = fbuf; offset = DIP(din, size) - bufleft; if (debug & DEBUG_FS_WRITE_FILE_BLOCK) printf( "ffs_write_file: write %p offset %lld size %lld left %lld\n", p, (long long)offset, (long long)chunk, (long long)bufleft); /* * XXX if holey support is desired, do the check here * * XXX might need to write out last bit in fragroundup * sized chunk. however, ffs_balloc() handles this for us */ errno = ffs_balloc(&in, offset, chunk, &bp); bad_ffs_write_file: if (errno != 0) err(1, "Writing inode %d (%s), bytes %lld + %lld", ino, isfile ? (char *)buf : inode_type(DIP(din, mode) & S_IFMT), (long long)offset, (long long)chunk); memcpy(bp->b_data, p, chunk); errno = bwrite(bp); if (errno != 0) goto bad_ffs_write_file; if (!isfile) p += chunk; } write_inode_and_leave: ffs_write_inode(&in.i_din, in.i_number, fsopts); if (fbuf) free(fbuf); if (ffd != -1) close(ffd); } static void ffs_dump_dirbuf(dirbuf_t *dbuf, const char *dir, int needswap) { doff_t i; struct direct *de; uint16_t reclen; assert (dbuf != NULL); assert (dir != NULL); printf("ffs_dump_dirbuf: dir %s size %d cur %d\n", dir, dbuf->size, dbuf->cur); for (i = 0; i < dbuf->size; ) { de = (struct direct *)(dbuf->buf + i); reclen = ufs_rw16(de->d_reclen, needswap); printf( " inode %4d %7s offset %4d reclen %3d namlen %3d name %s\n", ufs_rw32(de->d_ino, needswap), inode_type(DTTOIF(de->d_type)), i, reclen, de->d_namlen, de->d_name); i += reclen; assert(reclen > 0); } } static void ffs_make_dirbuf(dirbuf_t *dbuf, const char *name, fsnode *node, int needswap) { struct direct de, *dp; uint16_t llen, reclen; u_char *newbuf; assert (dbuf != NULL); assert (name != NULL); assert (node != NULL); /* create direct entry */ (void)memset(&de, 0, sizeof(de)); de.d_ino = ufs_rw32(node->inode->ino, needswap); de.d_type = IFTODT(node->type); de.d_namlen = (uint8_t)strlen(name); strcpy(de.d_name, name); reclen = DIRSIZ_SWAP(0, &de, needswap); de.d_reclen = ufs_rw16(reclen, needswap); dp = (struct direct *)(dbuf->buf + dbuf->cur); llen = 0; if (dp != NULL) llen = DIRSIZ_SWAP(0, dp, needswap); if (debug & DEBUG_FS_MAKE_DIRBUF) printf( "ffs_make_dirbuf: dbuf siz %d cur %d lastlen %d\n" " ino %d type %d reclen %d namlen %d name %.30s\n", dbuf->size, dbuf->cur, llen, ufs_rw32(de.d_ino, needswap), de.d_type, reclen, de.d_namlen, de.d_name); if (reclen + dbuf->cur + llen > roundup(dbuf->size, DIRBLKSIZ)) { if (debug & DEBUG_FS_MAKE_DIRBUF) printf("ffs_make_dirbuf: growing buf to %d\n", dbuf->size + DIRBLKSIZ); newbuf = erealloc(dbuf->buf, dbuf->size + DIRBLKSIZ); dbuf->buf = newbuf; dbuf->size += DIRBLKSIZ; memset(dbuf->buf + dbuf->size - DIRBLKSIZ, 0, DIRBLKSIZ); dbuf->cur = dbuf->size - DIRBLKSIZ; } else if (dp) { /* shrink end of previous */ dp->d_reclen = ufs_rw16(llen,needswap); dbuf->cur += llen; } dp = (struct direct *)(dbuf->buf + dbuf->cur); memcpy(dp, &de, reclen); dp->d_reclen = ufs_rw16(dbuf->size - dbuf->cur, needswap); } /* * cribbed from sys/ufs/ffs/ffs_alloc.c */ static void ffs_write_inode(union dinode *dp, uint32_t ino, const fsinfo_t *fsopts) { char *buf; struct ufs1_dinode *dp1; struct ufs2_dinode *dp2, *dip; struct cg *cgp; struct fs *fs; int cg, cgino; uint32_t i; daddr_t d; char sbbuf[FFS_MAXBSIZE]; uint32_t initediblk; ffs_opt_t *ffs_opts = fsopts->fs_specific; assert (dp != NULL); assert (ino > 0); assert (fsopts != NULL); assert (ffs_opts != NULL); fs = (struct fs *)fsopts->superblock; cg = ino_to_cg(fs, ino); cgino = ino % fs->fs_ipg; if (debug & DEBUG_FS_WRITE_INODE) printf("ffs_write_inode: din %p ino %u cg %d cgino %d\n", dp, ino, cg, cgino); ffs_rdfs(fsbtodb(fs, cgtod(fs, cg)), (int)fs->fs_cgsize, &sbbuf, fsopts); cgp = (struct cg *)sbbuf; if (!cg_chkmagic_swap(cgp, fsopts->needswap)) errx(1, "ffs_write_inode: cg %d: bad magic number", cg); assert (isclr(cg_inosused_swap(cgp, fsopts->needswap), cgino)); buf = emalloc(fs->fs_bsize); dp1 = (struct ufs1_dinode *)buf; dp2 = (struct ufs2_dinode *)buf; if (fs->fs_cstotal.cs_nifree == 0) errx(1, "ffs_write_inode: fs out of inodes for ino %u", ino); if (fs->fs_cs(fs, cg).cs_nifree == 0) errx(1, "ffs_write_inode: cg %d out of inodes for ino %u", cg, ino); setbit(cg_inosused_swap(cgp, fsopts->needswap), cgino); ufs_add32(cgp->cg_cs.cs_nifree, -1, fsopts->needswap); fs->fs_cstotal.cs_nifree--; fs->fs_cs(fs, cg).cs_nifree--; if (S_ISDIR(DIP(dp, mode))) { ufs_add32(cgp->cg_cs.cs_ndir, 1, fsopts->needswap); fs->fs_cstotal.cs_ndir++; fs->fs_cs(fs, cg).cs_ndir++; } /* * Initialize inode blocks on the fly for UFS2. */ initediblk = ufs_rw32(cgp->cg_initediblk, fsopts->needswap); while (ffs_opts->version == 2 && cgino + INOPB(fs) > initediblk && initediblk < ufs_rw32(cgp->cg_niblk, fsopts->needswap)) { memset(buf, 0, fs->fs_bsize); dip = (struct ufs2_dinode *)buf; for (i = 0; i < INOPB(fs); i++) { dip->di_gen = random(); dip++; } ffs_wtfs(fsbtodb(fs, ino_to_fsba(fs, cg * fs->fs_ipg + initediblk)), fs->fs_bsize, buf, fsopts); initediblk += INOPB(fs); cgp->cg_initediblk = ufs_rw32(initediblk, fsopts->needswap); } ffs_wtfs(fsbtodb(fs, cgtod(fs, cg)), (int)fs->fs_cgsize, &sbbuf, fsopts); /* now write inode */ d = fsbtodb(fs, ino_to_fsba(fs, ino)); ffs_rdfs(d, fs->fs_bsize, buf, fsopts); if (fsopts->needswap) { if (ffs_opts->version == 1) ffs_dinode1_swap(&dp->dp1, &dp1[ino_to_fsbo(fs, ino)]); else ffs_dinode2_swap(&dp->dp2, &dp2[ino_to_fsbo(fs, ino)]); } else { if (ffs_opts->version == 1) dp1[ino_to_fsbo(fs, ino)] = dp->dp1; else dp2[ino_to_fsbo(fs, ino)] = dp->dp2; } ffs_wtfs(d, fs->fs_bsize, buf, fsopts); free(buf); } void panic(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vwarnx(fmt, ap); va_end(ap); exit(1); } diff --git a/usr.sbin/makefs/makefs.h b/usr.sbin/makefs/makefs.h index db0addc4bc94..62c7e430a00c 100644 --- a/usr.sbin/makefs/makefs.h +++ b/usr.sbin/makefs/makefs.h @@ -1,309 +1,324 @@ /* $NetBSD: makefs.h,v 1.20 2008/12/28 21:51:46 christos Exp $ */ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 2001 Wasabi Systems, Inc. * All rights reserved. * * Written by Luke Mewburn for Wasabi Systems, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed for the NetBSD Project by * Wasabi Systems, Inc. * 4. The name of Wasabi Systems, Inc. may not be used to endorse * or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef _MAKEFS_H #define _MAKEFS_H +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#else +#define HAVE_STRUCT_STAT_ST_FLAGS 1 +#endif + #include #include /* * fsnode - * a component of the tree; contains a filename, a pointer to * fsinode, optional symlink name, and tree pointers * * fsinode - * equivalent to an inode, containing target file system inode number, * refcount (nlink), and stat buffer * * A tree of fsnodes looks like this: * * name "." "bin" "netbsd" * type S_IFDIR S_IFDIR S_IFREG * next > > NULL * parent NULL NULL NULL * child NULL v * * name "." "ls" * type S_IFDIR S_IFREG * next > NULL * parent ^ ^ (to "bin") * child NULL NULL * * Notes: * - first always points to first entry, at current level, which * must be "." when the tree has been built; during build it may * not be if "." hasn't yet been found by readdir(2). */ enum fi_flags { FI_SIZED = 1<<0, /* inode sized */ FI_ALLOCATED = 1<<1, /* fsinode->ino allocated */ FI_WRITTEN = 1<<2, /* inode written */ FI_ROOT = 1<<3, /* root of a ZFS dataset */ }; typedef struct { uint32_t ino; /* inode number used on target fs */ uint32_t nlink; /* number of links to this entry */ enum fi_flags flags; /* flags used by fs specific code */ void *param; /* for use by individual fs impls */ struct stat st; /* stat entry */ +#if !HAVE_STRUCT_STAT_ST_FLAGS + uint32_t st_flags; /* stand-in for st.st_flags */ +#endif } fsinode; +#if HAVE_STRUCT_STAT_ST_FLAGS +#define FSINODE_ST_FLAGS(inode) (inode).st.st_flags +#else +#define FSINODE_ST_FLAGS(inode) (inode).st_flags +#endif + typedef struct _fsnode { struct _fsnode *parent; /* parent (NULL if root) */ struct _fsnode *child; /* child (if type == S_IFDIR) */ struct _fsnode *next; /* next */ struct _fsnode *first; /* first node of current level (".") */ uint32_t type; /* type of entry */ fsinode *inode; /* actual inode data */ char *symlink; /* symlink target */ char *contents; /* file to provide contents */ const char *root; /* root path */ char *path; /* directory name */ char *name; /* file name */ int flags; /* misc flags */ } fsnode; #define FSNODE_F_HASSPEC 0x01 /* fsnode has a spec entry */ #define FSNODE_F_OPTIONAL 0x02 /* fsnode is optional */ /* * option_t - contains option name, description, pointer to location to store * result, and range checks for the result. Used to simplify fs specific * option setting */ typedef enum { OPT_STRARRAY, OPT_STRPTR, OPT_STRBUF, OPT_BOOL, OPT_INT8, OPT_INT16, OPT_INT32, OPT_INT64 } opttype_t; typedef struct { char letter; /* option letter NUL for none */ const char *name; /* option name */ void *value; /* where to stuff the value */ opttype_t type; /* type of entry */ long long minimum; /* minimum for value */ long long maximum; /* maximum for value */ const char *desc; /* option description */ } option_t; /* * fsinfo_t - contains various settings and parameters pertaining to * the image, including current settings, global options, and fs * specific options */ typedef struct makefs_fsinfo { /* current settings */ off_t size; /* total size */ off_t inodes; /* number of inodes */ uint32_t curinode; /* current inode */ /* image settings */ int fd; /* file descriptor of image */ void *superblock; /* superblock */ int onlyspec; /* only add entries in specfile */ /* global options */ off_t minsize; /* minimum size image should be */ off_t maxsize; /* maximum size image can be */ off_t freefiles; /* free file entries to leave */ off_t freeblocks; /* free blocks to leave */ off_t offset; /* offset from start of file */ off_t roundup; /* round image size up to this value */ int freefilepc; /* free file % */ int freeblockpc; /* free block % */ int needswap; /* non-zero if byte swapping needed */ int sectorsize; /* sector size */ int sparse; /* sparse image, don't fill it with zeros */ void *fs_specific; /* File system specific additions. */ option_t *fs_options; /* File system specific options */ } fsinfo_t; void apply_specfile(const char *, const char *, fsnode *, int); void dump_fsnodes(fsnode *); const char * inode_type(mode_t); fsnode * read_mtree(const char *, fsnode *); int set_option(const option_t *, const char *, char *, size_t); int set_option_var(const option_t *, const char *, const char *, char *, size_t); fsnode * walk_dir(const char *, const char *, fsnode *, fsnode *); void free_fsnodes(fsnode *); option_t * copy_opts(const option_t *); #define DECLARE_FUN(fs) \ void fs ## _prep_opts(fsinfo_t *); \ int fs ## _parse_opts(const char *, fsinfo_t *); \ void fs ## _cleanup_opts(fsinfo_t *); \ void fs ## _makefs(const char *, const char *, fsnode *, fsinfo_t *) DECLARE_FUN(cd9660); DECLARE_FUN(ffs); DECLARE_FUN(msdos); #ifdef HAVE_ZFS DECLARE_FUN(zfs); #endif extern u_int debug; extern int dupsok; extern struct timespec start_time; extern struct stat stampst; /* * If -x is specified, we want to exclude nodes which do not appear * in the spec file. */ #define FSNODE_EXCLUDE_P(opts, fsnode) \ ((opts)->onlyspec != 0 && ((fsnode)->flags & FSNODE_F_HASSPEC) == 0) #define DEBUG_TIME 0x00000001 /* debug bits 1..3 unused at this time */ #define DEBUG_WALK_DIR 0x00000010 #define DEBUG_WALK_DIR_NODE 0x00000020 #define DEBUG_WALK_DIR_LINKCHECK 0x00000040 #define DEBUG_DUMP_FSNODES 0x00000080 #define DEBUG_DUMP_FSNODES_VERBOSE 0x00000100 #define DEBUG_FS_PARSE_OPTS 0x00000200 #define DEBUG_FS_MAKEFS 0x00000400 #define DEBUG_FS_VALIDATE 0x00000800 #define DEBUG_FS_CREATE_IMAGE 0x00001000 #define DEBUG_FS_SIZE_DIR 0x00002000 #define DEBUG_FS_SIZE_DIR_NODE 0x00004000 #define DEBUG_FS_SIZE_DIR_ADD_DIRENT 0x00008000 #define DEBUG_FS_POPULATE 0x00010000 #define DEBUG_FS_POPULATE_DIRBUF 0x00020000 #define DEBUG_FS_POPULATE_NODE 0x00040000 #define DEBUG_FS_WRITE_FILE 0x00080000 #define DEBUG_FS_WRITE_FILE_BLOCK 0x00100000 #define DEBUG_FS_MAKE_DIRBUF 0x00200000 #define DEBUG_FS_WRITE_INODE 0x00400000 #define DEBUG_BUF_BREAD 0x00800000 #define DEBUG_BUF_BWRITE 0x01000000 #define DEBUG_BUF_GETBLK 0x02000000 #define DEBUG_APPLY_SPECFILE 0x04000000 #define DEBUG_APPLY_SPECENTRY 0x08000000 #define DEBUG_APPLY_SPECONLY 0x10000000 #define DEBUG_MSDOSFS 0x20000000 #define TIMER_START(x) \ if (debug & DEBUG_TIME) \ gettimeofday(&(x), NULL) #define TIMER_RESULTS(x,d) \ if (debug & DEBUG_TIME) { \ struct timeval end, td; \ gettimeofday(&end, NULL); \ timersub(&end, &(x), &td); \ printf("%s took %lld.%06ld seconds\n", \ (d), (long long)td.tv_sec, \ (long)td.tv_usec); \ } #ifndef DEFAULT_FSTYPE #define DEFAULT_FSTYPE "ffs" #endif /* * ffs specific settings * --------------------- */ #define FFS_EI /* for opposite endian support in ffs headers */ /* * Write-arounds/compat shims for endian-agnostic support. * These belong in the kernel if/when it's possible to mount * filesystems w/ either byte order. */ /* * File system internal flags, also in fs_flags. * (Pick highest number to avoid conflicts with others) */ #define FS_SWAPPED 0x80000000 /* file system is endian swapped */ #define FS_INTERNAL 0x80000000 /* mask for internal flags */ #define FS_ISCLEAN 1 #define DINODE1_SIZE (sizeof(struct ufs1_dinode)) #define DINODE2_SIZE (sizeof(struct ufs2_dinode)) #define UFS1_MAXSYMLINKLEN ((UFS_NDADDR + UFS_NIADDR) * sizeof(ufs1_daddr_t)) #define UFS2_MAXSYMLINKLEN ((UFS_NDADDR + UFS_NIADDR) * sizeof(ufs2_daddr_t)) #if (BYTE_ORDER == LITTLE_ENDIAN) #define DIRSIZ_SWAP(oldfmt, dp, needswap) \ (((oldfmt) && !(needswap)) ? \ DIRECTSIZ((dp)->d_type) : DIRECTSIZ((dp)->d_namlen)) #else #define DIRSIZ_SWAP(oldfmt, dp, needswap) \ (((oldfmt) && (needswap)) ? \ DIRECTSIZ((dp)->d_type) : DIRECTSIZ((dp)->d_namlen)) #endif #define cg_chkmagic_swap(cgp, ns) \ (ufs_rw32((cgp)->cg_magic, (ns)) == CG_MAGIC) #define cg_inosused_swap(cgp, ns) \ ((u_int8_t *)((u_int8_t *)(cgp) + ufs_rw32((cgp)->cg_iusedoff, (ns)))) #define cg_blksfree_swap(cgp, ns) \ ((u_int8_t *)((u_int8_t *)(cgp) + ufs_rw32((cgp)->cg_freeoff, (ns)))) #define cg_clustersfree_swap(cgp, ns) \ ((u_int8_t *)((u_int8_t *)(cgp) + ufs_rw32((cgp)->cg_clusteroff, (ns)))) #define cg_clustersum_swap(cgp, ns) \ ((int32_t *)((uintptr_t)(cgp) + ufs_rw32((cgp)->cg_clustersumoff, ns))) struct fs; void ffs_fragacct_swap(struct fs *, int, uint32_t [], int, int); fsinode *link_check(fsinode *); #endif /* _MAKEFS_H */ diff --git a/usr.sbin/makefs/mtree.c b/usr.sbin/makefs/mtree.c index fddc32b85322..83e207b07402 100644 --- a/usr.sbin/makefs/mtree.c +++ b/usr.sbin/makefs/mtree.c @@ -1,1123 +1,1121 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2011 Marcel Moolenaar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if HAVE_NBTOOL_CONFIG_H #include "nbtool_config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "makefs.h" #ifndef ENOATTR #define ENOATTR ENODATA #endif #define IS_DOT(nm) ((nm)[0] == '.' && (nm)[1] == '\0') #define IS_DOTDOT(nm) ((nm)[0] == '.' && (nm)[1] == '.' && (nm)[2] == '\0') struct mtree_fileinfo { SLIST_ENTRY(mtree_fileinfo) next; FILE *fp; const char *name; u_int line; }; /* Global state used while parsing. */ static SLIST_HEAD(, mtree_fileinfo) mtree_fileinfo = SLIST_HEAD_INITIALIZER(mtree_fileinfo); static fsnode *mtree_root; static fsnode *mtree_current; static fsnode mtree_global; static fsinode mtree_global_inode; static u_int errors, warnings; static void mtree_error(const char *, ...) __printflike(1, 2); static void mtree_warning(const char *, ...) __printflike(1, 2); static int mtree_file_push(const char *name, FILE *fp) { struct mtree_fileinfo *fi; fi = emalloc(sizeof(*fi)); if (strcmp(name, "-") == 0) fi->name = estrdup("(stdin)"); else fi->name = estrdup(name); if (fi->name == NULL) { free(fi); return (ENOMEM); } fi->fp = fp; fi->line = 0; SLIST_INSERT_HEAD(&mtree_fileinfo, fi, next); return (0); } static void mtree_print(const char *msgtype, const char *fmt, va_list ap) { struct mtree_fileinfo *fi; if (msgtype != NULL) { fi = SLIST_FIRST(&mtree_fileinfo); if (fi != NULL) fprintf(stderr, "%s:%u: ", fi->name, fi->line); fprintf(stderr, "%s: ", msgtype); } vfprintf(stderr, fmt, ap); } static void mtree_error(const char *fmt, ...) { va_list ap; va_start(ap, fmt); mtree_print("error", fmt, ap); va_end(ap); errors++; fputc('\n', stderr); } static void mtree_warning(const char *fmt, ...) { va_list ap; va_start(ap, fmt); mtree_print("warning", fmt, ap); va_end(ap); warnings++; fputc('\n', stderr); } #ifndef MAKEFS_MAX_TREE_DEPTH # define MAKEFS_MAX_TREE_DEPTH (MAXPATHLEN/2) #endif /* construct path to node->name */ static char * mtree_file_path(fsnode *node) { fsnode *pnode; struct sbuf *sb; char *res, *rp[MAKEFS_MAX_TREE_DEPTH]; int depth; depth = 0; rp[depth] = node->name; for (pnode = node->parent; pnode && depth < MAKEFS_MAX_TREE_DEPTH - 1; pnode = pnode->parent) { if (strcmp(pnode->name, ".") == 0) break; rp[++depth] = pnode->name; } sb = sbuf_new_auto(); if (sb == NULL) { errno = ENOMEM; return (NULL); } while (depth > 0) { sbuf_cat(sb, rp[depth--]); sbuf_putc(sb, '/'); } sbuf_cat(sb, rp[depth]); sbuf_finish(sb); res = estrdup(sbuf_data(sb)); sbuf_delete(sb); if (res == NULL) errno = ENOMEM; return res; } /* mtree_resolve() sets errno to indicate why NULL was returned. */ static char * mtree_resolve(const char *spec, int *istemp) { struct sbuf *sb; char *res, *var = NULL; const char *base, *p, *v; size_t len; int c, error, quoted, subst; len = strlen(spec); if (len == 0) { errno = EINVAL; return (NULL); } c = (len > 1) ? (spec[0] == spec[len - 1]) ? spec[0] : 0 : 0; *istemp = (c == '`') ? 1 : 0; subst = (c == '`' || c == '"') ? 1 : 0; quoted = (subst || c == '\'') ? 1 : 0; if (!subst) { res = estrdup(spec + quoted); if (quoted) res[len - 2] = '\0'; return (res); } sb = sbuf_new_auto(); if (sb == NULL) { errno = ENOMEM; return (NULL); } base = spec + 1; len -= 2; error = 0; while (len > 0) { p = strchr(base, '$'); if (p == NULL) { sbuf_bcat(sb, base, len); base += len; len = 0; continue; } /* The following is safe. spec always starts with a quote. */ if (p[-1] == '\\') p--; if (base != p) { sbuf_bcat(sb, base, p - base); len -= p - base; base = p; } if (*p == '\\') { sbuf_putc(sb, '$'); base += 2; len -= 2; continue; } /* Skip the '$'. */ base++; len--; /* Handle ${X} vs $X. */ v = base; if (*base == '{') { p = strchr(v, '}'); if (p == NULL) p = v; } else p = v; len -= (p + 1) - base; base = p + 1; if (v == p) { sbuf_putc(sb, *v); continue; } error = ENOMEM; var = ecalloc(p - v, 1); memcpy(var, v + 1, p - v - 1); if (strcmp(var, ".CURDIR") == 0) { res = getcwd(NULL, 0); if (res == NULL) break; } else if (strcmp(var, ".PROG") == 0) { res = estrdup(getprogname()); } else { v = getenv(var); if (v != NULL) { res = estrdup(v); } else res = NULL; } error = 0; if (res != NULL) { sbuf_cat(sb, res); free(res); } free(var); var = NULL; } free(var); sbuf_finish(sb); res = (error == 0) ? strdup(sbuf_data(sb)) : NULL; sbuf_delete(sb); if (res == NULL) errno = ENOMEM; return (res); } static int skip_over(FILE *fp, const char *cs) { int c; c = getc(fp); while (c != EOF && strchr(cs, c) != NULL) c = getc(fp); if (c != EOF) { ungetc(c, fp); return (0); } return (ferror(fp) ? errno : -1); } static int skip_to(FILE *fp, const char *cs) { int c; c = getc(fp); while (c != EOF && strchr(cs, c) == NULL) c = getc(fp); if (c != EOF) { ungetc(c, fp); return (0); } return (ferror(fp) ? errno : -1); } static int read_word(FILE *fp, char *buf, size_t bufsz) { struct mtree_fileinfo *fi; size_t idx, qidx; int c, done, error, esc, qlvl; if (bufsz == 0) return (EINVAL); done = 0; esc = 0; idx = 0; qidx = -1; qlvl = 0; do { c = getc(fp); switch (c) { case EOF: buf[idx] = '\0'; error = ferror(fp) ? errno : -1; if (error == -1) mtree_error("unexpected end of file"); return (error); case '#': /* comment -- skip to end of line. */ if (!esc) { error = skip_to(fp, "\n"); if (!error) continue; } break; case '\\': esc++; break; case '`': case '\'': case '"': if (esc) break; if (qlvl == 0) { qlvl++; qidx = idx; } else if (c == buf[qidx]) { qlvl--; if (qlvl > 0) { do { qidx--; } while (buf[qidx] != '`' && buf[qidx] != '\'' && buf[qidx] != '"'); } else qidx = -1; } else { qlvl++; qidx = idx; } break; case ' ': case '\t': case '\n': if (!esc && qlvl == 0) { ungetc(c, fp); c = '\0'; done = 1; break; } if (c == '\n') { /* * We going to eat the newline ourselves. */ if (qlvl > 0) mtree_warning("quoted word straddles " "onto next line."); fi = SLIST_FIRST(&mtree_fileinfo); fi->line++; } break; default: if (esc) buf[idx++] = '\\'; break; } buf[idx++] = c; esc = 0; } while (idx < bufsz && !done); if (idx >= bufsz) { mtree_error("word too long to fit buffer (max %zu characters)", bufsz); skip_to(fp, " \t\n"); } return (0); } static fsnode * create_node(const char *name, u_int type, fsnode *parent, fsnode *global) { fsnode *n; n = ecalloc(1, sizeof(*n)); n->name = estrdup(name); n->type = (type == 0) ? global->type : type; n->parent = parent; n->inode = ecalloc(1, sizeof(*n->inode)); /* Assign global options/defaults. */ memcpy(n->inode, global->inode, sizeof(*n->inode)); n->inode->st.st_mode = (n->inode->st.st_mode & ~S_IFMT) | n->type; if (n->type == S_IFLNK) n->symlink = global->symlink; else if (n->type == S_IFREG) n->contents = global->contents; return (n); } static void destroy_node(fsnode *n) { assert(n != NULL); assert(n->name != NULL); assert(n->inode != NULL); free(n->inode); free(n->name); free(n); } static int read_number(const char *tok, u_int base, intmax_t *res, intmax_t min, intmax_t max) { char *end; intmax_t val; val = strtoimax(tok, &end, base); if (end == tok || end[0] != '\0') return (EINVAL); if (val < min || val > max) return (EDOM); *res = val; return (0); } static int read_mtree_keywords(FILE *fp, fsnode *node) { char keyword[PATH_MAX]; char *name, *p, *value; gid_t gid; uid_t uid; struct stat *st, sb; intmax_t num; u_long flset, flclr; int error, istemp; uint32_t type; st = &node->inode->st; do { error = skip_over(fp, " \t"); if (error) break; error = read_word(fp, keyword, sizeof(keyword)); if (error) break; if (keyword[0] == '\0') break; value = strchr(keyword, '='); if (value != NULL) *value++ = '\0'; /* * We use EINVAL, ENOATTR, ENOSYS and ENXIO to signal * certain conditions: * EINVAL - Value provided for a keyword that does * not take a value. The value is ignored. * ENOATTR - Value missing for a keyword that needs * a value. The keyword is ignored. * ENOSYS - Unsupported keyword encountered. The * keyword is ignored. * ENXIO - Value provided for a keyword that does * not take a value. The value is ignored. */ switch (keyword[0]) { case 'c': if (strcmp(keyword, "contents") == 0) { if (value == NULL) { error = ENOATTR; break; } node->contents = estrdup(value); } else error = ENOSYS; break; case 'f': if (strcmp(keyword, "flags") == 0) { if (value == NULL) { error = ENOATTR; break; } flset = flclr = 0; -#if HAVE_STRUCT_STAT_ST_FLAGS if (!strtofflags(&value, &flset, &flclr)) { - st->st_flags &= ~flclr; - st->st_flags |= flset; + FSINODE_ST_FLAGS(*node->inode) &= ~flclr; + FSINODE_ST_FLAGS(*node->inode) |= flset; } else error = errno; -#endif } else error = ENOSYS; break; case 'g': if (strcmp(keyword, "gid") == 0) { if (value == NULL) { error = ENOATTR; break; } error = read_number(value, 10, &num, 0, UINT_MAX); if (!error) st->st_gid = num; } else if (strcmp(keyword, "gname") == 0) { if (value == NULL) { error = ENOATTR; break; } if (gid_from_group(value, &gid) == 0) st->st_gid = gid; else error = EINVAL; } else error = ENOSYS; break; case 'l': if (strcmp(keyword, "link") == 0) { if (value == NULL) { error = ENOATTR; break; } node->symlink = emalloc(strlen(value) + 1); if (node->symlink == NULL) { error = errno; break; } if (strunvis(node->symlink, value) < 0) { error = errno; break; } } else error = ENOSYS; break; case 'm': if (strcmp(keyword, "mode") == 0) { if (value == NULL) { error = ENOATTR; break; } if (value[0] >= '0' && value[0] <= '9') { error = read_number(value, 8, &num, 0, 07777); if (!error) { st->st_mode &= S_IFMT; st->st_mode |= num; } } else { /* Symbolic mode not supported. */ error = EINVAL; break; } } else error = ENOSYS; break; case 'o': if (strcmp(keyword, "optional") == 0) { if (value != NULL) error = ENXIO; node->flags |= FSNODE_F_OPTIONAL; } else error = ENOSYS; break; case 's': if (strcmp(keyword, "size") == 0) { if (value == NULL) { error = ENOATTR; break; } error = read_number(value, 10, &num, 0, INTMAX_MAX); if (!error) st->st_size = num; } else error = ENOSYS; break; case 't': if (strcmp(keyword, "tags") == 0) { if (value == NULL) { error = ENOATTR; break; } /* Ignore. */ } else if (strcmp(keyword, "time") == 0) { if (value == NULL) { error = ENOATTR; break; } p = strchr(value, '.'); if (p != NULL) *p++ = '\0'; error = read_number(value, 10, &num, 0, INTMAX_MAX); if (error) break; st->st_atime = num; st->st_ctime = num; st->st_mtime = num; #if HAVE_STRUCT_STAT_ST_MTIMENSEC if (p == NULL) break; error = read_number(p, 10, &num, 0, INTMAX_MAX); if (error) break; st->st_atimensec = num; st->st_ctimensec = num; st->st_mtimensec = num; #endif } else if (strcmp(keyword, "type") == 0) { if (value == NULL) { error = ENOATTR; break; } if (strcmp(value, "dir") == 0) node->type = S_IFDIR; else if (strcmp(value, "file") == 0) node->type = S_IFREG; else if (strcmp(value, "link") == 0) node->type = S_IFLNK; else error = EINVAL; } else error = ENOSYS; break; case 'u': if (strcmp(keyword, "uid") == 0) { if (value == NULL) { error = ENOATTR; break; } error = read_number(value, 10, &num, 0, UINT_MAX); if (!error) st->st_uid = num; } else if (strcmp(keyword, "uname") == 0) { if (value == NULL) { error = ENOATTR; break; } if (uid_from_user(value, &uid) == 0) st->st_uid = uid; else error = EINVAL; } else error = ENOSYS; break; default: error = ENOSYS; break; } switch (error) { case EINVAL: mtree_error("%s: invalid value '%s'", keyword, value); break; case ENOATTR: mtree_error("%s: keyword needs a value", keyword); break; case ENOSYS: mtree_warning("%s: unsupported keyword", keyword); break; case ENXIO: mtree_error("%s: keyword does not take a value", keyword); break; } } while (1); if (error) return (error); st->st_mode = (st->st_mode & ~S_IFMT) | node->type; /* Nothing more to do for the global defaults. */ if (node->name == NULL) return (0); /* * Be intelligent about the file type. */ if (node->contents != NULL) { if (node->symlink != NULL) { mtree_error("%s: both link and contents keywords " "defined", node->name); return (0); } type = S_IFREG; } else if (node->type != 0) { type = node->type; if (type == S_IFLNK && node->symlink == NULL) { mtree_error("%s: link type requires link keyword", node->name); return (0); } else if (type == S_IFREG) { /* the named path is the default contents */ node->contents = mtree_file_path(node); } } else type = (node->symlink != NULL) ? S_IFLNK : S_IFDIR; if (node->type == 0) node->type = type; if (node->type != type) { mtree_error("%s: file type and defined keywords to not match", node->name); return (0); } st->st_mode = (st->st_mode & ~S_IFMT) | node->type; if (node->contents == NULL) return (0); name = mtree_resolve(node->contents, &istemp); if (name == NULL) return (errno); if (stat(name, &sb) != 0) { mtree_error("%s: contents file '%s' not found", node->name, name); free(name); return (0); } /* * Check for hardlinks. If the contents key is used, then the check * will only trigger if the contents file is a link even if it is used * by more than one file */ if (sb.st_nlink > 1) { fsinode *curino; st->st_ino = sb.st_ino; st->st_dev = sb.st_dev; curino = link_check(node->inode); if (curino != NULL) { free(node->inode); node->inode = curino; node->inode->nlink++; /* Reset st since node->inode has been updated. */ st = &node->inode->st; } } free(node->contents); node->contents = name; st->st_size = sb.st_size; return (0); } static int read_mtree_command(FILE *fp) { char cmd[10]; int error; error = read_word(fp, cmd, sizeof(cmd)); if (error) goto out; error = read_mtree_keywords(fp, &mtree_global); out: skip_to(fp, "\n"); (void)getc(fp); return (error); } static int read_mtree_spec1(FILE *fp, bool def, const char *name) { fsnode *last, *node, *parent; u_int type; int error; assert(name[0] != '\0'); /* * Treat '..' specially, because it only changes our current * directory. We don't create a node for it. We simply ignore * any keywords that may appear on the line as well. * Going up a directory is a little non-obvious. A directory * node has a corresponding '.' child. The parent of '.' is * not the '.' node of the parent directory, but the directory * node within the parent to which the child relates. However, * going up a directory means we need to find the '.' node to * which the directory node is linked. This we can do via the * first * pointer, because '.' is always the first entry in a * directory. */ if (IS_DOTDOT(name)) { /* This deals with NULL pointers as well. */ if (mtree_current == mtree_root) { mtree_warning("ignoring .. in root directory"); return (0); } node = mtree_current; assert(node != NULL); assert(IS_DOT(node->name)); assert(node->first == node); /* Get the corresponding directory node in the parent. */ node = mtree_current->parent; assert(node != NULL); assert(!IS_DOT(node->name)); node = node->first; assert(node != NULL); assert(IS_DOT(node->name)); assert(node->first == node); mtree_current = node; return (0); } /* * If we don't have a current directory and the first specification * (either implicit or defined) is not '.', then we need to create * a '.' node first (using a recursive call). */ if (!IS_DOT(name) && mtree_current == NULL) { error = read_mtree_spec1(fp, false, "."); if (error) return (error); } /* * Lookup the name in the current directory (if we have a current * directory) to make sure we do not create multiple nodes for the * same component. For non-definitions, if we find a node with the * same name, simply change the current directory. For definitions * more happens. */ last = NULL; node = mtree_current; while (node != NULL) { assert(node->first == mtree_current); if (strcmp(name, node->name) == 0) { if (def == true) { if (dupsok == 0) mtree_error( "duplicate definition of %s", name); else if (dupsok == 1) mtree_warning( "duplicate definition of %s", name); return (0); } if (node->type != S_IFDIR) { mtree_error("%s is not a directory", name); return (0); } assert(!IS_DOT(name)); node = node->child; assert(node != NULL); assert(IS_DOT(node->name)); mtree_current = node; return (0); } last = node; node = last->next; } parent = (mtree_current != NULL) ? mtree_current->parent : NULL; type = (def == false || IS_DOT(name)) ? S_IFDIR : 0; node = create_node(name, type, parent, &mtree_global); if (node == NULL) return (ENOMEM); if (def == true) { error = read_mtree_keywords(fp, node); if (error) { destroy_node(node); return (error); } } node->first = (mtree_current != NULL) ? mtree_current : node; if (last != NULL) last->next = node; if (node->type != S_IFDIR) return (0); if (!IS_DOT(node->name)) { parent = node; node = create_node(".", S_IFDIR, parent, parent); if (node == NULL) { last->next = NULL; destroy_node(parent); return (ENOMEM); } parent->child = node; node->first = node; } assert(node != NULL); assert(IS_DOT(node->name)); assert(node->first == node); mtree_current = node; if (mtree_root == NULL) mtree_root = node; return (0); } static int read_mtree_spec(FILE *fp) { char pathspec[PATH_MAX], pathtmp[4*PATH_MAX + 1]; char *cp; int error; error = read_word(fp, pathtmp, sizeof(pathtmp)); if (error) goto out; if (strnunvis(pathspec, PATH_MAX, pathtmp) == -1) { error = errno; goto out; } error = 0; cp = strchr(pathspec, '/'); if (cp != NULL) { /* Absolute pathname */ mtree_current = mtree_root; do { *cp++ = '\0'; /* Disallow '..' as a component. */ if (IS_DOTDOT(pathspec)) { mtree_error("absolute path cannot contain " ".. component"); goto out; } /* Ignore multiple adjacent slashes and '.'. */ if (pathspec[0] != '\0' && !IS_DOT(pathspec)) error = read_mtree_spec1(fp, false, pathspec); memmove(pathspec, cp, strlen(cp) + 1); cp = strchr(pathspec, '/'); } while (!error && cp != NULL); /* Disallow '.' and '..' as the last component. */ if (!error && (IS_DOT(pathspec) || IS_DOTDOT(pathspec))) { mtree_error("absolute path cannot contain . or .. " "components"); goto out; } } /* Ignore absolute specifications that end with a slash. */ if (!error && pathspec[0] != '\0') error = read_mtree_spec1(fp, true, pathspec); out: skip_to(fp, "\n"); (void)getc(fp); return (error); } fsnode * read_mtree(const char *fname, fsnode *node) { struct mtree_fileinfo *fi; FILE *fp; int c, error; /* We do not yet support nesting... */ assert(node == NULL); if (strcmp(fname, "-") == 0) fp = stdin; else { fp = fopen(fname, "r"); if (fp == NULL) err(1, "Can't open `%s'", fname); } error = mtree_file_push(fname, fp); if (error) goto out; memset(&mtree_global, 0, sizeof(mtree_global)); memset(&mtree_global_inode, 0, sizeof(mtree_global_inode)); mtree_global.inode = &mtree_global_inode; mtree_global_inode.nlink = 1; mtree_global_inode.st.st_nlink = 1; mtree_global_inode.st.st_atime = mtree_global_inode.st.st_ctime = mtree_global_inode.st.st_mtime = time(NULL); errors = warnings = 0; setgroupent(1); setpassent(1); mtree_root = node; mtree_current = node; do { /* Start of a new line... */ fi = SLIST_FIRST(&mtree_fileinfo); fi->line++; error = skip_over(fp, " \t"); if (error) break; c = getc(fp); if (c == EOF) { error = ferror(fp) ? errno : -1; break; } switch (c) { case '\n': /* empty line */ error = 0; break; case '#': /* comment -- skip to end of line. */ error = skip_to(fp, "\n"); if (!error) (void)getc(fp); break; case '/': /* special commands */ error = read_mtree_command(fp); break; default: /* specification */ ungetc(c, fp); error = read_mtree_spec(fp); break; } } while (!error); endpwent(); endgrent(); if (error <= 0 && (errors || warnings)) { warnx("%u error(s) and %u warning(s) in mtree manifest", errors, warnings); if (errors) exit(1); } out: if (error > 0) errc(1, error, "Error reading mtree file"); if (fp != stdin) fclose(fp); if (mtree_root != NULL) return (mtree_root); /* Handle empty specifications. */ node = create_node(".", S_IFDIR, NULL, &mtree_global); node->first = node; return (node); } diff --git a/usr.sbin/makefs/walk.c b/usr.sbin/makefs/walk.c index fe1fe8df80db..b2afa9e78094 100644 --- a/usr.sbin/makefs/walk.c +++ b/usr.sbin/makefs/walk.c @@ -1,692 +1,690 @@ /* $NetBSD: walk.c,v 1.24 2008/12/28 21:51:46 christos Exp $ */ /* * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 2001 Wasabi Systems, Inc. * All rights reserved. * * Written by Luke Mewburn for Wasabi Systems, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed for the NetBSD Project by * Wasabi Systems, Inc. * 4. The name of Wasabi Systems, Inc. may not be used to endorse * or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include "makefs.h" #include "mtree.h" #include "extern.h" static void apply_specdir(const char *, NODE *, fsnode *, int); static void apply_specentry(const char *, NODE *, fsnode *); static fsnode *create_fsnode(const char *, const char *, const char *, struct stat *); /* * walk_dir -- * build a tree of fsnodes from `root' and `dir', with a parent * fsnode of `parent' (which may be NULL for the root of the tree). * append the tree to a fsnode of `join' if it is not NULL. * each "level" is a directory, with the "." entry guaranteed to be * at the start of the list, and without ".." entries. */ fsnode * walk_dir(const char *root, const char *dir, fsnode *parent, fsnode *join) { fsnode *first, *cur, *prev, *last; DIR *dirp; struct dirent *dent; char path[MAXPATHLEN + 1]; struct stat stbuf; char *name, *rp; size_t len; int dot; assert(root != NULL); assert(dir != NULL); len = snprintf(path, sizeof(path), "%s/%s", root, dir); if (len >= sizeof(path)) errx(1, "Pathname too long."); if (debug & DEBUG_WALK_DIR) printf("walk_dir: %s %p\n", path, parent); if ((dirp = opendir(path)) == NULL) err(1, "Can't opendir `%s'", path); rp = path + strlen(root) + 1; if (join != NULL) { first = cur = join; while (cur->next != NULL) cur = cur->next; prev = cur; } else first = prev = NULL; last = prev; while ((dent = readdir(dirp)) != NULL) { name = dent->d_name; dot = 0; if (name[0] == '.') switch (name[1]) { case '\0': /* "." */ if (join != NULL) continue; dot = 1; break; case '.': /* ".." */ if (name[2] == '\0') continue; /* FALLTHROUGH */ default: dot = 0; } if (debug & DEBUG_WALK_DIR_NODE) printf("scanning %s/%s/%s\n", root, dir, name); if ((size_t)snprintf(path + len, sizeof(path) - len, "/%s", name) >= sizeof(path) - len) errx(1, "Pathname too long."); if (lstat(path, &stbuf) == -1) err(1, "Can't lstat `%s'", path); #ifdef S_ISSOCK if (S_ISSOCK(stbuf.st_mode & S_IFMT)) { if (debug & DEBUG_WALK_DIR_NODE) printf(" skipping socket %s\n", path); continue; } #endif if (join != NULL) { cur = join->next; for (;;) { if (cur == NULL || strcmp(cur->name, name) == 0) break; if (cur == last) { cur = NULL; break; } cur = cur->next; } if (cur != NULL) { if (S_ISDIR(cur->type) && S_ISDIR(stbuf.st_mode)) { if (debug & DEBUG_WALK_DIR_NODE) printf("merging %s with %p\n", path, cur->child); cur->child = walk_dir(root, rp, cur, cur->child); continue; } errx(1, "Can't merge %s `%s' with existing %s", inode_type(stbuf.st_mode), path, inode_type(cur->type)); } } cur = create_fsnode(root, dir, name, &stbuf); cur->parent = parent; if (dot) { /* ensure "." is at the start of the list */ cur->next = first; first = cur; if (! prev) prev = cur; cur->first = first; } else { /* not "." */ if (prev) prev->next = cur; prev = cur; if (!first) first = cur; cur->first = first; if (S_ISDIR(cur->type)) { cur->child = walk_dir(root, rp, cur, NULL); continue; } } if (stbuf.st_nlink > 1) { fsinode *curino; curino = link_check(cur->inode); if (curino != NULL) { free(cur->inode); cur->inode = curino; cur->inode->nlink++; if (debug & DEBUG_WALK_DIR_LINKCHECK) printf("link_check: found [%llu, %llu]\n", (unsigned long long)curino->st.st_dev, (unsigned long long)curino->st.st_ino); } } if (S_ISLNK(cur->type)) { char slink[PATH_MAX+1]; int llen; llen = readlink(path, slink, sizeof(slink) - 1); if (llen == -1) err(1, "Readlink `%s'", path); slink[llen] = '\0'; cur->symlink = estrdup(slink); } } assert(first != NULL); if (join == NULL) for (cur = first->next; cur != NULL; cur = cur->next) cur->first = first; if (closedir(dirp) == -1) err(1, "Can't closedir `%s/%s'", root, dir); return (first); } static fsnode * create_fsnode(const char *root, const char *path, const char *name, struct stat *stbuf) { fsnode *cur; cur = ecalloc(1, sizeof(*cur)); cur->path = estrdup(path); cur->name = estrdup(name); cur->inode = ecalloc(1, sizeof(*cur->inode)); cur->root = root; cur->type = stbuf->st_mode & S_IFMT; cur->inode->nlink = 1; cur->inode->st = *stbuf; if (stampst.st_ino) { cur->inode->st.st_atime = stampst.st_atime; cur->inode->st.st_mtime = stampst.st_mtime; cur->inode->st.st_ctime = stampst.st_ctime; #if HAVE_STRUCT_STAT_ST_MTIMENSEC cur->inode->st.st_atimensec = stampst.st_atimensec; cur->inode->st.st_mtimensec = stampst.st_mtimensec; cur->inode->st.st_ctimensec = stampst.st_ctimensec; #endif #if HAVE_STRUCT_STAT_BIRTHTIME cur->inode->st.st_birthtime = stampst.st_birthtime; cur->inode->st.st_birthtimensec = stampst.st_birthtimensec; #endif } return (cur); } /* * free_fsnodes -- * Removes node from tree and frees it and all of * its descendants. */ void free_fsnodes(fsnode *node) { fsnode *cur, *next; assert(node != NULL); /* for ".", start with actual parent node */ if (node->first == node) { assert(node->name[0] == '.' && node->name[1] == '\0'); if (node->parent) { assert(node->parent->child == node); node = node->parent; } } /* Find ourselves in our sibling list and unlink */ if (node->first != node) { for (cur = node->first; cur->next; cur = cur->next) { if (cur->next == node) { cur->next = node->next; node->next = NULL; break; } } } for (cur = node; cur != NULL; cur = next) { next = cur->next; if (cur->child) { cur->child->parent = NULL; free_fsnodes(cur->child); } if (cur->inode->nlink-- == 1) free(cur->inode); if (cur->symlink) free(cur->symlink); free(cur->path); free(cur->name); free(cur); } } /* * apply_specfile -- * read in the mtree(8) specfile, and apply it to the tree * at dir,parent. parameters in parent on equivalent types * will be changed to those found in specfile, and missing * entries will be added. */ void apply_specfile(const char *specfile, const char *dir, fsnode *parent, int speconly) { struct timeval start; FILE *fp; NODE *root; assert(specfile != NULL); assert(parent != NULL); if (debug & DEBUG_APPLY_SPECFILE) printf("apply_specfile: %s, %s %p\n", specfile, dir, parent); /* read in the specfile */ if ((fp = fopen(specfile, "r")) == NULL) err(1, "Can't open `%s'", specfile); TIMER_START(start); root = spec(fp); TIMER_RESULTS(start, "spec"); if (fclose(fp) == EOF) err(1, "Can't close `%s'", specfile); /* perform some sanity checks */ if (root == NULL) errx(1, "Specfile `%s' did not contain a tree", specfile); assert(strcmp(root->name, ".") == 0); assert(root->type == F_DIR); /* merge in the changes */ apply_specdir(dir, root, parent, speconly); free_nodes(root); } static void apply_specdir(const char *dir, NODE *specnode, fsnode *dirnode, int speconly) { char path[MAXPATHLEN + 1]; NODE *curnode; fsnode *curfsnode; assert(specnode != NULL); assert(dirnode != NULL); if (debug & DEBUG_APPLY_SPECFILE) printf("apply_specdir: %s %p %p\n", dir, specnode, dirnode); if (specnode->type != F_DIR) errx(1, "Specfile node `%s/%s' is not a directory", dir, specnode->name); if (dirnode->type != S_IFDIR) errx(1, "Directory node `%s/%s' is not a directory", dir, dirnode->name); apply_specentry(dir, specnode, dirnode); /* Remove any filesystem nodes not found in specfile */ /* XXX inefficient. This is O^2 in each dir and it would * have been better never to have walked this part of the tree * to begin with */ if (speconly) { fsnode *next; assert(dirnode->name[0] == '.' && dirnode->name[1] == '\0'); for (curfsnode = dirnode->next; curfsnode != NULL; curfsnode = next) { next = curfsnode->next; for (curnode = specnode->child; curnode != NULL; curnode = curnode->next) { if (strcmp(curnode->name, curfsnode->name) == 0) break; } if (curnode == NULL) { if (debug & DEBUG_APPLY_SPECONLY) { printf("apply_specdir: trimming %s/%s %p\n", dir, curfsnode->name, curfsnode); } free_fsnodes(curfsnode); } } } /* now walk specnode->child matching up with dirnode */ for (curnode = specnode->child; curnode != NULL; curnode = curnode->next) { if (debug & DEBUG_APPLY_SPECENTRY) printf("apply_specdir: spec %s\n", curnode->name); for (curfsnode = dirnode->next; curfsnode != NULL; curfsnode = curfsnode->next) { #if 0 /* too verbose for now */ if (debug & DEBUG_APPLY_SPECENTRY) printf("apply_specdir: dirent %s\n", curfsnode->name); #endif if (strcmp(curnode->name, curfsnode->name) == 0) break; } if ((size_t)snprintf(path, sizeof(path), "%s/%s", dir, curnode->name) >= sizeof(path)) errx(1, "Pathname too long."); if (curfsnode == NULL) { /* need new entry */ struct stat stbuf; /* * don't add optional spec entries * that lack an existing fs entry */ if ((curnode->flags & F_OPT) && lstat(path, &stbuf) == -1) continue; /* check that enough info is provided */ #define NODETEST(t, m) \ if (!(t)) \ errx(1, "`%s': %s not provided", path, m) NODETEST(curnode->flags & F_TYPE, "type"); NODETEST(curnode->flags & F_MODE, "mode"); /* XXX: require F_TIME ? */ NODETEST(curnode->flags & F_GID || curnode->flags & F_GNAME, "group"); NODETEST(curnode->flags & F_UID || curnode->flags & F_UNAME, "user"); /* if (curnode->type == F_BLOCK || curnode->type == F_CHAR) NODETEST(curnode->flags & F_DEV, "device number");*/ #undef NODETEST if (debug & DEBUG_APPLY_SPECFILE) printf("apply_specdir: adding %s\n", curnode->name); /* build minimal fsnode */ memset(&stbuf, 0, sizeof(stbuf)); stbuf.st_mode = nodetoino(curnode->type); stbuf.st_nlink = 1; stbuf.st_mtime = stbuf.st_atime = stbuf.st_ctime = start_time.tv_sec; #if HAVE_STRUCT_STAT_ST_MTIMENSEC stbuf.st_mtimensec = stbuf.st_atimensec = stbuf.st_ctimensec = start_time.tv_nsec; #endif curfsnode = create_fsnode(".", ".", curnode->name, &stbuf); curfsnode->parent = dirnode->parent; curfsnode->first = dirnode; curfsnode->next = dirnode->next; dirnode->next = curfsnode; if (curfsnode->type == S_IFDIR) { /* for dirs, make "." entry as well */ curfsnode->child = create_fsnode(".", ".", ".", &stbuf); curfsnode->child->parent = curfsnode; curfsnode->child->first = curfsnode->child; } if (curfsnode->type == S_IFLNK) { assert(curnode->slink != NULL); /* for symlinks, copy the target */ curfsnode->symlink = estrdup(curnode->slink); } } apply_specentry(dir, curnode, curfsnode); if (curnode->type == F_DIR) { if (curfsnode->type != S_IFDIR) errx(1, "`%s' is not a directory", path); assert (curfsnode->child != NULL); apply_specdir(path, curnode, curfsnode->child, speconly); } } } static void apply_specentry(const char *dir, NODE *specnode, fsnode *dirnode) { assert(specnode != NULL); assert(dirnode != NULL); if (nodetoino(specnode->type) != dirnode->type) errx(1, "`%s/%s' type mismatch: specfile %s, tree %s", dir, specnode->name, inode_type(nodetoino(specnode->type)), inode_type(dirnode->type)); if (debug & DEBUG_APPLY_SPECENTRY) printf("apply_specentry: %s/%s\n", dir, dirnode->name); #define ASEPRINT(t, b, o, n) \ if (debug & DEBUG_APPLY_SPECENTRY) \ printf("\t\t\tchanging %s from " b " to " b "\n", \ t, o, n) if (specnode->flags & (F_GID | F_GNAME)) { ASEPRINT("gid", "%d", dirnode->inode->st.st_gid, specnode->st_gid); dirnode->inode->st.st_gid = specnode->st_gid; } if (specnode->flags & F_MODE) { ASEPRINT("mode", "%#o", dirnode->inode->st.st_mode & ALLPERMS, specnode->st_mode); dirnode->inode->st.st_mode &= ~ALLPERMS; dirnode->inode->st.st_mode |= (specnode->st_mode & ALLPERMS); } /* XXX: ignoring F_NLINK for now */ if (specnode->flags & F_SIZE) { ASEPRINT("size", "%lld", (long long)dirnode->inode->st.st_size, (long long)specnode->st_size); dirnode->inode->st.st_size = specnode->st_size; } if (specnode->flags & F_SLINK) { assert(dirnode->symlink != NULL); assert(specnode->slink != NULL); ASEPRINT("symlink", "%s", dirnode->symlink, specnode->slink); free(dirnode->symlink); dirnode->symlink = estrdup(specnode->slink); } if (specnode->flags & F_TIME) { ASEPRINT("time", "%ld", (long)dirnode->inode->st.st_mtime, (long)specnode->st_mtimespec.tv_sec); dirnode->inode->st.st_mtime = specnode->st_mtimespec.tv_sec; dirnode->inode->st.st_atime = specnode->st_mtimespec.tv_sec; dirnode->inode->st.st_ctime = start_time.tv_sec; #if HAVE_STRUCT_STAT_ST_MTIMENSEC dirnode->inode->st.st_mtimensec = specnode->st_mtimespec.tv_nsec; dirnode->inode->st.st_atimensec = specnode->st_mtimespec.tv_nsec; dirnode->inode->st.st_ctimensec = start_time.tv_nsec; #endif } if (specnode->flags & (F_UID | F_UNAME)) { ASEPRINT("uid", "%d", dirnode->inode->st.st_uid, specnode->st_uid); dirnode->inode->st.st_uid = specnode->st_uid; } -#if HAVE_STRUCT_STAT_ST_FLAGS if (specnode->flags & F_FLAGS) { ASEPRINT("flags", "%#lX", - (unsigned long)dirnode->inode->st.st_flags, + (unsigned long)FSINODE_ST_FLAGS(*dirnode->inode), (unsigned long)specnode->st_flags); - dirnode->inode->st.st_flags = specnode->st_flags; + FSINODE_ST_FLAGS(*dirnode->inode) = specnode->st_flags; } -#endif /* if (specnode->flags & F_DEV) { ASEPRINT("rdev", "%#llx", (unsigned long long)dirnode->inode->st.st_rdev, (unsigned long long)specnode->st_rdev); dirnode->inode->st.st_rdev = specnode->st_rdev; }*/ #undef ASEPRINT dirnode->flags |= FSNODE_F_HASSPEC; } /* * dump_fsnodes -- * dump the fsnodes from `cur' */ void dump_fsnodes(fsnode *root) { fsnode *cur; char path[MAXPATHLEN + 1]; printf("dump_fsnodes: %s %p\n", root->path, root); for (cur = root; cur != NULL; cur = cur->next) { if (snprintf(path, sizeof(path), "%s/%s", cur->path, cur->name) >= (int)sizeof(path)) errx(1, "Pathname too long."); if (debug & DEBUG_DUMP_FSNODES_VERBOSE) printf("cur=%8p parent=%8p first=%8p ", cur, cur->parent, cur->first); printf("%7s: %s", inode_type(cur->type), path); if (S_ISLNK(cur->type)) { assert(cur->symlink != NULL); printf(" -> %s", cur->symlink); } else { assert (cur->symlink == NULL); } if (cur->inode->nlink > 1) printf(", nlinks=%d", cur->inode->nlink); putchar('\n'); if (cur->child) { assert (cur->type == S_IFDIR); dump_fsnodes(cur->child); } } printf("dump_fsnodes: finished %s/%s\n", root->path, root->name); } /* * inode_type -- * for a given inode type `mode', return a descriptive string. * for most cases, uses inotype() from mtree/misc.c */ const char * inode_type(mode_t mode) { if (S_ISREG(mode)) return ("file"); if (S_ISLNK(mode)) return ("symlink"); if (S_ISDIR(mode)) return ("dir"); if (S_ISFIFO(mode)) return ("fifo"); if (S_ISSOCK(mode)) return ("socket"); /* XXX should not happen but handle them */ if (S_ISCHR(mode)) return ("char"); if (S_ISBLK(mode)) return ("block"); return ("unknown"); } /* * link_check -- * return pointer to fsinode matching `entry's st_ino & st_dev if it exists, * otherwise add `entry' to table and return NULL */ /* This was borrowed from du.c and tweaked to keep an fsnode * pointer instead. -- dbj@netbsd.org */ fsinode * link_check(fsinode *entry) { static struct entry { fsinode *data; } *htable; static int htshift; /* log(allocated size) */ static int htmask; /* allocated size - 1 */ static int htused; /* 2*number of insertions */ int h, h2; uint64_t tmp; /* this constant is (1<<64)/((1+sqrt(5))/2) * aka (word size)/(golden ratio) */ const uint64_t HTCONST = 11400714819323198485ULL; const int HTBITS = 64; /* Never store zero in hashtable */ assert(entry); /* Extend hash table if necessary, keep load under 0.5 */ if (htused<<1 >= htmask) { struct entry *ohtable; if (!htable) htshift = 10; /* starting hashtable size */ else htshift++; /* exponential hashtable growth */ htmask = (1 << htshift) - 1; htused = 0; ohtable = htable; htable = ecalloc(htmask+1, sizeof(*htable)); /* populate newly allocated hashtable */ if (ohtable) { int i; for (i = 0; i <= htmask>>1; i++) if (ohtable[i].data) link_check(ohtable[i].data); free(ohtable); } } /* multiplicative hashing */ tmp = entry->st.st_dev; tmp <<= HTBITS>>1; tmp |= entry->st.st_ino; tmp *= HTCONST; h = tmp >> (HTBITS - htshift); h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */ /* open address hashtable search with double hash probing */ while (htable[h].data) { if ((htable[h].data->st.st_ino == entry->st.st_ino) && (htable[h].data->st.st_dev == entry->st.st_dev)) { return htable[h].data; } h = (h + h2) & htmask; } /* Insert the current entry into hashtable */ htable[h].data = entry; htused++; return NULL; }