# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # /usr/src/cmd/lib/fs/ufs is the directory of all ufs specific commands # whose executable reside in $(INSDIR1) and $(INSDIR2). # # NOTE: ufsdump and ufsrestore have been merged into hsmdump and # hsmrestore. The source is located in usr/src/cmd/backup. # When the Online Backup producted is no longer supported # they should be moved here again. # SUBDIR1= clri edquota ff fsck fsckall fsdb fsirand fstyp \ labelit lockfs ncheck quot quota quotacheck quotaon \ repquota tunefs SUBDIR2= df fssnap mkfs mount newfs volcopy ROLLDIR= roll_log SUBDIRS= $(ROLLDIR) $(SUBDIR1) $(SUBDIR2) all: TARGET= all install: TARGET= install clean: TARGET= clean clobber: TARGET= clobber catalog: TARGET= catalog # for messaging catalog # POFILE= ufs.po # Hammerhead: GNU Make % substitution only replaces first %; use foreach. POFILES= $(foreach d,$(SUBDIR2),$(d)/$(d).po) .KEEP_STATE: .PARALLEL: $(SUBDIRS) all install: $(ROLLDIR) .WAIT $(SUBDIR1) $(SUBDIR2) catalog: $(POFILE) $(POFILE): $(SUBDIR2) $(RM) $@ cat $(POFILES) > $@ clean clobber: $(SUBDIRS) $(SUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Copyright (c) 1996,1997 by Sun Microsystems, Inc. # All rights reserved. # # cmd/fs.d/ufs/Makefile.roll # # Common definitions for UFS specific file system utilities that need to be # able to roll the log. ROLLDIR= ../roll_log ROLLOBJS= $(ROLLDIR)/$(MACH)/roll_log.o ROLLSRCS= $(ROLLDIR)/roll_log.c CPPFLAGS += -I $(ROLLDIR) LDLIBS += -ladm $(ROLLOBJS): cd $(ROLLDIR) ; pwd ; $(MAKE) * Copyright (c) 1980,1986,1988,1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. PORTIONS OF UFS FUNCTIONALITY # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # FSTYPE= ufs LIBPROG= clri DCOPY= dcopy ATTMK= $(LIBPROG) OTHERINSTALL= $(ROOTLIBFSTYPE)/$(DCOPY) include ../../Makefile.fstype include ../Makefile.roll OBJS= $(LIBPROG).o $(ROLLOBJS) SRCS= $(LIBPROG).c $(ROLLSRCS) # No msg catalog here. POFILE= CFLAGS += $(CCVERBOSE) CPPFLAGS += -D_LARGEFILE64_SOURCE $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(LIBPROG).o $(ROOTLIBFSTYPE)/$(DCOPY): $(ROOTLIBFSTYPE)/$(LIBPROG) -$(RM) $@; $(LN) $(ROOTLIBFSTYPE)/$(LIBPROG) $@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * clri filsys inumber ... */ #include #include #include #include #include #include #include #include #include #include #include #include #include "roll_log.h" #define ISIZE (sizeof (struct dinode)) #define NI (MAXBSIZE/ISIZE) static struct dinode buf[NI]; static union { char dummy[SBSIZE]; struct fs sblk; } sb_un; #define sblock sb_un.sblk static int status; static int read_sb(int fd, const char *dev); static int isnumber(const char *s); int main(int argc, char *argv[]) { int i, f; unsigned int n; int j; offset_t off; int32_t gen; time_t t; int sbrr; if (argc < 3) { (void) printf("ufs usage: clri filsys inumber ...\n"); return (35); } f = open64(argv[1], 2); if (f < 0) { (void) printf("cannot open %s\n", argv[1]); return (35); } if ((sbrr = read_sb(f, argv[1])) != 0) { return (sbrr); } if ((sblock.fs_magic != FS_MAGIC) && (sblock.fs_magic != MTB_UFS_MAGIC)) { (void) printf("bad super block magic number\n"); return (35); } if (sblock.fs_magic == FS_MAGIC && (sblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && sblock.fs_version != UFS_VERSION_MIN)) { (void) printf( "unrecognized version of UFS on-disk format: %d\n", sblock.fs_version); return (35); } if (sblock.fs_magic == MTB_UFS_MAGIC && (sblock.fs_version > MTB_UFS_VERSION_1 || sblock.fs_version < MTB_UFS_VERSION_MIN)) { (void) printf( "unrecognized version of UFS on-disk format: %d\n", sblock.fs_version); return (35); } /* If fs is logged, roll the log. */ if (sblock.fs_logbno) { switch (rl_roll_log(argv[1])) { case RL_SUCCESS: /* * Reread the superblock. Rolling the log may have * changed it. */ if ((sbrr = read_sb(f, argv[1])) != 0) { return (sbrr); } break; case RL_SYSERR: (void) printf("Warning: Cannot roll log for %s. %s. " "Inodes will be cleared anyway.\n", argv[1], strerror(errno)); break; default: (void) printf("Cannot roll log for %s. " "Inodes will be cleared anyway.\n", argv[1]); break; } } for (i = 2; i < argc; i++) { if (!isnumber(argv[i])) { (void) printf("%s: is not a number\n", argv[i]); status = 1; continue; } n = atoi(argv[i]); if (n == 0) { (void) printf("%s: is zero\n", argv[i]); status = 1; continue; } off = fsbtodb(&sblock, itod(&sblock, n)); off *= DEV_BSIZE; (void) llseek(f, off, 0); if (read(f, (char *)buf, sblock.fs_bsize) != sblock.fs_bsize) { (void) printf("%s: read error\n", argv[i]); status = 1; } } if (status) return (status+31); /* * Update the time in superblock, so fsck will check this filesystem. */ (void) llseek(f, (offset_t)(SBLOCK * DEV_BSIZE), 0); (void) time(&t); sblock.fs_time = (time32_t)t; if (write(f, &sblock, SBSIZE) != SBSIZE) { (void) printf("cannot update %s\n", argv[1]); return (35); } for (i = 2; i < argc; i++) { n = atoi(argv[i]); (void) printf("clearing %u\n", n); off = fsbtodb(&sblock, itod(&sblock, n)); off *= DEV_BSIZE; (void) llseek(f, off, 0); (void) read(f, (char *)buf, sblock.fs_bsize); j = itoo(&sblock, n); gen = buf[j].di_gen; memset(&buf[j], 0, ISIZE); buf[j].di_gen = gen + 1; (void) llseek(f, off, 0); (void) write(f, (char *)buf, sblock.fs_bsize); } if (status) return (status+31); (void) close(f); return (0); } static int isnumber(const char *s) { int c; while ((c = *s++) != '\0') if (c < '0' || c > '9') return (0); return (1); } static int read_sb(int fd, const char *dev) { (void) llseek(fd, (offset_t)(SBLOCK * DEV_BSIZE), 0); if (read(fd, &sblock, SBSIZE) != SBSIZE) { (void) printf("cannot read %s\n", dev); return (35); } else { return (0); } } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= df ATTMK= $(LIBPROG) include ../../Makefile.fstype OBJS= df.o CPPFLAGS += -D_LARGEFILE64_SOURCE LDLIBS += -ladm CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-parentheses CERRWARN += -Wno-unused-function # not linted SMATCH=off # for messaging catalogue file # POFILE= df.po XGETFLAGS += -a -x df.xcl catalog: $(POFILE) SRCS= $(LIBPROG).c $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(LIBPROG).o /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * df */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern char *getenv(); extern char *getcwd(); extern char *realpath(); extern off_t lseek(); /* * Raw name to block device name translation function. * This comes from libadm. */ extern char *getfullblkname(); static void usage(), pheader(); static char *mpath(char *); static char *zap_chroot(char *); static char *pathsuffix(char *, char *); static char *xmalloc(unsigned int); static int chroot_stat(char *, int (*)(), char *, char **); static int bread(char *, int, daddr_t, char *, int); static int subpath(char *, char *); static int abspath(char *, char *, char *); static void show_inode_usage(); static void dfreedev(char *); static void dfreemnt(char *, struct mnttab *); static void print_totals(); static void print_itotals(); static void print_statvfs(struct statvfs64 *); static int mdev(char *, struct mnttab **); static struct mntlist *mkmntlist(); static struct mnttab *mntdup(struct mnttab *mnt); static struct mntlist *findmntent(char *, struct stat64 *, struct mntlist *); #define bcopy(f, t, n) memcpy(t, f, n) #define bzero(s, n) memset(s, 0, n) #define bcmp(s, d, n) memcmp(s, d, n) #define index(s, r) strchr(s, r) #define rindex(s, r) strrchr(s, r) #define dbtok(x, b) \ ((b) < (fsblkcnt64_t)1024 ? \ (x) / ((fsblkcnt64_t)1024 / (b)) : (x) * ((b) / (fsblkcnt64_t)1024)) int aflag = 0; /* even the uninteresting ones */ int bflag = 0; /* print only number of kilobytes free */ int eflag = 0; /* print only number of file entries free */ int gflag = 0; /* print entire statvfs structure */ int hflag = 0; /* don't print header */ int iflag = 0; /* information for inodes */ int nflag = 0; /* print VFStype name */ int tflag = 0; /* print totals */ int errflag = 0; int errcode = 0; char *typestr = "ufs"; fsblkcnt64_t t_totalblks, t_avail, t_free, t_used, t_reserved; int t_inodes, t_iused, t_ifree; /* * cached information recording previous chroot history. */ static char *chrootpath; extern int optind; extern char *optarg; union { struct fs iu_fs; char dummy[SBSIZE]; } sb; #define sblock sb.iu_fs /* * This structure is used to chain mntent structures into a list * and to cache stat information for each member of the list. */ struct mntlist { struct mnttab *mntl_mnt; struct mntlist *mntl_next; dev_t mntl_dev; int mntl_devvalid; }; char *subopts [] = { #define A_FLAG 0 "a", #define I_FLAG 1 "i", NULL }; int main(int argc, char *argv[]) { struct mnttab mnt; int opt; char *suboptions, *value; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); while ((opt = getopt(argc, argv, "beghkno:t")) != EOF) { switch (opt) { case 'b': /* print only number of kilobytes free */ bflag++; break; case 'e': eflag++; /* print only number of file entries free */ iflag++; break; case 'g': gflag++; break; case 'n': nflag++; break; case 'k': break; case 'h': hflag++; break; case 'o': /* * ufs specific options. */ suboptions = optarg; while (*suboptions != '\0') { switch (getsubopt(&suboptions, subopts, &value)) { case I_FLAG: /* information for inodes */ iflag++; break; default: usage(); } } break; case 't': /* print totals */ tflag++; break; case 'V': /* Print command line */ { char *opt_text; int opt_count; (void) fprintf(stdout, "df -F ufs "); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) fprintf(stdout, " %s ", opt_text); } (void) fprintf(stdout, "\n"); } break; case '?': errflag++; } } if (errflag) usage(); if (gflag && iflag) { printf(gettext("df: '-g' and '-o i' are mutually exclusive\n")); exit(1); } if (bflag || eflag) tflag = 0; /* * Cache CHROOT information for later use; assume that $CHROOT matches * the cumulative arguments given to chroot calls. */ chrootpath = getenv("CHROOT"); if (chrootpath != NULL && strcmp(chrootpath, "/") == 0) chrootpath = NULL; if (argc <= optind) { /* * Take this path when "/usr/lib/fs/ufs/df" is specified, and * there are no mountpoints specified. * E.g., these command lines take us down this path * /usr/lib/fs/ufs/df -o i * /usr/lib/fs/ufs/df */ FILE *mtabp; if ((mtabp = fopen(MNTTAB, "r")) == NULL) { (void) fprintf(stderr, "df: "); perror(MNTTAB); exit(1); } pheader(); while (getmntent(mtabp, &mnt) == 0) { if (strcmp(typestr, mnt.mnt_fstype) != 0) { continue; } dfreemnt(mnt.mnt_mountp, &mnt); } if (tflag) if (iflag) print_itotals(); else print_totals(); (void) fclose(mtabp); } else { int i; struct mntlist *mntl; struct stat64 *argstat; char **devnames; char *cp; /* Arguments are processed till optind, adjust the pointers */ argv += optind; argc -= optind; /* * Obtain stat64 information for each argument before * constructing the list of mounted file systems. This * ordering forces the automounter to establish any * mounts required to access the arguments, so that the * corresponding mount table entries will exist when * we look for them. */ argstat = (struct stat64 *)xmalloc(argc * sizeof (*argstat)); devnames = (char **)xmalloc(argc * sizeof (char *)); for (i = 0; i < argc; i++) { /* * Given a raw device name, get the block device name */ cp = getfullblkname(argv[i]); if (cp == NULL || *cp == '\0') { if (cp != NULL) free(cp); cp = strdup(argv[i]); if (cp == NULL) { int j; fprintf(stderr, gettext( "df: memory allocation failure\n")); for (j = 0; j < i; j++) free(devnames[j]); free(devnames); free(argstat); exit(1); } } if (stat64(cp, &argstat[i]) < 0) { errcode = errno; /* * Mark as no longer interesting. */ argv[i] = NULL; devnames[i] = NULL; free(cp); } else { devnames[i] = cp; } } pheader(); aflag++; /* * Construct the list of mounted file systems. */ mntl = mkmntlist(); /* * Iterate through the argument list, reporting on each one. */ for (i = 0; i < argc; i++) { struct mntlist *mlp; int isblk; /* * Skip if we've already determined that we can't * process it. */ if (argv[i] == NULL) continue; /* * If the argument names a device, report on the file * system associated with the device rather than on * the one containing the device's directory entry */ cp = devnames[i]; if ((isblk = (argstat[i].st_mode&S_IFMT) == S_IFBLK) || (argstat[i].st_mode & S_IFMT) == S_IFCHR) { if (isblk && strcmp(mpath(cp), "") != 0) { struct mnttab *mp; if (mdev(cp, &mp)) return (1); dfreemnt(mp->mnt_mountp, mp); } else { dfreedev(cp); } free(cp); devnames[i] = NULL; continue; } /* * Get this argument's corresponding mount table * entry. */ mlp = findmntent(cp, &argstat[i], mntl); free(cp); devnames[i] = NULL; if (mlp == NULL) { (void) fprintf(stderr, gettext("Could not find mount point for %s\n"), argv[i]); continue; } dfreemnt(mlp->mntl_mnt->mnt_mountp, mlp->mntl_mnt); } free(devnames); free(argstat); } return (0); } void pheader() { if (hflag) return; if (nflag) (void) printf(gettext("VFStype name - ufs\n")); if (iflag) { if (eflag) /* * TRANSLATION_NOTE * Following string is used as a table header. * Translated items should start at the same * columns as the original items. */ (void) printf(gettext( "Filesystem ifree\n")); else { /* * TRANSLATION_NOTE * Following string is used as a table header. * Translated items should start at the same * columns as the original items. */ (void) printf(gettext( "Filesystem iused ifree %%iused Mounted on\n")); } } else { if (gflag) /* * TRANSLATION_NOTE * Following string is used as a table header. * Translated items should start at the same * columns as the original items. */ (void) printf(gettext( "Filesystem f_type f_fsize f_bfree f_bavail f_files f_ffree " "f_fsid f_flag f_fstr\n")); else if (bflag) /* * TRANSLATION_NOTE * Following string is used as a table header. * Translated items should start at the same * columns as the original items. */ (void) printf(gettext( "Filesystem avail\n")); else { /* * TRANSLATION_NOTE * Following string is used as a table header. * Translated items should start at the same * columns as the original items. */ (void) printf(gettext( "Filesystem kbytes used avail capacity Mounted on\n")); } } } /* * Report on a block or character special device. Assumed not to be * mounted. N.B. checks for a valid UFS superblock. */ void dfreedev(char *file) { fsblkcnt64_t totalblks, availblks, avail, free, used; int fi; fi = open64(file, 0); if (fi < 0) { (void) fprintf(stderr, "df: "); perror(file); return; } if (bread(file, fi, SBLOCK, (char *)&sblock, SBSIZE) == 0) { (void) close(fi); return; } if ((sblock.fs_magic != FS_MAGIC) && (sblock.fs_magic != MTB_UFS_MAGIC)) { (void) fprintf(stderr, gettext( "df: %s: not a ufs file system\n"), file); (void) close(fi); return; } if (sblock.fs_magic == FS_MAGIC && (sblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && sblock.fs_version != UFS_VERSION_MIN)) { (void) fprintf(stderr, gettext( "df: %s: unrecognized version of UFS: %d\n"), file, sblock.fs_version); (void) close(fi); return; } if (sblock.fs_magic == MTB_UFS_MAGIC && (sblock.fs_version > MTB_UFS_VERSION_1 || sblock.fs_version < MTB_UFS_VERSION_MIN)) { (void) fprintf(stderr, gettext( "df: %s: unrecognized version of UFS: %d\n"), file, sblock.fs_version); (void) close(fi); return; } (void) printf("%-20.20s", file); if (iflag) { if (eflag) { (void) printf("%8ld", sblock.fs_cstotal.cs_nifree); } else { show_inode_usage( (fsfilcnt64_t)sblock.fs_ncg * (fsfilcnt64_t)sblock.fs_ipg, (fsfilcnt64_t)sblock.fs_cstotal.cs_nifree); } } else { totalblks = (fsblkcnt64_t)sblock.fs_dsize; free = (fsblkcnt64_t)sblock.fs_cstotal.cs_nbfree * (fsblkcnt64_t)sblock.fs_frag + (fsblkcnt64_t)sblock.fs_cstotal.cs_nffree; used = totalblks - free; availblks = totalblks / (fsblkcnt64_t)100 * ((fsblkcnt64_t)100 - (fsblkcnt64_t)sblock.fs_minfree); avail = availblks > used ? availblks - used : (fsblkcnt64_t)0; if (bflag) { (void) printf("%8lld\n", dbtok(avail, (fsblkcnt64_t)sblock.fs_fsize)); } else { (void) printf(" %7lld %7lld %7lld", dbtok(totalblks, (fsblkcnt64_t)sblock.fs_fsize), dbtok(used, (fsblkcnt64_t)sblock.fs_fsize), dbtok(avail, (fsblkcnt64_t)sblock.fs_fsize)); (void) printf("%6.0f%%", availblks == 0 ? 0.0 : (double)used / (double)availblks * 100.0); (void) printf(" "); } if (tflag) { t_totalblks += dbtok(totalblks, (fsblkcnt64_t)sblock.fs_fsize); t_used += dbtok(used, (fsblkcnt64_t)sblock.fs_fsize); t_avail += dbtok(avail, (fsblkcnt64_t)sblock.fs_fsize); t_free += free; } } if ((!bflag) && (!eflag)) (void) printf(" %s\n", mpath(file)); else if (eflag) (void) printf("\n"); (void) close(fi); } void dfreemnt(char *file, struct mnttab *mnt) { struct statvfs64 fs; if (statvfs64(file, &fs) < 0 && chroot_stat(file, statvfs64, (char *)&fs, &file) < 0) { (void) fprintf(stderr, "df: "); perror(file); return; } if (!aflag && fs.f_blocks == 0) { return; } if (!isatty(fileno(stdout))) { (void) printf("%s", mnt->mnt_special); } else { if (strlen(mnt->mnt_special) > (size_t)20) { (void) printf("%s\n", mnt->mnt_special); (void) printf(" "); } else { (void) printf("%-20.20s", mnt->mnt_special); } } if (iflag) { if (eflag) { (void) printf("%8lld", fs.f_ffree); } else { show_inode_usage(fs.f_files, fs.f_ffree); } } else { if (gflag) { print_statvfs(&fs); } else { fsblkcnt64_t totalblks, avail, free, used, reserved; totalblks = fs.f_blocks; free = fs.f_bfree; used = totalblks - free; avail = fs.f_bavail; reserved = free - avail; if ((long long)avail < 0) avail = 0; if (bflag) { (void) printf("%8lld\n", dbtok(avail, (fsblkcnt64_t)fs.f_frsize)); } else { (void) printf(" %7lld %7lld %7lld", dbtok(totalblks, (fsblkcnt64_t)fs.f_frsize), dbtok(used, (fsblkcnt64_t)fs.f_frsize), dbtok(avail, (fsblkcnt64_t)fs.f_frsize)); totalblks -= reserved; (void) printf("%6.0f%%", totalblks == 0 ? 0.0 : (double)used / (double)totalblks * 100.0); (void) printf(" "); if (tflag) { t_totalblks += dbtok(totalblks + reserved, (fsblkcnt64_t)fs.f_bsize); t_reserved += reserved; t_used += dbtok(used, (fsblkcnt64_t)fs.f_frsize); t_avail += dbtok(avail, (fsblkcnt64_t)fs.f_frsize); t_free += free; } } } } if ((!bflag) && (!eflag) && (!gflag)) (void) printf(" %s\n", mnt->mnt_mountp); else if (eflag) (void) printf("\n"); } static void show_inode_usage(fsfilcnt64_t total, fsfilcnt64_t free) { fsfilcnt64_t used = total - free; int missing_info = ((long long)total == (long long)-1 || (long long)free == (long long)-1); if (missing_info) (void) printf("%8s", "*"); else (void) printf("%8lld", used); if ((long long)free == (long long)-1) (void) printf("%8s", "*"); else (void) printf(" %7lld", free); if (missing_info) (void) printf("%6s ", "*"); else (void) printf("%6.0f%% ", (double)used / (double)total * 100.0); } /* * Return the suffix of path obtained by stripping off the prefix * that is the value of the CHROOT environment variable. If this * value isn't obtainable or if it's not a prefix of path, return NULL. */ static char * zap_chroot(char *path) { return (pathsuffix(path, chrootpath)); } /* * Stat/statfs a file after stripping off leading directory to which we are * chroot'd. Used to find the TFS mount that applies to the current * activated NSE environment. */ static int chroot_stat(char *dir, int (*statfunc)(), char *statp, char **dirp) { if ((dir = zap_chroot(dir)) == NULL) return (-1); if (dirp) *dirp = dir; return (*statfunc)(dir, statp); } /* * Given a name like /dev/dsk/c1d0s2, returns the mounted path, like /usr. */ char * mpath(char *file) { struct mnttab mnt; FILE *mnttab; struct stat64 device_stat, mount_stat; char *mname; mnttab = fopen(MNTTAB, "r"); if (mnttab == NULL) { return (""); } mname = ""; while ((getmntent(mnttab, &mnt)) == 0) { if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) != 0) { continue; } if (strcmp(file, mnt.mnt_special) == 0) { if (stat64(mnt.mnt_mountp, &mount_stat) != 0) continue; if (stat64(mnt.mnt_special, &device_stat) != 0) continue; if (device_stat.st_rdev == mount_stat.st_dev) { mname = mnt.mnt_mountp; break; } } } fclose(mnttab); return (mname); } /* * Given a special device, return mnttab entry * Returns 0 on success */ int mdev(char *spec, struct mnttab **mntbp) { FILE *mntp; struct mnttab mnt; if ((mntp = fopen(MNTTAB, "r")) == 0) { (void) fprintf(stderr, "df: "); perror(MNTTAB); return (1); } while (getmntent(mntp, &mnt) == 0) { if (strcmp(spec, mnt.mnt_special) == 0) { (void) fclose(mntp); *mntbp = mntdup(&mnt); return (0); } } (void) fclose(mntp); (void) fprintf(stderr, "df : couldn't find mnttab entry for %s", spec); return (1); } /* * Find the entry in mlist that corresponds to the file named by path * (i.e., that names a mount table entry for the file system in which * path lies). The pstat argument must point to stat information for * path. * * Return the entry or NULL if there's no match. * * As it becomes necessary to obtain stat information about previously * unexamined mlist entries, gather the information and cache it with the * entries. * * The routine's strategy is to convert path into its canonical, symlink-free * representation canon (which will require accessing the file systems on the * branch from the root to path and thus may cause the routine to hang if any * of them are inaccessible) and to use it to search for a mount point whose * name is a substring of canon and whose corresponding device matches that of * canon. This technique avoids accessing unnecessary file system resources * and thus prevents the program from hanging on inaccessible resources unless * those resources are necessary for accessing path. */ static struct mntlist * findmntent(char *path, struct stat64 *pstat, struct mntlist *mlist) { static char cwd[MAXPATHLEN]; char canon[MAXPATHLEN]; char scratch[MAXPATHLEN]; struct mntlist *mlp; /* * If path is relative and we haven't already determined the current * working directory, do so now. Calculating the working directory * here lets us do the work once, instead of (potentially) repeatedly * in realpath(). */ if (*path != '/' && cwd[0] == '\0') { if (getcwd(cwd, MAXPATHLEN) == NULL) { cwd[0] = '\0'; return (NULL); } } /* * Find an absolute pathname in the native file system name space that * corresponds to path, stuffing it into canon. * * If CHROOT is set in the environment, assume that chroot($CHROOT) * (or an equivalent series of calls) was executed and convert the * path to the equivalent name in the native file system's name space. * Doing so allows direct comparison with the names in mtab entires, * which are assumed to be recorded relative to the native name space. */ if (abspath(cwd, path, scratch) < 0) return (NULL); if (strcmp(scratch, "/") == 0 && chrootpath != NULL) { /* * Force canon to be in canonical form; if the result from * abspath was "/" and chrootpath isn't the null string, we * must strip off a trailing slash. */ scratch[0] = '\0'; } (void) sprintf(canon, "%s%s", chrootpath ? chrootpath : "", scratch); for (mlp = mlist; mlp; mlp = mlp->mntl_next) { struct mnttab *mnt = mlp->mntl_mnt; /* * Ignore uninteresting mounts. */ if (strcmp(mnt->mnt_fstype, typestr) != 0) continue; /* * The mount entry covers some prefix of the file. * See whether it's the entry for the file system * containing the file by comparing device ids. */ if (mlp->mntl_dev == NODEV) { struct stat64 fs_sb; if (stat64(mnt->mnt_mountp, &fs_sb) < 0 && chroot_stat(mnt->mnt_mountp, stat64, (char *)&fs_sb, (char **)NULL) < 0) { continue; } mlp->mntl_dev = fs_sb.st_dev; } if (pstat->st_dev == mlp->mntl_dev) return (mlp); } return (NULL); } /* * Convert the path given in raw to canonical, absolute, symlink-free * form, storing the result in the buffer named by canon, which must be * at least MAXPATHLEN bytes long. "wd" contains the current working * directory; accepting this value as an argument lets our caller cache * the value, so that realpath (called from this routine) doesn't have * to recalculate it each time it's given a relative pathname. * * Return 0 on success, -1 on failure. */ static int abspath(char *wd, char *raw, char *canon) { char absbuf[MAXPATHLEN]; /* * Preliminary sanity check. */ if (wd == NULL || raw == NULL || canon == NULL) return (-1); /* * If the path is relative, convert it to absolute form, * using wd if it's been supplied. */ if (raw[0] != '/') { char *limit = absbuf + sizeof (absbuf); char *d; /* Fill in working directory. */ if (strlcpy(absbuf, wd, sizeof (absbuf)) >= sizeof (absbuf)) return (-1); /* Add separating slash. */ d = absbuf + strlen(absbuf); if (d < limit) *d++ = '/'; /* Glue on the relative part of the path. */ while (d < limit && (*d++ = *raw++)) continue; raw = absbuf; } /* * Call realpath to canonicalize and resolve symlinks. */ return (realpath(raw, canon) == NULL ? -1 : 0); } /* * Return a pointer to the trailing suffix of full that follows the prefix * given by pref. If pref isn't a prefix of full, return NULL. Apply * pathname semantics to the prefix test, so that pref must match at a * component boundary. */ static char * pathsuffix(char *full, char *pref) { int preflen; if (full == NULL || pref == NULL) return (NULL); preflen = strlen(pref); if (strncmp(pref, full, preflen) != 0) return (NULL); /* * pref is a substring of full. To be a subpath, it cannot cover a * partial component of full. The last clause of the test handles the * special case of the root. */ if (full[preflen] != '\0' && full[preflen] != '/' && preflen > 1) return (NULL); if (preflen == 1 && full[0] == '/') return (full); else return (full + preflen); } /* * Return zero iff the path named by sub is a leading subpath * of the path named by full. * * Treat null paths as matching nothing. */ static int subpath(char *full, char *sub) { return (pathsuffix(full, sub) == NULL); } offset_t llseek(); int bread(char *file, int fi, daddr_t bno, char *buf, int cnt) { int n; (void) llseek(fi, (offset_t)bno * DEV_BSIZE, 0); if ((n = read(fi, buf, cnt)) < 0) { /* probably a dismounted disk if errno == EIO */ if (errno != EIO) { (void) fprintf(stderr, gettext("df: read error on ")); perror(file); (void) fprintf(stderr, "bno = %ld\n", bno); } else { (void) fprintf(stderr, gettext( "df: premature EOF on %s\n"), file); (void) fprintf(stderr, "bno = %ld expected = %d count = %d\n", bno, cnt, n); } return (0); } return (1); } char * xmalloc(unsigned int size) { char *ret; char *malloc(); if ((ret = (char *)malloc(size)) == NULL) { (void) fprintf(stderr, gettext("umount: ran out of memory!\n")); exit(1); } return (ret); } struct mnttab * mntdup(struct mnttab *mnt) { struct mnttab *new; new = (struct mnttab *)xmalloc(sizeof (*new)); new->mnt_special = (char *)xmalloc((unsigned)(strlen(mnt->mnt_special) + 1)); (void) strcpy(new->mnt_special, mnt->mnt_special); new->mnt_mountp = (char *)xmalloc((unsigned)(strlen(mnt->mnt_mountp) + 1)); (void) strcpy(new->mnt_mountp, mnt->mnt_mountp); new->mnt_fstype = (char *)xmalloc((unsigned)(strlen(mnt->mnt_fstype) + 1)); (void) strcpy(new->mnt_fstype, mnt->mnt_fstype); if (mnt->mnt_mntopts != NULL) { new->mnt_mntopts = (char *)xmalloc((unsigned)(strlen(mnt->mnt_mntopts) + 1)); (void) strcpy(new->mnt_mntopts, mnt->mnt_mntopts); } else { new->mnt_mntopts = NULL; } #ifdef never new->mnt_freq = mnt->mnt_freq; new->mnt_passno = mnt->mnt_passno; #endif /* never */ return (new); } void usage() { (void) fprintf(stderr, gettext( "ufs usage: df [generic options] [-o i] [directory | special]\n")); exit(1); } struct mntlist * mkmntlist() { FILE *mounted; struct mntlist *mntl; struct mntlist *mntst = NULL; struct extmnttab mnt; if ((mounted = fopen(MNTTAB, "r")) == NULL) { (void) fprintf(stderr, "df : "); perror(MNTTAB); exit(1); } resetmnttab(mounted); while (getextmntent(mounted, &mnt, sizeof (struct extmnttab)) == 0) { mntl = (struct mntlist *)xmalloc(sizeof (*mntl)); mntl->mntl_mnt = mntdup((struct mnttab *)(&mnt)); mntl->mntl_next = mntst; mntl->mntl_devvalid = 1; mntl->mntl_dev = makedev(mnt.mnt_major, mnt.mnt_minor); mntst = mntl; } (void) fclose(mounted); return (mntst); } void print_statvfs(struct statvfs64 *fs) { int i; for (i = 0; i < FSTYPSZ; i++) (void) printf("%c", fs->f_basetype[i]); (void) printf(" %7d %7lld %7lld", fs->f_frsize, fs->f_blocks, fs->f_bavail); (void) printf(" %7lld %7lld %7d", fs->f_files, fs->f_ffree, fs->f_fsid); (void) printf(" 0x%x ", fs->f_flag); for (i = 0; i < 14; i++) (void) printf("%c", (fs->f_fstr[i] == '\0') ? ' ' : fs->f_fstr[i]); printf("\n"); } void print_totals() { /* * TRANSLATION_NOTE * Following string is used as a table header. * Translated items should start at the same * columns as the original items. */ (void) printf(gettext("Totals %8lld %7lld %7lld"), t_totalblks, t_used, t_avail); (void) printf("%6.0f%%\n", (t_totalblks - t_reserved) == (fsblkcnt64_t)0 ? 0.0 : (double)t_used / (double)(t_totalblks - t_reserved) * 100.0); } void print_itotals() { /* * TRANSLATION_NOTE * Following string is used as a table header. * Translated items should start at the same * columns as the original items. */ (void) printf(gettext("Totals %8d %7d%6.0f%%\n"), t_iused, t_ifree, t_inodes == 0 ? 0.0 : (double)t_iused / (double)t_inodes * 100.0); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # msgid " " msgid " " msgid " %s\n" msgid " %s " msgid "" msgid "%-20.20s" msgid "%6.0f%%" msgid "%6.0f%%\n" msgid "%8d%8d%8d" msgid "%8d%8d%8d\n" msgid "%8d\n" msgid "%8ld%8ld%6.0f%% " msgid "%8ld\n" msgid "%c" msgid "%s\n" msgid "/etc/mnttab" msgid "0x%x " msgid "Filesystem iused ifree %%iused" msgid "Filesystem avail\n" msgid "Filesystem ifree\n" msgid "Filesystem kbytes used avail capacity" msgid "Filesystem f_type f_fsize f_bfree f_bavail f_files f_ffree f_fsid f_flag f_fstr\n" msgid "Totals %8d%8d%6.0f%%\n" msgid "Totals %8d%8d%8d" msgid "\n" msgid "a" msgid "begko:t" msgid "bno = %ld expected = %d count = %d\n" msgid "bno = %ld\n" msgid "df -F ufs " msgid "df : " msgid "df: " msgid "i" msgid "ignore" msgid "r" msgid "swap" msgid "ufs" # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1989,1996 by Sun Microsystems, Inc. # All rights reserved. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= edquota ATTMK= $(LIBPROG) OTHERINSTALL= $(ROOTUSRSBIN)/$(LIBPROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(LIBPROG) include ../../Makefile.fstype CPPFLAGS += -D_LARGEFILE64_SOURCE CERRWARN += -Wno-parentheses # Hammerhead: Suppress pointer/int cast warnings in legacy UFS code CERRWARN += -Wno-pointer-to-int-cast # not linted SMATCH=off $(ROOTUSRSBIN)/$(LIBPROG): -$(RM) $@; $(SYMLINK) $(LINKVALUE) $@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Disk quota editor. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFEDITOR "/usr/bin/vi" #if DEV_BSIZE < 1024 #define dbtok(x) ((x) / (1024 / DEV_BSIZE)) #define ktodb(x) ((x) * (1024 / DEV_BSIZE)) #else #define dbtok(x) ((x) * (DEV_BSIZE / 1024)) #define ktodb(x) ((x) / (DEV_BSIZE / 1024)) #endif struct fsquot { struct fsquot *fsq_next; struct dqblk fsq_dqb; char *fsq_fs; char *fsq_dev; char *fsq_qfile; }; static struct fsquot *fsqlist; static char tmpfil[] = "/tmp/EdP.aXXXXXX"; #define QFNAME "quotas" static uid_t getentry(char *); static int editit(void); static void getprivs(uid_t); static void putprivs(uid_t); static void gettimes(uid_t); static void puttimes(uid_t); static char *next(char *, char *); static int alldigits(char *); static void fmttime(char *, ulong_t); static int unfmttime(double, char *, uint32_t *); static void setupfs(void); static void getdiscq(uid_t); static void putdiscq(uid_t); static void sigsetmask(uint_t); static uint_t sigblock(uint_t); static void usage(void); static int quotactl(int, char *, uid_t, caddr_t); int main(int argc, char **argv) { uid_t uid; char *basename; int opt; int i; int tmpfd = -1; basename = argv[0]; if (argc < 2) { usage(); } if (quotactl(Q_SYNC, (char *)NULL, 0, (caddr_t)NULL) < 0 && errno == EINVAL) { (void) printf("Warning: " "Quotas are not compiled into this kernel\n"); (void) sleep(3); } if (getuid()) { (void) fprintf(stderr, "%s: permission denied\n", basename); exit(32); } setupfs(); if (fsqlist == NULL) { (void) fprintf(stderr, "%s: no UFS filesystems with %s file\n", MNTTAB, QFNAME); exit(32); } tmpfd = mkstemp(tmpfil); if (tmpfd == -1 || fchown(tmpfd, getuid(), getgid()) == -1) { fprintf(stderr, "failure in temporary file %s\n", tmpfil); exit(32); } (void) close(tmpfd); while ((opt = getopt(argc, argv, "p:tV")) != EOF) switch (opt) { case 't': gettimes(0); if (editit()) puttimes(0); (void) unlink(tmpfil); exit(0); /*NOTREACHED*/ case 'p': uid = getentry(optarg); if (uid > MAXUID) { (void) unlink(tmpfil); exit(32); } getprivs(uid); if (optind == argc) { (void) unlink(tmpfil); usage(); } for (i = optind; i < argc; i++) { uid = getentry(argv[i]); if (uid > MAXUID) { (void) unlink(tmpfil); exit(32); } getdiscq(uid); putprivs(uid); } (void) unlink(tmpfil); exit(0); /*NOTREACHED*/ case 'V': /* Print command line */ { char *optt; int optc; (void) printf("edquota -F UFS"); for (optc = 1; optc < argc; optc++) { optt = argv[optc]; if (optt) (void) printf(" %s ", optt); } (void) putchar('\n'); } break; case '?': usage(); } for (i = optind; i < argc; i++) { uid = getentry(argv[i]); if (uid > MAXUID) continue; getprivs(uid); if (editit()) putprivs(uid); if (uid == 0) { (void) printf("edquota: Note that uid 0's quotas " "are used as default values for other users,\n"); (void) printf("not as a limit on the uid 0 user.\n"); } } (void) unlink(tmpfil); return (0); } static uid_t getentry(char *name) { struct passwd *pw; uid_t uid; if (alldigits(name)) { errno = 0; uid = strtol(name, NULL, 10); if (errno == ERANGE) { /* name would cause overflow in uid */ (void) fprintf(stderr, "edquota: uid %s too large\n", name); (void) sleep(1); return (-1); } } else if (pw = getpwnam(name)) uid = pw->pw_uid; else { (void) fprintf(stderr, "%s: no such user\n", name); (void) sleep(1); return (-1); } return (uid); } #define RESPSZ 128 static int editit(void) { pid_t pid, xpid; char *ed; char resp[RESPSZ]; int status, omask; #define mask(s) (1 << ((s) - 1)) omask = sigblock(mask(SIGINT)|mask(SIGQUIT)|mask(SIGHUP)); if ((ed = getenv("EDITOR")) == (char *)0) ed = DEFEDITOR; /*CONSTANTCONDITION*/ while (1) { if ((pid = fork()) < 0) { if (errno == EAGAIN) { (void) fprintf(stderr, "You have too many processes\n"); return (0); } perror("fork"); return (0); } if (pid == 0) { (void) sigsetmask(omask); (void) setgid(getgid()); (void) setuid(getuid()); (void) execlp(ed, ed, tmpfil, 0); (void) fprintf(stderr, "Can't exec editor \"%s\": ", ed); perror(""); exit(32); } while ((xpid = wait(&status)) >= 0) if (xpid == pid) break; if (!isatty(fileno(stdin))) { /* Non-interactive */ break; } /* * Certain editors can exit with a non-zero status even * though everything is peachy. Best to ask the user what * they really wants to do. (N.B.: if we're non-interactive * we'll "break" the while loop before we get here.) */ if (WIFEXITED(status) && (WEXITSTATUS(status) != 0)) { (void) printf("Non-zero return from \"%s\", ", ed); (void) printf("updated file may contain errors.\n"); /*CONSTANTCONDITION*/ while (1) { (void) printf("Edit again (e) or quit, " "discarding changes (q)? "); (void) fflush(stdout); if (gets(resp) == NULL) { return (0); } if ((*resp == 'e') || (*resp == 'q')) { break; } } if (*resp == 'e') { continue; } else { /* * Since (*resp == 'q'), then we just * want to break out of here and return * the failure. */ break; } } else { break; /* Successful return from editor */ } } (void) sigsetmask(omask); return (!status); } static void getprivs(uid_t uid) { struct fsquot *fsqp; FILE *fd; getdiscq(uid); if ((fd = fopen64(tmpfil, "w")) == NULL) { (void) fprintf(stderr, "edquota: "); perror(tmpfil); (void) unlink(tmpfil); exit(32); } for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) (void) fprintf(fd, "fs %s blocks (soft = %lu, hard = %lu) " "inodes (soft = %lu, hard = %lu)\n", fsqp->fsq_fs, dbtok(fsqp->fsq_dqb.dqb_bsoftlimit), dbtok(fsqp->fsq_dqb.dqb_bhardlimit), fsqp->fsq_dqb.dqb_fsoftlimit, fsqp->fsq_dqb.dqb_fhardlimit); (void) fclose(fd); } static void putprivs(uid_t uid) { FILE *fd; uint64_t tmp_bsoftlimit, tmp_bhardlimit, tmp_fsoftlimit, tmp_fhardlimit; char line[BUFSIZ]; int changed = 0; uint32_t max_limit; int quota_entry_printed; fd = fopen64(tmpfil, "r"); if (fd == NULL) { (void) fprintf(stderr, "Can't re-read temp file!!\n"); return; } while (fgets(line, sizeof (line), fd) != NULL) { struct fsquot *fsqp; char *cp, *dp; int n; cp = next(line, " \t"); if (cp == NULL) break; *cp++ = '\0'; while (*cp && *cp == '\t' && *cp == ' ') cp++; dp = cp, cp = next(cp, " \t"); if (cp == NULL) break; *cp++ = '\0'; for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) { if (strcmp(dp, fsqp->fsq_fs) == 0) break; } if (fsqp == NULL) { (void) fprintf(stderr, "%s: unknown file system\n", cp); continue; } while (*cp && *cp == '\t' && *cp == ' ') cp++; /* * At this point, dp points to the mount point of the * file system and cp points to the remainder of the * quota definition string. */ n = sscanf(cp, "blocks (soft = %llu, hard = %llu) " "inodes (soft = %llu, hard = %llu)\n", &tmp_bsoftlimit, &tmp_bhardlimit, &tmp_fsoftlimit, &tmp_fhardlimit); if (n != 4) { (void) fprintf(stderr, "%s: bad format\n", cp); continue; } /* * The values in dqb_bsoftlimit and dqb_bhardlimit * are specified in 1k blocks in the edited quota * file (the one we're reading), but are specified in * disk blocks in the data structure passed to quotactl(). * That means that the maximum allowed value for the * hard and soft block limits in the edited quota file * is the maximum number of disk blocks allowed in a * quota (which is 2^32 - 1, since it's a 32-bit unsigned * quantity), converted to 1k blocks. */ max_limit = dbtok(UINT_MAX); quota_entry_printed = 0; /* only print quota entry once */ if (tmp_bsoftlimit > max_limit) { tmp_bsoftlimit = max_limit; if (!quota_entry_printed) { (void) fprintf(stderr, "%s %s%\n", dp, cp); quota_entry_printed = 1; } (void) fprintf(stderr, "error: soft limit for blocks exceeds maximum allowed value,\n" " soft limit for blocks set to %lu\n", max_limit); } if (tmp_bhardlimit > max_limit) { tmp_bhardlimit = max_limit; if (!quota_entry_printed) { (void) fprintf(stderr, "%s %s%\n", dp, cp); quota_entry_printed = 1; } (void) fprintf(stderr, "error: hard limit for blocks exceeds maximum allowed value,\n" " hard limit for blocks set to %lu\n", max_limit); } /* * Now check the file limits against their maximum, which * is UINT_MAX (since it must fit in a uint32_t). */ max_limit = UINT_MAX; if (tmp_fsoftlimit > max_limit) { tmp_fsoftlimit = max_limit; if (!quota_entry_printed) { (void) fprintf(stderr, "%s %s%\n", dp, cp); quota_entry_printed = 1; } (void) fprintf(stderr, "error: soft limit for files exceeds maximum allowed value,\n" " soft limit for files set to %lu\n", max_limit); } if (tmp_fhardlimit > max_limit) { tmp_fhardlimit = max_limit; if (!quota_entry_printed) { (void) fprintf(stderr, "%s %s%\n", dp, cp); quota_entry_printed = 1; } (void) fprintf(stderr, "error: hard limit for files exceeds maximum allowed value,\n" " hard limit for files set to %lu\n", max_limit); } changed++; tmp_bsoftlimit = ktodb(tmp_bsoftlimit); tmp_bhardlimit = ktodb(tmp_bhardlimit); /* * It we are decreasing the soft limits, set the time limits * to zero, in case the user is now over quota. * the time limit will be started the next time the * user does an allocation. */ if (tmp_bsoftlimit < fsqp->fsq_dqb.dqb_bsoftlimit) fsqp->fsq_dqb.dqb_btimelimit = 0; if (tmp_fsoftlimit < fsqp->fsq_dqb.dqb_fsoftlimit) fsqp->fsq_dqb.dqb_ftimelimit = 0; fsqp->fsq_dqb.dqb_bsoftlimit = tmp_bsoftlimit; fsqp->fsq_dqb.dqb_bhardlimit = tmp_bhardlimit; fsqp->fsq_dqb.dqb_fsoftlimit = tmp_fsoftlimit; fsqp->fsq_dqb.dqb_fhardlimit = tmp_fhardlimit; } (void) fclose(fd); if (changed) putdiscq(uid); } static void gettimes(uid_t uid) { struct fsquot *fsqp; FILE *fd; char btime[80], ftime[80]; getdiscq(uid); if ((fd = fopen64(tmpfil, "w")) == NULL) { (void) fprintf(stderr, "edquota: "); perror(tmpfil); (void) unlink(tmpfil); exit(32); } for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) { fmttime(btime, fsqp->fsq_dqb.dqb_btimelimit); fmttime(ftime, fsqp->fsq_dqb.dqb_ftimelimit); (void) fprintf(fd, "fs %s blocks time limit = %s, files time limit = %s\n", fsqp->fsq_fs, btime, ftime); } (void) fclose(fd); } static void puttimes(uid_t uid) { FILE *fd; char line[BUFSIZ]; int changed = 0; double btimelimit, ftimelimit; char bunits[80], funits[80]; fd = fopen64(tmpfil, "r"); if (fd == NULL) { (void) fprintf(stderr, "Can't re-read temp file!!\n"); return; } while (fgets(line, sizeof (line), fd) != NULL) { struct fsquot *fsqp; char *cp, *dp; int n; cp = next(line, " \t"); if (cp == NULL) break; *cp++ = '\0'; while (*cp && *cp == '\t' && *cp == ' ') cp++; dp = cp, cp = next(cp, " \t"); if (cp == NULL) break; *cp++ = '\0'; for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) { if (strcmp(dp, fsqp->fsq_fs) == 0) break; } if (fsqp == NULL) { (void) fprintf(stderr, "%s: unknown file system\n", cp); continue; } while (*cp && *cp == '\t' && *cp == ' ') cp++; n = sscanf(cp, "blocks time limit = %lf %[^,], " "files time limit = %lf %s\n", &btimelimit, bunits, &ftimelimit, funits); if (n != 4 || !unfmttime(btimelimit, bunits, &fsqp->fsq_dqb.dqb_btimelimit) || !unfmttime(ftimelimit, funits, &fsqp->fsq_dqb.dqb_ftimelimit)) { (void) fprintf(stderr, "%s: bad format\n", cp); continue; } changed++; } (void) fclose(fd); if (changed) putdiscq(uid); } static char * next(char *cp, char *match) { char *dp; while (cp && *cp) { for (dp = match; dp && *dp; dp++) if (*dp == *cp) return (cp); cp++; } return ((char *)0); } static int alldigits(char *s) { int c = *s++; do { if (!isdigit(c)) return (0); } while ((c = *s++) != '\0'); return (1); } static struct { int c_secs; /* conversion units in secs */ char *c_str; /* unit string */ } cunits [] = { {60*60*24*28, "month"}, {60*60*24*7, "week"}, {60*60*24, "day"}, {60*60, "hour"}, {60, "min"}, {1, "sec"} }; static void fmttime(char *buf, ulong_t time) { double value; int i; if (time == 0) { (void) strcpy(buf, "0 (default)"); return; } for (i = 0; i < sizeof (cunits) / sizeof (cunits[0]); i++) if (time >= cunits[i].c_secs) break; value = (double)time / cunits[i].c_secs; (void) sprintf(buf, "%.2f %s%s", value, cunits[i].c_str, value > 1.0 ? "s" : ""); } static int unfmttime(double value, char *units, uint32_t *timep) { int i; if (value == 0.0) { *timep = 0; return (1); } for (i = 0; i < sizeof (cunits) / sizeof (cunits[0]); i++) { if (strncmp(cunits[i].c_str, units, strlen(cunits[i].c_str)) == 0) break; } if (i >= sizeof (cunits) / sizeof (cunits[0])) return (0); *timep = (ulong_t)(value * cunits[i].c_secs); return (1); } static void setupfs(void) { struct mnttab mntp; struct fsquot *fsqp; struct stat64 statb; dev_t fsdev; FILE *mtab; char qfilename[MAXPATHLEN]; if ((mtab = fopen(MNTTAB, "r")) == (FILE *)0) { perror("/etc/mnttab"); exit(31+1); } while (getmntent(mtab, &mntp) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0) continue; if (stat64(mntp.mnt_special, &statb) < 0) continue; if ((statb.st_mode & S_IFMT) != S_IFBLK) continue; fsdev = statb.st_rdev; (void) snprintf(qfilename, sizeof (qfilename), "%s/%s", mntp.mnt_mountp, QFNAME); if (stat64(qfilename, &statb) < 0 || statb.st_dev != fsdev) continue; fsqp = malloc(sizeof (struct fsquot)); if (fsqp == NULL) { (void) fprintf(stderr, "out of memory\n"); exit(31+1); } fsqp->fsq_next = fsqlist; fsqp->fsq_fs = strdup(mntp.mnt_mountp); fsqp->fsq_dev = strdup(mntp.mnt_special); fsqp->fsq_qfile = strdup(qfilename); if (fsqp->fsq_fs == NULL || fsqp->fsq_dev == NULL || fsqp->fsq_qfile == NULL) { (void) fprintf(stderr, "out of memory\n"); exit(31+1); } fsqlist = fsqp; } (void) fclose(mtab); } static void getdiscq(uid_t uid) { struct fsquot *fsqp; int fd; for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) { if (quotactl(Q_GETQUOTA, fsqp->fsq_dev, uid, (caddr_t)&fsqp->fsq_dqb) != 0) { if ((fd = open64(fsqp->fsq_qfile, O_RDONLY)) < 0) { (void) fprintf(stderr, "edquota: "); perror(fsqp->fsq_qfile); continue; } (void) llseek(fd, (offset_t)dqoff(uid), L_SET); switch (read(fd, (char *)&fsqp->fsq_dqb, sizeof (struct dqblk))) { case 0: /* * Convert implicit 0 quota (EOF) * into an explicit one (zero'ed dqblk) */ bzero((caddr_t)&fsqp->fsq_dqb, sizeof (struct dqblk)); break; case sizeof (struct dqblk): /* OK */ break; default: /* ERROR */ (void) fprintf(stderr, "edquota: read error in "); perror(fsqp->fsq_qfile); break; } (void) close(fd); } } } static void putdiscq(uid_t uid) { struct fsquot *fsqp; for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) { if (quotactl(Q_SETQLIM, fsqp->fsq_dev, uid, (caddr_t)&fsqp->fsq_dqb) != 0) { int fd; if ((fd = open64(fsqp->fsq_qfile, O_RDWR)) < 0) { (void) fprintf(stderr, "edquota: "); perror(fsqp->fsq_qfile); continue; } (void) llseek(fd, (offset_t)dqoff(uid), L_SET); if (write(fd, (char *)&fsqp->fsq_dqb, sizeof (struct dqblk)) != sizeof (struct dqblk)) { (void) fprintf(stderr, "edquota: "); perror(fsqp->fsq_qfile); } (void) close(fd); } } } static void sigsetmask(uint_t omask) { int i; for (i = 0; i < 32; i++) if (omask & (1 << i)) { if (sigignore(1 << i) == (int)SIG_ERR) { (void) fprintf(stderr, "Bad signal 0x%x\n", (1 << i)); exit(31+1); } } } static uint_t sigblock(uint_t omask) { uint_t previous = 0; uint_t temp; int i; for (i = 0; i < 32; i++) if (omask & (1 << i)) { if ((temp = sigignore(1 << i)) == (int)SIG_ERR) { (void) fprintf(stderr, "Bad signal 0x%x\n", (1 << i)); exit(31+1); } if (i == 0) previous = temp; } return (previous); } static void usage(void) { (void) fprintf(stderr, "ufs usage:\n"); (void) fprintf(stderr, "\tedquota [-p username] username ...\n"); (void) fprintf(stderr, "\tedquota -t\n"); exit(1); } static int quotactl(int cmd, char *special, uid_t uid, caddr_t addr) { int fd; int status; struct quotctl quota; char qfile[MAXPATHLEN]; FILE *fstab; struct mnttab mntp; if ((special == NULL) && (cmd == Q_SYNC)) { cmd = Q_ALLSYNC; /* * need to find an acceptable fd to send this Q_ALLSYNC down * on, it needs to be a ufs fd for vfs to at least call the * real quotactl() in the kernel * Here, try to simply find the starting mountpoint of the * first mounted ufs file system */ } /* * Find the mount point of the special device. This is * because the fcntl that implements the quotactl call has * to go to a real file, and not to the block device. */ if ((fstab = fopen(MNTTAB, "r")) == NULL) { (void) fprintf(stderr, "%s: ", MNTTAB); perror("open"); exit(31+1); } qfile[0] = '\0'; while ((status = getmntent(fstab, &mntp)) == 0) { /* * check that it is a ufs file system * for all quotactl()s except Q_ALLSYNC check that * the file system is read-write since changes in the * quotas file may be required * for Q_ALLSYNC, this check is skipped since this option * is to determine if quotas are configured into the system */ if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 || ((cmd != Q_ALLSYNC) && hasmntopt(&mntp, MNTOPT_RO))) continue; if (cmd == Q_ALLSYNC) { /* implies (special==0) too */ if (strlcpy(qfile, mntp.mnt_mountp, sizeof (qfile)) >= sizeof (qfile)) { errno = ENOENT; return (-1); } break; } if (strcmp(special, mntp.mnt_special) == 0) { if (strlcpy(qfile, mntp.mnt_mountp, sizeof (qfile)) >= sizeof (qfile)) { errno = ENOENT; return (-1); } } } (void) fclose(fstab); if (qfile[0] == '\0') { errno = ENOENT; return (-1); } { int open_flags; if (cmd == Q_ALLSYNC) { open_flags = O_RDONLY; } else { if (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof (qfile)) { errno = ENOENT; return (-1); } open_flags = O_RDWR; } if ((fd = open64(qfile, open_flags)) < 0) { (void) fprintf(stderr, "quotactl: "); perror("open"); exit(31+1); } } quota.op = cmd; quota.uid = uid; quota.addr = addr; status = ioctl(fd, Q_QUOTACTL, "a); (void) close(fd); return (status); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1989,1996 by Sun Microsystems, Inc. # All rights reserved. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= ff ATTMK= $(LIBPROG) include ../../Makefile.fstype include ../Makefile.roll OBJS= $(LIBPROG).o $(ROLLOBJS) SRCS= $(LIBPROG).c $(ROLLSRCS) # No msg catalog here. POFILE= CPPFLAGS += -D_LARGEFILE64_SOURCE CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress pointer/int cast warnings in legacy UFS code CERRWARN += -Wno-pointer-to-int-cast # not linted SMATCH=off $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(LIBPROG).o /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * ff -- obtain file names from reading filesystem */ #define NB 500 #define MAXNINDIR (MAXBSIZE / sizeof (daddr32_t)) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "roll_log.h" #define MIN_PHYS_READ BBSIZE #define DAY (24*60*60) union { struct fs sblk; char xxx[SBSIZE]; /* because fs is variable length */ } real_fs; #define sblock real_fs.sblk struct dinode *itab; /* = (struct dinode *)itab; */ struct dinode *gip; struct ilist { ino_t ino; ushort_t mode; uid_t uid; gid_t gid; } ilist[NB]; struct htab { ino_t h_ino; ino_t h_pino; int h_name_index; /* index into string table */ } *htab; char *strngtab; long hsize; int strngloc; int strngtab_size; #define STRNGTAB_INCR (1024*16) /* amount to grow strngtab */ #define MAX_STRNGTAB_INDEX() (strngtab_size - 1) #define AVG_PATH_LEN 30 /* average (?) length of name */ struct dirstuff { int loc; struct dinode *ip; char dbuf[MAXBSIZE]; }; int Aflg = 0; /* accessed in n days */ int Mflg = 0; /* modified in n days */ int Nflg = 0; /* modified more recently than 'file' */ int Cflg = 0; /* changed within n days */ int aflg = 0; /* print the names `.' and `..' */ int sflg = 0; /* print only special files and files with set-user-ID mode */ int Sflg = 0; /* print file size */ int iflg = 0; /* number of inodes being searched for */ int Iflg = 0; /* do not print i-number */ int Lflg = 0; /* supplementary list of multiply linked files */ int mflg = 0; int pflg = 0; /* a prefix exists */ int uflg = 0; /* print the owner's login name */ int fi; ino_t ino; int nhent; int nxfile; int imax; /* highest inode number */ int inode_reads; int passwd_lookups; int Adelay; /* Access delay */ int Asign; /* Access sign */ int Mdelay; /* Modify delay */ int Msign; /* Modify sign */ int Cdelay; /* change delay */ int Csign; /* change sign */ time_t Nage; /* Last modification time of the file */ char *Lname; /* filename for supplementary list */ FILE *Lfile; /* file for supplementary list */ /* * Function prototypes */ void check(char *file); void pass1(struct dinode *ip); void pass2(struct dinode *ip); void pass3(struct dinode *ip); struct direct *dreaddir(struct dirstuff *dirp); int dotname(struct direct *dp); void pname(FILE *stream, ino_t i, int lev); struct htab *lookup(ino_t i, int ef); void bread(diskaddr_t bno, char *buf, int cnt); diskaddr_t bmap(diskaddr_t i); struct dinode *ginode(ino_t inumber); char *user_name(int uid); int cmp(int a, int b, int s); time_t mod_time(char *file); void out_multilinks(); void usage(); int extend_strngtab(unsigned int size); long atol(); offset_t llseek(); char *strcpy(); char *prefix; time_t Today; int nerror; extern int optind; extern char *optarg; char *subopts [] = { #define A_FLAG 0 "a", #define M_FLAG 1 "m", #define S_FLAG 2 "s", NULL }; int main(int argc, char *argv[]) { long n; int opt; char *suboptions, *value; char *p; int first = 0; Today = time((time_t *)0); while ((opt = getopt(argc, argv, "Ia:c:i:lm:n:o:p:su")) != EOF) { switch (opt) { case 'a': Aflg++; Adelay = atoi(optarg); Asign = optarg[0]; break; case 'I': Iflg++; break; case 'c': Cflg++; Cdelay = atoi(optarg); Csign = optarg[0]; break; case 'l': Lflg++; Lname = tmpnam((char *)0); if ((Lfile = fopen(Lname, "w+")) == NULL) { perror("open"); (void) fprintf(stderr, "ff: unable to open temp file, -l ignored\n"); Lflg = 0; } break; case 'm': Mflg++; Mdelay = atoi(optarg); Msign = optarg[0]; break; case 'n': Nflg++; Nage = mod_time(optarg); break; case 'o': /* * ufs specific options. */ suboptions = optarg; if (*suboptions == '\0') usage(); while (*suboptions != '\0') { switch ((getsubopt(&suboptions, subopts, &value))) { case A_FLAG: aflg++; break; case M_FLAG: mflg++; break; case S_FLAG: sflg++; break; default: usage(); } } break; case 'i': while ((p = (char *)strtok(((first++ == 0) ? optarg: ((char *)0)), ", ")) != NULL) { if ((n = atoi(p)) == 0) break; ilist[iflg].ino = n; nxfile = iflg; iflg++; } break; case 'p': prefix = optarg; pflg++; break; case 's': Sflg++; break; case 'u': uflg++; break; case '?': usage(); } } argc -= optind; argv = &argv[optind]; while (argc--) { check(*argv); argv++; } if (Lflg) { out_multilinks(); } if (nerror) return (32); return (0); } void check(char *file) { int i, j, c; fi = open64(file, 0); if (fi < 0) { (void) fprintf(stderr, "ff: cannot open %s\n", file); nerror++; return; } nhent = 0; (void) printf("%s:\n", file); sync(); bread(SBLOCK, (char *)&sblock, SBSIZE); if ((sblock.fs_magic != FS_MAGIC) && (sblock.fs_magic != MTB_UFS_MAGIC)) { (void) fprintf(stderr, "%s: not a ufs file system\n", file); nerror++; return; } if (sblock.fs_magic == FS_MAGIC && (sblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && sblock.fs_version != UFS_VERSION_MIN)) { (void) fprintf(stderr, "%s: unrecognized version of UFS: %d\n", file, sblock.fs_version); nerror++; return; } if (sblock.fs_magic == MTB_UFS_MAGIC && (sblock.fs_version > MTB_UFS_VERSION_1 || sblock.fs_version < MTB_UFS_VERSION_MIN)) { (void) fprintf(stderr, "%s: unrecognized version of UFS: %d\n", file, sblock.fs_version); nerror++; return; } /* If fs is logged, roll the log. */ if (sblock.fs_logbno) { switch (rl_roll_log(file)) { case RL_SUCCESS: /* * Reread the superblock. Rolling the log may have * changed it. */ bread(SBLOCK, (char *)&sblock, SBSIZE); break; case RL_SYSERR: (void) printf("Warning: Cannot roll log for %s. %s\n", file, strerror(errno)); break; default: (void) printf("Warning: Cannot roll log for %s.\n ", file); break; } } itab = (struct dinode *)calloc(sblock.fs_ipg, sizeof (struct dinode)); imax = sblock.fs_ncg * sblock.fs_ipg; hsize = sblock.fs_ipg * sblock.fs_ncg - sblock.fs_cstotal.cs_nifree + 1; htab = (struct htab *)calloc(hsize, sizeof (struct htab)); if (!extend_strngtab(AVG_PATH_LEN * hsize)) { (void) printf("not enough memory to allocate tables\n"); nerror++; return; } strngloc = 0; if ((itab == NULL) || (htab == NULL)) { (void) printf("not enough memory to allocate tables\n"); nerror++; return; } ino = 0; for (c = 0; c < sblock.fs_ncg; c++) { bread(fsbtodb(&sblock, cgimin(&sblock, c)), (char *)itab, (int)(sblock.fs_ipg * sizeof (struct dinode))); for (j = 0; j < sblock.fs_ipg; j++) { if (itab[j].di_smode != 0) { itab[j].di_mode = itab[j].di_smode; if (itab[j].di_suid != (o_uid_t)UID_LONG) itab[j].di_uid = (unsigned int)itab[j].di_suid; if (itab[j].di_sgid != GID_LONG) itab[j].di_gid = (unsigned int)itab[j].di_sgid; pass1(&itab[j]); } ino++; } } ilist[nxfile+1].ino = 0; ino = 0; for (c = 0; c < sblock.fs_ncg; c++) { bread(fsbtodb(&sblock, cgimin(&sblock, c)), (char *)itab, (int)(sblock.fs_ipg * sizeof (struct dinode))); for (j = 0; j < sblock.fs_ipg; j++) { if (itab[j].di_smode != 0) { itab[j].di_mode = itab[j].di_smode; pass2(&itab[j]); } ino++; } } ino = 0; for (c = 0; c < sblock.fs_ncg; c++) { bread(fsbtodb(&sblock, cgimin(&sblock, c)), (char *)itab, (int)(sblock.fs_ipg * sizeof (struct dinode))); for (j = 0; j < sblock.fs_ipg; j++) { if (itab[j].di_smode != 0) { itab[j].di_mode = itab[j].di_smode; pass3(&itab[j]); } ino++; } } (void) close(fi); for (i = iflg; i < NB; i++) ilist[i].ino = 0; nxfile = iflg; free(itab); free(htab); free(strngtab); } void pass1(struct dinode *ip) { int i; if (mflg) for (i = 0; i < iflg; i++) if (ino == ilist[i].ino) { ilist[i].mode = ip->di_mode; ilist[i].uid = ip->di_uid; ilist[i].gid = ip->di_gid; } if ((ip->di_mode & IFMT) != IFDIR) { if (sflg == 0 || nxfile >= NB) return; if ((ip->di_mode&IFMT) == IFBLK || (ip->di_mode&IFMT) == IFCHR || ip->di_mode&(ISUID|ISGID)) { ilist[nxfile].ino = ino; ilist[nxfile].mode = ip->di_mode; ilist[nxfile].uid = ip->di_uid; ilist[nxfile++].gid = ip->di_gid; return; } } (void) lookup(ino, 1); } void pass2(struct dinode *ip) { struct direct *dp; struct dirstuff dirp; struct htab *hp; if ((ip->di_mode&IFMT) != IFDIR) return; dirp.loc = 0; dirp.ip = ip; gip = ip; for (dp = dreaddir(&dirp); dp != NULL; dp = dreaddir(&dirp)) { int nmlen; if (dp->d_ino == 0) continue; hp = lookup(dp->d_ino, 0); if (hp == 0) continue; if (dotname(dp)) continue; hp->h_pino = ino; nmlen = strlen(dp->d_name); if (strngloc + nmlen + 1 > MAX_STRNGTAB_INDEX()) { if (!extend_strngtab(STRNGTAB_INCR)) { perror("ncheck: can't grow string table\n"); exit(32); } } hp->h_name_index = strngloc; (void) strcpy(&strngtab[strngloc], dp->d_name); strngloc += nmlen + 1; } } void pass3(struct dinode *ip) { struct direct *dp; struct dirstuff dirp; struct dinode *dip; int k; if ((ip->di_mode&IFMT) != IFDIR) return; dirp.loc = 0; dirp.ip = ip; gip = ip; for (dp = dreaddir(&dirp); dp != NULL; dp = dreaddir(&dirp)) { if (aflg == 0 && dotname(dp)) continue; if (sflg == 0 && iflg == 0) goto pr; for (k = 0; ilist[k].ino != 0; k++) if (ilist[k].ino == dp->d_ino) break; if (ilist[k].ino == 0) continue; if (mflg) (void) printf("mode %-6o uid %-5ld gid %-5ld ino ", ilist[k].mode, ilist[k].uid, ilist[k].gid); pr: if (Sflg || uflg || Aflg || Mflg || Cflg || Nflg || Lflg) dip = ginode(dp->d_ino); if ((!Aflg || cmp((Today - dip->di_un.di_icom.ic_atime)/DAY, Adelay, Asign)) && (!Mflg || cmp((Today - dip->di_un.di_icom.ic_mtime)/DAY, Mdelay, Msign)) && (!Cflg || cmp((Today - dip->di_un.di_icom.ic_mtime)/DAY, Cdelay, Csign)) && (!Nflg || cmp(dip->di_un.di_icom.ic_mtime, Nage, '+'))) { if (Iflg == 0) (void) printf("%-5u\t", dp->d_ino); pname(stdout, ino, 0); (void) printf("/%s", dp->d_name); if (lookup(dp->d_ino, 0)) (void) printf("/."); if (Sflg) (void) printf("\t%6lld", dip->di_un.di_icom.ic_lsize); if (uflg) (void) printf("\t%s", user_name(dip->di_un.di_icom.ic_uid)); (void) printf("\n"); if (Lflg && (dip->di_un.di_icom.ic_nlink > 1)) { (void) fprintf(Lfile, "%-5u\t", dp->d_ino); (void) fprintf(Lfile, "%-5u\t", dip->di_un.di_icom.ic_nlink); pname(Lfile, ino, 0); (void) fprintf(Lfile, "/%s\n", dp->d_name); } } } } /* * get next entry in a directory. */ struct direct * dreaddir(struct dirstuff *dirp) { struct direct *dp; diskaddr_t lbn, d; for (;;) { if (dirp->loc >= (int)dirp->ip->di_size) return (NULL); if (blkoff(&sblock, dirp->loc) == 0) { lbn = lblkno(&sblock, dirp->loc); d = bmap(lbn); if (d == 0) return (NULL); bread(fsbtodb(&sblock, d), dirp->dbuf, (int)dblksize(&sblock, dirp->ip, (int)lbn)); } dp = (struct direct *) (dirp->dbuf + blkoff(&sblock, dirp->loc)); dirp->loc += dp->d_reclen; if (dp->d_ino == 0) continue; return (dp); } } int dotname(struct direct *dp) { if (dp->d_name[0] == '.') if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0)) return (1); return (0); } void pname(FILE *stream, ino_t i, int lev) { struct htab *hp; if (i == UFSROOTINO) return; if ((hp = lookup(i, 0)) == 0) { (void) fprintf(stream, "???"); return; } if (lev > 10) { (void) fprintf(stream, "..."); return; } pname(stream, hp->h_pino, ++lev); if (pflg) (void) fprintf(stream, "%s/%s", prefix, &(strngtab[hp->h_name_index])); else (void) fprintf(stream, "/%s", &(strngtab[hp->h_name_index])); } struct htab * lookup(ino_t i, int ef) { struct htab *hp; for (hp = &htab[(int)i%hsize]; hp->h_ino; ) { if (hp->h_ino == i) return (hp); if (++hp >= &htab[hsize]) hp = htab; } if (ef == 0) return (0); if (++nhent >= hsize) { (void) fprintf(stderr, "ff: hsize of %ld is too small\n", hsize); exit(32); } hp->h_ino = i; return (hp); } void bread(diskaddr_t bno, char *buf, int cnt) { int i; int got; offset_t offset; offset = (offset_t)bno * DEV_BSIZE; if (llseek(fi, offset, 0) == (offset_t)-1) { (void) fprintf(stderr, "ff: llseek error %lx %lx\n", ((long *)&offset)[0], ((long *)&offset)[1]); for (i = 0; i < cnt; i++) buf[i] = 0; return; } got = read((int)fi, buf, cnt); if (got != cnt) { perror("read"); (void) fprintf(stderr, "ff: (wanted %d got %d blk %lld)\n", cnt, got, bno); for (i = 0; i < cnt; i++) buf[i] = 0; } } diskaddr_t bmap(diskaddr_t i) { daddr32_t ibuf[MAXNINDIR]; if (i < NDADDR) return ((diskaddr_t)gip->di_db[i]); i -= NDADDR; if (i > NINDIR(&sblock)) { (void) fprintf(stderr, "ff : %lu - huge directory\n", ino); return ((diskaddr_t)0); } bread(fsbtodb(&sblock, gip->di_ib[0]), (char *)ibuf, sizeof (ibuf)); return ((diskaddr_t)ibuf[i]); } struct dinode * ginode(ino_t inumber) { diskaddr_t iblk; diskaddr_t dblk; int ioff; static diskaddr_t curr_dblk; static char buf[MIN_PHYS_READ]; struct dinode *ibuf; if (inumber < UFSROOTINO || (int)inumber > imax) { (void) fprintf(stderr, "bad inode number %ld to ginode\n", inumber); exit(32); } iblk = itod(&sblock, (int)inumber); dblk = fsbtodb(&sblock, iblk); ioff = itoo(&sblock, (int)inumber); if (dblk != curr_dblk) { bread(dblk, &buf[0], sizeof (buf)); curr_dblk = dblk; inode_reads++; } ibuf = (struct dinode *)&buf[0]; ibuf += ioff; return (ibuf); } #define HASHNAMESIZE 16 struct name_ent { struct name_ent *name_nxt; int name_uid; char *name_string; }; struct name_ent *hashtable[HASHNAMESIZE]; char * user_name(int uid) { int h_index; struct name_ent *hp; struct passwd *pwent; h_index = uid % HASHNAMESIZE; for (hp = hashtable[h_index]; hp != NULL; hp = hp->name_nxt) { if (hp->name_uid == uid) { return (hp->name_string); } } hp = (struct name_ent *)calloc(1, sizeof (struct name_ent)); hp->name_nxt = hashtable[h_index]; hp->name_uid = uid; hashtable[h_index] = hp; if ((pwent = getpwuid(uid)) == NULL) { hp->name_string = "unknown"; } else { hp->name_string = (char *)strdup(pwent->pw_name); } passwd_lookups++; return (hp->name_string); } int cmp(int a, int b, int s) { if (s == '+') return (a > b); if (s == '-') return (a < -(b)); return (a == b); } /* * We can't do this one by reading the disk directly, since there * is no guarantee that the file is even on a local disk. */ time_t mod_time(char *file) { struct stat64 stat_buf; if (stat64(file, &stat_buf) < 0) { (void) fprintf(stderr, "ff: can't stat '%s' - ignored\n", file); return (0); } return (stat_buf.st_mtime); } void out_multilinks() { int length; if ((length = fseek(Lfile, 0L, 2)) < 0) { perror("fseek"); exit(32); } else if ((length = ftell(Lfile)) > 0) { (void) fprintf(stdout, "\nmultilink files\nIno\tLinks\tPathname\n\n"); rewind(Lfile); while (length-- > 0) (void) putc(getc(Lfile), stdout); } else (void) fprintf(stdout, "No multilink files\n"); (void) fclose(Lfile); } void usage() { (void) fprintf(stderr, "ufs usage: ff [-F ufs] [generic options] [-o a,m,s] special\n"); exit(32); } /* * Extend or create the string table. * Preserves contents. * Return non-zero for success. */ int extend_strngtab(unsigned int size) { strngtab_size += size; strngtab = (char *)realloc(strngtab, strngtab_size); return ((int)strngtab); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= fsck ATTMK= $(LIBPROG) include ../../Makefile.fstype include ../Makefile.roll FSCKOBJS= main.o dir.o dup_avl.o inode.o pass1.o pass1b.o \ pass2.o \ pass3.o pass3b.o pass4.o pass5.o setup.o \ utilities.o FSCKSRCS= $(FSCKOBJS:%.o=%.c) UFSDIR= ../../../../uts/common/fs/ufs UFSOBJS= ufs_subr.o ufs_tables.o UFSSRCS= $(UFSOBJS:%.o=$(UFSDIR)/%.c) ROLLDIR= ../roll_log OBJS= $(FSCKOBJS) $(UFSOBJS) $(ROLLOBJS) $(FSLIB) SRCS= $(FSCKSRCS) $(UFSSRCS) $(ROLLSRCS) $(FSLIBSRC) CPPFLAGS += -D_LARGEFILE64_SOURCE -I../../ -I../../../../lib/libadm/inc LDLIBS += -lefi -lavl CERRWARN += -Wno-parentheses CERRWARN += -Wno-implicit-function-declaration CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress pointer/int cast warnings in legacy code CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast # not linted SMATCH=off $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(CTFMERGE_HOOK) $(POST_PROCESS) %.o: $(UFSDIR)/%.c $(COMPILE.c) $< $(CTFCONVERT_HOOK) clean: $(RM) $(FSCKOBJS) $(UFSOBJS) $(FSLIB) include ../../../../Makefile.xref XREFFLAGS= -f -x /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include "fsck.h" struct rc_queue { struct rc_queue *rc_next; fsck_ino_t rc_orphan; fsck_ino_t rc_parent; caddr_t rc_name; }; caddr_t lfname = "lost+found"; /* name to use for l+f dir */ static int lfmode = 01700; /* mode to use when creating l+f dir */ static struct dirtemplate emptydir = { 0, DIRBLKSIZ }; static struct dirtemplate dirhead = { 0, 12, 1, ".", 0, DIRBLKSIZ - 12, 2, ".." }; static void lftempname(char *, fsck_ino_t); static int do_reconnect(fsck_ino_t, fsck_ino_t, caddr_t); static caddr_t mkuniqname(caddr_t, caddr_t, fsck_ino_t, fsck_ino_t); static int chgino(struct inodesc *); static int dircheck(struct inodesc *, struct direct *); static int expanddir(fsck_ino_t, char *); static void freedir(fsck_ino_t, fsck_ino_t); static struct direct *fsck_readdir(struct inodesc *); static struct bufarea *getdirblk(daddr32_t, size_t); static int mkentry(struct inodesc *); static fsck_ino_t newdir(fsck_ino_t, fsck_ino_t, int, caddr_t); static fsck_ino_t reallocdir(fsck_ino_t, fsck_ino_t, int, caddr_t); /* * Propagate connected state through the tree. */ void propagate(void) { struct inoinfo **inpp, *inp; struct inoinfo **inpend; int change, inorphan; inpend = &inpsort[inplast]; do { change = 0; for (inpp = inpsort; inpp < inpend; inpp++) { inp = *inpp; if (inp->i_parent == 0) continue; if (statemap[inp->i_parent] == DFOUND && INO_IS_DUNFOUND(inp->i_number)) { inorphan = statemap[inp->i_number] & INORPHAN; statemap[inp->i_number] = DFOUND | inorphan; change++; } } } while (change > 0); } /* * Scan each entry in a directory block. */ int dirscan(struct inodesc *idesc) { struct direct *dp; struct bufarea *bp; uint_t dsize, n; size_t blksiz; union { /* keep lint happy about alignment */ char dbuf[DIRBLKSIZ]; struct direct dir; } u; if (idesc->id_type != DATA) errexit("wrong type to dirscan %d\n", idesc->id_type); if (idesc->id_entryno == 0 && (idesc->id_filesize & (DIRBLKSIZ - 1)) != 0) idesc->id_filesize = roundup(idesc->id_filesize, DIRBLKSIZ); blksiz = idesc->id_numfrags * sblock.fs_fsize; if (chkrange(idesc->id_blkno, idesc->id_numfrags)) { idesc->id_filesize -= (offset_t)blksiz; return (SKIP); } idesc->id_loc = 0; for (dp = fsck_readdir(idesc); dp != NULL; dp = fsck_readdir(idesc)) { /* * If we were just passed a corrupt directory entry with * d_reclen > DIRBLKSIZ, we don't want to memmove() all over * our stack. This directory gets cleaned up later. */ dsize = MIN(dp->d_reclen, sizeof (u.dbuf)); (void) memmove((void *)u.dbuf, (void *)dp, (size_t)dsize); idesc->id_dirp = &u.dir; if ((n = (*idesc->id_func)(idesc)) & ALTERED) { /* * We can ignore errors from getdirblk() here, * as the block is still in memory thanks to * buffering and fsck_readdir(). If there was * an error reading it before, then all decisions * leading to getting us here were based on the * resulting zeros. As such, we have nothing * to worry about at this point. */ bp = getdirblk(idesc->id_blkno, blksiz); (void) memmove((void *)(bp->b_un.b_buf + idesc->id_loc - dsize), (void *)u.dbuf, (size_t)dsize); dirty(bp); sbdirty(); } if (n & STOP) return (n); } return (idesc->id_filesize > 0 ? KEEPON : STOP); } /* * Get current entry in a directory (and peek at the next entry). */ static struct direct * fsck_readdir(struct inodesc *idesc) { struct direct *dp, *ndp = 0; struct bufarea *bp; ushort_t size; /* of directory entry */ size_t blksiz; int dofixret; int salvaged; /* when to report SALVAGED in preen mode */ int origloc = idesc->id_loc; blksiz = idesc->id_numfrags * sblock.fs_fsize; /* * Sanity check id_filesize and id_loc fields. The latter * has to be within the block we're looking at, as well as * aligned to a four-byte boundary. The alignment is due to * a struct direct containing four-byte integers. It's * unfortunate that the four is a magic number, but there's * really no good way to derive it from the ufs header files. */ if ((idesc->id_filesize <= 0) || (idesc->id_loc >= blksiz) || ((idesc->id_loc & 3) != 0)) return (NULL); /* * We don't have to worry about holes in the directory's * block list, because that was checked for when the * inode was first encountered during pass1. We never * scan a directory until after we've vetted its block list. */ /* * We can ignore errors from getdirblk() here, as dircheck() * will reject any entries that would have been in the bad * sectors (fsck_bread() fills in zeros on failures). The main * reject keys are that d_reclen would be zero and/or that it * is less than the minimal size of a directory entry. Since * entries can't span sectors, there's no worry about having * a good beginning in one sector and the rest in the next, * where that second sector was unreadable and therefore * replaced with zeros. */ bp = getdirblk(idesc->id_blkno, blksiz); /* LINTED b_buf is aligned and id_loc was verified above */ dp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc); /* * Check the current entry in the directory. */ if (dircheck(idesc, dp) == 0) { /* * If we are in here, then either the current directory * entry is bad or the next directory entry is bad. */ next_is_bad: /* * Find the amount of space left to the end of the * directory block for either directory entry. */ size = DIRBLKSIZ - (idesc->id_loc & (DIRBLKSIZ - 1)); /* * Advance to the end of the directory block. */ idesc->id_loc += size; idesc->id_filesize -= (offset_t)size; /* * Ask the question before we fix the in-core directory * block because dofix() may reuse the buffer. */ salvaged = (idesc->id_fix == DONTKNOW); dofixret = dofix(idesc, "DIRECTORY CORRUPTED"); /* * If there was an error reading the block, then that * same error can reasonably be expected to have occurred * when it was read previously. As such, the decision * to come here was based on the results of that partially- * zerod block, and so anything we change should be * based on it as well. Upshot: no need to check for * errors here. */ bp = getdirblk(idesc->id_blkno, blksiz); /* LINTED b_buf is aligned and id_loc/origloc was verified */ dp = (struct direct *)(bp->b_un.b_buf + origloc); /* * This is the current directory entry and since it is * corrupt we cannot trust the rest of the directory * block so change the current directory entry to * contain nothing and encompass the rest of the block. */ if (ndp == NULL) { dp->d_reclen = size; dp->d_ino = 0; dp->d_namlen = 0; dp->d_name[0] = '\0'; } /* * This is the next directory entry, i.e., we got here * via a "goto next_is_bad". That directory entry is * corrupt. However, the current directory entry is okay * so if we are in fix mode, just extend its record size * to encompass the rest of the block. */ else if (dofixret) { dp->d_reclen += size; } /* * If the user said to fix the directory corruption, then * mark the block as dirty. Otherwise, our "repairs" only * apply to the in-core copy so we don't hand back trash * to the caller. * * Note: It is possible that saying "no" to a change in * one part of the I/O buffer and "yes" to a later change * in the same I/O buffer may still flush the change to * which we said "no". This is the pathological case and * no fix is planned at this time. */ if (dofixret) { dirty(bp); if (preen && salvaged) (void) printf(" (SALVAGED)\n"); if (idesc->id_number == lfdir) lfdir = 0; } /* * dp points into bp, which will get re-used at some * arbitrary time in the future. We rely on the fact * that we're singled-threaded, and that we'll be done * with this directory entry by the time the next one * is needed. */ return (dp); } /* * The current directory entry checked out so advance past it. */ idesc->id_loc += dp->d_reclen; idesc->id_filesize -= (offset_t)dp->d_reclen; /* * If we are not at the directory block boundary, then peek * at the next directory entry and if it is bad we can add * its space to the current directory entry (compression). * Again, we sanity check the id_loc and id_filesize fields * since we modified them above. */ if ((idesc->id_loc & (DIRBLKSIZ - 1)) && /* not at start */ (idesc->id_loc < blksiz) && /* within block */ ((idesc->id_loc & 3) == 0) && /* properly aligned */ (idesc->id_filesize > 0)) { /* data follows */ /* LINTED b_buf is aligned and id_loc verified to be ok */ ndp = (struct direct *)(bp->b_un.b_buf + idesc->id_loc); if (dircheck(idesc, ndp) == 0) goto next_is_bad; } /* * See comment above about dp pointing into bp. */ return (dp); } /* * Verify that a directory entry is valid. * This is a superset of the checks made in the kernel. */ static int dircheck(struct inodesc *idesc, struct direct *dp) { size_t size; char *cp; int spaceleft; /* * Recall that id_filesize is the number of bytes left to * process in the directory. We check id_filesize >= size * instead of id_filesize >= d_reclen because all that the * directory is actually required to contain is the entry * itself (and it's how the kernel does the allocation). * * We indirectly check for d_reclen going past the end of * the allocated space by comparing it against spaceleft. */ size = DIRSIZ(dp); spaceleft = DIRBLKSIZ - (idesc->id_loc % DIRBLKSIZ); if (dp->d_ino < maxino && dp->d_reclen != 0 && (int)dp->d_reclen <= spaceleft && (dp->d_reclen & 0x3) == 0 && (int)dp->d_reclen >= size && idesc->id_filesize >= (offset_t)size && dp->d_namlen <= MAXNAMLEN) { if (dp->d_ino == 0) return (1); for (cp = dp->d_name, size = 0; size < (size_t)dp->d_namlen; size++, cp++) if ((*cp == '\0') || (*cp == '/')) goto bad; if (*cp == '\0') return (1); } bad: if (debug) { (void) printf("Bad dir in inode %d at lbn %d, loc %d:\n", idesc->id_number, idesc->id_lbn, idesc->id_loc); (void) printf(" ino %d reclen %d namlen %d name `%s'\n", dp->d_ino, dp->d_reclen, dp->d_namlen, dp->d_name); } return (0); } void adjust(struct inodesc *idesc, int lcnt) { struct dinode *dp; caddr_t flow; int saveiscorrupt; struct inodesc lcidesc; dp = ginode(idesc->id_number); if (dp->di_nlink == lcnt) { /* * If we have not hit any unresolved problems, are running * in preen mode, and are on a file system using logging, * then just toss any partially allocated files, as they are * an expected occurrence. */ if (!iscorrupt && preen && islog) { clri(idesc, "UNREF", CLRI_VERBOSE, CLRI_NOP_OK); return; } else { /* * The file system can be considered clean even if * a file is not linked up, but is cleared. In * other words, the kernel won't panic over it. * Hence, iscorrupt should not be set when * linkup is answered no, but clri is answered yes. * * If neither is answered yes, then we have a * non-panic-inducing known corruption that the * user needs to be reminded of when we exit. */ saveiscorrupt = iscorrupt; if (linkup(idesc->id_number, (fsck_ino_t)0, NULL) == 0) { iscorrupt = saveiscorrupt; clri(idesc, "UNREF", CLRI_QUIET, CLRI_NOP_OK); if (statemap[idesc->id_number] != USTATE) iscorrupt = 1; return; } dp = ginode(idesc->id_number); } lcnt = lncntp[idesc->id_number]; } /* * It doesn't happen often, but it's possible to get a true * excess of links (especially if a lot of directories got * orphaned and reattached to lost+found). Instead of wrapping * around, do something semi-useful (i.e., give progress towards * a less-broken filesystem) when this happens. */ LINK_RANGE(flow, dp->di_nlink, -lcnt); if (flow != NULL) { LINK_CLEAR(flow, idesc->id_number, dp->di_mode, &lcidesc); if (statemap[idesc->id_number] == USTATE) return; } dp = ginode(idesc->id_number); if (lcnt && dp->di_nlink != lcnt) { pwarn("LINK COUNT %s", file_id(idesc->id_number, dp->di_mode)); pinode(idesc->id_number); dp = ginode(idesc->id_number); (void) printf(" COUNT %d SHOULD BE %d", dp->di_nlink, dp->di_nlink - lcnt); /* * Even lost+found is subject to this, as whenever * we modify it, we update both the in-memory and * on-disk counts. Thus, they should still be in * sync. */ if (preen) { if (lcnt < 0) { (void) printf("\n"); if ((dp->di_mode & IFMT) == IFSHAD) pwarn("LINK COUNT INCREASING"); else pfatal("LINK COUNT INCREASING"); } } if (preen || reply("ADJUST") == 1) { dp->di_nlink -= lcnt; inodirty(); if (preen) (void) printf(" (ADJUSTED)\n"); } else if (((dp->di_mode & IFMT) == IFDIR) || ((dp->di_mode & IFMT) == IFATTRDIR)) { /* * File counts can be off relatively harmlessly, * but a bad directory count can cause the * kernel to lose its mind. */ iscorrupt = 1; } } } static int mkentry(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; struct direct newent; int newlen, oldlen; newent.d_namlen = strlen(idesc->id_name); newlen = DIRSIZ(&newent); if (dirp->d_ino != 0) oldlen = DIRSIZ(dirp); else oldlen = 0; if ((int)dirp->d_reclen - oldlen < newlen) return (KEEPON); newent.d_reclen = dirp->d_reclen - (ushort_t)oldlen; dirp->d_reclen = (ushort_t)oldlen; /* LINTED dirp is aligned and DIRSIZ() forces oldlen to be aligned */ dirp = (struct direct *)(((char *)dirp) + oldlen); dirp->d_ino = idesc->id_parent; /* ino to be entered is in id_parent */ dirp->d_reclen = newent.d_reclen; dirp->d_namlen = newent.d_namlen; (void) memmove(dirp->d_name, idesc->id_name, (size_t)newent.d_namlen + 1); return (ALTERED|STOP); } static int chgino(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; if (memcmp(dirp->d_name, idesc->id_name, (size_t)dirp->d_namlen + 1) != 0) return (KEEPON); dirp->d_ino = idesc->id_parent; return (ALTERED|STOP); } int linkup(fsck_ino_t orphan, fsck_ino_t parentdir, caddr_t name) { int rval; struct dinode *dp; int lostdir; int lostshadow; fsck_ino_t oldlfdir; fsck_ino_t *intree; struct inodesc idesc; init_inodesc(&idesc); dp = ginode(orphan); lostdir = (((dp->di_mode & IFMT) == IFDIR) || ((dp->di_mode & IFMT) == IFATTRDIR)); if (debug && lostdir && dp->di_nlink <= 0 && lncntp[orphan] == -1) (void) printf( "old fsck would have left inode %d for reclaim thread\n", orphan); lostshadow = (dp->di_mode & IFMT) == IFSHAD; pwarn("UNREF %s ", file_id(orphan, dp->di_mode)); pinode(orphan); if (lostshadow || (dp->di_size == 0 && dp->di_oeftflag == 0)) return (0); if (!preen && (reply("RECONNECT") == 0)) goto noconnect; if (lfdir == 0) { dp = ginode(UFSROOTINO); idesc.id_name = lfname; idesc.id_type = DATA; idesc.id_func = findino; idesc.id_number = UFSROOTINO; idesc.id_fix = DONTKNOW; if ((ckinode(dp, &idesc, CKI_TRAVERSE) & FOUND) != 0) { lfdir = idesc.id_parent; } else { pwarn("NO %s DIRECTORY", lfname); if (preen || reply("CREATE") == 1) { lfdir = newdir(UFSROOTINO, (fsck_ino_t)0, lfmode, lfname); if (lfdir != 0) { if (preen) (void) printf(" (CREATED)\n"); else (void) printf("\n"); statemap[lfdir] |= INFOUND; /* * XXX What if we allocate an inode * that's already been scanned? Then * we need to leave lnctnp[] alone. */ TRACK_LNCNTP(UFSROOTINO, lncntp[UFSROOTINO]++); } } } if (lfdir == 0) { pfatal("SORRY. CANNOT CREATE %s DIRECTORY\n", lfname); pwarn("Could not reconnect inode %d\n", orphan); goto noconnect; } else { /* * We searched for it via the namespace, so by * definition it's been found. We have to do this * because it is possible that we're called before * the full namespace mapping is complete (especially * from pass 1, if it encounters a corrupt directory * that has to be cleared). */ statemap[lfdir] |= INFOUND; } } dp = ginode(lfdir); if ((dp->di_mode & IFMT) != IFDIR) { pfatal("%s IS NOT A DIRECTORY", lfname); if (reply("REALLOCATE") == 0) { iscorrupt = 1; goto noconnect; } oldlfdir = lfdir; lfdir = reallocdir(UFSROOTINO, (fsck_ino_t)0, lfmode, lfname); if (lfdir == 0) { iscorrupt = 1; pfatal("SORRY. CANNOT CREATE %s DIRECTORY\n\n", lfname); goto noconnect; } inodirty(); statemap[lfdir] |= INFOUND; freeino(oldlfdir, TI_PARENT); } if (statemap[lfdir] != DFOUND) { /* * Not a consistency problem of the sort that'll * cause the kernel heartburn, so don't set iscorrupt. */ if (debug) (void) printf("lfdir %d is in state 0x%x\n", lfdir, (int)statemap[lfdir]); lfdir = 0; pfatal("SORRY. %s DIRECTORY DISAPPEARED\n\n", lfname); pwarn("Could not reconnect inode %d\n", orphan); goto noconnect; } rval = do_reconnect(orphan, parentdir, name); return (rval); /* * Leaving things unconnected is harmless as far as trying to * use the filesystem later, so don't set iscorrupt yet (it's * just lost blocks and inodes, after all). * * Lost directories get noted for reporting after all checks * are done - they may get cleared later. */ noconnect: if (lostdir) { intree = tsearch((void *)orphan, &limbo_dirs, ino_t_cmp); if (intree == NULL) errexit("linkup: out of memory"); } return (0); } /* * Connect an orphaned inode to lost+found. * * Returns non-zero for success, zero for failure. */ static int do_reconnect(fsck_ino_t orphan, fsck_ino_t parentdir, caddr_t name) { caddr_t flow_msg; struct dinode *dp; int lostdir; mode_t mode; fsck_ino_t *intree; struct inodesc idesc; dp = ginode(orphan); mode = dp->di_mode & IFMT; lostdir = (mode == IFDIR) || (mode == IFATTRDIR); name = mkuniqname(name, lfname, lfdir, orphan); if (name == NULL) goto noconnect; if (makeentry(lfdir, orphan, name) == 0) { pfatal("SORRY. NO SPACE IN %s DIRECTORY\n", lfname); pwarn("Could not reconnect inode %d\n", orphan); goto noconnect; } dp = ginode(orphan); LINK_RANGE(flow_msg, lncntp[orphan], -1); if (flow_msg != NULL) { LINK_CLEAR(flow_msg, orphan, dp->di_mode, &idesc); if (statemap[orphan] == USTATE) goto noconnect; } TRACK_LNCNTP(orphan, lncntp[orphan]--); /* * Make sure that anything we put into the normal namespace * looks like it belongs there. Attributes can only be in * attribute directories, not the normal directory lost+found. */ maybe_convert_attrdir_to_dir(orphan); if (lostdir) { /* * Can't be creating a duplicate entry with makeentry(), * because changeino() will succeed if ".." already * exists. */ if ((changeino(orphan, "..", lfdir) & ALTERED) == 0 && parentdir != (fsck_ino_t)-1) (void) makeentry(orphan, lfdir, ".."); /* * If we were half-detached, don't try to get * inode 0 later on. */ if (parentdir == 0) parentdir = -1; /* * Fix up link counts. * * XXX This section is getting pretty byzantine, espcially * when combined with changeino()/chgino()'s link manipulation. */ LFDIR_LINK_RANGE_RVAL(flow_msg, lncntp[lfdir], 1, &idesc, 0); TRACK_LNCNTP(lfdir, lncntp[lfdir]--); pwarn("DIR I=%lu CONNECTED. ", (long)orphan); reattached_dir = 1; if (parentdir != (fsck_ino_t)-1) { /* * Have to clear the parent's reference. Otherwise, * if it's an orphan, then we may clear this orphan * in pass 4 even though we've reconnected it. * * We already have the reference count * allowing for a parent link, so undo the * adjustment done above. Otherwise we come * out high by one. */ (void) printf("PARENT WAS I=%lu\n", (long)parentdir); (void) cleardirentry(parentdir, orphan); } if (!preen) (void) printf("\n"); } else if (preen) { (void) printf(" (RECONNECTED)\n"); } statemap[orphan] &= ~INDELAYD; return (1); /* * Leaving things unconnected is harmless as far as trying to * use the filesystem later, so don't set iscorrupt yet (it's * just lost blocks and inodes, after all). * * Lost directories get noted for reporting after all checks * are done - they may get cleared later. */ noconnect: if (lostdir) { intree = tsearch((void *)orphan, &limbo_dirs, ino_t_cmp); if (intree == NULL) errexit("linkup: out of memory"); } return (0); } /* * fix an entry in a directory. */ int changeino(fsck_ino_t dir, char *name, fsck_ino_t newnum) { struct inodesc idesc; init_inodesc(&idesc); idesc.id_type = DATA; idesc.id_func = chgino; idesc.id_number = dir; idesc.id_fix = DONTKNOW; idesc.id_name = name; idesc.id_parent = newnum; /* new value for name */ return (ckinode(ginode(dir), &idesc, CKI_TRAVERSE)); } /* * make an entry in a directory */ int makeentry(fsck_ino_t parent, fsck_ino_t ino, char *name) { int repeat; struct dinode *dp; struct inoinfo *iip; struct inodesc idesc; char pathbuf[MAXPATHLEN + 1]; if (parent < UFSROOTINO || parent >= maxino || ino < UFSROOTINO || ino >= maxino) return (0); init_inodesc(&idesc); idesc.id_type = DATA; idesc.id_func = mkentry; idesc.id_number = parent; idesc.id_parent = ino; /* this is the inode to enter */ idesc.id_fix = DONTKNOW; idesc.id_name = name; repeat = 0; again: dp = ginode(parent); if ((dp->di_size % DIRBLKSIZ) != 0) { dp->di_size = roundup(dp->di_size, DIRBLKSIZ); inodirty(); iip = getinoinfo(ino); if (iip != NULL) iip->i_isize = dp->di_size; } if ((ckinode(dp, &idesc, CKI_TRAVERSE) & ALTERED) != 0) { iip = getinoinfo(ino); if (iip != NULL) iip->i_isize = dp->di_size; return (1); } if (repeat == 0) { getpathname(pathbuf, parent, parent); if (expanddir(parent, pathbuf) == 0) return (0); repeat = 1; goto again; } return (0); } /* * Attempt to expand the size of a directory */ static int expanddir(fsck_ino_t ino, char *name) { struct bufarea *bpback, *bp[2]; daddr32_t nxtibn, nxtbn; daddr32_t newblk[2]; struct dinode *dp; char *cp; int bc, f; int n; int allocIndir; int frag2blks; int lffragsz = 0; int c = 0; int retval = 0; bp[0] = bp[1] = NULL; dp = ginode(ino); if (dp->di_size == 0) { goto bail; } nxtbn = lblkno(&sblock, dp->di_size - 1) + 1; /* * Check that none of the nominally in-use direct block * addresses for the directory are bogus. */ for (bc = 0; ((nxtbn > 0) && (bc < nxtbn) && (bc < NDADDR)); bc++) { if (dp->di_db[bc] == 0) { goto bail; } } /* * Determine our data block allocation needs. We always need to * allocate at least one data block. We may need a second, the * indirect block itself. */ allocIndir = 0; nxtibn = -1; n = 0; if (nxtbn <= NDADDR) { /* * Still in direct blocks. Check for the unlikely * case where the last block is a frag rather than * a full block. This would only happen if someone had * created a file in lost+found, and then that caused * the dynamic directory shrinking capabilities of ufs * to kick in. * * Note that we test nxtbn <= NDADDR, as it's the * next block (i.e., one greater than the current/ * actual block being examined). */ lffragsz = dp->di_size % sblock.fs_bsize; } if (nxtbn >= NDADDR && !lffragsz) { n = sblock.fs_bsize / sizeof (daddr32_t); nxtibn = nxtbn - NDADDR; /* * Only go one level of indirection */ if (nxtibn >= n) { goto bail; } /* * First indirect block means we need to pick up * the actual indirect pointer block as well. */ if (nxtibn == 0) allocIndir++; } /* * Allocate all the new blocks we need. */ if ((newblk[0] = allocblk(sblock.fs_frag)) == 0) { goto bail; } c++; if (allocIndir) { if ((newblk[1] = allocblk(sblock.fs_frag)) == 0) { goto bail; } c++; } /* * Take care of the block that will hold new directory entries. * This one is always allocated. */ bp[0] = getdirblk(newblk[0], (size_t)sblock.fs_bsize); if (bp[0]->b_errs) { goto bail; } if (lffragsz) { /* * Preserve the partially-populated existing directory. */ bpback = getdirblk(dp->di_db[nxtbn - 1], (size_t)dblksize(&sblock, dp, nxtbn - 1)); if (!bpback->b_errs) { (void) memmove(bp[0]->b_un.b_buf, bpback->b_un.b_buf, (size_t)lffragsz); } } /* * Initialize the new fragments. lffragsz is zero if this * is a completely-new block. */ for (cp = &(bp[0]->b_un.b_buf[lffragsz]); cp < &(bp[0]->b_un.b_buf[sblock.fs_bsize]); cp += DIRBLKSIZ) { (void) memmove(cp, (char *)&emptydir, sizeof (emptydir)); } dirty(bp[0]); /* * If we allocated the indirect block, zero it out. Otherwise * read it in if we're using one. */ if (allocIndir) { bp[1] = getdatablk(newblk[1], (size_t)sblock.fs_bsize); if (bp[1]->b_errs) { goto bail; } (void) memset(bp[1]->b_un.b_buf, 0, sblock.fs_bsize); dirty(bp[1]); } else if (nxtibn >= 0) { /* Check that the indirect block pointer looks okay */ if (dp->di_ib[0] == 0) { goto bail; } bp[1] = getdatablk(dp->di_ib[0], (size_t)sblock.fs_bsize); if (bp[1]->b_errs) { goto bail; } for (bc = 0; ((bc < nxtibn) && (bc < n)); bc++) { /* LINTED pointer cast alignment */ if (((daddr32_t *)bp[1]->b_un.b_buf)[bc] == 0) { goto bail; } } } /* * Since the filesystem's consistency isn't affected by * whether or not we actually do the expansion, iscorrupt * is left alone for any of the approval paths. */ pwarn("NO SPACE LEFT IN %s", name); if (!preen && (reply("EXPAND") == 0)) goto bail; /* * Now that everything we need is gathered up and the * necessary approvals acquired, we can make our provisional * changes permanent. */ if (lffragsz) { /* * We've saved the data from the old end fragment(s) in * our new block, so we can just swap the new one in. * Make sure the size reflects the expansion of the * final fragments/block. */ frag2blks = roundup(lffragsz, sblock.fs_fsize); freeblk(ino, dp->di_db[nxtbn - 1], frag2blks / sblock.fs_fsize); frag2blks = btodb(frag2blks); dp->di_size -= (u_offset_t)lffragsz; dp->di_blocks = dp->di_blocks - frag2blks; dp->di_db[nxtbn - 1] = newblk[0]; dp->di_size += (u_offset_t)sblock.fs_bsize; dp->di_blocks += btodb(sblock.fs_bsize); inodirty(); retval = 1; goto done; } /* * Full-block addition's much easier. It's just an append. */ dp->di_size += (u_offset_t)sblock.fs_bsize; dp->di_blocks += btodb(sblock.fs_bsize); if (allocIndir) { dp->di_blocks += btodb(sblock.fs_bsize); } inodirty(); if (nxtibn < 0) { /* * Still in direct blocks */ dp->di_db[nxtbn] = newblk[0]; } else { /* * Last indirect is always going to point at the * new directory buffer */ if (allocIndir) dp->di_ib[0] = newblk[1]; /* LINTED pointer case alignment */ ((daddr32_t *)bp[1]->b_un.b_buf)[nxtibn] = newblk[0]; dirty(bp[1]); } if (preen) (void) printf(" (EXPANDED)\n"); retval = 1; goto done; bail: for (f = 0; f < c; f++) freeblk(ino, newblk[f], sblock.fs_frag); done: /* * bp[0] is handled by the directory cache's auto-release. */ if (bp[1] != NULL) brelse(bp[1]); return (retval); } static fsck_ino_t newdir(fsck_ino_t parent, fsck_ino_t request, int mode, caddr_t name) { fsck_ino_t dino; char pname[BUFSIZ]; /* * This function creates a new directory and populates it with * "." and "..", then links to it as NAME in PARENT. */ dino = allocdir(parent, request, mode, 1); if (dino != 0) { getpathname(pname, parent, parent); name = mkuniqname(name, pname, parent, dino); /* * We don't touch numdirs, because it's just a cache of * what the filesystem claimed originally and is used * to calculate hash keys. */ if (makeentry(parent, dino, name) == 0) { freedir(dino, parent); dino = 0; } } return (dino); } /* * Replace whatever NAME refers to in PARENT with a new directory. * Note that if the old inode REQUEST is a directory, all of its * contents will be freed and reaped. */ static fsck_ino_t reallocdir(fsck_ino_t parent, fsck_ino_t request, int mode, caddr_t name) { int retval; fsck_ino_t newino; if ((request != 0) && (statemap[request] != USTATE)) freeino(request, TI_PARENT); newino = allocdir(parent, request, mode, 0); if (newino != 0) { retval = changeino(parent, name, newino); if ((retval & ALTERED) == 0) { /* * No change made, so name doesn't exist, so * unwind allocation rather than leak it. */ freedir(newino, parent); newino = 0; } } return (newino); } /* * allocate a new directory */ fsck_ino_t allocdir(fsck_ino_t parent, fsck_ino_t request, int mode, int update_parent) { fsck_ino_t ino; caddr_t cp; caddr_t flow; struct dinode *dp; struct bufarea *bp; struct inoinfo *inp; struct inodesc idesc; struct dirtemplate *dirp; ino = allocino(request, IFDIR|mode); if (ino == 0) return (0); dirp = &dirhead; dirp->dot_ino = ino; dirp->dotdot_ino = parent; dp = ginode(ino); bp = getdirblk(dp->di_db[0], (size_t)sblock.fs_fsize); if (bp->b_errs) { freeino(ino, TI_PARENT); return (0); } (void) memmove(bp->b_un.b_buf, (void *)dirp, sizeof (struct dirtemplate)); for (cp = &bp->b_un.b_buf[DIRBLKSIZ]; cp < &bp->b_un.b_buf[sblock.fs_fsize]; cp += DIRBLKSIZ) (void) memmove(cp, (void *)&emptydir, sizeof (emptydir)); dirty(bp); dp->di_nlink = 2; inodirty(); if (!inocached(ino)) { cacheino(dp, ino); } else { /* * re-using an old directory inode */ inp = getinoinfo(ino); if (inp == NULL) { if (debug) errexit("allocdir got NULL from getinoinfo " "for existing entry I=%d\n", ino); cacheino(dp, ino); } else { init_inoinfo(inp, dp, ino); inp->i_parent = parent; inp->i_dotdot = parent; } } /* * Short-circuit all the dancing around below if it's the * root inode. The net effect's the same. */ if (ino == UFSROOTINO) { TRACK_LNCNTP(ino, lncntp[ino] = dp->di_nlink); return (ino); } if (!update_parent) return (ino); /* * We never create attribute directories, which can have * non-directory parents. So, the parent of the directory * we're creating must itself be a directory. */ if (!INO_IS_DVALID(parent)) { freeino(ino, TI_PARENT); return (0); } /* * Make sure the parent can handle another link. * Since we might only update one version of the * count (disk versus in-memory), we have to check both. */ LINK_RANGE(flow, lncntp[parent], -1); if (flow == NULL) LINK_RANGE(flow, (int)dp->di_nlink, 1); if (flow != NULL) { LINK_CLEAR(flow, parent, dp->di_mode, &idesc); if (statemap[parent] == USTATE) { /* * No parent any more, so bail out. Callers * are expected to handle this possibility. * Since most just throw up their hands if * we return 0, this just happens to work. */ freeino(ino, TI_PARENT); return (0); } } /* * We've created a directory with two entries, "." and "..", * and a link count of two ("." and one from its parent). If * the parent's not been scanned yet, which means this inode * will get scanned later as well, then make our in-core count * match what we pushed out to disk. Similarly, update the * parent. On the other hand, if the parent's already been * looked at (statemap[ino] == DFOUND), the discrepancy * between lncntp[] and di_nlink will be noted later, with * appropriate reporting and propagation, in pass2. * * We're explicitly skipping where the parent was DZLINK or * DFOUND. If it has zero links, it can't be gotten to, so * we want a discrepancy set up that will be caught in pass2. * DFOUND was discussed above. * * Regarding the claim of a link from the parent: we've not * done anything to create such a link here. We depend on the * semantics of our callers attaching the inode we return to * an existing entry in the directory or creating the entry * themselves, but in either case, not modifying the link * count. * * Note that setting lncntp[ino] to zero means that both claimed * links have been ``found''. */ statemap[ino] = statemap[parent]; if (INO_IS_DVALID(parent)) { TRACK_LNCNTP(ino, lncntp[ino] = 0); TRACK_LNCNTP(parent, lncntp[parent]--); } dp = ginode(parent); dp->di_nlink++; inodirty(); return (ino); } /* * free a directory inode */ static void freedir(fsck_ino_t ino, fsck_ino_t parent) { struct inoinfo *iip; if (ino != parent) { /* * Make sure that the desired parent gets a link * count update from freeino()/truncino(). If * we can't look it up, then it's not really a * directory, so there's nothing to worry about. */ iip = getinoinfo(ino); if (iip != NULL) iip->i_parent = parent; } freeino(ino, TI_PARENT); } /* * generate a temporary name for use in the lost+found directory. */ static void lftempname(char *bufp, fsck_ino_t ino) { fsck_ino_t in; caddr_t cp; int namlen; cp = bufp + 2; for (in = maxino; in > 0; in /= 10) cp++; *--cp = '\0'; /* LINTED difference will not overflow an int */ namlen = cp - bufp; if ((namlen > BUFSIZ) || (namlen > MAXPATHLEN)) { errexit("buffer overflow in lftempname()\n"); } in = ino; while (cp > bufp) { *--cp = (in % 10) + '0'; in /= 10; } *cp = '#'; } /* * Get a directory block. * Insure that it is held until another is requested. * * Our callers are expected to check for errors and/or be * prepared to handle blocks of zeros in the middle of a * directory. */ static struct bufarea * getdirblk(daddr32_t blkno, size_t size) { if (pdirbp != 0) { brelse(pdirbp); } pdirbp = getdatablk(blkno, size); return (pdirbp); } /* * Create a unique name for INODE to be created in directory PARENT. * Use NAME if it is provided (non-NULL) and doesn't already exist. * Returning NULL indicates no unique name could be generated. * * If we were given a name, and it conflicts with an existing * entry, use our usual temp name instead. Without this check, * we could end up creating duplicate entries for multiple * orphaned directories in lost+found with the same name (but * different parents). Of course, our usual name might already * be in use as well, so be paranoid. * * We could do something like keep tacking something onto the * end of tempname until we come up with something that's not * in use, but that has liabilities as well. This is a * sufficiently rare case that it's not worth going that * overboard for. */ static caddr_t mkuniqname(caddr_t name, caddr_t pname, fsck_ino_t parent, fsck_ino_t inode) { fsck_ino_t oldino; struct dinode *dp; caddr_t flow_msg; struct inodesc idesc; static char tempname[BUFSIZ]; lftempname(tempname, inode); if ((name != NULL) && (lookup_named_ino(parent, name) != 0)) { name = NULL; } if (name == NULL) { /* * No name given, or it wasn't unique. */ name = tempname; if ((oldino = lookup_named_ino(parent, name)) != 0) { pfatal( "Name ``%s'' for inode %d already exists in %s \n", name, oldino, pname); if (reply("REMOVE OLD ENTRY") == 0) { if (parent == lfdir) pwarn( "Could not reconnect inode %d\n\n", inode); else pwarn( "Could not create entry for %d\n\n", inode); name = NULL; goto noconnect; } (void) changeino(parent, name, inode); LINK_RANGE(flow_msg, lncntp[oldino], 1); if (flow_msg != NULL) { /* * Do a best-effort, but if we're not * allowed to do the clear, the fs is * corrupt in any case, so just carry on. */ dp = ginode(oldino); LINK_CLEAR(flow_msg, oldino, dp->di_mode, &idesc); if (statemap[oldino] != USTATE) iscorrupt = 1; } else { TRACK_LNCNTP(oldino, lncntp[oldino]++); } } } noconnect: return (name); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Keep track of duplicate fragment references (elsewhere called * blocks for ancient historical reasons). * * The duplicates are kept in a binary tree to attempt to minimize * search times when checking the block lists of all active inodes * for multiple uses. This is opposed to using a simple linear list * that is traversed for every block, as is used in the traditional * fsck. It can be very time-expensive if there's more than just a * very few duplicates, and typically there are either none or lots. * * For each multiply-claimed fragment, we note all of the claiming * inodes and their corresponding logical block numbers. This allows * reporting exactly which parts of which files were damaged, which * provides at least a chance of recovering the bulk of the data on * a seriously-corrupted filesystem. */ #include #include #include #include #define _KERNEL #include /* for struct direct */ #undef _KERNEL #include #include "fsck.h" #define OFFSETOF(type, elt) ((size_t)(&((type *)NULL)->elt)) /* * For each physical fragment with multiple claimants, the specifics * of each claim are recorded. This means there are N+1 AVL trees in * use: one for each fragment's claimant table, plus one that orders * the fragments themselves. * * The table of fragments simply has the physical fragment number * (pfn) and has the root of the tree of the associated claimants. It * is keyed by the pfn and called dup_frags. * * The subsidiary trees list inodes and logical fragment number (lfn) * for each claimant. They are keyed first by inode number and then * by lfn. Both are needed, as it is possible for one inode to have * multiple claims on the same fragment. */ typedef struct claimant { fsck_ino_t cl_inode; daddr32_t cl_lfn; avl_node_t cl_avl; } claimant_t; typedef struct fragment { daddr32_t fr_pfn; avl_tree_t fr_claimants; avl_node_t fr_avl; } fragment_t; typedef struct reference { daddr32_t ref_lfn; daddr32_t ref_pfn; avl_node_t ref_avl; } reference_t; typedef struct inode_dup { fsck_ino_t id_ino; avl_tree_t id_fragments; avl_node_t id_avl; } inode_dup_t; static avl_tree_t dup_frags; static void free_invert_frags(avl_tree_t *); static void report_dup_lfn_pfn(daddr32_t, daddr32_t, daddr32_t, daddr32_t); static inode_dup_t *new_inode_dup(fsck_ino_t); static void invert_frags(avl_tree_t *, avl_tree_t *); static void report_inode_dups(inode_dup_t *); static int by_ino_cmp(const void *, const void *); static int by_lfn_cmp(const void *, const void *); static claimant_t *alloc_claimant(fsck_ino_t, daddr32_t); static fragment_t *alloc_dup(daddr32_t); static int claimant_cmp(const void *, const void *); static int fragment_cmp(const void *, const void *); static int decrement_claimant(fragment_t *, fsck_ino_t, daddr32_t); static int increment_claimant(fragment_t *, fsck_ino_t, daddr32_t); /* * Simple accessor function for the outside world so only we need to * see and interpret our data structures. */ int have_dups(void) { return (avl_numnodes(&dup_frags) > 0); } /* * Locates, creates, and deletes a record of a duplicate reference. * * For DB_INCR, returns true if the dup was added to the tree. * For DB_DECR, returns true if the dup was in the tree. */ int find_dup_ref(daddr32_t fragno, fsck_ino_t ino, daddr32_t lfn, int flags) { fragment_t key; fragment_t *dup; avl_index_t where; int added = 0; int removed = 0; if (avl_first(&dup_frags) == NULL) { if (flags & DB_CREATE) avl_create(&dup_frags, fragment_cmp, sizeof (fragment_t), OFFSETOF(fragment_t, fr_avl)); else return (0); } key.fr_pfn = fragno; dup = avl_find(&dup_frags, (void *)&key, &where); if ((dup == NULL) & (flags & DB_CREATE)) { dup = alloc_dup(fragno); avl_insert(&dup_frags, (void *)dup, where); } if (dup != NULL) { if (flags & DB_INCR) { if (debug) (void) printf( "adding claim by ino %d as lfn %d\n", ino, lfn); added = increment_claimant(dup, ino, lfn); } else if (flags & DB_DECR) { /* * Note that dup may be invalidated by this call. */ removed = decrement_claimant(dup, ino, lfn); if (debug) (void) printf( "check for claimant ino %d lfn %d returned %d\n", ino, lfn, removed); } } return (added || removed || (dup != NULL)); } /* * Dump the duplicates table in a relatively user-friendly form. * The idea is that the output can be useful when trying to manually * work out which block belongs to which of the claiming inodes. * * What we have is a tree of duplicates indexed by physical * fragment number. What we want to report is: * * Inode %d: * Logical Offset 0x%08llx, Physical Fragment %d * Logical Offsets 0x%08llx - 0x%08llx, Physical Fragments %d - %d * ... * Inode %d: * Logical Offsets 0x%08llx - 0x%08llx, Physical Fragments %d - %d * ... */ int report_dups(int quiet) { int overlaps; inode_dup_t *inode; fragment_t *dup; avl_tree_t inode_frags; overlaps = 0; ASSERT(have_dups()); /* * Figure out how many actual dups are still around. * This tells us whether or not we can mark the * filesystem clean. */ dup = avl_first(&dup_frags); while (dup != NULL) { if (avl_numnodes(&dup->fr_claimants) > 1) { overlaps++; break; } dup = AVL_NEXT(&dup_frags, dup); } /* * Now report on every object that still exists that * had *any* dups associated with it. */ if (!quiet) { (void) puts("\nSome blocks that were found to be in " "multiple files are still\nassigned to " "file(s).\nFragments sorted by inode and " "logical offsets:"); invert_frags(&dup_frags, &inode_frags); inode = avl_first(&inode_frags); while (inode != NULL) { report_inode_dups(inode); inode = AVL_NEXT(&inode_frags, inode); } (void) printf("\n"); free_invert_frags(&inode_frags); } return (overlaps); } static void report_inode_dups(inode_dup_t *inode) { reference_t *dup; daddr32_t first_lfn, last_lfn, first_pfn, last_pfn; (void) printf("Inode %d:\n", inode->id_ino); dup = avl_first(&inode->id_fragments); first_lfn = last_lfn = dup->ref_lfn; first_pfn = last_pfn = dup->ref_pfn; while ((dup = AVL_NEXT(&inode->id_fragments, dup)) != NULL) { if (((last_lfn + 1) != dup->ref_lfn) || ((last_pfn + 1) != dup->ref_pfn)) { report_dup_lfn_pfn(first_lfn, last_lfn, first_pfn, last_pfn); first_lfn = last_lfn = dup->ref_lfn; first_pfn = last_pfn = dup->ref_pfn; } } report_dup_lfn_pfn(first_lfn, last_lfn, first_pfn, last_pfn); } static void report_dup_lfn_pfn(daddr32_t first_lfn, daddr32_t last_lfn, daddr32_t first_pfn, daddr32_t last_pfn) { if ((first_lfn == last_lfn) && (first_pfn == last_pfn)) { (void) printf( " Logical Offset 0x%08llx Physical Fragment %d\n", (longlong_t)first_lfn * sblock.fs_fsize, first_pfn); } else { (void) printf( " Logical Offsets 0x%08llx - 0x%08llx, " "Physical Fragments %d - %d\n", (longlong_t)first_lfn * sblock.fs_fsize, (longlong_t)last_lfn * sblock.fs_fsize, first_pfn, last_pfn); } } /* * Given a tree of fragment_ts, each element of which has an integral * sub-tree of claimant_ts, produce a tree of inode_dup_ts, each element * of which has an integral sub-tree of reference_ts. */ static void invert_frags(avl_tree_t *source, avl_tree_t *target) { fragment_t *src_frag; claimant_t *src_claim; inode_dup_t *tgt_inode; inode_dup_t tgt_inode_key; reference_t *tgt_ref; reference_t tgt_ref_key; avl_index_t where; avl_create(target, by_ino_cmp, sizeof (inode_dup_t), OFFSETOF(inode_dup_t, id_avl)); src_frag = avl_first(source); while (src_frag != NULL) { src_claim = avl_first(&src_frag->fr_claimants); while (src_claim != NULL) { /* * Have we seen this inode before? */ tgt_inode_key.id_ino = src_claim->cl_inode; tgt_inode = avl_find(target, (void *)&tgt_inode_key, &where); if (tgt_inode == NULL) { /* * No, so set up a record for it. */ tgt_inode = new_inode_dup(src_claim->cl_inode); avl_insert(target, (void *)tgt_inode, where); } /* * Now, how about this logical fragment? In * theory, we should never see a duplicate, since * a given lfn only exists once for a given inode. * As such, we ignore duplicate hits. */ tgt_ref_key.ref_lfn = src_claim->cl_lfn; tgt_ref = avl_find(&tgt_inode->id_fragments, (void *)&tgt_ref_key, &where); if (tgt_ref == NULL) { /* * Haven't seen it, add it. */ tgt_ref = (reference_t *)malloc( sizeof (reference_t)); if (tgt_ref == NULL) errexit("Out of memory in " "invert_frags\n"); tgt_ref->ref_lfn = src_claim->cl_lfn; tgt_ref->ref_pfn = src_frag->fr_pfn; avl_insert(&tgt_inode->id_fragments, (void *)tgt_ref, where); } src_claim = AVL_NEXT(&src_frag->fr_claimants, src_claim); } src_frag = AVL_NEXT(source, src_frag); } } /* * Discard memory associated with the inverted fragments tree created * by report_dups() via invert_frags(). */ static void free_invert_frags(avl_tree_t *tree) { void *outer = NULL; /* traversal cookie */ void *inner; /* traversal cookie */ inode_dup_t *inode_dup; reference_t *ref_dup; while ((inode_dup = avl_destroy_nodes(tree, &outer)) != NULL) { inner = NULL; while ((ref_dup = avl_destroy_nodes(&inode_dup->id_fragments, &inner)) != NULL) { free((void *)ref_dup); } avl_destroy(&inode_dup->id_fragments); free((void *)inode_dup); } avl_destroy(tree); } /* * Discard all memory allocations associated with the current duplicates * table. */ void free_dup_state(void) { void *dup_cookie = NULL; void *claim_cookie; fragment_t *fragv; claimant_t *claimv; while ((fragv = avl_destroy_nodes(&dup_frags, &dup_cookie)) != NULL) { claim_cookie = NULL; while ((claimv = avl_destroy_nodes(&fragv->fr_claimants, &claim_cookie)) != NULL) { free((void *)claimv); } avl_destroy(&fragv->fr_claimants); free((void *)fragv); } avl_destroy(&dup_frags); } /* * If the given claimant has not been seen before, add it to DUP's * list of them. It's not fatal for the same PFN/INODE/LFN to get * added twice, because pass1b() will add the same dups that pass1() * did, plus one. */ static int increment_claimant(fragment_t *dup, fsck_ino_t ino, daddr32_t lfn) { avl_index_t where; claimant_t *claimant; claimant_t key; int added = 0; key.cl_inode = ino; key.cl_lfn = lfn; claimant = avl_find(&dup->fr_claimants, &key, &where); if (claimant == NULL) { if (debug) (void) printf("inserting claimant\n"); claimant = alloc_claimant(ino, lfn); avl_insert(&dup->fr_claimants, (void *)claimant, where); statemap[ino] |= INCLEAR; /* * If the inode is to be cleared and has zero links then remove * the zero link bit as it will be cleared anyway. If INZLINK * is being removed and it's a directory inode then add the * inode to the orphan directory list. */ if (statemap[ino] & INZLINK) { statemap[ino] &= ~INZLINK; if (statemap[ino] & DSTATE) { add_orphan_dir(ino); } } added = 1; } return (added); } /* * If the given claimant is on DUP's list, remove it. It is not * an error for the claimant to not be on the list. */ static int decrement_claimant(fragment_t *dup, fsck_ino_t ino, daddr32_t lfn) { avl_index_t where; claimant_t *claimant; claimant_t key; int busy = 0; key.cl_inode = ino; key.cl_lfn = lfn; claimant = avl_find(&dup->fr_claimants, &key, &where); if (claimant != NULL) { avl_remove(&dup->fr_claimants, claimant); if (avl_numnodes(&dup->fr_claimants) == 0) { avl_destroy(&dup->fr_claimants); avl_remove(&dup_frags, (void *)dup); free((void *)dup); } else { busy = 1; } } return (busy); } static claimant_t * alloc_claimant(fsck_ino_t inode, daddr32_t lfn) { claimant_t *new = (claimant_t *)malloc(sizeof (claimant_t)); if (new == NULL) errexit("Out of memory in alloc_claimant()\n"); new->cl_inode = inode; new->cl_lfn = lfn; return (new); } static fragment_t * alloc_dup(daddr32_t pfn) { fragment_t *new = (fragment_t *)malloc(sizeof (fragment_t)); if (new == NULL) errexit("Out of memory in alloc_dup()\n"); new->fr_pfn = pfn; avl_create(&new->fr_claimants, claimant_cmp, sizeof (fragment_t), OFFSETOF(claimant_t, cl_avl)); return (new); } /* * Compare two fragment_t instances for avl_find(). It requires a * return value of -1/0/1, so we can't just hand back left - right. */ static int fragment_cmp(const void *vlp, const void *vrp) { const fragment_t *lp = (const fragment_t *)vlp; const fragment_t *rp = (const fragment_t *)vrp; int cmp = lp->fr_pfn - rp->fr_pfn; if (cmp < 0) cmp = -1; else if (cmp > 0) cmp = 1; return (cmp); } /* * Compare two claimant_t instances for avl_find(). It requires a * return value of -1/0/1, so we can't just hand back left - right. */ static int claimant_cmp(const void *vlp, const void *vrp) { const claimant_t *lp = (const claimant_t *)vlp; const claimant_t *rp = (const claimant_t *)vrp; int cmp; cmp = lp->cl_inode - rp->cl_inode; if (cmp == 0) { /* * lfn < 0 is a wildcard lfn match. */ if ((lp->cl_lfn >= 0) && (rp->cl_lfn >= 0)) cmp = lp->cl_lfn - rp->cl_lfn; } if (cmp < 0) cmp = -1; else if (cmp > 0) cmp = 1; return (cmp); } static int by_ino_cmp(const void *vlp, const void *vrp) { const inode_dup_t *lp = (const inode_dup_t *)vlp; const inode_dup_t *rp = (const inode_dup_t *)vrp; int cmp; cmp = lp->id_ino - rp->id_ino; if (cmp < 0) cmp = -1; else if (cmp > 0) cmp = 1; return (cmp); } static int by_lfn_cmp(const void *vlp, const void *vrp) { const reference_t *lp = (const reference_t *)vlp; const reference_t *rp = (const reference_t *)vrp; int cmp; cmp = lp->ref_lfn - rp->ref_lfn; if (cmp < 0) cmp = -1; else if (cmp > 0) cmp = 1; return (cmp); } static inode_dup_t * new_inode_dup(fsck_ino_t inode) { inode_dup_t *new; new = (inode_dup_t *)malloc(sizeof (inode_dup_t)); if (new == NULL) errexit("Out of memory in new_inode_dup\n"); new->id_ino = inode; avl_create(&new->id_fragments, by_lfn_cmp, sizeof (reference_t), OFFSETOF(reference_t, ref_avl)); return (new); } /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _FSCK_FSCK_H #define _FSCK_FSCK_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #define MAXDUP 10 /* limit on dup blks (per inode) */ #define MAXBAD 10 /* limit on bad blks (per inode) */ #define MAXBUFSPACE 40*1024 /* initial space to allocate to buffers */ #define INOBUFSIZE 56*1024 /* size of buffer to read inodes in pass1 */ #ifndef BUFSIZ #define BUFSIZ MAXPATHLEN #endif /* * Inode states in statemap[]. */ #define USTATE 0x01 /* inode not allocated */ #define FSTATE 0x02 /* inode is file */ #define DSTATE 0x04 /* inode is directory */ #define SSTATE 0x08 /* inode is a shadow/acl */ #define STMASK 0x0f /* pick off the basic state/type */ /* flags OR'd into the above */ #define INZLINK 0x0010 /* inode has zero links */ #define INFOUND 0x0020 /* inode was found during descent */ #define INCLEAR 0x0040 /* inode is to be cleared */ #define INORPHAN 0x0080 /* inode is a known orphan (pass3 only) */ #define INDELAYD 0x0200 /* link count update delayed */ #define INMASK 0xfff0 /* pick off the modifiers */ #define FZLINK (FSTATE | INZLINK) #define DZLINK (DSTATE | INZLINK) #define SZLINK (SSTATE | INZLINK) #define DFOUND (DSTATE | INFOUND) #define DCLEAR (DSTATE | INCLEAR) #define FCLEAR (FSTATE | INCLEAR) #define SCLEAR (SSTATE | INCLEAR) /* * These tests depend on the state/type defines above not overlapping bits. * * DUNFOUND === (state == DSTATE || state == DZLINK) * INCLEAR is irrelevant to the determination of * connectedness, so it's not included in this test. * * DVALID === (state == DSTATE || state == DZLINK || state == DFOUND) */ #define S_IS_DUNFOUND(state) (((state) & (DSTATE | INZLINK)) \ == (state)) #define S_IS_DVALID(state) (((state) & (DSTATE | INZLINK | INFOUND | \ INORPHAN)) == (state)) #define S_IS_ZLINK(state) (((state) & INZLINK) != 0) #define INO_IS_DUNFOUND(ino) S_IS_DUNFOUND(statemap[ino]) #define INO_IS_DVALID(ino) S_IS_DVALID(statemap[ino]) /* * buffer cache structure. */ struct bufarea { struct bufarea *b_next; /* free list queue */ struct bufarea *b_prev; /* free list queue */ diskaddr_t b_bno; /* physical sector number */ int b_size; int b_errs; int b_flags; int b_cnt; /* reference cnt */ union { char *b_buf; /* buffer space */ daddr32_t *b_indir; /* indirect block */ struct fs *b_fs; /* super block */ struct cg *b_cg; /* cylinder group */ struct dinode *b_dinode; /* inode block */ } b_un; char b_dirty; }; #define B_INUSE 1 #define MINBUFS 5 /* minimum number of buffers required */ extern struct bufarea sblk; /* file system superblock */ extern struct bufarea cgblk; /* cylinder group blocks */ extern struct bufarea *pbp; /* pointer to inode data in buffer pool */ extern struct bufarea *pdirbp; /* pointer to directory data in buffer pool */ #define sbdirty() dirty(&sblk) #define cgdirty() dirty(&cgblk) #define sblock (*sblk.b_un.b_fs) #define cgrp (*cgblk.b_un.b_cg) /* * inodesc.id_fix values. See inode.c for a description of their usage. */ enum fixstate { DONTKNOW, NOFIX, FIX, IGNORE }; /* * Tells truncino() whether or not to attempt to update the parent * directory's link count. Also, TI_NODUP flags when we're discarding * fragments that are beyond the original end of the file, and so * should not be considered duplicate-claim candidates. */ #define TI_NOPARENT 0x0001 /* leave parent's di_nlink alone */ #define TI_PARENT 0x0002 /* update parent's di_nlink */ #define TI_NODUP 0x0004 /* not a dup candidate */ /* * Modes for ckinode() and ckinode_common(). * * CKI_TRAVERSE is the common case, and requests a traditional * traversal of blocks or directory entries. * * CKI_TRUNCATE indicates that we're truncating the file, and that any * block indices beyond the end of the target length should be cleared * after the callback has returned (i.e., this is a superset of * CKI_TRAVERSE). idesc->id_truncto is the first logical block number * to clear. If it is less than zero, then the traversal will be * equivalent to a simple CKI_TRAVERSE. */ enum cki_action { CKI_TRAVERSE, CKI_TRUNCATE }; /* * The general definition of an ino_t is an unsigned quantity. * However, the on-disk version is an int32_t, which is signed. * Since we really want to be able to detect wrapped-around * inode numbers and such, we'll use something that's compatible * with what's on disk since that's the only context that really * matters. If an int32_t is found not to be sufficiently large, * this will make it much easier to change later. * * Note that there is one unsigned inode field in the on-disk * inode, ic_oeftflag. Since all other inode fields are signed, * no legitimate inode number can be put into ic_oeftflag that * would overflow into the high bit. Essentially, it should * actually be declared as int32_t just like all the others, and * we're going to pretend that it was. * * None of the routines that we use in ufs_subr.c do anything with * inode numbers. If that changes, then great care will be needed * to deal with the differences in definition of ino_t and fsck_ino_t. * Lint is your friend. */ typedef int32_t fsck_ino_t; /* * See the full discussion of the interactions between struct inodesc * and ckinode() in inode.c */ struct inodesc { enum fixstate id_fix; /* policy on fixing errors */ int (*id_func)(struct inodesc *); /* function to be applied to blocks of inode */ fsck_ino_t id_number; /* inode number described */ fsck_ino_t id_parent; /* for DATA nodes, their parent */ /* also used for extra (*id_func) parameter */ /* and return values */ daddr32_t id_lbn; /* logical fragment number of current block */ daddr32_t id_blkno; /* physical fragment number being examined */ int id_numfrags; /* number of frags contained in block */ daddr32_t id_truncto; /* # blocks to truncate to, -1 for no trunc. */ offset_t id_filesize; /* for DATA nodes, the size of the directory */ uint_t id_loc; /* for DATA nodes, current location in dir */ daddr32_t id_entryno; /* for DATA nodes, current dir entry number */ daddr32_t id_firsthole; /* for DATA inode, logical block that is */ /* zero but shouldn't be, -1 for no holes */ struct direct *id_dirp; /* for DATA nodes, ptr to current entry */ caddr_t id_name; /* for DATA nodes, name to find or enter */ char id_type; /* type of descriptor, DATA or ADDR */ }; /* file types (0 is reserved for catching bugs) */ #define DATA 1 /* a directory */ #define ACL 2 /* an acl/shadow */ #define ADDR 3 /* anything but a directory or an acl/shadow */ /* * OR'd flags for find_dup_ref()'s mode argument */ #define DB_CREATE 0x01 /* if dup record found, make one */ #define DB_INCR 0x02 /* increment block's reference count */ #define DB_DECR 0x04 /* decrement block's reference count */ /* * Cache data structures */ struct inoinfo { struct inoinfo *i_nextlist; /* next inode/acl cache entry */ fsck_ino_t i_number; /* inode number of this entry */ fsck_ino_t i_parent; /* inode number of parent */ fsck_ino_t i_dotdot; /* inode number of .. */ fsck_ino_t i_extattr; /* inode of hidden attr dir */ offset_t i_isize; /* size of inode */ size_t i_blkssize; /* size of block array in bytes */ daddr32_t i_blks[1]; /* actually longer */ }; /* * Inode cache */ extern struct inoinfo **inphead, **inpsort; extern int64_t numdirs, listmax, inplast; /* * ACL cache */ extern struct inoinfo **aclphead, **aclpsort; extern int64_t numacls, aclmax, aclplast; /* * Tree of directories we haven't reconnected or cleared. Any * dir inode that linkup() fails on gets added, any that clri() * succeeds on gets removed. If there are any left at the end of * pass four, then we have a user-forced corrupt filesystem, and * need to set iscorrupt. * * Elements are fsck_ino_t instances (not pointers). */ extern void *limbo_dirs; /* * Number of directories we actually found in the filesystem, * as opposed to how many the superblock claims there are. */ extern fsck_ino_t countdirs; /* * shadowclients and shadowclientinfo are structures for keeping track of * shadow inodes that exist, and which regular inodes use them (i.e. are * their clients). */ struct shadowclients { fsck_ino_t *client; /* an array of inode numbers */ int nclients; /* how many inodes in the array are in use (valid) */ struct shadowclients *next; /* link to more client inode numbers */ }; struct shadowclientinfo { fsck_ino_t shadow; /* the shadow inode that this info is for */ int totalClients; /* how many inodes total refer to this */ struct shadowclients *clients; /* a linked list of wads of clients */ struct shadowclientinfo *next; /* link to the next shadow inode */ }; /* global pointer to this shadow/client information */ extern struct shadowclientinfo *shadowclientinfo; extern struct shadowclientinfo *attrclientinfo; /* * In ufs_inode.h ifdef _KERNEL, this is defined as `/@/'. However, * to avoid all sorts of potential confusion (you can't actually use * `foo/@/bar' to get to an attribute), we use something that doesn't * look quite so much like a simple pathname. */ #define XATTR_DIR_NAME " " /* * granularity -- how many client inodes do we make space for at a time * initialized in setup.c; */ extern int maxshadowclients; /* * Initialized global variables. */ extern caddr_t lfname; /* * Unitialized globals. */ extern char *devname; /* name of device being checked */ extern size_t dev_bsize; /* computed value of DEV_BSIZE */ extern int secsize; /* actual disk sector size */ extern char nflag; /* assume a no response */ extern char yflag; /* assume a yes response */ extern daddr32_t bflag; /* location of alternate super block */ extern int debug; /* output debugging info */ extern int rflag; /* check raw file systems */ extern int fflag; /* check regardless of clean flag (force) */ extern int mflag; /* sanity check only */ extern int verbose; /* be chatty */ extern char preen; /* just fix normal inconsistencies */ extern char mountedfs; /* checking mounted device */ extern int exitstat; /* exit status (see EX* defines below) */ extern char hotroot; /* checking root device */ extern char rerun; /* rerun fsck. Only used in non-preen mode */ extern int interrupted; /* 1 => exit EXSIGNAL on exit */ extern char havesb; /* superblock has been read */ extern int fsmodified; /* 1 => write done to file system */ extern int fsreadfd; /* file descriptor for reading file system */ extern int fswritefd; /* file descriptor for writing file system */ extern int iscorrupt; /* known to be corrupt/inconsistent */ /* -1 means mark clean so user can mount+fix */ extern int isdirty; /* 1 => write pending to file system */ extern int islog; /* logging file system */ extern int islogok; /* log is okay */ extern int errorlocked; /* set => mounted fs has been error-locked */ /* implies fflag "force check flag" */ extern char *elock_combuf; /* error lock comment buffer */ extern char *elock_mountp; /* mount point; used to unlock error-lock */ extern int pid; /* fsck's process id (put in lockfs comment) */ extern int mountfd; /* fd of mount point */ extern daddr32_t maxfsblock; /* number of blocks in the file system */ extern uint_t largefile_count; /* global largefile counter */ extern char *mount_point; /* if mounted, this is where */ extern char *blockmap; /* ptr to primary blk allocation map */ extern fsck_ino_t maxino; /* number of inodes in file system */ extern fsck_ino_t lastino; /* last inode in use */ extern ushort_t *statemap; /* ptr to inode state table */ extern short *lncntp; /* ptr to link count table */ extern fsck_ino_t lfdir; /* lost & found directory inode number */ extern int overflowed_lf; /* tried to wrap lost & found's link count */ extern int reattached_dir; /* reconnected at least one directory */ extern int broke_dir_link; /* broke at least one directory hardlink */ extern daddr32_t n_blks; /* number of blocks in use */ extern fsck_ino_t n_files; /* number of files in use */ #define clearinode(dp) { \ *(dp) = zino; \ } extern struct dinode zino; #define testbmap(blkno) isset(blockmap, blkno) #define setbmap(blkno) setbit(blockmap, blkno) #define clrbmap(blkno) clrbit(blockmap, blkno) #define STOP 0x01 #define SKIP 0x02 #define KEEPON 0x04 #define ALTERED 0x08 #define FOUND 0x10 /* * Support relatively easy debugging of lncntp[] updates. This can't * be a function, because of the (_op) step. Normally, we just do that. */ #define TRACK_LNCNTP(_ino, _op) (_op) /* * See if the net link count for an inode has gone outside * what can be represented on disk. Returning text as NULL * indicates no. * * Remember that link counts are effectively inverted, so * underflow and overflow are reversed as well. * * This check should be done before modifying the actual link * count. */ #define LINK_RANGE(text, current, offset) { \ int net = ((int)(current)) + ((int)(offset)); \ text = NULL; \ if (net > (MAXLINK)) \ text = "UNDERFLOW"; \ else if (net < -(MAXLINK)) \ text = "OVERFLOW"; \ } /* * If LINK_RANGE() indicated a problem, this is the boiler-plate * for dealing with it. Usage is: * * LINK_RANGE(text, current, offset); * if (text != NULL) { * LINK_CLEAR(text, ino, mode, idp); * if (statemap[ino] == USTATE) * ...inode was cleared... * } * * Note that clri() will set iscorrupt if the user elects not to * clear the problem inode, so the filesystem won't get reported * as clean when it shouldn't be. */ #define LINK_CLEAR(text, ino, mode, idp) { \ pwarn("%s LINK COUNT %s", file_id((ino), (mode)), (text)); \ pinode((ino)); \ pfatal(""); \ init_inodesc((idp)); \ (idp)->id_type = ADDR; \ (idp)->id_func = pass4check; \ (idp)->id_number = ino; \ (idp)->id_fix = DONTKNOW; \ clri((idp), (text), CLRI_QUIET, CLRI_NOP_CORRUPT); \ } /* * Used for checking link count under/overflow specifically on * the lost+found directory. If the user decides not to do the * clri(), then flag that we've hit this problem and refuse to do * the reconnect. */ #define LFDIR_LINK_RANGE_RVAL(text, current, offset, idp, rval) { \ LINK_RANGE(text, current, offset); \ if (text != NULL) { \ LINK_CLEAR(text, lfdir, IFDIR, idp); \ if (statemap[lfdir] == USTATE) { \ lfdir = 0; \ return (rval); \ } else { \ overflowed_lf++; \ } \ } \ } #define LFDIR_LINK_RANGE_NORVAL(text, current, offset, idp) { \ LINK_RANGE(text, current, offset); \ if (text != NULL) { \ LINK_CLEAR(text, lfdir, IFDIR, idp); \ if (statemap[lfdir] == USTATE) { \ lfdir = 0; \ return; \ } else { \ overflowed_lf++; \ } \ } \ } /* * Values for mounted() and mountedfs. */ #define M_NOMNT 0 /* filesystem is not mounted */ #define M_RO 1 /* filesystem is mounted read-only */ #define M_RW 2 /* filesystem is mounted read-write */ #define EXOKAY 0 /* file system is unmounted and ok */ #define EXBADPARM 1 /* bad parameter(s) given */ #define EXUMNTCHK 32 /* fsck -m: unmounted, needs checking */ #define EXMOUNTED 33 /* file system already mounted, not magic, */ /* or it is magic and mounted read/write */ #define EXNOSTAT 34 /* cannot stat device */ #define EXREBOOTNOW 35 /* modified root or something equally scary */ #define EXFNDERRS 36 /* uncorrectable errors, terminate normally */ #define EXSIGNAL 37 /* a signal was caught during processing */ #define EXERRFATAL 39 /* uncorrectable errors, exit immediately */ #define EXROOTOKAY 40 /* for root, same as 0 */ /* * Values for clri()'s `verbose' and `corrupting' arguments (third * and fourth, respectively). */ #define CLRI_QUIET 1 #define CLRI_VERBOSE 2 #define CLRI_NOP_OK 1 #define CLRI_NOP_CORRUPT 2 /* * Filesystems that are `magical' - if they exist in vfstab, * then they have to be mounted for the system to have gotten * far enough to be able to run fsck. Thus, don't get all * bent out of shape if we're asked to check it and it is mounted. * Actual initialization of the array is in main.c */ enum magic { MAGIC_NONE = 0, MAGIC_ROOT = 1, MAGIC_USR = 2, MAGIC_LIMIT = 3 }; extern char *magic_fs[]; /* * Paths needed by calcsb(). */ #define MKFS_PATH "/usr/lib/fs/ufs/mkfs" #define NEWFS_PATH "/usr/lib/fs/ufs/newfs" int acltypeok(struct dinode *); void add_orphan_dir(fsck_ino_t); void adjust(struct inodesc *, int); daddr32_t allocblk(int); fsck_ino_t allocdir(fsck_ino_t, fsck_ino_t, int, int); fsck_ino_t allocino(fsck_ino_t, int); void blkerror(fsck_ino_t, caddr_t, daddr32_t, daddr32_t); void brelse(struct bufarea *); void bufinit(void); void bwrite(int, caddr_t, diskaddr_t, int64_t); void cacheacl(struct dinode *, fsck_ino_t); void cacheino(struct dinode *, fsck_ino_t); void catch(int); void catchquit(int); caddr_t cg_sanity(struct cg *, int); void cgflush(void); int cgisdirty(void); int changeino(fsck_ino_t, caddr_t, fsck_ino_t); int check_mnttab(caddr_t, caddr_t, size_t); int check_vfstab(caddr_t, caddr_t, size_t); int chkrange(daddr32_t, int); void ckfini(void); int ckinode(struct dinode *, struct inodesc *, enum cki_action); void clearattrref(fsck_ino_t); int cleardirentry(fsck_ino_t, fsck_ino_t); void clearshadow(fsck_ino_t, struct shadowclientinfo **); void clri(struct inodesc *, caddr_t, int, int); void deshadow(struct shadowclientinfo *, void (*)(fsck_ino_t)); void direrror(fsck_ino_t, caddr_t, ...); int dirscan(struct inodesc *); void dirty(struct bufarea *); int do_errorlock(int); int dofix(struct inodesc *, caddr_t, ...); void examinelog(void (*)(daddr32_t)); void errexit(caddr_t, ...); void fileerror(fsck_ino_t, fsck_ino_t, caddr_t, ...); caddr_t file_id(fsck_ino_t, mode_t); int find_dup_ref(daddr32_t, fsck_ino_t, daddr32_t, int); int findino(struct inodesc *); int findname(struct inodesc *); void fix_cg(struct cg *, int); void flush(int, struct bufarea *); void free_dup_state(void); void freeblk(fsck_ino_t, daddr32_t, int); void freeino(fsck_ino_t, int); void freeinodebuf(void); int fsck_asprintf(caddr_t *, caddr_t, ...); int fsck_bread(int, caddr_t, diskaddr_t, size_t); int ftypeok(struct dinode *); struct bufarea *getblk(struct bufarea *, daddr32_t, size_t); struct bufarea *getdatablk(daddr32_t, size_t size); diskaddr_t getdisksize(caddr_t, int); struct inoinfo *getinoinfo(fsck_ino_t); struct dinode *getnextinode(fsck_ino_t); struct dinode *getnextrefresh(void); void getpathname(caddr_t, fsck_ino_t, fsck_ino_t); struct dinode *ginode(fsck_ino_t); caddr_t hasvfsopt(struct vfstab *, caddr_t); int have_dups(void); void init_inodesc(struct inodesc *); void init_inoinfo(struct inoinfo *, struct dinode *, fsck_ino_t); void initbarea(struct bufarea *); int ino_t_cmp(const void *, const void *); int inocached(fsck_ino_t); void inocleanup(void); void inodirty(void); int is_errorlocked(caddr_t); int linkup(fsck_ino_t, fsck_ino_t, caddr_t); int lookup_named_ino(fsck_ino_t, caddr_t); int makeentry(fsck_ino_t, fsck_ino_t, caddr_t); void maybe_convert_attrdir_to_dir(fsck_ino_t); int mounted(caddr_t, caddr_t, size_t); void pass1(void); void pass1b(void); int pass1check(struct inodesc *); void pass2(void); void pass3a(void); void pass3b(void); int pass3bcheck(struct inodesc *); void pass4(void); int pass4check(struct inodesc *); void pass5(void); void pfatal(caddr_t, ...); void pinode(fsck_ino_t); void printclean(void); void propagate(void); void pwarn(caddr_t, ...); caddr_t rawname(caddr_t); void registershadowclient(fsck_ino_t, fsck_ino_t, struct shadowclientinfo **); void remove_orphan_dir(fsck_ino_t); int reply(caddr_t, ...); int report_dups(int); void resetinodebuf(void); char *setup(caddr_t); void truncino(fsck_ino_t, offset_t, int); void unbufinit(void); caddr_t unrawname(caddr_t); void unregistershadow(fsck_ino_t, struct shadowclientinfo **); int updateclean(void); int writable(caddr_t); void write_altsb(int); /* * Functions from the kernel sources (ufs_subr.c, etc). */ extern void fragacct(struct fs *, int, int32_t *, int); #ifdef __cplusplus } #endif #endif /* _FSCK_FSCK_H */ #!/bin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # find_files "s.*"\ usr/src/uts/common/fs/ufs /* * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include #include "fsck.h" uint_t largefile_count = 0; fsck_ino_t lastino; struct bufarea cgblk; struct inoinfo **aclphead, **aclpsort; struct dinode zino; static int get_indir_offsets(int, daddr_t, int *, int *); static int clearanentry(struct inodesc *); static void pdinode(struct dinode *); static void inoflush(void); static void mark_delayed_inodes(fsck_ino_t, daddr32_t); static int iblock(struct inodesc *, int, u_offset_t, enum cki_action); static struct inoinfo *search_cache(struct inoinfo *, fsck_ino_t); static int ckinode_common(struct dinode *, struct inodesc *, enum cki_action); static int lookup_dotdot_ino(fsck_ino_t); /* * ckinode() essentially traverses the blocklist of the provided * inode. For each block either the caller-supplied callback (id_func * in the provided struct inodesc) or dirscan() is invoked. Which is * chosen is controlled by what type of traversal was requested * (id_type) - if it was for an ADDR or ACL, use the callback, * otherwise it is assumed to be DATA (i.e., a directory) whose * contents need to be scanned. * * Note that a directory inode can get passed in with a type of ADDR; * the type field is orthogonal to the IFMT value. This is so that * the file aspects (no duplicate blocks, etc) of a directory can be * verified just like is done for any other file, or the actual * contents can be scanned so that connectivity and such can be * investigated. * * The traversal is controlled by flags in the return value of * dirscan() or the callback. Five flags are defined, STOP, SKIP, * KEEPON, ALTERED, and FOUND. Their semantics are: * * STOP - no further processing of this inode is desired/possible/ * feasible/etc. This can mean that whatever the scan * was searching for was found, or a serious * inconsistency was encountered, or anything else * appropriate. * * SKIP - something that made it impossible to continue was * encountered, and the caller should go on to the next * inode. This is more for i/o failures than for * logical inconsistencies. Nothing actually looks for * this. * * KEEPON - no more blocks of this inode need to be scanned, but * nothing's wrong, so keep on going with the next * inode. It is similar to STOP, except that * ckinode()'s caller will typically advance to the next * inode for KEEPON, whereas it ceases scanning through * the inodes completely for STOP. * * ALTERED - a change was made to the inode. If the caller sees * this set, it should make sure to flush out the * changes. Note that any data blocks read in by the * function need to be marked dirty by it directly; * flushing of those will happen automatically later. * * FOUND - whatever was being searched for was located. * Typically combined with STOP to avoid wasting time * doing additional looking. * * During a traversal, some state needs to be carried around. At the * least, the callback functions need to know what inode they're * working on, which logical block, and whether or not fixing problems * when they're encountered is desired. Rather than try to guess what * else might be needed (and thus end up passing way more arguments * than is reasonable), all the possibilities have been bundled in * struct inodesc. About half of the fields are specific to directory * traversals, and the rest are pretty much generic to any traversal. * * The general fields are: * * id_fix What to do when an error is found. Generally, this * is set to DONTKNOW before a traversal. If a * problem is encountered, it is changed to either FIX * or NOFIX by the dofix() query function. If id_fix * has already been set to FIX when dofix() is called, then * it includes the ALTERED flag (see above) in its return * value; the net effect is that the inode's buffer * will get marked dirty and written to disk at some * point. If id_fix is DONTKNOW, then dofix() will * query the user. If it is NOFIX, then dofix() * essentially does nothing. A few routines set NOFIX * as the initial value, as they are performing a best- * effort informational task, rather than an actual * repair operation. * * id_func This is the function that will be called for every * logical block in the file (assuming id_type is not * DATA). The logical block may represent a hole, so * the callback needs to be prepared to handle that * case. Its return value is a combination of the flags * described above (SKIP, ALTERED, etc). * * id_number The inode number whose block list or data is being * scanned. * * id_parent When id_type is DATA, this is the inode number for * the parent of id_number. Otherwise, it is * available for use as an extra parameter or return * value between the callback and ckinode()'s caller. * Which, if either, of those is left completely up to * the two routines involved, so nothing can generally * be assumed about the id_parent value for non-DATA * traversals. * * id_lbn This is the current logical block (not fragment) * number being visited by the traversal. * * id_blkno This is the physical block corresponding to id_lbn. * * id_numfrags This defines how large a block is being processed in * this particular invocation of the callback. * Usually, it will be the same as sblock.fs_frag. * However, if a direct block is being processed and * it is less than a full filesystem block, * id_numfrags will indicate just how many fragments * (starting from id_lbn) are actually part of the * file. * * id_truncto The pass 4 callback is used in several places to * free the blocks of a file (the `FILE HAS PROBLEM * FOO; CLEAR?' scenario). This has been generalized * to allow truncating a file to a particular length * rather than always completely discarding it. If * id_truncto is -1, then the entire file is released, * otherwise it is logical block number to truncate * to. This generalized interface was motivated by a * desire to be able to discard everything after a * hole in a directory, rather than the entire * directory. * * id_type Selects the type of traversal. DATA for dirscan(), * ADDR or ACL for using the provided callback. * * There are several more fields used just for dirscan() traversals: * * id_filesize The number of bytes in the overall directory left to * process. * * id_loc Byte position within the directory block. Should always * point to the start of a directory entry. * * id_entryno Which logical directory entry is being processed (0 * is `.', 1 is `..', 2 and on are normal entries). * This field is primarily used to enable special * checks when looking at the first two entries. * * The exception (there's always an exception in fsck) * is that in pass 1, it tracks how many fragments are * being used by a particular inode. * * id_firsthole The first logical block number that was found to * be zero. As directories are not supposed to have * holes, this marks where a directory should be * truncated down to. A value of -1 indicates that * no holes were found. * * id_dirp A pointer to the in-memory copy of the current * directory entry (as identified by id_loc). * * id_name This is a directory entry name to either create * (callback is mkentry) or locate (callback is * chgino, findino, or findname). */ int ckinode(struct dinode *dp, struct inodesc *idesc, enum cki_action action) { struct inodesc cleardesc; mode_t mode; if (idesc->id_filesize == 0) idesc->id_filesize = (offset_t)dp->di_size; /* * Our caller should be filtering out completely-free inodes * (mode == zero), so we'll work on the assumption that what * we're given has some basic validity. * * The kernel is inconsistent about MAXPATHLEN including the * trailing \0, so allow the more-generous length for symlinks. */ mode = dp->di_mode & IFMT; if (mode == IFBLK || mode == IFCHR) return (KEEPON); if (mode == IFLNK && dp->di_size > MAXPATHLEN) { pwarn("I=%d Symlink longer than supported maximum\n", idesc->id_number); init_inodesc(&cleardesc); cleardesc.id_type = ADDR; cleardesc.id_number = idesc->id_number; cleardesc.id_fix = DONTKNOW; clri(&cleardesc, "BAD", CLRI_VERBOSE, CLRI_NOP_CORRUPT); return (STOP); } return (ckinode_common(dp, idesc, action)); } /* * This was split out from ckinode() to allow it to be used * without having to pass in kludge flags to suppress the * wrong-for-deletion initialization and irrelevant checks. * This feature is no longer needed, but is being kept in case * the need comes back. */ static int ckinode_common(struct dinode *dp, struct inodesc *idesc, enum cki_action action) { offset_t offset; struct dinode dino; daddr_t ndb; int indir_data_blks, last_indir_blk; int ret, i, frags; (void) memmove(&dino, dp, sizeof (struct dinode)); ndb = howmany(dino.di_size, (u_offset_t)sblock.fs_bsize); for (i = 0; i < NDADDR; i++) { idesc->id_lbn++; offset = blkoff(&sblock, dino.di_size); if ((--ndb == 0) && (offset != 0)) { idesc->id_numfrags = numfrags(&sblock, fragroundup(&sblock, offset)); } else { idesc->id_numfrags = sblock.fs_frag; } if (dino.di_db[i] == 0) { if ((ndb > 0) && (idesc->id_firsthole < 0)) { idesc->id_firsthole = i; } continue; } idesc->id_blkno = dino.di_db[i]; if (idesc->id_type == ADDR || idesc->id_type == ACL) ret = (*idesc->id_func)(idesc); else ret = dirscan(idesc); /* * Need to clear the entry, now that we're done with * it. We depend on freeblk() ignoring a request to * free already-free fragments to handle the problem of * a partial block. */ if ((action == CKI_TRUNCATE) && (idesc->id_truncto >= 0) && (idesc->id_lbn >= idesc->id_truncto)) { dp = ginode(idesc->id_number); /* * The (int) cast is safe, in that if di_size won't * fit, it'll be a multiple of any legal fs_frag, * thus giving a zero result. That value, in turn * means we're doing an entire block. */ frags = howmany((int)dp->di_size, sblock.fs_fsize) % sblock.fs_frag; if (frags == 0) frags = sblock.fs_frag; freeblk(idesc->id_number, dp->di_db[i], frags); dp = ginode(idesc->id_number); dp->di_db[i] = 0; inodirty(); ret |= ALTERED; } if (ret & STOP) return (ret); } #ifdef lint /* * Cure a lint complaint of ``possible use before set''. * Apparently it can't quite figure out the switch statement. */ indir_data_blks = 0; #endif /* * indir_data_blks contains the number of data blocks in all * the previous levels for this iteration. E.g., for the * single indirect case (i = 0, di_ib[i] != 0), NDADDR's worth * of blocks have already been covered by the direct blocks * (di_db[]). At the triple indirect level (i = NIADDR - 1), * it is all of the number of data blocks that were covered * by the second indirect, single indirect, and direct block * levels. */ idesc->id_numfrags = sblock.fs_frag; ndb = howmany(dino.di_size, (u_offset_t)sblock.fs_bsize); for (i = 0; i < NIADDR; i++) { (void) get_indir_offsets(i, ndb, &indir_data_blks, &last_indir_blk); if (dino.di_ib[i] != 0) { /* * We'll only clear di_ib[i] if the first entry (and * therefore all of them) is to be cleared, since we * only go through this code on the first entry of * each level of indirection. The +1 is to account * for the fact that we don't modify id_lbn until * we actually start processing on a data block. */ idesc->id_blkno = dino.di_ib[i]; ret = iblock(idesc, i + 1, (u_offset_t)howmany(dino.di_size, (u_offset_t)sblock.fs_bsize) - indir_data_blks, action); if ((action == CKI_TRUNCATE) && (idesc->id_truncto <= indir_data_blks) && ((idesc->id_lbn + 1) >= indir_data_blks) && ((idesc->id_lbn + 1) <= last_indir_blk)) { dp = ginode(idesc->id_number); if (dp->di_ib[i] != 0) { freeblk(idesc->id_number, dp->di_ib[i], sblock.fs_frag); } } if (ret & STOP) return (ret); } else { /* * Need to know which of the file's logical blocks * reside in the missing indirect block. However, the * precise location is only needed for truncating * directories, and level-of-indirection precision is * sufficient for that. */ if ((indir_data_blks < ndb) && (idesc->id_firsthole < 0)) { idesc->id_firsthole = indir_data_blks; } } } return (KEEPON); } static int get_indir_offsets(int ilevel_wanted, daddr_t ndb, int *data_blks, int *last_blk) { int ndb_ilevel = -1; int ilevel; int dblks, lblk; for (ilevel = 0; ilevel < NIADDR; ilevel++) { switch (ilevel) { case 0: /* SINGLE */ dblks = NDADDR; lblk = dblks + NINDIR(&sblock) - 1; break; case 1: /* DOUBLE */ dblks = NDADDR + NINDIR(&sblock); lblk = dblks + (NINDIR(&sblock) * NINDIR(&sblock)) - 1; break; case 2: /* TRIPLE */ dblks = NDADDR + NINDIR(&sblock) + (NINDIR(&sblock) * NINDIR(&sblock)); lblk = dblks + (NINDIR(&sblock) * NINDIR(&sblock) * NINDIR(&sblock)) - 1; break; default: exitstat = EXERRFATAL; /* * Translate from zero-based array to * one-based human-style counting. */ errexit("panic: indirection level %d not 1, 2, or 3", ilevel + 1); /* NOTREACHED */ } if (dblks < ndb && ndb <= lblk) ndb_ilevel = ilevel; if (ilevel == ilevel_wanted) { if (data_blks != NULL) *data_blks = dblks; if (last_blk != NULL) *last_blk = lblk; } } return (ndb_ilevel); } static int iblock(struct inodesc *idesc, int ilevel, u_offset_t iblks, enum cki_action action) { struct bufarea *bp; int i, n; int (*func)(struct inodesc *) = NULL; u_offset_t fsbperindirb; daddr32_t last_lbn; int nif; char buf[BUFSIZ]; n = KEEPON; switch (idesc->id_type) { case ADDR: func = idesc->id_func; if (((n = (*func)(idesc)) & KEEPON) == 0) return (n); break; case ACL: func = idesc->id_func; break; case DATA: func = dirscan; break; default: errexit("unknown inodesc type %d in iblock()", idesc->id_type); /* NOTREACHED */ } if (chkrange(idesc->id_blkno, idesc->id_numfrags)) { return ((idesc->id_type == ACL) ? STOP : SKIP); } bp = getdatablk(idesc->id_blkno, (size_t)sblock.fs_bsize); if (bp->b_errs != 0) { brelse(bp); return (SKIP); } ilevel--; /* * Trivia note: the BSD fsck has the number of bytes remaining * as the third argument to iblock(), so the equivalent of * fsbperindirb starts at fs_bsize instead of one. We're * working in units of filesystem blocks here, not bytes or * fragments. */ for (fsbperindirb = 1, i = 0; i < ilevel; i++) { fsbperindirb *= (u_offset_t)NINDIR(&sblock); } /* * nif indicates the next "free" pointer (as an array index) in this * indirect block, based on counting the blocks remaining in the * file after subtracting all previously processed blocks. * This figure is based on the size field of the inode. * * Note that in normal operation, nif may initially be calculated * as larger than the number of pointers in this block (as when * there are more indirect blocks following); if that is * the case, nif is limited to the max number of pointers per * indirect block. * * Also note that if an inode is inconsistent (has more blocks * allocated to it than the size field would indicate), the sweep * through any indirect blocks directly pointed at by the inode * continues. Since the block offset of any data blocks referenced * by these indirect blocks is greater than the size of the file, * the index nif may be computed as a negative value. * In this case, we reset nif to indicate that all pointers in * this retrieval block should be zeroed and the resulting * unreferenced data and/or retrieval blocks will be recovered * through garbage collection later. */ nif = (offset_t)howmany(iblks, fsbperindirb); if (nif > NINDIR(&sblock)) nif = NINDIR(&sblock); else if (nif < 0) nif = 0; /* * first pass: all "free" retrieval pointers (from [nif] thru * the end of the indirect block) should be zero. (This * assertion does not hold for directories, which may be * truncated without releasing their allocated space) */ if (nif < NINDIR(&sblock) && (idesc->id_func == pass1check || idesc->id_func == pass3bcheck)) { for (i = nif; i < NINDIR(&sblock); i++) { if (bp->b_un.b_indir[i] == 0) continue; (void) sprintf(buf, "PARTIALLY TRUNCATED INODE I=%lu", (ulong_t)idesc->id_number); if (preen) { pfatal(buf); } else if (dofix(idesc, buf)) { freeblk(idesc->id_number, bp->b_un.b_indir[i], sblock.fs_frag); bp->b_un.b_indir[i] = 0; dirty(bp); } } flush(fswritefd, bp); } /* * second pass: all retrieval pointers referring to blocks within * a valid range [0..filesize] (both indirect and data blocks) * are examined in the same manner as ckinode() checks the * direct blocks in the inode. Sweep through from * the first pointer in this retrieval block to [nif-1]. */ last_lbn = howmany(idesc->id_filesize, sblock.fs_bsize); for (i = 0; i < nif; i++) { if (ilevel == 0) idesc->id_lbn++; if (bp->b_un.b_indir[i] != 0) { idesc->id_blkno = bp->b_un.b_indir[i]; if (ilevel > 0) { n = iblock(idesc, ilevel, iblks, action); /* * Each iteration decreases "remaining block * count" by the number of blocks accessible * by a pointer at this indirect block level. */ iblks -= fsbperindirb; } else { /* * If we're truncating, func will discard * the data block for us. */ n = (*func)(idesc); } if ((action == CKI_TRUNCATE) && (idesc->id_truncto >= 0) && (idesc->id_lbn >= idesc->id_truncto)) { freeblk(idesc->id_number, bp->b_un.b_indir[i], sblock.fs_frag); } /* * Note that truncation never gets STOP back * under normal circumstances. Abnormal would * be a bad acl short-circuit in iblock() or * an out-of-range failure in pass4check(). * We still want to keep going when truncating * under those circumstances, since the whole * point of truncating is to get rid of all * that. */ if ((n & STOP) && (action != CKI_TRUNCATE)) { brelse(bp); return (n); } } else { if ((idesc->id_lbn < last_lbn) && (idesc->id_firsthole < 0)) { idesc->id_firsthole = idesc->id_lbn; } if (idesc->id_type == DATA) { /* * No point in continuing in the indirect * blocks of a directory, since they'll just * get freed anyway. */ brelse(bp); return ((n & ~KEEPON) | STOP); } } } brelse(bp); return (KEEPON); } /* * Check that a block is a legal block number. * Return 0 if in range, 1 if out of range. */ int chkrange(daddr32_t blk, int cnt) { int c; if (cnt <= 0 || blk <= 0 || ((unsigned)blk >= (unsigned)maxfsblock) || ((cnt - 1) > (maxfsblock - blk))) { if (debug) (void) printf( "Bad fragment range: should be 1 <= %d..%d < %d\n", blk, blk + cnt, maxfsblock); return (1); } if ((cnt > sblock.fs_frag) || ((fragnum(&sblock, blk) + cnt) > sblock.fs_frag)) { if (debug) (void) printf("Bad fragment size: size %d\n", cnt); return (1); } c = dtog(&sblock, blk); if (blk < cgdmin(&sblock, c)) { if ((unsigned)(blk + cnt) > (unsigned)cgsblock(&sblock, c)) { if (debug) (void) printf( "Bad fragment position: %d..%d spans start of cg metadata\n", blk, blk + cnt); return (1); } } else { if ((unsigned)(blk + cnt) > (unsigned)cgbase(&sblock, c+1)) { if (debug) (void) printf( "Bad frag pos: %d..%d crosses end of cg\n", blk, blk + cnt); return (1); } } return (0); } /* * General purpose interface for reading inodes. */ /* * Note that any call to ginode() can potentially invalidate any * dinode pointers previously acquired from it. To avoid pain, * make sure to always call inodirty() immediately after modifying * an inode, if there's any chance of ginode() being called after * that. Also, always call ginode() right before you need to access * an inode, so that there won't be any surprises from functions * called between the previous ginode() invocation and the dinode * use. * * Despite all that, we aren't doing the amount of i/o that's implied, * as we use the buffer cache that getdatablk() and friends maintain. */ static fsck_ino_t startinum = -1; struct dinode * ginode(fsck_ino_t inum) { daddr32_t iblk; struct dinode *dp; if (inum < UFSROOTINO || inum > maxino) { errexit("bad inode number %d to ginode\n", inum); } if (startinum == -1 || pbp == NULL || inum < startinum || inum >= (fsck_ino_t)(startinum + (fsck_ino_t)INOPB(&sblock))) { iblk = itod(&sblock, inum); if (pbp != NULL) { brelse(pbp); } /* * We don't check for errors here, because we can't * tell our caller about it, and the zeros that will * be in the buffer are just as good as anything we * could fake. */ pbp = getdatablk(iblk, (size_t)sblock.fs_bsize); startinum = (fsck_ino_t)((inum / INOPB(&sblock)) * INOPB(&sblock)); } dp = &pbp->b_un.b_dinode[inum % INOPB(&sblock)]; if (dp->di_suid != UID_LONG) dp->di_uid = dp->di_suid; if (dp->di_sgid != GID_LONG) dp->di_gid = dp->di_sgid; return (dp); } /* * Special purpose version of ginode used to optimize first pass * over all the inodes in numerical order. It bypasses the buffer * system used by ginode(), etc in favour of reading the bulk of a * cg's inodes at one time. */ static fsck_ino_t nextino, lastinum; static int64_t readcnt, readpercg, fullcnt, inobufsize; static int64_t partialcnt, partialsize; static size_t lastsize; static struct dinode *inodebuf; static diskaddr_t currentdblk; static struct dinode *currentinode; struct dinode * getnextinode(fsck_ino_t inum) { size_t size; diskaddr_t dblk; static struct dinode *dp; if (inum != nextino++ || inum > maxino) errexit("bad inode number %d to nextinode\n", inum); /* * Will always go into the if() the first time we're called, * so dp will always be valid. */ if (inum >= lastinum) { readcnt++; dblk = fsbtodb(&sblock, itod(&sblock, lastinum)); currentdblk = dblk; if (readcnt % readpercg == 0) { if (partialsize > SIZE_MAX) errexit( "Internal error: partialsize overflow"); size = (size_t)partialsize; lastinum += partialcnt; } else { if (inobufsize > SIZE_MAX) errexit("Internal error: inobufsize overflow"); size = (size_t)inobufsize; lastinum += fullcnt; } /* * If fsck_bread() returns an error, it will already have * zeroed out the buffer, so we do not need to do so here. */ (void) fsck_bread(fsreadfd, (caddr_t)inodebuf, dblk, size); lastsize = size; dp = inodebuf; } currentinode = dp; return (dp++); } /* * Reread the current getnext() buffer. This allows for changing inodes * other than the current one via ginode()/inodirty()/inoflush(). * * Just reuses all the interesting variables that getnextinode() set up * last time it was called. This shouldn't get called often, so we don't * try to figure out if the caller's actually touched an inode in the * range we have cached. There could have been an arbitrary number of * them, after all. */ struct dinode * getnextrefresh(void) { if (inodebuf == NULL) { return (NULL); } inoflush(); (void) fsck_bread(fsreadfd, (caddr_t)inodebuf, currentdblk, lastsize); return (currentinode); } void resetinodebuf(void) { startinum = 0; nextino = 0; lastinum = 0; readcnt = 0; inobufsize = blkroundup(&sblock, INOBUFSIZE); fullcnt = inobufsize / sizeof (struct dinode); readpercg = sblock.fs_ipg / fullcnt; partialcnt = sblock.fs_ipg % fullcnt; partialsize = partialcnt * sizeof (struct dinode); if (partialcnt != 0) { readpercg++; } else { partialcnt = fullcnt; partialsize = inobufsize; } if (inodebuf == NULL && (inodebuf = (struct dinode *)malloc((unsigned)inobufsize)) == NULL) errexit("Cannot allocate space for inode buffer\n"); while (nextino < UFSROOTINO) (void) getnextinode(nextino); } void freeinodebuf(void) { if (inodebuf != NULL) { free((void *)inodebuf); } inodebuf = NULL; } /* * Routines to maintain information about directory inodes. * This is built during the first pass and used during the * second and third passes. * * Enter inodes into the cache. */ void cacheino(struct dinode *dp, fsck_ino_t inum) { struct inoinfo *inp; struct inoinfo **inpp; uint_t blks; blks = NDADDR + NIADDR; inp = (struct inoinfo *) malloc(sizeof (*inp) + (blks - 1) * sizeof (daddr32_t)); if (inp == NULL) errexit("Cannot increase directory list\n"); init_inoinfo(inp, dp, inum); /* doesn't touch i_nextlist or i_number */ inpp = &inphead[inum % numdirs]; inp->i_nextlist = *inpp; *inpp = inp; inp->i_number = inum; if (inplast == listmax) { listmax += 100; inpsort = (struct inoinfo **)realloc((void *)inpsort, (unsigned)listmax * sizeof (struct inoinfo *)); if (inpsort == NULL) errexit("cannot increase directory list"); } inpsort[inplast++] = inp; } /* * Look up an inode cache structure. */ struct inoinfo * getinoinfo(fsck_ino_t inum) { struct inoinfo *inp; inp = search_cache(inphead[inum % numdirs], inum); return (inp); } /* * Determine whether inode is in cache. */ int inocached(fsck_ino_t inum) { return (search_cache(inphead[inum % numdirs], inum) != NULL); } /* * Clean up all the inode cache structure. */ void inocleanup(void) { struct inoinfo **inpp; if (inphead == NULL) return; for (inpp = &inpsort[inplast - 1]; inpp >= inpsort; inpp--) { free((void *)(*inpp)); } free((void *)inphead); free((void *)inpsort); inphead = inpsort = NULL; } /* * Routines to maintain information about acl inodes. * This is built during the first pass and used during the * second and third passes. * * Enter acl inodes into the cache. */ void cacheacl(struct dinode *dp, fsck_ino_t inum) { struct inoinfo *aclp; struct inoinfo **aclpp; uint_t blks; blks = NDADDR + NIADDR; aclp = (struct inoinfo *) malloc(sizeof (*aclp) + (blks - 1) * sizeof (daddr32_t)); if (aclp == NULL) return; aclpp = &aclphead[inum % numacls]; aclp->i_nextlist = *aclpp; *aclpp = aclp; aclp->i_number = inum; aclp->i_isize = (offset_t)dp->di_size; aclp->i_blkssize = (size_t)(blks * sizeof (daddr32_t)); (void) memmove(&aclp->i_blks[0], &dp->di_db[0], aclp->i_blkssize); if (aclplast == aclmax) { aclmax += 100; aclpsort = (struct inoinfo **)realloc((char *)aclpsort, (unsigned)aclmax * sizeof (struct inoinfo *)); if (aclpsort == NULL) errexit("cannot increase acl list"); } aclpsort[aclplast++] = aclp; } /* * Generic cache search function. * ROOT is the first entry in a hash chain (the caller is expected * to have done the initial bucket lookup). KEY is what's being * searched for. * * Returns a pointer to the entry if it is found, NULL otherwise. */ static struct inoinfo * search_cache(struct inoinfo *element, fsck_ino_t key) { while (element != NULL) { if (element->i_number == key) break; element = element->i_nextlist; } return (element); } void inodirty(void) { dirty(pbp); } static void inoflush(void) { if (pbp != NULL) flush(fswritefd, pbp); } /* * Interactive wrapper for freeino(), for those times when we're * not sure if we should throw something away. */ void clri(struct inodesc *idesc, char *type, int verbose, int corrupting) { int need_parent; struct dinode *dp; if (statemap[idesc->id_number] == USTATE) return; dp = ginode(idesc->id_number); if (verbose == CLRI_VERBOSE) { pwarn("%s %s", type, file_id(idesc->id_number, dp->di_mode)); pinode(idesc->id_number); } if (preen || (reply("CLEAR") == 1)) { need_parent = (corrupting == CLRI_NOP_OK) ? TI_NOPARENT : TI_PARENT; freeino(idesc->id_number, need_parent); if (preen) (void) printf(" (CLEARED)\n"); remove_orphan_dir(idesc->id_number); } else if (corrupting == CLRI_NOP_CORRUPT) { iscorrupt = 1; } (void) printf("\n"); } /* * Find the directory entry for the inode noted in id_parent (which is * not necessarily the parent of anything, we're just using a convenient * field. */ int findname(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; if (dirp->d_ino != idesc->id_parent) return (KEEPON); (void) memmove(idesc->id_name, dirp->d_name, MIN(dirp->d_namlen, MAXNAMLEN) + 1); return (STOP|FOUND); } /* * Find the inode number associated with the given name. */ int findino(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; if (dirp->d_ino == 0) return (KEEPON); if (strcmp(dirp->d_name, idesc->id_name) == 0 && dirp->d_ino >= UFSROOTINO && dirp->d_ino <= maxino) { idesc->id_parent = dirp->d_ino; return (STOP|FOUND); } return (KEEPON); } int cleardirentry(fsck_ino_t parentdir, fsck_ino_t target) { struct inodesc idesc; struct dinode *dp; dp = ginode(parentdir); init_inodesc(&idesc); idesc.id_func = clearanentry; idesc.id_parent = target; idesc.id_type = DATA; idesc.id_fix = NOFIX; return (ckinode(dp, &idesc, CKI_TRAVERSE)); } static int clearanentry(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; if (dirp->d_ino != idesc->id_parent || idesc->id_entryno < 2) { idesc->id_entryno++; return (KEEPON); } dirp->d_ino = 0; return (STOP|FOUND|ALTERED); } void pinode(fsck_ino_t ino) { struct dinode *dp; (void) printf(" I=%lu ", (ulong_t)ino); if (ino < UFSROOTINO || ino > maxino) return; dp = ginode(ino); pdinode(dp); } static void pdinode(struct dinode *dp) { char *p; struct passwd *pw; time_t t; (void) printf(" OWNER="); if ((pw = getpwuid((int)dp->di_uid)) != 0) (void) printf("%s ", pw->pw_name); else (void) printf("%lu ", (ulong_t)dp->di_uid); (void) printf("MODE=%o\n", dp->di_mode); if (preen) (void) printf("%s: ", devname); (void) printf("SIZE=%lld ", (longlong_t)dp->di_size); /* ctime() ignores LOCALE, so this is safe */ t = (time_t)dp->di_mtime; p = ctime(&t); (void) printf("MTIME=%12.12s %4.4s ", p + 4, p + 20); } void blkerror(fsck_ino_t ino, char *type, daddr32_t blk, daddr32_t lbn) { pfatal("FRAGMENT %d %s I=%u LFN %d", blk, type, ino, lbn); (void) printf("\n"); switch (statemap[ino] & ~INDELAYD) { case FSTATE: case FZLINK: statemap[ino] = FCLEAR; return; case DFOUND: case DSTATE: case DZLINK: statemap[ino] = DCLEAR; add_orphan_dir(ino); return; case SSTATE: statemap[ino] = SCLEAR; return; case FCLEAR: case DCLEAR: case SCLEAR: return; default: errexit("BAD STATE 0x%x TO BLKERR\n", statemap[ino]); /* NOTREACHED */ } } /* * allocate an unused inode */ fsck_ino_t allocino(fsck_ino_t request, int type) { fsck_ino_t ino; struct dinode *dp; struct cg *cgp = &cgrp; int cg; time_t t; caddr_t err; if (debug && (request != 0) && (request != UFSROOTINO)) errexit("assertion failed: allocino() asked for " "inode %d instead of 0 or %d", (int)request, (int)UFSROOTINO); /* * We know that we're only going to get requests for UFSROOTINO * or 0. If UFSROOTINO is wanted, then it better be available * because our caller is trying to recreate the root directory. * If we're asked for 0, then which one we return doesn't matter. * We know that inodes 0 and 1 are never valid to return, so we * the start at the lowest-legal inode number. * * If we got a request for UFSROOTINO, then request != 0, and * this pair of conditionals is the only place that treats * UFSROOTINO specially. */ if (request == 0) request = UFSROOTINO; else if (statemap[request] != USTATE) return (0); /* * Doesn't do wrapping, since we know we started at * the smallest inode. */ for (ino = request; ino < maxino; ino++) if (statemap[ino] == USTATE) break; if (ino == maxino) return (0); /* * In pass5, we'll calculate the bitmaps and counts all again from * scratch and do a comparison, but for that to work the cg has * to know what in-memory changes we've made to it. If we have * trouble reading the cg, cg_sanity() should kick it out so * we can skip explicit i/o error checking here. */ cg = itog(&sblock, ino); (void) getblk(&cgblk, cgtod(&sblock, cg), (size_t)sblock.fs_cgsize); err = cg_sanity(cgp, cg); if (err != NULL) { pfatal("CG %d: %s\n", cg, err); free((void *)err); if (reply("REPAIR") == 0) errexit("Program terminated."); fix_cg(cgp, cg); } setbit(cg_inosused(cgp), ino % sblock.fs_ipg); cgp->cg_cs.cs_nifree--; cgdirty(); if (lastino < ino) lastino = ino; /* * Don't currently support IFATTRDIR or any of the other * types, as they aren't needed. */ switch (type & IFMT) { case IFDIR: statemap[ino] = DSTATE; cgp->cg_cs.cs_ndir++; break; case IFREG: case IFLNK: statemap[ino] = FSTATE; break; default: /* * Pretend nothing ever happened. This clears the * dirty flag, among other things. */ initbarea(&cgblk); if (debug) (void) printf("allocino: unknown type 0%o\n", type & IFMT); return (0); } /* * We're allocating what should be a completely-unused inode, * so make sure we don't inherit anything from any previous * incarnations. */ dp = ginode(ino); (void) memset((void *)dp, 0, sizeof (struct dinode)); dp->di_db[0] = allocblk(1); if (dp->di_db[0] == 0) { statemap[ino] = USTATE; return (0); } dp->di_mode = (mode_t)type; (void) time(&t); dp->di_atime = (time32_t)t; dp->di_ctime = dp->di_atime; dp->di_mtime = dp->di_ctime; dp->di_size = (u_offset_t)sblock.fs_fsize; dp->di_blocks = btodb(sblock.fs_fsize); n_files++; inodirty(); return (ino); } /* * Release some or all of the blocks of an inode. * Only truncates down. Assumes new_length is appropriately aligned * to a block boundary (or a directory block boundary, if it's a * directory). * * If this is a directory, discard all of its contents first, so * we don't create a bunch of orphans that would need another fsck * run to clean up. * * Even if truncating to zero length, the inode remains allocated. */ void truncino(fsck_ino_t ino, offset_t new_length, int update) { struct inodesc idesc; struct inoinfo *iip; struct dinode *dp; fsck_ino_t parent; mode_t mode; caddr_t message; int isdir, islink; int ilevel, dblk; dp = ginode(ino); mode = (dp->di_mode & IFMT); isdir = (mode == IFDIR) || (mode == IFATTRDIR); islink = (mode == IFLNK); if (isdir) { /* * Go with the parent we found by chasing references, * if we've gotten that far. Otherwise, use what the * directory itself claims. If there's no ``..'' entry * in it, give up trying to get the link counts right. */ if (update == TI_NOPARENT) { parent = -1; } else { iip = getinoinfo(ino); if (iip != NULL) { parent = iip->i_parent; } else { parent = lookup_dotdot_ino(ino); if (parent != 0) { /* * Make sure that the claimed * parent actually has a * reference to us. */ dp = ginode(parent); idesc.id_name = lfname; idesc.id_type = DATA; idesc.id_func = findino; idesc.id_number = ino; idesc.id_fix = DONTKNOW; if ((ckinode(dp, &idesc, CKI_TRAVERSE) & FOUND) == 0) parent = 0; } } } mark_delayed_inodes(ino, numfrags(&sblock, new_length)); if (parent > 0) { dp = ginode(parent); LINK_RANGE(message, dp->di_nlink, -1); if (message != NULL) { LINK_CLEAR(message, parent, dp->di_mode, &idesc); if (statemap[parent] == USTATE) goto no_parent_update; } TRACK_LNCNTP(parent, lncntp[parent]--); } else if ((mode == IFDIR) && (parent == 0)) { /* * Currently don't have a good way to * handle this, so throw up our hands. * However, we know that we can still * do some good if we continue, so * don't actually exit yet. * * We don't do it for attrdirs, * because there aren't link counts * between them and their parents. */ pwarn("Could not determine former parent of " "inode %d, link counts are possibly\n" "incorrect. Please rerun fsck(8) to " "correct this.\n", ino); iscorrupt = 1; } /* * ...else if it's a directory with parent == -1, then * we've not gotten far enough to know connectivity, * and it'll get handled automatically later. */ } no_parent_update: init_inodesc(&idesc); idesc.id_type = ADDR; idesc.id_func = pass4check; idesc.id_number = ino; idesc.id_fix = DONTKNOW; idesc.id_truncto = howmany(new_length, sblock.fs_bsize); dp = ginode(ino); if (!islink && ckinode(dp, &idesc, CKI_TRUNCATE) & ALTERED) inodirty(); /* * This has to be done after ckinode(), so that all of * the fragments get visited. Note that we assume we're * always truncating to a block boundary, rather than a * fragment boundary. */ dp = ginode(ino); dp->di_size = new_length; /* * Clear now-obsolete pointers. */ for (dblk = idesc.id_truncto + 1; dblk < NDADDR; dblk++) { dp->di_db[dblk] = 0; } ilevel = get_indir_offsets(-1, idesc.id_truncto, NULL, NULL); for (ilevel++; ilevel < NIADDR; ilevel++) { dp->di_ib[ilevel] = 0; } inodirty(); } /* * Release an inode's resources, then release the inode itself. */ void freeino(fsck_ino_t ino, int update_parent) { int cg; struct dinode *dp; struct cg *cgp; n_files--; dp = ginode(ino); /* * We need to make sure that the file is really a large file. * Everything bigger than UFS_MAXOFFSET_T is treated as a file with * negative size, which shall be cleared. (see verify_inode() in * pass1.c) */ if (dp->di_size > (u_offset_t)MAXOFF_T && dp->di_size <= (u_offset_t)UFS_MAXOFFSET_T && ftypeok(dp) && (dp->di_mode & IFMT) != IFBLK && (dp->di_mode & IFMT) != IFCHR) { largefile_count--; } truncino(ino, 0, update_parent); dp = ginode(ino); if ((dp->di_mode & IFMT) == IFATTRDIR) { clearshadow(ino, &attrclientinfo); dp = ginode(ino); } clearinode(dp); inodirty(); statemap[ino] = USTATE; /* * Keep the disk in sync with us so that pass5 doesn't get * upset about spurious inconsistencies. */ cg = itog(&sblock, ino); (void) getblk(&cgblk, (diskaddr_t)cgtod(&sblock, cg), (size_t)sblock.fs_cgsize); cgp = cgblk.b_un.b_cg; clrbit(cg_inosused(cgp), ino % sblock.fs_ipg); cgp->cg_cs.cs_nifree += 1; cgdirty(); sblock.fs_cstotal.cs_nifree += 1; sbdirty(); } void init_inoinfo(struct inoinfo *inp, struct dinode *dp, fsck_ino_t inum) { inp->i_parent = ((inum == UFSROOTINO) ? UFSROOTINO : (fsck_ino_t)0); inp->i_dotdot = (fsck_ino_t)0; inp->i_isize = (offset_t)dp->di_size; inp->i_blkssize = (NDADDR + NIADDR) * sizeof (daddr32_t); inp->i_extattr = dp->di_oeftflag; (void) memmove((void *)&inp->i_blks[0], (void *)&dp->di_db[0], inp->i_blkssize); } /* * Return the inode number in the ".." entry of the provided * directory inode. */ static int lookup_dotdot_ino(fsck_ino_t ino) { struct inodesc idesc; init_inodesc(&idesc); idesc.id_type = DATA; idesc.id_func = findino; idesc.id_name = ".."; idesc.id_number = ino; idesc.id_fix = NOFIX; if ((ckinode(ginode(ino), &idesc, CKI_TRAVERSE) & FOUND) != 0) { return (idesc.id_parent); } return (0); } /* * Convenience wrapper around ckinode(findino()). */ int lookup_named_ino(fsck_ino_t dir, caddr_t name) { struct inodesc idesc; init_inodesc(&idesc); idesc.id_type = DATA; idesc.id_func = findino; idesc.id_name = name; idesc.id_number = dir; idesc.id_fix = NOFIX; if ((ckinode(ginode(dir), &idesc, CKI_TRAVERSE) & FOUND) != 0) { return (idesc.id_parent); } return (0); } /* * Marks inodes that are being orphaned and might need to be reconnected * by pass4(). The inode we're traversing is the directory whose * contents will be reconnected later. id_parent is the lfn at which * to start looking at said contents. */ static int mark_a_delayed_inode(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; if (idesc->id_lbn < idesc->id_parent) { return (KEEPON); } if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") != 0 && strcmp(dirp->d_name, "..") != 0) { statemap[dirp->d_ino] &= ~INFOUND; statemap[dirp->d_ino] |= INDELAYD; } return (KEEPON); } static void mark_delayed_inodes(fsck_ino_t ino, daddr32_t first_lfn) { struct dinode *dp; struct inodesc idelayed; init_inodesc(&idelayed); idelayed.id_number = ino; idelayed.id_type = DATA; idelayed.id_fix = NOFIX; idelayed.id_func = mark_a_delayed_inode; idelayed.id_parent = first_lfn; idelayed.id_entryno = 2; dp = ginode(ino); (void) ckinode(dp, &idelayed, CKI_TRAVERSE); } /* * Clear the i_oeftflag/extended attribute pointer from INO. */ void clearattrref(fsck_ino_t ino) { struct dinode *dp; dp = ginode(ino); if (debug) { if (dp->di_oeftflag == 0) (void) printf("clearattref: no attr to clear on %d\n", ino); } dp->di_oeftflag = 0; inodirty(); } /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 '`AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* * In-core structures: * blockmap[] * A bitmap of block usage very similar to what's on disk, but * for the entire filesystem rather than just a cylinder group. * Zero indicates free, one indicates allocated. Note that this * is opposite the interpretation of a cylinder group's free block * bitmap. * * statemap[] * Tracks what is known about each inode in the filesystem. * The fundamental state value is one of USTATE, FSTATE, DSTATE, * or SSTATE (unallocated, file, directory, shadow/acl). * * There are optional modifying attributes as well: INZLINK, * INFOUND, INCLEAR, INORPHAN, and INDELAYD. The IN prefix * stands for inode. INZLINK declares that no links (di_nlink == * 0) to the inode have been found. It is used instead of * examining di_nlink because we've always got the statemap[] in * memory, and on average the odds are against having any given * inode in the cache. INFOUND flags that an inode was * encountered during the descent of the filesystem. In other * words, it's reachable, either by name or by being an acl or * attribute. INCLEAR declares an intent to call clri() on an * inode. The INCLEAR and INZLINK attributes are treated in a * mutually exclusive manner with INCLEAR taking higher precedence * as the intent is to clear the inode. * * INORPHAN indicates that the inode has already been seen once * in pass3 and determined to be an orphan, so any additional * encounters don't need to waste cycles redetermining that status. * It also means we don't ask the user about doing something to the * inode N times. * * INDELAYD marks inodes that pass1 determined needed to be truncated. * They can't be truncated during that pass, because it depends on * having a stable world for building the block and inode tables from. * * The IN flags rarely used directly, but instead are * pre-combined through the {D,F,S}ZLINK, DFOUND, and * {D,F,S}CLEAR convenience macros. This mainly matters when * trying to use grep on the source. * * Three state-test macros are provided: S_IS_DUNFOUND(), * S_IS_DVALID(), and S_IS_ZLINK(). The first is true when an * inode's state indicates that it is either a simple directory * (DSTATE without the INFOUND or INCLEAR modifiers) or a * directory with the INZLINK modifier set. By definition, if a * directory has zero links, then it can't be found. As for * S_IS_DVALID(), it decides if a directory inode is alive. * Effectively, this translates to whether or not it's been * flagged for clearing. If not, then it's valid for current * purposes. This is true even if INZLINK is set, as we may find * a reference to it later. Finally, S_IS_ZLINK() just picks out * the INZLINK flag from the state. * * The S_*() macros all work on a state value. To simplify a * bit, the INO_IS_{DUNFOUND,DVALID}() macros take an inode * number argument. The inode is looked up in the statemap[] and * the result handed off to the corresponding S_*() macro. This * is partly a holdover from working with different data * structures (with the same net intent) in the BSD fsck. * * lncntp * Each entry is initialized to the di_link from the on-disk * inode. Each time we find one of those links, we decrement it. * Once all the traversing is done, we should have a zero. If we * have a positive value, then some reference disappeared * (probably from a directory that got nuked); deal with it by * fixing the count. If we have a negative value, then we found * an extra reference. This is a can't-happen, except in the * special case of when we reconnect a directory to its parent or * to lost+found. An exact match between lncntp[] and the on-disk * inode means it's completely unreferenced. * * aclphead * This is a hash table of the acl inodes in the filesystem. * * aclpsort * The same acls as in aclphead, but as a simple linear array. * It is used to hold the acl pointers for sorting and scanning * in pass3b. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fsck.h" static void usage(void) __NORETURN; static long argtol(int, char *, char *, int); static void checkfilesys(char *); static void check_sanity(char *); static void report_limbo(const void *, VISIT, int); #define QUICK_CHECK 'm' /* are things ok according to superblock? */ #define ALL_no 'n' /* auto-answer interactive questions `no' */ #define ALL_NO 'N' /* auto-answer interactive questions `no' */ #define UFS_OPTS 'o' /* ufs-specific options, see subopts[] */ #define ECHO_CMD 'V' /* echo the command line */ #define ALL_yes 'y' /* auto-answer interactive questions `yes' */ #define ALL_YES 'Y' /* auto-answer interactive questions `yes' */ #define VERBOSE 'v' /* be chatty */ static char *subopts[] = { #define PREEN 0 /* non-interactive mode (parent is parallel) */ "p", #define BLOCK 1 /* alternate superblock */ "b", #define DEBUG 2 /* yammer */ "d", #define ONLY_WRITES 3 /* check all writable filesystems */ "w", #define FORCE 4 /* force checking, even if clean */ "f", NULL }; /* * Filesystems that are `magical' - if they exist in vfstab, * then they have to be mounted for the system to have gotten * far enough to be able to run fsck. Thus, don't get all * bent out of shape if we're asked to check it and it is mounted. */ char *magic_fs[] = { "", /* MAGIC_NONE, for normal filesystems */ "/", /* MAGIC_ROOT */ "/usr", /* MAGIC_USR */ NULL /* MAGIC_LIMIT */ }; daddr32_t bflag; daddr32_t n_blks; daddr32_t maxfsblock; int debug; int errorlocked; int exitstat; int fflag; int fsmodified; int fswritefd; int iscorrupt; int islog; int islogok; int interrupted; int mflag; int mountfd; int overflowed_lf; int rflag; int reattached_dir; int broke_dir_link; int verbose; char hotroot; char mountedfs; char nflag; char preen; char rerun; char *blockmap; char *devname; char yflag; short *lncntp; ushort_t *statemap; fsck_ino_t maxino; fsck_ino_t countdirs; fsck_ino_t n_files; void *limbo_dirs; int main(int argc, char *argv[]) { int c; int wflag = 0; char *suboptions, *value; struct rlimit rlimit; extern int optind; extern char *optarg; while ((c = getopt(argc, argv, "mnNo:VvyY")) != EOF) { switch (c) { case QUICK_CHECK: mflag++; break; case ALL_no: case ALL_NO: nflag++; yflag = 0; break; case VERBOSE: verbose++; break; case UFS_OPTS: /* * ufs specific options. */ if (optarg == NULL) { usage(); } suboptions = optarg; while (*suboptions != '\0') { switch (getsubopt(&suboptions, subopts, &value)) { case PREEN: preen++; break; case BLOCK: bflag = argtol(BLOCK, "block", value, 10); (void) printf("Alternate super block " "location: %ld.\n", (long)bflag); break; case DEBUG: debug++; verbose++; break; case ONLY_WRITES: /* check only writable filesystems */ wflag++; break; case FORCE: fflag++; break; default: usage(); } } break; case ECHO_CMD: { int opt_count; char *opt_text; (void) printf("fsck -F ufs "); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) printf("%s ", opt_text); } (void) printf("\n"); } break; case ALL_yes: case ALL_YES: yflag++; nflag = 0; break; default: usage(); } } argc -= optind; argv += optind; if (argc == 0) usage(); rflag++; /* check raw devices where we can */ if (signal(SIGINT, SIG_IGN) != SIG_IGN) (void) signal(SIGINT, catch); if (preen) (void) signal(SIGQUIT, catchquit); /* * Push up our allowed memory limit so we can cope * with huge file systems. */ if (getrlimit(RLIMIT_DATA, &rlimit) == 0) { rlimit.rlim_cur = rlimit.rlim_max; (void) setrlimit(RLIMIT_DATA, &rlimit); } /* * There are a lot of places where we just exit if a problem is * found. This means that we won't necessarily check everything * we were asked to. It would be nice to do everything, and * then provide a summary when we're done. However, the * interface doesn't really allow us to do that in any useful * way. So, we'll just bail on the first unrecoverable * problem encountered. If we've been run by the generic * wrapper, we were only given one filesystem to check, so the * multi-fs case implies being run manually; that means the * user can rerun us on the remaining filesystems when it's * convenient for them. */ while (argc-- > 0) { if (wflag && !writable(*argv)) { (void) fprintf(stderr, "not writeable '%s'\n", *argv); argv++; if (exitstat == 0) exitstat = EXBADPARM; } else { checkfilesys(*argv++); } } if (interrupted) exitstat = EXSIGNAL; exit(exitstat); } /* * A relatively intelligent strtol(). Note that if str is NULL, we'll * exit, so ret does not actually need to be pre-initialized. Lint * doesn't believe this, and it's harmless enough to make lint happy here. */ static long argtol(int flag, char *req, char *str, int base) { char *cp = str; long ret = -1; errno = 0; if (str != NULL) ret = strtol(str, &cp, base); if (cp == str || *cp) { (void) fprintf(stderr, "-%c flag requires a %s\n", flag, req); exit(EXBADPARM); } if (errno != 0) { (void) fprintf(stderr, "-%c %s value out of range\n", flag, req); } return (ret); } /* * Check the specified file system. */ static void checkfilesys(char *filesys) { daddr32_t n_ffree, n_bfree; char *devstr; fsck_ino_t files; daddr32_t blks; fsck_ino_t inumber; int zlinks_printed; fsck_ino_t limbo_victim; double dbl_nffree, dbl_dsize; int quiet_dups; mountfd = -1; hotroot = 0; mountedfs = M_NOMNT; reattached_dir = 0; broke_dir_link = 0; iscorrupt = 1; /* assume failure in setup() */ islog = 0; islogok = 0; overflowed_lf = 0; errorlocked = is_errorlocked(filesys); limbo_dirs = NULL; if ((devstr = setup(filesys)) == NULL) { if (!iscorrupt) { return; } if (preen) pfatal("CAN'T CHECK FILE SYSTEM."); if (exitstat == 0) exitstat = mflag ? EXUMNTCHK : EXERRFATAL; exit(exitstat); } else { devname = devstr; } if (mflag) { check_sanity(filesys); /* NOTREACHED */ } if (debug) printclean(); iscorrupt = 0; /* setup() succeeded, assume good filesystem */ /* * 1: scan inodes tallying blocks used */ if (!preen) { /* hotroot is reported as such in setup() if debug is on */ if (mountedfs != M_NOMNT) (void) printf("** Currently Mounted on %s\n", sblock.fs_fsmnt); else (void) printf("** Last Mounted on %s\n", sblock.fs_fsmnt); (void) printf("** Phase 1 - Check Blocks and Sizes\n"); } pass1(); /* * 1b: locate first references to duplicates, if any */ if (have_dups()) { if (preen) pfatal("INTERNAL ERROR: dups with -o p"); (void) printf("** Phase 1b - Rescan For More DUPS\n"); pass1b(); } /* * 2: traverse directories from root to mark all connected directories */ if (!preen) (void) printf("** Phase 2 - Check Pathnames\n"); pass2(); /* * 3a: scan inodes looking for disconnected directories. */ if (!preen) (void) printf("** Phase 3a - Check Connectivity\n"); pass3a(); /* * 3b: check acls */ if (!preen) (void) printf("** Phase 3b - Verify Shadows/ACLs\n"); pass3b(); /* * 4: scan inodes looking for disconnected files; check reference counts */ if (!preen) (void) printf("** Phase 4 - Check Reference Counts\n"); pass4(); /* * 5: check and repair resource counts in cylinder groups */ if (!preen) (void) printf("** Phase 5 - Check Cylinder Groups\n"); recount: pass5(); if (overflowed_lf) { iscorrupt = 1; } if (!nflag && mountedfs == M_RW) { (void) printf("FILESYSTEM MAY STILL BE INCONSISTENT.\n"); rerun = 1; } if (have_dups()) { quiet_dups = (reply("LIST REMAINING DUPS") == 0); if (report_dups(quiet_dups) > 0) iscorrupt = 1; (void) printf("WARNING: DATA LOSS MAY HAVE OCCURRED DUE TO " "DUP BLOCKS.\nVERIFY FILE CONTENTS BEFORE USING.\n"); } if (limbo_dirs != NULL) { /* * Don't force iscorrupt, as this is sufficiently * harmless that the filesystem can be mounted and * used. We just leak some inodes and/or blocks. */ pwarn("Orphan directories not cleared or reconnected:\n"); twalk(limbo_dirs, report_limbo); while (limbo_dirs != NULL) { limbo_victim = *(fsck_ino_t *)limbo_dirs; if (limbo_victim != 0) { (void) tdelete((void *)limbo_victim, &limbo_dirs, ino_t_cmp); } } rerun = 1; } if (iscorrupt) { if (mountedfs == M_RW) (void) printf("FS IS MOUNTED R/W AND" " FSCK DID ITS BEST TO FIX" " INCONSISTENCIES.\n"); else (void) printf("FILESYSTEM MAY STILL BE" " INCONSISTENT.\n"); rerun = 1; } /* * iscorrupt must be stable at this point. * updateclean() returns true when it had to discard the log. * This can only happen once, since sblock.fs_logbno gets * cleared as part of that operation. */ if (updateclean()) { if (!preen) (void) printf( "Log was discarded, updating cyl groups\n"); goto recount; } if (debug) printclean(); ckfini(); /* * print out summary statistics */ n_ffree = sblock.fs_cstotal.cs_nffree; n_bfree = sblock.fs_cstotal.cs_nbfree; files = maxino - UFSROOTINO - sblock.fs_cstotal.cs_nifree - n_files; blks = n_blks + sblock.fs_ncg * (cgdmin(&sblock, 0) - cgsblock(&sblock, 0)); blks += cgsblock(&sblock, 0) - cgbase(&sblock, 0); blks += howmany(sblock.fs_cssize, sblock.fs_fsize); blks = maxfsblock - (n_ffree + sblock.fs_frag * n_bfree) - blks; if (debug && (files > 0 || blks > 0)) { countdirs = sblock.fs_cstotal.cs_ndir - countdirs; pwarn("Reclaimed: %d directories, %d files, %lld fragments\n", countdirs, files - countdirs, (longlong_t)blks); } dbl_nffree = (double)n_ffree; dbl_dsize = (double)sblock.fs_dsize; if (!verbose) { /* * Done as one big string to try for a single write, * so the output doesn't get interleaved with other * preening fscks. */ pwarn("%ld files, %lld used, %lld free " "(%lld frags, %lld blocks, %.1f%% fragmentation)\n", (long)n_files, (longlong_t)n_blks, (longlong_t)n_ffree + sblock.fs_frag * n_bfree, (longlong_t)n_ffree, (longlong_t)n_bfree, (dbl_nffree * 100.0) / dbl_dsize); } else { pwarn("\nFilesystem summary:\n"); pwarn("Inodes in use: %ld\n", (long)n_files); pwarn("Blocks in use: %lld\n", (longlong_t)n_blks); pwarn("Total free fragments: %lld\n", (longlong_t)n_ffree + sblock.fs_frag * n_bfree); pwarn("Free fragments not in blocks: %lld\n", (longlong_t)n_ffree); pwarn("Total free blocks: %lld\n", (longlong_t)n_bfree); pwarn("Fragment/block fragmentation: %.1f%%\n", (dbl_nffree * 100.0) / dbl_dsize); pwarn(""); if (files < 0) pwarn("%d inodes missing\n", -files); if (blks < 0) pwarn("%lld blocks missing\n", -(longlong_t)blks); zlinks_printed = 0; for (inumber = UFSROOTINO; inumber < maxino; inumber++) { if (S_IS_ZLINK(statemap[inumber])) { if (zlinks_printed == 0) { pwarn("The following zero " "link count inodes remain:"); } if (zlinks_printed) { if ((zlinks_printed % 9) == 0) (void) puts(",\n"); else (void) puts(", "); } (void) printf("%u", inumber); zlinks_printed++; } } if ((zlinks_printed != 0) && ((zlinks_printed % 9) != 0)) (void) putchar('\n'); } /* * Clean up after ourselves, so we can do the next filesystem. */ free_dup_state(); inocleanup(); free(blockmap); free(statemap); free((void *)lncntp); lncntp = NULL; blockmap = NULL; statemap = NULL; if (iscorrupt && exitstat == 0) exitstat = EXFNDERRS; if (fsmodified) (void) printf("\n***** FILE SYSTEM WAS MODIFIED *****\n"); if (overflowed_lf) (void) printf("\n***** %s FULL, MUST REMOVE ENTRIES *****\n", lfname); if (reattached_dir) { (void) printf("ORPHANED DIRECTORIES REATTACHED; DIR LINK " "COUNTS MAY NOT BE CORRECT.\n"); rerun = 1; } if (broke_dir_link) { (void) printf( "DIRECTORY HARDLINK BROKEN; LOOPS MAY STILL EXIST.\n"); rerun = 1; } if (iscorrupt) (void) printf("***** FILE SYSTEM IS BAD *****\n"); if (rerun) { if (mountedfs == M_RW) (void) printf("\n***** PLEASE RERUN FSCK ON UNMOUNTED" " FILE SYSTEM *****\n"); else (void) printf("\n***** PLEASE RERUN FSCK *****\n"); } if ((exitstat == 0) && (((mountedfs != M_NOMNT) && !errorlocked) || hotroot)) { exitstat = EXROOTOKAY; } if ((exitstat == 0) && rerun) exitstat = EXFNDERRS; if (mountedfs != M_NOMNT) { if (!fsmodified) return; /* * _FIOFFS is much more effective than a simple sync(). * Note that the original fswritefd was discarded in * ckfini(). */ fswritefd = open(devstr, O_RDWR, 0); if (fswritefd != -1) { (void) ioctl(fswritefd, _FIOFFS, NULL); (void) close(fswritefd); } if (!preen) (void) printf("\n***** REBOOT NOW *****\n"); exitstat = EXREBOOTNOW; } } /* * fsck -m: does the filesystem pass cursory examination * * XXX This is very redundant with setup(). The right thing would be * for setup() to modify its behaviour when mflag is set (less * chatty, exit instead of return, etc). */ void check_sanity(char *filename) { struct stat64 stbd, stbr; char *devname; struct ustat usb; char vfsfilename[MAXPATHLEN]; struct vfstab vfsbuf; FILE *vfstab; struct statvfs vfs_stat; int found_magic[MAGIC_LIMIT]; int magic_cnt; int is_magic = 0; int is_block = 0; int is_file = 0; (void) memset((void *)found_magic, 0, sizeof (found_magic)); if (stat64(filename, &stbd) < 0) { (void) fprintf(stderr, "ufs fsck: sanity check failed : cannot stat %s\n", filename); exit(EXNOSTAT); } if (S_ISBLK(stbd.st_mode)) { is_block = 1; } else if (S_ISCHR(stbd.st_mode)) { is_block = 0; } else if (S_ISREG(stbd.st_mode)) { is_file = 1; } /* * Determine if this is the root file system via vfstab. Give up * silently on failures. The whole point of this is to be tolerant * of the magic file systems being already mounted. */ if (!is_file && (vfstab = fopen(VFSTAB, "r")) != NULL) { for (magic_cnt = 0; magic_cnt < MAGIC_LIMIT; magic_cnt++) { if (magic_cnt == MAGIC_NONE) continue; if (getvfsfile(vfstab, &vfsbuf, magic_fs[magic_cnt]) == 0) { if (is_block) devname = vfsbuf.vfs_special; else devname = vfsbuf.vfs_fsckdev; if (stat64(devname, &stbr) == 0) { if (stbr.st_rdev == stbd.st_rdev) { found_magic[magic_cnt] = 1; is_magic = magic_cnt; break; } } } } } /* * Only works if filename is a block device or if * character and block device has the same dev_t value. * This is currently true, but nothing really forces it. */ if (!is_magic && (ustat(stbd.st_rdev, &usb) == 0)) { (void) fprintf(stderr, "ufs fsck: sanity check: %s already mounted\n", filename); exit(EXMOUNTED); } if (is_magic) { (void) strcpy(vfsfilename, magic_fs[is_magic]); if (statvfs(vfsfilename, &vfs_stat) != 0) { (void) fprintf(stderr, "ufs fsck: Cannot stat %s\n", vfsfilename); exit(EXNOSTAT); } if (!(vfs_stat.f_flag & ST_RDONLY)) { /* * The file system is mounted read/write * We need to exit saying this. If it's only * mounted readonly, we can continue. */ (void) fprintf(stderr, "ufs fsck: sanity check:" "%s already mounted read/write\n", filename); exit(EXMOUNTED); } } /* * We know that at boot, the ufs root file system is mounted * read-only first. After fsck runs, it is remounted as * read-write. Therefore, we do not need to check for different * values for fs_state between the root file system and the * rest of the file systems. */ if (islog && !islogok) { (void) fprintf(stderr, "ufs fsck: sanity check: %s needs checking\n", filename); exit(EXUMNTCHK); } if ((sblock.fs_state + (long)sblock.fs_time == FSOKAY) && (sblock.fs_clean == FSCLEAN || sblock.fs_clean == FSSTABLE || (sblock.fs_clean == FSLOG && islog))) { (void) fprintf(stderr, "ufs fsck: sanity check: %s okay\n", filename); } else { (void) fprintf(stderr, "ufs fsck: sanity check: %s needs checking\n", filename); exit(EXUMNTCHK); } exit(EXOKAY); } caddr_t hasvfsopt(struct vfstab *vfs, char *opt) { struct mnttab mtab; if (vfs->vfs_mntopts == NULL) return (NULL); mtab.mnt_mntopts = vfs->vfs_mntopts; return (hasmntopt(&mtab, opt)); } static void __NORETURN usage(void) { (void) fprintf(stderr, "ufs usage: fsck [-F ufs] [-m] [-n] [-V] [-v] [-y] " "[-o p,b=#,w,f] [special ....]\n"); exit(EXBADPARM); } /*ARGSUSED*/ static void report_limbo(const void *node, VISIT order, int level) { fsck_ino_t ino = *(fsck_ino_t *)node; if ((order == postorder) || (order == leaf)) { (void) printf(" Inode %d\n", ino); } } /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include #include "fsck.h" /* * for each large file (size > MAXOFF_T), the global largefile_count * gets incremented during this pass. */ static uint32_t badblk; /* number seen for the current inode */ static uint32_t dupblk; /* number seen for the current inode */ static void clear_attr_acl(fsck_ino_t, fsck_ino_t, char *); static void verify_inode(fsck_ino_t, struct inodesc *, fsck_ino_t); static void check_dirholes(fsck_ino_t, struct inodesc *); static void collapse_dirhole(fsck_ino_t, struct inodesc *); static void note_used(daddr32_t); void pass1(void) { uint_t c, i; daddr32_t cgd; struct inodesc idesc; fsck_ino_t inumber; fsck_ino_t maxinumber; /* * Set file system reserved blocks in used block map. */ for (c = 0; c < sblock.fs_ncg; c++) { cgd = cgdmin(&sblock, c); if (c == 0) { /* * Doing the first cylinder group, account for * the cg summaries as well. */ i = cgbase(&sblock, c); cgd += howmany(sblock.fs_cssize, sblock.fs_fsize); } else { i = cgsblock(&sblock, c); } for (; i < cgd; i++) { note_used(i); } } /* * Note blocks being used by the log, so we don't declare * them as available and some time in the future we get a * freeing free block panic. */ if (islog && islogok && sblock.fs_logbno) examinelog(¬e_used); /* * Find all allocated blocks. This must be completed before * we read the contents of any directories, as dirscan() et al * don't want to know about block allocation holes. So, part * of this pass is to truncate any directories with holes to * just before those holes, so dirscan() can remain blissfully * ignorant. */ inumber = 0; n_files = n_blks = 0; resetinodebuf(); maxinumber = sblock.fs_ncg * sblock.fs_ipg; for (c = 0; c < sblock.fs_ncg; c++) { for (i = 0; i < sblock.fs_ipg; i++, inumber++) { if (inumber < UFSROOTINO) continue; init_inodesc(&idesc); idesc.id_type = ADDR; idesc.id_func = pass1check; verify_inode(inumber, &idesc, maxinumber); } } freeinodebuf(); } /* * Perform checks on an inode and setup/track the state of the inode * in maps (statemap[], lncntp[]) for future reference and validation. * Initiate the calls to ckinode and in turn pass1check() to handle * further validation. */ static void verify_inode(fsck_ino_t inumber, struct inodesc *idesc, fsck_ino_t maxinumber) { int j, clear, flags; int isdir; char *err; fsck_ino_t shadow, attrinode; daddr32_t ndb; struct dinode *dp; struct inoinfo *iip; dp = getnextinode(inumber); if ((dp->di_mode & IFMT) == 0) { /* mode and type of file is not set */ if ((memcmp((void *)dp->di_db, (void *)zino.di_db, NDADDR * sizeof (daddr32_t)) != 0) || (memcmp((void *)dp->di_ib, (void *)zino.di_ib, NIADDR * sizeof (daddr32_t)) != 0) || (dp->di_mode != 0) || (dp->di_size != 0)) { pfatal("PARTIALLY ALLOCATED INODE I=%u", inumber); if (reply("CLEAR") == 1) { dp = ginode(inumber); clearinode(dp); inodirty(); } else { iscorrupt = 1; } } statemap[inumber] = USTATE; return; } isdir = ((dp->di_mode & IFMT) == IFDIR) || ((dp->di_mode & IFMT) == IFATTRDIR); lastino = inumber; if (dp->di_size > (u_offset_t)UFS_MAXOFFSET_T) { pfatal("NEGATIVE SIZE %lld I=%d", (longlong_t)dp->di_size, inumber); goto bogus; } /* * A more precise test of the type is done later on. Just get * rid of the blatantly-wrong ones before we do any * significant work. */ if ((dp->di_mode & IFMT) == IFMT) { pfatal("BAD MODE 0%o I=%d", dp->di_mode & IFMT, inumber); if (reply("BAD MODE: MAKE IT A FILE") == 1) { statemap[inumber] = FSTATE; dp = ginode(inumber); dp->di_mode = IFREG | 0600; inodirty(); truncino(inumber, sblock.fs_fsize, TI_NOPARENT); dp = getnextrefresh(); } else { iscorrupt = 1; } } ndb = howmany(dp->di_size, (u_offset_t)sblock.fs_bsize); if (ndb < 0) { /* extra space to distinguish from previous pfatal() */ pfatal("NEGATIVE SIZE %lld I=%d", (longlong_t)dp->di_size, inumber); goto bogus; } if ((dp->di_mode & IFMT) == IFBLK || (dp->di_mode & IFMT) == IFCHR) { if (dp->di_size != 0) { pfatal("SPECIAL FILE WITH NON-ZERO LENGTH %lld I=%d", (longlong_t)dp->di_size, inumber); goto bogus; } for (j = 0; j < NDADDR; j++) { /* * It's a device, so all the block pointers * should be zero except for di_ordev. * di_ordev is overlayed on the block array, * but where varies between big and little * endian, so make sure that the only non-zero * element is the correct one. There can be * a device whose ordev is zero, so we can't * check for the reverse. */ if (dp->di_db[j] != 0 && &dp->di_db[j] != &dp->di_ordev) { if (debug) { (void) printf( "spec file di_db[%d] has %d\n", j, dp->di_db[j]); } pfatal( "SPECIAL FILE WITH NON-ZERO FRAGMENT LIST I=%d", inumber); goto bogus; } } for (j = 0; j < NIADDR; j++) { if (dp->di_ib[j] != 0) { if (debug) (void) printf( "special has %d at ib[%d]\n", dp->di_ib[j], j); pfatal( "SPECIAL FILE WITH NON-ZERO FRAGMENT LIST I=%d", inumber); goto bogus; } } } else { /* * This assignment is mostly here to appease lint, but * doesn't hurt. */ err = "Internal error: unexpected variant of having " "blocks past end of file I=%d"; clear = 0; /* * If it's not a device, it has to follow the * rules for files. In particular, no blocks after * the last one that di_size says is in use. */ for (j = ndb; j < NDADDR; j++) { if (dp->di_db[j] != 0) { if (debug) { (void) printf("bad file direct " "addr[%d]: block 0x%x " "format: 0%o\n", j, dp->di_db[j], dp->di_mode & IFMT); } err = "FILE WITH FRAGMENTS PAST END I=%d"; clear = 1; break; } } /* * Find last indirect pointer that should be in use, * and make sure any after it are clear. */ if (!clear) { for (j = 0, ndb -= NDADDR; ndb > 0; j++) { ndb /= NINDIR(&sblock); } for (; j < NIADDR; j++) { if (dp->di_ib[j] != 0) { if (debug) { (void) printf("bad file " "indirect addr: block %d\n", dp->di_ib[j]); } err = "FILE WITH FRAGMENTS PAST END I=%d"; clear = 2; break; } } } if (clear) { /* * The discarded blocks will be garbage- * collected in pass5. If we're told not to * discard them, it's just lost blocks, which * isn't worth setting iscorrupt for. */ pwarn(err, inumber); if (preen || reply("DISCARD EXCESS FRAGMENTS") == 1) { dp = ginode(inumber); if (clear == 1) { for (; j < NDADDR; j++) dp->di_db[j] = 0; j = 0; } for (; j < NIADDR; j++) dp->di_ib[j] = 0; inodirty(); dp = getnextrefresh(); if (preen) (void) printf(" (TRUNCATED)"); } } } if (ftypeok(dp) == 0) { pfatal("UNKNOWN FILE TYPE 0%o I=%d", dp->di_mode, inumber); goto bogus; } n_files++; TRACK_LNCNTP(inumber, lncntp[inumber] = dp->di_nlink); /* * We can't do anything about it right now, so note that its * processing is being delayed. Otherwise, we'd be changing * the block allocations out from under ourselves, which causes * no end of confusion. */ flags = statemap[inumber] & INDELAYD; /* * if errorlocked or logging, then open deleted files will * manifest as di_nlink <= 0 and di_mode != 0 * so skip them; they're ok. * Also skip anything already marked to be cleared. */ if (dp->di_nlink <= 0 && !((errorlocked || islog) && dp->di_mode == 0) && !(flags & INCLEAR)) { flags |= INZLINK; if (debug) (void) printf( "marking i=%d INZLINK; nlink %d, mode 0%o, islog %d\n", inumber, dp->di_nlink, dp->di_mode, islog); } switch (dp->di_mode & IFMT) { case IFDIR: case IFATTRDIR: if (dp->di_size == 0) { /* * INCLEAR means it will be ignored by passes 2 & 3. */ if ((dp->di_mode & IFMT) == IFDIR) (void) printf("ZERO-LENGTH DIR I=%d\n", inumber); else (void) printf("ZERO-LENGTH ATTRDIR I=%d\n", inumber); add_orphan_dir(inumber); flags |= INCLEAR; flags &= ~INZLINK; /* It will be cleared anyway */ } statemap[inumber] = DSTATE | flags; cacheino(dp, inumber); countdirs++; break; case IFSHAD: if (dp->di_size == 0) { (void) printf("ZERO-LENGTH SHADOW I=%d\n", inumber); flags |= INCLEAR; flags &= ~INZLINK; /* It will be cleared anyway */ } statemap[inumber] = SSTATE | flags; cacheacl(dp, inumber); break; default: statemap[inumber] = FSTATE | flags; } badblk = 0; dupblk = 0; idesc->id_number = inumber; idesc->id_fix = DONTKNOW; if (dp->di_size > (u_offset_t)MAXOFF_T) { largefile_count++; } (void) ckinode(dp, idesc, CKI_TRAVERSE); if (isdir && (idesc->id_firsthole >= 0)) check_dirholes(inumber, idesc); if (dp->di_blocks != idesc->id_entryno) { /* * The kernel releases any blocks it finds in the lists, * ignoring the block count itself. So, a bad count is * not grounds for setting iscorrupt. */ pwarn("INCORRECT DISK BLOCK COUNT I=%u (%d should be %d)", inumber, (uint32_t)dp->di_blocks, idesc->id_entryno); if (!preen && (reply("CORRECT") == 0)) return; dp = ginode(inumber); dp->di_blocks = idesc->id_entryno; iip = getinoinfo(inumber); if (iip != NULL) iip->i_isize = dp->di_size; inodirty(); if (preen) (void) printf(" (CORRECTED)\n"); } if (isdir && (dp->di_blocks == 0)) { /* * INCLEAR will cause passes 2 and 3 to skip it. */ (void) printf("DIR WITH ZERO BLOCKS I=%d\n", inumber); statemap[inumber] = DCLEAR; add_orphan_dir(inumber); } /* * Check that the ACL is on a valid file type */ shadow = dp->di_shadow; if (shadow != 0) { if (acltypeok(dp) == 0) { clear_attr_acl(inumber, -1, "NON-ZERO ACL REFERENCE, I=%d\n"); } else if ((shadow <= UFSROOTINO) || (shadow > maxinumber)) { clear_attr_acl(inumber, -1, "BAD ACL REFERENCE I=%d\n"); } else { registershadowclient(shadow, inumber, &shadowclientinfo); } } attrinode = dp->di_oeftflag; if (attrinode != 0) { if ((attrinode <= UFSROOTINO) || (attrinode > maxinumber)) { clear_attr_acl(attrinode, inumber, "BAD ATTRIBUTE REFERENCE TO I=%d FROM I=%d\n"); } else { dp = ginode(attrinode); if ((dp->di_mode & IFMT) != IFATTRDIR) { clear_attr_acl(attrinode, inumber, "BAD ATTRIBUTE DIR REF TO I=%d FROM I=%d\n"); } else if (dp->di_size == 0) { clear_attr_acl(attrinode, inumber, "REFERENCE TO ZERO-LENGTH ATTRIBUTE DIR I=%d from I=%d\n"); } else { registershadowclient(attrinode, inumber, &attrclientinfo); } } } return; /* * If we got here, we've not had the chance to see if a * directory has holes, but we know the directory's bad, * so it's safe to always return false (no holes found). * * Also, a pfatal() is always done before jumping here, so * we know we're not in preen mode. */ bogus: if (isdir) { /* * INCLEAR makes passes 2 & 3 skip it. */ statemap[inumber] = DCLEAR; add_orphan_dir(inumber); cacheino(dp, inumber); } else { statemap[inumber] = FCLEAR; } if (reply("CLEAR") == 1) { (void) tdelete((void *)inumber, &limbo_dirs, ino_t_cmp); freeino(inumber, TI_PARENT); inodirty(); } else { iscorrupt = 1; } } /* * Do fixup for bad acl/attr references. If PARENT is -1, then * we assume we're working on a shadow, otherwise an extended attribute. * FMT must be a printf format string, with one %d directive for * the inode number. */ static void clear_attr_acl(fsck_ino_t inumber, fsck_ino_t parent, char *fmt) { fsck_ino_t victim = inumber; struct dinode *dp; if (parent != -1) victim = parent; if (fmt != NULL) { if (parent == -1) pwarn(fmt, (int)inumber); else pwarn(fmt, (int)inumber, (int)parent); } if (debug) (void) printf("parent file/dir I=%d\nvictim I=%d", (int)parent, (int)victim); if (!preen && (reply("REMOVE REFERENCE") == 0)) { iscorrupt = 1; return; } dp = ginode(victim); if (parent == -1) { /* * The file had a bad shadow/acl, so lock it down * until someone can protect it the way they need it * to be (i.e., be conservatively paranoid). */ dp->di_shadow = 0; dp->di_mode &= IFMT; } else { dp->di_oeftflag = 0; } inodirty(); if (preen) (void) printf(" (CORRECTED)\n"); } /* * Check if we have holes in the directory's indirect * blocks. If there are, get rid of everything after * the first hole. */ static void check_dirholes(fsck_ino_t inumber, struct inodesc *idesc) { char pathbuf[MAXPATHLEN + 1]; getpathname(pathbuf, idesc->id_number, idesc->id_number); pfatal("I=%d DIRECTORY %s: CONTAINS EMPTY BLOCKS", idesc->id_number, pathbuf); if (reply("TRUNCATE AT FIRST EMPTY BLOCK") == 1) { /* * We found a hole, so get rid of it. */ collapse_dirhole(inumber, idesc); if (preen) (void) printf(" (TRUNCATED)\n"); } else { iscorrupt = 1; } } /* * Truncate a directory to its first hole. If there are non-holes * in the direct blocks after the problem block, move them down so * that there's somewhat less lossage. Doing this for indirect blocks * is left as an exercise for the reader. */ static void collapse_dirhole(fsck_ino_t inumber, struct inodesc *idesc) { offset_t new_size; int blocks; if (idesc->id_firsthole < 0) { return; } /* * Since truncino() adjusts the size, we don't need to do that here, * but we have to tell it what final size we want. * * We need to count from block zero up through the last block * before the hole. If the hole is in the indirect blocks, chop at * the start of the nearest level of indirection. Orphans will * get reconnected, so we're not actually losing anything by doing * it this way, and we're simplifying truncation significantly. */ new_size = idesc->id_firsthole * (offset_t)sblock.fs_bsize; blocks = howmany(new_size, sblock.fs_bsize); if (blocks > NDADDR) { if (blocks < (NDADDR + NINDIR(&sblock))) blocks = NDADDR; else if (blocks < (NDADDR + NINDIR(&sblock) + (NINDIR(&sblock) * NINDIR(&sblock)))) blocks = NDADDR + NINDIR(&sblock); else blocks = NDADDR + NINDIR(&sblock) + (NINDIR(&sblock) * NINDIR(&sblock)); new_size = blocks * sblock.fs_bsize; if (debug) (void) printf("to %lld (blocks %d)\n", (longlong_t)new_size, blocks); } truncino(inumber, new_size, TI_NOPARENT); /* * Technically, there are still the original number of fragments * associated with the object. However, that number is not used * to control anything, so we can do the in-memory truncation of * it without bad things happening. */ idesc->id_entryno = btodb(new_size); } int pass1check(struct inodesc *idesc) { int res = KEEPON; int anyout; int nfrags; daddr32_t lbn; daddr32_t fragno = idesc->id_blkno; struct dinode *dp; /* * If this is a fallocate'd file, block numbers may be stored * as negative. In that case negate the negative numbers. */ dp = ginode(idesc->id_number); if (dp->di_cflags & IFALLOCATE && fragno < 0) fragno = -fragno; if ((anyout = chkrange(fragno, idesc->id_numfrags)) != 0) { /* * Note that blkerror() exits when preening. */ blkerror(idesc->id_number, "OUT OF RANGE", fragno, idesc->id_lbn * sblock.fs_frag); dp = ginode(idesc->id_number); if ((((dp->di_mode & IFMT) == IFDIR) || ((dp->di_mode & IFMT) == IFATTRDIR)) && (idesc->id_firsthole < 0)) { idesc->id_firsthole = idesc->id_lbn; } if (++badblk >= MAXBAD) { pwarn("EXCESSIVE BAD FRAGMENTS I=%u", idesc->id_number); if (reply("CONTINUE") == 0) errexit("Program terminated."); /* * See discussion below as to why we don't * want to short-circuit the processing of * this inode. However, we know that this * particular block is bad, so we don't need * to go through the dup check loop. */ return (SKIP | STOP); } } /* * For each fragment, verify that it is a legal one (either * by having already found the entire run to be legal, or by * individual inspection), and if it is legal, see if we've * seen it before or not. If we haven't, note that we've seen * it and continue on. If we have (our in-core bitmap shows * it as already being busy), then this must be a duplicate * allocation. Whine and moan accordingly. * * Note that for full-block allocations, this will produce * a complaint for each fragment making up the block (i.e., * fs_frags' worth). Among other things, this could be * considered artificially inflating the dup-block count. * However, since it is possible that one file has a full * fs block allocated, but another is only claiming a frag * or two out of the middle, we'll just live it. */ for (nfrags = 0; nfrags < idesc->id_numfrags; fragno++, nfrags++) { if (anyout && chkrange(fragno, 1)) { /* bad fragment number */ res = SKIP; } else if (!testbmap(fragno)) { /* no other claims seen as yet */ note_used(fragno); } else { /* * We have a duplicate claim for the same fragment. * * blkerror() exits when preening. * * We want to report all the dups up until * hitting MAXDUP. Fortunately, blkerror()'s * side-effects on statemap[] are idempotent, * so the ``extra'' calls are harmless. */ lbn = idesc->id_lbn * sblock.fs_frag + nfrags; if (dupblk < MAXDUP) blkerror(idesc->id_number, "DUP", fragno, lbn); /* * Use ==, so we only complain once, no matter * how far over the limit we end up going. */ if (++dupblk == MAXDUP) { pwarn("EXCESSIVE DUPLICATE FRAGMENTS I=%u", idesc->id_number); if (reply("CONTINUE") == 0) errexit("Program terminated."); /* * If we stop the traversal here, then * there may be more dups in the * inode's block list that don't get * flagged. Later, if we're told to * clear one of the files claiming * these blocks, but not the other, we * will release blocks that are * actually still in use. An additional * fsck run would be necessary to undo * the damage. So, instead of the * traditional return (STOP) when told * to continue, we really do just continue. */ } (void) find_dup_ref(fragno, idesc->id_number, lbn, DB_CREATE | DB_INCR); } /* * id_entryno counts the number of disk blocks found. */ idesc->id_entryno += btodb(sblock.fs_fsize); } return (res); } static void note_used(daddr32_t frag) { n_blks++; setbmap(frag); } /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include "fsck.h" static int pass1bcheck(struct inodesc *); void pass1b(void) { struct dinode *dp; struct inodesc idesc; fsck_ino_t inumber; /* * We can get STOP failures from ckinode() that * are completely independent of our dup checks. * If that were not the case, then we could track * when we've seen all of the dups and short- * circuit our search. As it is, we need to * keep going, so there's no point in looking * at what ckinode() returns to us. */ for (inumber = UFSROOTINO; inumber < maxino; inumber++) { init_inodesc(&idesc); idesc.id_type = ADDR; idesc.id_func = pass1bcheck; idesc.id_number = inumber; idesc.id_fix = DONTKNOW; dp = ginode(inumber); if (statemap[inumber] != USTATE) (void) ckinode(dp, &idesc, CKI_TRAVERSE); } } static int pass1bcheck(struct inodesc *idesc) { int res = KEEPON; int nfrags; daddr32_t lbn; daddr32_t blkno = idesc->id_blkno; for (nfrags = 0; nfrags < idesc->id_numfrags; blkno++, nfrags++) { if (chkrange(blkno, 1)) { res = SKIP; } else { /* * Note that we only report additional dup claimants * in this pass, as the first claimant found was * listed during pass 1. */ lbn = idesc->id_lbn * sblock.fs_frag + nfrags; if (find_dup_ref(blkno, idesc->id_number, lbn, DB_INCR)) blkerror(idesc->id_number, "DUP", blkno, lbn); } } return (res); } /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include #include "fsck.h" #define MINDIRSIZE (sizeof (struct dirtemplate)) static int blksort(const void *, const void *); static int pass2check(struct inodesc *); void pass2(void) { struct dinode *dp, *dp2, *dpattr; struct inoinfo **inpp, *inp; struct inoinfo **inpend; struct inodesc curino; struct inodesc ldesc; struct dinode dino; char pathbuf[MAXPATHLEN + 1]; int found; int dirtype; caddr_t errmsg; struct shadowclientinfo *sci; switch (statemap[UFSROOTINO] & ~INDELAYD) { case USTATE: pfatal("ROOT INODE UNALLOCATED"); if (reply("ALLOCATE") == 0) { errexit("Program terminated."); } if (allocdir(UFSROOTINO, UFSROOTINO, 0755, 0) != UFSROOTINO) errexit("CANNOT ALLOCATE ROOT INODE\n"); break; case DCLEAR: pfatal("DUPS/BAD IN ROOT INODE"); if (reply("REALLOCATE") == 1) { freeino(UFSROOTINO, TI_NOPARENT); if (allocdir(UFSROOTINO, UFSROOTINO, 0755, 0) != UFSROOTINO) errexit("CANNOT ALLOCATE ROOT INODE\n"); break; } if (reply("CONTINUE") == 0) { errexit("Program terminated."); } break; case FSTATE: case FCLEAR: case FZLINK: case SSTATE: case SCLEAR: pfatal("ROOT INODE NOT DIRECTORY"); if (reply("REALLOCATE") == 1) { freeino(UFSROOTINO, TI_NOPARENT); if (allocdir(UFSROOTINO, UFSROOTINO, 0755, 0) != UFSROOTINO) errexit("CANNOT ALLOCATE ROOT INODE\n"); break; } if (reply("FIX") == 0) { ckfini(); errexit("Program terminated."); } dp = ginode(UFSROOTINO); dp->di_mode &= ~IFMT; dp->di_mode |= IFDIR; inodirty(); break; case DSTATE: case DZLINK: break; default: errexit("BAD STATE 0x%x FOR ROOT INODE\n", statemap[UFSROOTINO]); } statemap[UFSROOTINO] = DFOUND; /* * Technically, we do know who the parent is. However, * if this is set, then we'll get confused during the * second-dir-entry-is-dotdot test for the root inode. */ inp = getinoinfo(UFSROOTINO); if (inp != NULL && inp->i_dotdot != 0) inp->i_dotdot = 0; /* * Sort the directory list into disk block order. There's no * requirement to do this, but it may help improve our i/o times * somewhat. */ qsort((void *)inpsort, (size_t)inplast, sizeof (*inpsort), blksort); /* * Check the integrity of each directory. In general, we treat * attribute directories just like normal ones. Only the handling * of .. is really different. */ (void) memset(&dino, 0, sizeof (struct dinode)); dino.di_mode = IFDIR; inpend = &inpsort[inplast]; for (inpp = inpsort; inpp < inpend; inpp++) { inp = *inpp; if (inp->i_isize == 0) continue; /* != DSTATE also covers case of == USTATE */ if (((statemap[inp->i_number] & STMASK) != DSTATE) || ((statemap[inp->i_number] & INCLEAR) == INCLEAR)) continue; if (inp->i_isize < (offset_t)MINDIRSIZE) { direrror(inp->i_number, "DIRECTORY TOO SHORT"); inp->i_isize = (offset_t)roundup(MINDIRSIZE, DIRBLKSIZ); if (reply("FIX") == 1) { dp = ginode(inp->i_number); dp->di_size = (u_offset_t)inp->i_isize; inodirty(); } else { iscorrupt = 1; } } if ((inp->i_isize & (offset_t)(DIRBLKSIZ - 1)) != 0) { getpathname(pathbuf, inp->i_number, inp->i_number); pwarn("DIRECTORY %s: LENGTH %lld NOT MULTIPLE OF %d", pathbuf, (longlong_t)inp->i_isize, DIRBLKSIZ); inp->i_isize = roundup(inp->i_isize, (offset_t)DIRBLKSIZ); if (preen || reply("ADJUST") == 1) { dp = ginode(inp->i_number); dp->di_size = (u_offset_t)roundup(inp->i_isize, (offset_t)DIRBLKSIZ); inodirty(); if (preen) (void) printf(" (ADJUSTED)\n"); } else { iscorrupt = 1; } } dp = ginode(inp->i_number); if ((dp->di_mode & IFMT) == IFATTRDIR && (dp->di_cflags & IXATTR) == 0) { pwarn("ATTRIBUTE DIRECTORY I=%d MISSING IXATTR FLAG", inp->i_number); if (preen || reply("CORRECT") == 1) { dp->di_cflags |= IXATTR; inodirty(); if (preen) (void) printf(" (CORRECTED)\n"); } } dp = &dino; dp->di_size = (u_offset_t)inp->i_isize; (void) memmove((void *)&dp->di_db[0], (void *)&inp->i_blks[0], inp->i_blkssize); init_inodesc(&curino); curino.id_type = DATA; curino.id_func = pass2check; curino.id_number = inp->i_number; curino.id_parent = inp->i_parent; curino.id_fix = DONTKNOW; (void) ckinode(dp, &curino, CKI_TRAVERSE); /* * Make sure we mark attrdirs as DFOUND, since they won't * be located during normal scan of standard directories. */ if (curino.id_parent == 0) { dpattr = ginode(inp->i_number); if ((dpattr->di_mode & IFMT) == IFATTRDIR) { for (sci = attrclientinfo; sci != NULL; sci = sci->next) { if (sci->shadow == inp->i_number) { curino.id_parent = sci->clients->client[0]; statemap[inp->i_number] = DFOUND; inp->i_parent = curino.id_parent; } } } } } /* * Now that the parents of all directories have been found, * make another pass to verify the value of .. */ for (inpp = inpsort; inpp < inpend; inpp++) { inp = *inpp; if (inp->i_parent == 0 || inp->i_isize == 0) continue; /* * There are only directories in inpsort[], so only * directory-related states need to be checked. There * should never be any flags associated with USTATE. */ if ((statemap[inp->i_number] & (STMASK | INCLEAR)) == DCLEAR || statemap[inp->i_number] == USTATE) { continue; } if (statemap[inp->i_parent] == DFOUND && S_IS_DUNFOUND(statemap[inp->i_number])) { statemap[inp->i_number] = DFOUND | (statemap[inp->i_number] & INCLEAR); } if (inp->i_dotdot == inp->i_parent || inp->i_dotdot == (fsck_ino_t)-1) { continue; } if (inp->i_dotdot == 0) { inp->i_dotdot = inp->i_parent; fileerror(inp->i_parent, inp->i_number, "MISSING '..'"); if (reply("FIX") == 0) { iscorrupt = 1; continue; } dp = ginode(inp->i_number); found = 0; dirtype = (dp->di_mode & IFMT); /* * See if this is an attrdir that we located in pass1. * i.e. it was on an i_oeftflag of some other inode. * if it isn't found then we have an orphaned attrdir * that needs to be tossed into lost+found. */ if (dirtype == IFATTRDIR) { for (sci = attrclientinfo; sci != NULL; sci = sci->next) { if (sci->shadow == inp->i_number) { inp->i_parent = sci->clients->client[0]; found = 1; } } } /* * We've already proven there's no "..", so this * can't create a duplicate. */ if (makeentry(inp->i_number, inp->i_parent, "..")) { /* * is it an orphaned attrdir? */ if (dirtype == IFATTRDIR && found == 0) { /* * Throw it into lost+found */ if (linkup(inp->i_number, lfdir, NULL) == 0) { pwarn( "Unable to move attrdir I=%d to lost+found\n", inp->i_number); iscorrupt = 1; } maybe_convert_attrdir_to_dir( inp->i_number); } if (dirtype == IFDIR) { LINK_RANGE(errmsg, lncntp[inp->i_parent], -1); if (errmsg != NULL) { LINK_CLEAR(errmsg, inp->i_parent, IFDIR, &ldesc); if (statemap[inp->i_parent] != USTATE) { /* * iscorrupt is * already set */ continue; } } TRACK_LNCNTP(inp->i_parent, lncntp[inp->i_parent]--); } continue; } pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n"); iscorrupt = 1; inp->i_dotdot = (fsck_ino_t)-1; continue; } dp2 = ginode(inp->i_parent); if ((dp2->di_mode & IFMT) == IFATTRDIR) { continue; } fileerror(inp->i_parent, inp->i_number, "BAD INODE NUMBER FOR '..'"); if (reply("FIX") == 0) { iscorrupt = 1; continue; } LINK_RANGE(errmsg, lncntp[inp->i_dotdot], 1); if (errmsg != NULL) { LINK_CLEAR(errmsg, inp->i_dotdot, IFDIR, &ldesc); if (statemap[inp->i_dotdot] != USTATE) { /* iscorrupt is already set */ continue; } } TRACK_LNCNTP(inp->i_dotdot, lncntp[inp->i_dotdot]++); LINK_RANGE(errmsg, lncntp[inp->i_parent], -1); if (errmsg != NULL) { LINK_CLEAR(errmsg, inp->i_parent, IFDIR, &ldesc); if (statemap[inp->i_parent] != USTATE) { /* iscorrupt is already set */ continue; } } TRACK_LNCNTP(inp->i_parent, lncntp[inp->i_parent]--); inp->i_dotdot = inp->i_parent; (void) changeino(inp->i_number, "..", inp->i_parent); } /* * Mark all the directories that can be found from the root. */ propagate(); } /* * Sanity-check a single directory entry. Which entry is being * examined is tracked via idesc->id_entryno. There are two * special ones, 0 (.) and 1 (..). Those have to exist in order * in the first two locations in the directory, and have the usual * properties. All other entries have to not be for either of * the special two, and the inode they reference has to be * reasonable. * * This is only called from dirscan(), which looks for the * ALTERED flag after each invocation. If it finds it, the * relevant buffer gets pushed out, so we don't have to worry * about it here. */ #define PASS2B_PROMPT "REMOVE DIRECTORY ENTRY FROM I=%d" static int pass2check(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; struct inodesc ldesc; struct inoinfo *inp; short reclen, entrysize; int ret = 0; int act, update_lncntp; struct dinode *dp, *pdirp, *attrdirp; caddr_t errmsg; struct direct proto; char namebuf[MAXPATHLEN + 1]; char pathbuf[MAXPATHLEN + 1]; int isattr; int pdirtype; int breakout = 0; int dontreconnect; if (idesc->id_entryno != 0) goto chk1; /* * check for "." */ if (dirp->d_ino != 0 && strcmp(dirp->d_name, ".") == 0) { if (dirp->d_ino != idesc->id_number) { direrror(idesc->id_number, "BAD INODE NUMBER FOR '.'"); dirp->d_ino = idesc->id_number; if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } } goto chk1; } /* * Build up a new one, and make sure there's room to put * it where it belongs. */ direrror(idesc->id_number, "MISSING '.'"); proto.d_ino = idesc->id_number; proto.d_namlen = 1; (void) strcpy(proto.d_name, "."); entrysize = DIRSIZ(&proto); if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) { pfatal("CANNOT FIX, FIRST ENTRY IN DIRECTORY CONTAINS %s\n", dirp->d_name); iscorrupt = 1; } else if ((int)dirp->d_reclen < entrysize) { pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '.'\n"); iscorrupt = 1; } else if ((int)dirp->d_reclen < 2 * entrysize) { /* * No room for another entry after us ("." is the * smallest entry you can have), so just put all * of the old entry's space into the new entry. * * Because we don't touch id_entryno, we end up going * through the chk2 tests as well. */ proto.d_reclen = dirp->d_reclen; (void) memmove((void *)dirp, (void *)&proto, (size_t)entrysize); if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } } else { /* * There's enough room for an entire additional entry * after this, so create the "." entry and follow it * with an empty entry that covers the rest of the * space. * * The increment of id_entryno means we'll skip the * "." case of chk1, doing the ".." tests instead. * Since we know that there's not a ".." where it * should be (because we just created an empty entry * there), that's the best way of getting it recreated * as well. */ reclen = dirp->d_reclen - entrysize; proto.d_reclen = entrysize; (void) memmove((void *)dirp, (void *)&proto, (size_t)entrysize); idesc->id_entryno++; /* * Make sure the link count is in range before updating * it. This makes the assumption that the link count * for this inode included one for ".", even though * there wasn't a "." entry. Even if that's not true, * it's a reasonable working hypothesis, and the link * count verification done in pass4 will fix it for * us anyway. */ LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1); if (errmsg != NULL) { LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc); if (statemap[dirp->d_ino] == USTATE) { /* * The inode got zapped, so reset the * directory entry. Extend it to also * cover the space we were going to make * into a new entry. */ dirp->d_ino = 0; dirp->d_reclen += reclen; ret |= ALTERED; return (ret); } } /* * Create the new empty entry. */ /* LINTED pointer cast alignment (entrysize is valid) */ dirp = (struct direct *)((char *)(dirp) + entrysize); (void) memset((void *)dirp, 0, (size_t)reclen); dirp->d_reclen = reclen; /* * Did the user want us to create a new "."? This * query assumes that the direrror(MISSING) was the * last thing printed, so if the LINK_RANGE() check * fails, it can't pass through here. */ if (reply("FIX") == 1) { TRACK_LNCNTP(idesc->id_number, lncntp[idesc->id_number]--); ret |= ALTERED; } else { iscorrupt = 1; } } /* * XXX The next few lines are needed whether we're processing "." * or "..". However, there are some extra steps still needed * for the former, hence the big block of code for * id_entryno == 0. Alternatively, there could be a label just * before this comment, and everything through the end of that * block moved there. In some ways, that might make the * control flow more logical (factoring out to separate functions * would be even better). */ chk1: if (idesc->id_entryno > 1) goto chk2; inp = getinoinfo(idesc->id_number); if (inp == NULL) { /* * This is a can't-happen, since inodes get cached before * we get called on them. */ errexit("pass2check got NULL from getinoinfo at chk1 I=%d\n", idesc->id_number); } proto.d_ino = inp->i_parent; proto.d_namlen = 2; (void) strcpy(proto.d_name, ".."); entrysize = DIRSIZ(&proto); if (idesc->id_entryno == 0) { /* * We may not actually need to split things up, but if * there's room to do so, we should, as that implies * that the "." entry is larger than it is supposed * to be, and therefore there's something wrong, albeit * possibly harmlessly so. */ reclen = DIRSIZ(dirp); if ((int)dirp->d_reclen < reclen + entrysize) { /* * Not enough room for inserting a ".." after * the "." entry. */ goto chk2; } /* * There's enough room for an entire additional entry * after "."'s, so split it up. There's no reason "." * should be bigger than the minimum, so shrink it to * fit, too. Since by the time we're done with this * part, dirp will be pointing at where ".." should be, * update id_entryno to show that that's the entry * we're on. */ proto.d_reclen = dirp->d_reclen - reclen; dirp->d_reclen = reclen; idesc->id_entryno++; if (dirp->d_ino > 0 && dirp->d_ino <= maxino) { /* * Account for the link to ourselves. */ LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1); if (errmsg != NULL) { LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc); if (statemap[dirp->d_ino] == USTATE) { /* * We were going to split the entry * up, but the link count overflowed. * Since we got rid of the inode, * we need to also zap the directory * entry, and restoring the original * state of things is the least-bad * result. */ dirp->d_ino = 0; dirp->d_reclen += proto.d_reclen; ret |= ALTERED; return (ret); } } TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino]--); /* * Make sure the new entry doesn't get interpreted * as having actual content. */ /* LINTED pointer cast alignment (reclen is valid) */ dirp = (struct direct *)((char *)(dirp) + reclen); (void) memset((void *)dirp, 0, (size_t)proto.d_reclen); dirp->d_reclen = proto.d_reclen; } else { /* * Everything was fine, up until we realized that * the indicated inode was impossible. By clearing * d_ino here, we'll trigger the recreation of it * down below, using i_parent. Unlike the other * half of this if(), we're everything so it shows * that we're still on the "." entry. */ fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE"); dirp->d_ino = 0; if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } } } /* * Record this ".." inode, but only if we haven't seen one before. * If this isn't the first, it'll get cleared below, and so we * want to remember the entry that'll still be around later. */ if (dirp->d_ino != 0 && inp->i_dotdot == 0 && strcmp(dirp->d_name, "..") == 0) { inp->i_dotdot = dirp->d_ino; goto chk2; } if (dirp->d_ino != 0 && strcmp(dirp->d_name, "..") != 0) { fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); pfatal("CANNOT FIX, SECOND ENTRY IN DIRECTORY CONTAINS %s\n", dirp->d_name); iscorrupt = 1; inp->i_dotdot = (fsck_ino_t)-1; } else if ((int)dirp->d_reclen < entrysize) { fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); pfatal("CANNOT FIX, INSUFFICIENT SPACE TO ADD '..'\n"); /* XXX Same consideration as immediately above. */ iscorrupt = 1; inp->i_dotdot = (fsck_ino_t)-1; } else if (inp->i_parent != 0) { /* * We know the parent, so fix now. */ proto.d_ino = inp->i_dotdot = inp->i_parent; fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); /* * Lint won't be quiet about d_reclen being set but not * used. It apparently doesn't understand the implications * of calling memmove(), and won't believe us that it's ok. */ proto.d_reclen = dirp->d_reclen; (void) memmove((void *)dirp, (void *)&proto, (size_t)entrysize); if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } } else if (inp->i_number == UFSROOTINO) { /* * Always know parent of root inode, so fix now. */ proto.d_ino = inp->i_dotdot = inp->i_parent = UFSROOTINO; fileerror(inp->i_parent, idesc->id_number, "MISSING '..'"); /* * Lint won't be quiet about d_reclen being set but not * used. It apparently doesn't understand the implications * of calling memmove(), and won't believe us that it's ok. */ proto.d_reclen = dirp->d_reclen; (void) memmove((void *)dirp, (void *)&proto, (size_t)entrysize); if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } } idesc->id_entryno++; if (dirp->d_ino != 0) { LINK_RANGE(errmsg, lncntp[dirp->d_ino], -1); if (errmsg != NULL) { LINK_CLEAR(errmsg, dirp->d_ino, IFDIR, &ldesc); if (statemap[dirp->d_ino] == USTATE) { dirp->d_ino = 0; ret |= ALTERED; } } TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino]--); } return (ret|KEEPON); chk2: if (dirp->d_ino == 0) return (ret|KEEPON); if (dirp->d_namlen <= 2 && dirp->d_name[0] == '.' && idesc->id_entryno >= 2) { if (dirp->d_namlen == 1) { direrror(idesc->id_number, "EXTRA '.' ENTRY"); dirp->d_ino = 0; if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } return (KEEPON | ret); } if (dirp->d_name[1] == '.') { direrror(idesc->id_number, "EXTRA '..' ENTRY"); dirp->d_ino = 0; if (reply("FIX") == 1) { ret |= ALTERED; } else { iscorrupt = 1; } return (KEEPON | ret); } } /* * Because of this increment, all tests for skipping . and .. * below are ``> 2'', not ``> 1'' as would logically be expected. */ idesc->id_entryno++; act = -1; /* * The obvious check would be for d_ino < UFSROOTINO. However, * 1 is a valid inode number. Although it isn't currently used, * as it was once the bad block list, there's nothing to prevent * it from acquiring a new purpose in the future. So, don't * arbitrarily disallow it. We don't test for <= zero, because * d_ino is unsigned. */ update_lncntp = 0; if (dirp->d_ino > maxino || dirp->d_ino == 0) { fileerror(idesc->id_number, dirp->d_ino, "I OUT OF RANGE"); act = (reply(PASS2B_PROMPT, idesc->id_number) == 1); } else { again: update_lncntp = 0; switch (statemap[dirp->d_ino] & ~(INDELAYD)) { case USTATE: if (idesc->id_entryno <= 2) break; fileerror(idesc->id_number, dirp->d_ino, "UNALLOCATED"); act = (reply(PASS2B_PROMPT, idesc->id_number) == 1); break; case DCLEAR: case FCLEAR: case SCLEAR: if (idesc->id_entryno <= 2) break; dp = ginode(dirp->d_ino); if (statemap[dirp->d_ino] == DCLEAR) { errmsg = ((dp->di_mode & IFMT) == IFATTRDIR) ? "REFERENCE TO ZERO LENGTH ATTRIBUTE DIRECTORY" : "REFERENCE TO ZERO LENGTH DIRECTORY"; inp = getinoinfo(dirp->d_ino); if (inp == NULL) { /* * The inode doesn't exist, as all * should be cached by now. This * gets caught by the range check * above, and so it is a can't-happen * at this point. */ errexit("pass2check found a zero-len " "reference to bad I=%d\n", dirp->d_ino); } if (inp->i_parent != 0) { (void) printf( "Multiple links to I=%d, link counts wrong, rerun fsck\n", inp->i_number); iscorrupt = 1; } } else if (statemap[dirp->d_ino] == SCLEAR) { /* * In theory, this is a can't-happen, * because shadows don't appear in directory * entries. However, an inode might've * been reused without a stale directory * entry having been cleared, so check * for it just in case. We'll check for * the no-dir-entry shadows in pass3b(). */ errmsg = "ZERO LENGTH SHADOW"; } else { errmsg = "DUP/BAD"; } fileerror(idesc->id_number, dirp->d_ino, errmsg); if ((act = reply(PASS2B_PROMPT, idesc->id_number)) == 1) break; /* * Not doing anything about it, so just try * again as whatever the base type was. * * fileerror() invalidated dp. Lint thinks this * is unnecessary, but we know better. */ dp = ginode(dirp->d_ino); statemap[dirp->d_ino] &= STMASK; TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino] = 0); goto again; case DSTATE: case DZLINK: if (statemap[idesc->id_number] == DFOUND) { statemap[dirp->d_ino] = DFOUND; } /* FALLTHROUGH */ case DFOUND: /* * This is encouraging the best-practice of not * hard-linking directories. It's legal (see POSIX), * but not a good idea. So, don't consider it an * instance of corruption, but offer to nuke it. */ inp = getinoinfo(dirp->d_ino); if (inp == NULL) { /* * Same can't-happen argument as in the * zero-len case above. */ errexit("pass2check found bad reference to " "hard-linked directory I=%d\n", dirp->d_ino); } dp = ginode(idesc->id_number); if (inp->i_parent != 0 && idesc->id_entryno > 2 && ((dp->di_mode & IFMT) != IFATTRDIR)) { /* * XXX For nested dirs, this can report * the same name for both paths. */ getpathname(pathbuf, idesc->id_number, dirp->d_ino); getpathname(namebuf, dirp->d_ino, dirp->d_ino); pwarn( "%s IS AN EXTRANEOUS HARD LINK TO DIRECTORY %s\n", pathbuf, namebuf); if (preen) { (void) printf(" (IGNORED)\n"); } else { act = reply(PASS2B_PROMPT, idesc->id_number); if (act == 1) { update_lncntp = 1; broke_dir_link = 1; break; } } } if ((idesc->id_entryno > 2) && (inp->i_extattr != idesc->id_number)) { inp->i_parent = idesc->id_number; } /* FALLTHROUGH */ case FSTATE: case FZLINK: /* * There's nothing to do for normal file-like * things. Extended attributes come through * here as well, though, and for them, .. may point * to a file. In this situation we don't want * to decrement link count as it was already * decremented when the entry was seen in the * directory it actually lives in. */ pdirp = ginode(idesc->id_number); pdirtype = (pdirp->di_mode & IFMT); dp = ginode(dirp->d_ino); isattr = (dp->di_cflags & IXATTR); act = -1; if (pdirtype == IFATTRDIR && (strcmp(dirp->d_name, "..") == 0)) { dontreconnect = 0; if (dp->di_oeftflag != 0) { attrdirp = ginode(dp->di_oeftflag); /* * is it really an attrdir? * if so, then don't do anything. */ if ((attrdirp->di_mode & IFMT) == IFATTRDIR) dontreconnect = 1; dp = ginode(dirp->d_ino); } /* * Rare corner case - the attrdir's .. * points to the attrdir itself. */ if (dirp->d_ino == idesc->id_number) { dontreconnect = 1; TRACK_LNCNTP(idesc->id_number, lncntp[idesc->id_number]--); } /* * Lets see if we have an orphaned attrdir * that thinks it belongs to this file. * Only re-connect it if the current * attrdir is 0 or not an attrdir. */ if ((dp->di_oeftflag != idesc->id_number) && (dontreconnect == 0)) { fileerror(idesc->id_number, dirp->d_ino, "Attribute directory I=%d not " "attached to file I=%d\n", idesc->id_number, dirp->d_ino); if ((act = reply("FIX")) == 1) { dp = ginode(dirp->d_ino); if (debug) (void) printf( "debug: changing i=%d's oeft from %d ", dirp->d_ino, dp->di_oeftflag); dp->di_oeftflag = idesc->id_number; if (debug) (void) printf("to %d\n", dp->di_oeftflag); inodirty(); registershadowclient( idesc->id_number, dirp->d_ino, &attrclientinfo); } dp = ginode(dirp->d_ino); } /* * This can only be true if we've modified * an inode/xattr connection, and we * don't keep track of those in the link * counts. So, skipping the checks just * after this is not a problem. */ if (act > 0) return (KEEPON | ALTERED); /* * Don't screw up link counts for directories. * If we aren't careful we can perform * an extra decrement, since the .. of * an attrdir could be either a file or a * directory. If it's a file then its link * should be correct after it is seen when the * directory it lives in scanned. */ if ((pdirtype == IFATTRDIR) && ((dp->di_mode & IFMT) == IFDIR)) breakout = 1; if ((dp->di_mode & IFMT) != IFDIR) breakout = 1; } else if ((pdirtype != IFATTRDIR) || (strcmp(dirp->d_name, ".") != 0)) { if ((pdirtype == IFDIR) && isattr) { fileerror(idesc->id_number, dirp->d_ino, "File should NOT be marked as " "extended attribute\n"); if ((act = reply("FIX")) == 1) { dp = ginode(dirp->d_ino); if (debug) (void) printf( "changing i=%d's cflags from 0x%x to ", dirp->d_ino, dp->di_cflags); dp->di_cflags &= ~IXATTR; if (debug) (void) printf("0x%x\n", dp->di_cflags); inodirty(); if ((dp->di_mode & IFMT) == IFATTRDIR) { dp->di_mode &= ~IFATTRDIR; dp->di_mode |= IFDIR; inodirty(); pdirp = ginode( idesc->id_number); if (pdirp->di_oeftflag != 0) { pdirp->di_oeftflag = 0; inodirty(); } } } } else { if (pdirtype == IFATTRDIR && (isattr == 0)) { fileerror(idesc->id_number, dirp->d_ino, "File should BE marked as " "extended attribute\n"); if ((act = reply("FIX")) == 1) { dp = ginode( dirp->d_ino); dp->di_cflags |= IXATTR; /* * Make sure it's a file * while we're at it. */ dp->di_mode &= ~IFMT; dp->di_mode |= IFREG; inodirty(); } } } } if (breakout == 0 || dontreconnect == 0) { TRACK_LNCNTP(dirp->d_ino, lncntp[dirp->d_ino]--); if (act > 0) return (KEEPON | ALTERED); } break; case SSTATE: errmsg = "ACL IN DIRECTORY"; fileerror(idesc->id_number, dirp->d_ino, errmsg); act = (reply(PASS2B_PROMPT, idesc->id_number) == 1); break; default: errexit("BAD STATE 0x%x FOR INODE I=%d", statemap[dirp->d_ino], dirp->d_ino); } } if (act == 0) { iscorrupt = 1; } if (act <= 0) return (ret|KEEPON); if (update_lncntp) { LINK_RANGE(errmsg, lncntp[idesc->id_number], 1); if (errmsg != NULL) { LINK_CLEAR(errmsg, idesc->id_number, IFDIR, &ldesc); if (statemap[idesc->id_number] == USTATE) { idesc->id_number = 0; ret |= ALTERED; } } TRACK_LNCNTP(idesc->id_number, lncntp[idesc->id_number]++); } dirp->d_ino = 0; return (ret|KEEPON|ALTERED); } #undef PASS2B_PROMPT /* * Routine to sort disk blocks. */ static int blksort(const void *arg1, const void *arg2) { const struct inoinfo **inpp1 = (const struct inoinfo **)arg1; const struct inoinfo **inpp2 = (const struct inoinfo **)arg2; return ((*inpp1)->i_blks[0] - (*inpp2)->i_blks[0]); } /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include "fsck.h" static int pass3acheck(struct inodesc *); static void setcurino(struct inodesc *, struct dinode *, struct inoinfo *); void pass3a(void) { caddr_t flow; struct inoinfo **inpp, *inp; fsck_ino_t orphan; int loopcnt; int state; struct shadowclientinfo *sci, *sci_victim, *sci_prev, **sci_rootp; struct inodesc curino; struct dinode *dp; struct inodesc idesc; char namebuf[MAXNAMLEN + 1]; for (inpp = &inpsort[inplast - 1]; inpp >= inpsort; inpp--) { inp = *inpp; state = statemap[inp->i_number]; if (inp->i_number == UFSROOTINO || (inp->i_parent != 0 && !S_IS_DUNFOUND(state))) continue; if (state == DCLEAR || state == USTATE || (state & INORPHAN)) continue; /* * If we are running with logging and we come * across unreferenced directories, we just leave * them in DSTATE which will cause them to be pitched * in pass 4. */ if (preen && !iscorrupt && islog && S_IS_DUNFOUND(state)) { if (inp->i_dotdot >= UFSROOTINO) { LINK_RANGE(flow, lncntp[inp->i_dotdot], 1); if (flow != NULL) { dp = ginode(inp->i_dotdot); LINK_CLEAR(flow, inp->i_dotdot, dp->di_mode, &idesc); if (statemap[inp->i_dotdot] == USTATE) continue; } TRACK_LNCNTP(inp->i_dotdot, lncntp[inp->i_dotdot]++); } continue; } for (loopcnt = 0; ; loopcnt++) { orphan = inp->i_number; /* * Skip out if we aren't connected to the name * space, or our parent is connected, or we've * looked at too many directories. Our parent * being connected means that orphan is the * first ancestor of *inpp with questionable * antecedents. */ if (inp->i_parent == 0 || !INO_IS_DUNFOUND(inp->i_parent) || loopcnt > numdirs) break; inp = getinoinfo(inp->i_parent); /* * Can't happen, because a non-zero parent's already * been seen and therefore cached. */ if (inp == NULL) errexit("pass3 could not find cached " "inode I=%d\n", inp->i_parent); } /* * Already did this one. Don't bother the user * with redundant questions. */ if (statemap[orphan] & INORPHAN) continue; /* * A link count of 0 with parent and .. inodes of 0 * indicates a partly deleted directory. * Clear it. */ dp = ginode(orphan); if (dp->di_nlink == 0 && inp->i_dotdot == 0 && inp->i_parent == 0) { /* * clri() just uses curino.id_number; in other * words, it won't use the callback that setcurino() * puts in. */ setcurino(&curino, dp, inp); clri(&curino, "UNREF", CLRI_VERBOSE, CLRI_NOP_OK); /* * If we didn't clear it, at least mark it so * we don't waste time on it again. */ if (statemap[orphan] != USTATE) { statemap[orphan] |= INORPHAN; } continue; } /* * We can call linkup() multiple times on the same directory * inode, if we were told not to reconnect it the first time. * This is because we find it as a disconnected parent of * of its children (and mark it found), and then finally get * to it in the inpsort array. This is better than in the * past, where we'd call it every time we found it as a * child's parent. Ideally, we'd suppress even the second * query, but that confuses pass 4's interpretation of * the state flags. */ if (loopcnt <= countdirs) { if (linkup(orphan, inp->i_dotdot, NULL)) { /* * Bookkeeping for any sort of relinked * directory. */ inp->i_dotdot = lfdir; inp->i_parent = inp->i_dotdot; statemap[orphan] &= ~(INORPHAN); } else { statemap[orphan] |= INORPHAN; } propagate(); continue; } /* * We visited more directories than exist in the * filesystem. The only way to do that is if there's * a loop. */ pfatal("ORPHANED DIRECTORY LOOP DETECTED I=%d\n", orphan); /* * Can never get here with inp->i_parent zero, because * of the interactions between the for() and the * if (loopcnt <= countdirs) above. */ init_inodesc(&idesc); idesc.id_type = DATA; idesc.id_number = inp->i_parent; idesc.id_parent = orphan; idesc.id_func = findname; idesc.id_name = namebuf; namebuf[0] = '\0'; /* * Theoretically, this lookup via ckinode can't fail * (if orphan doesn't exist in i_parent, then i_parent * would not have been filled in by pass2check()). * However, if we're interactive, we want to at least * attempt to continue. The worst case is that it * gets reconnected as #nnn into lost+found instead of * to its old parent with its old name. */ if ((ckinode(ginode(inp->i_parent), &idesc, CKI_TRAVERSE) & FOUND) == 0) pfatal("COULD NOT FIND NAME IN PARENT DIRECTORY"); if (linkup(orphan, inp->i_parent, namebuf)) { if (cleardirentry(inp->i_parent, orphan) & FOUND) { LFDIR_LINK_RANGE_NORVAL(flow, lncntp[lfdir], 1, &idesc); TRACK_LNCNTP(orphan, lncntp[orphan]++); } inp->i_parent = inp->i_dotdot = lfdir; LFDIR_LINK_RANGE_NORVAL(flow, lncntp[lfdir], -1, &idesc); TRACK_LNCNTP(lfdir, lncntp[lfdir]--); statemap[orphan] = DFOUND; } else { /* * Represents a on-disk leak, not an inconsistency, * so don't set iscorrupt. Such leaks are harmless * in the context of discrepancies that the kernel * will panic over. * * We don't care if tsearch() returns non-NULL * != orphan, since there's no dynamic memory * to free here. */ if (tsearch((void *)orphan, &limbo_dirs, ino_t_cmp) == NULL) errexit("out of memory"); statemap[orphan] |= INORPHAN; continue; } propagate(); } /* * The essence of the inner loop is to update the inode of * every shadow or attribute inode's lncntp[] by the number of * links we've found to them in pass 2 and above. Logically, * all that is needed is just the one line: * * lncntp[sci->shadow] -= sci->totalclients; * * However, there's the possibility of wrapping the link count * (this is especially true for shadows, which are expected to * be shared amongst many files). This means that we have to * range-check before changing anything, and if the check * fails, offer to clear the shadow or attribute. If we do * clear it, then we have to remove it from the linked list of * all of the type of inodes that we're going through. * * Just to make things a little more complicated, these are * singly-linked lists, so we have to do all the extra * bookkeeping that goes along with that as well. * * The only connection between the shadowclientinfo and * attrclientinfo lists is that they use the same underlying * struct. Both need this scan, so the outer loop is just to * pick which one we're working on at the moment. There is no * requirement as to which of these lists is scanned first. */ for (loopcnt = 0; loopcnt < 2; loopcnt++) { if (loopcnt == 0) sci_rootp = &shadowclientinfo; else sci_rootp = &attrclientinfo; sci = *sci_rootp; sci_prev = NULL; while (sci != NULL) { sci_victim = NULL; LINK_RANGE(flow, lncntp[sci->shadow], -(sci->totalClients)); if (flow != NULL) { /* * Overflowed the link count. */ dp = ginode(sci->shadow); LINK_CLEAR(flow, sci->shadow, dp->di_mode, &idesc); if (statemap[sci->shadow] == USTATE) { /* * It's been cleared, fix the * lists. */ if (sci_prev == NULL) { *sci_rootp = sci->next; } else { sci_prev->next = sci->next; } sci_victim = sci; } } /* * If we did not clear the shadow, then we * need to update the count and advance the * previous pointer. Otherwise, finish the * clean up once we're done with the struct. */ if (sci_victim == NULL) { TRACK_LNCNTP(sci->shadow, lncntp[sci->shadow] -= sci->totalClients); sci_prev = sci; } sci = sci->next; if (sci_victim != NULL) deshadow(sci_victim, NULL); } } } /* * This is used to verify the cflags of files * under a directory that used to be an attrdir. */ static int pass3acheck(struct inodesc *idesc) { struct direct *dirp = idesc->id_dirp; int n = 0, ret = 0; struct dinode *dp, *pdirp; int isattr; int dirtype; int inotype; if (dirp->d_ino == 0) return (KEEPON); idesc->id_entryno++; if ((strcmp(dirp->d_name, ".") == 0) || (strcmp(dirp->d_name, "..") == 0)) { return (KEEPON); } switch (statemap[dirp->d_ino] & ~(INDELAYD)) { case DSTATE: case DFOUND: case FSTATE: /* * Accept DSTATE and DFOUND so we can handle normal * directories as well as xattr directories. * * For extended attribute directories .. may point * to a file. In this situation we don't want * to decrement link count as it was already * decremented when the entry was seen and decremented * in the directory it actually lives in. */ dp = ginode(dirp->d_ino); isattr = (dp->di_cflags & IXATTR); inotype = (dp->di_mode & IFMT); pdirp = ginode(idesc->id_number); dirtype = (pdirp->di_mode & IFMT); /* * IXATTR indicates that an object is itself an extended * attribute. An IFMT of IFATTRDIR means we are looking * at a directory which contains files which should all * have IXATTR set. The IFATTRDIR case was handled in * pass 2b. * * Note that the following code actually handles * anything that's marked as an extended attribute but * in a regular directory, not just files. */ if ((dirtype == IFDIR) && isattr) { fileerror(idesc->id_number, dirp->d_ino, "%s I=%d should NOT be marked as extended attribute\n", (inotype == IFDIR) ? "Directory" : "File", dirp->d_ino); dp = ginode(dirp->d_ino); dp->di_cflags &= ~IXATTR; if ((n = reply("FIX")) == 1) { inodirty(); } else { iscorrupt = 1; } if (n != 0) return (KEEPON | ALTERED); } break; default: errexit("PASS3: BAD STATE %d FOR INODE I=%d", statemap[dirp->d_ino], dirp->d_ino); /* NOTREACHED */ } if (n == 0) return (ret|KEEPON); return (ret|KEEPON|ALTERED); } static void setcurino(struct inodesc *idesc, struct dinode *dp, struct inoinfo *inp) { (void) memmove((void *)&dp->di_db[0], (void *)&inp->i_blks[0], inp->i_blkssize); init_inodesc(idesc); idesc->id_number = inp->i_number; idesc->id_parent = inp->i_parent; idesc->id_fix = DONTKNOW; idesc->id_type = DATA; idesc->id_func = pass3acheck; } void maybe_convert_attrdir_to_dir(fsck_ino_t orphan) { struct dinode *dp = ginode(orphan); struct inoinfo *inp = getinoinfo(orphan); struct inodesc idesc; if (dp->di_cflags & IXATTR) { dp->di_cflags &= ~IXATTR; inodirty(); } if ((dp->di_mode & IFMT) == IFATTRDIR) { dp->di_mode &= ~IFATTRDIR; dp->di_mode |= IFDIR; inodirty(); setcurino(&idesc, dp, inp); idesc.id_fix = FIX; idesc.id_filesize = dp->di_size; (void) ckinode(dp, &idesc, CKI_TRAVERSE); } } /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include #include #include "fsck.h" /* * We can be run on multiple filesystems (processed serially), so * these need to be re-initialized each time we start the pass. */ static caddr_t aclbuf; /* hold acl's for parsing */ static int64_t aclbufoff; /* offset into aclbuf */ static int64_t maxaclsize; /* how big aclbuf is */ static int aclblksort(const void *, const void *); static int bufchk(char *, int64_t, fsck_ino_t); static void clear_shadow_client(struct shadowclientinfo *, struct shadowclients *, int); void pass3b(void) { fsck_ino_t inumber; struct dinode *dp; struct inoinfo *aclp; struct inodesc curino; struct shadowclientinfo *sci; struct shadowclients *scc; int64_t acl_size_limit; int i; /* * Sort the acl list into disk block order. */ qsort((char *)aclpsort, (int)aclplast, sizeof (*aclpsort), aclblksort); /* * Scan all the acl inodes, finding the largest acl file. * * The largest legal size is (4 * MAX_ACL_ENTRIES + 8) entries. * The four are the categories of specific users, specific * groups, default specific users, and default specific groups. * The eight are the entries for the owning user/group/other/class * plus the equivalent defaults. * * We double this to allow for a truly worst-case but legal * situation of every single acl having its own fsd_t wrapper. * Doubling is a bit pessimistic (sizeof (acl_t) > sizeof (fsd_t)). */ acl_size_limit = sizeof (ufs_acl_t) * (4 * MAX_ACL_ENTRIES + 8); acl_size_limit *= 2; maxaclsize = 0; for (inumber = 0; inumber < aclplast; inumber++) { aclp = aclpsort[inumber]; if ((int64_t)aclp->i_isize > acl_size_limit) { (void) printf( "ACL I=%d is excessively large (%lld > %lld)", inumber, (longlong_t)aclp->i_isize, (longlong_t)acl_size_limit); if (preen) { (void) printf(" (IGNORING)\n"); } else if (reply("CLEAR") == 1) { freeino(inumber, TI_PARENT); } else { iscorrupt = 1; (void) printf("IGNORING SHADOW I=%d\n", inumber); } continue; } if ((int64_t)aclp->i_isize > maxaclsize) maxaclsize = (int64_t)aclp->i_isize; } maxaclsize = ((maxaclsize / sblock.fs_bsize) + 1) * sblock.fs_bsize; if (maxaclsize == 0) goto noacls; if (aclbuf != NULL) { free((void *)aclbuf); } if ((aclbuf = malloc(maxaclsize)) == NULL) { errexit("cannot alloc %lld bytes for aclbuf\n", (longlong_t)maxaclsize); } /* * Scan all the acl inodes, checking contents */ for (inumber = 0; inumber < aclplast; inumber++) { aclp = aclpsort[inumber]; if ((int64_t)aclp->i_isize > acl_size_limit) { continue; } if ((statemap[aclp->i_number] & STMASK) != SSTATE) { continue; } dp = ginode(aclp->i_number); init_inodesc(&curino); curino.id_fix = FIX; curino.id_type = ACL; curino.id_func = pass3bcheck; curino.id_number = aclp->i_number; curino.id_filesize = aclp->i_isize; aclbufoff = 0; (void) memset(aclbuf, 0, (size_t)maxaclsize); if ((ckinode(dp, &curino, CKI_TRAVERSE) & KEEPON) == 0 || bufchk(aclbuf, (int64_t)aclp->i_isize, aclp->i_number)) { dp = ginode(aclp->i_number); /* defensive no-op */ if (dp->di_nlink <= 0) { statemap[aclp->i_number] = FSTATE; continue; } (void) printf("ACL I=%d BAD/CORRUPT", aclp->i_number); if (preen || reply("CLEAR") == 1) { if (preen) (void) printf("\n"); freeino(aclp->i_number, TI_PARENT); } else { iscorrupt = 1; } } } /* * Now scan all shadow inodes, checking that any inodes that previously * had an acl still have an acl. */ noacls: for (sci = shadowclientinfo; sci; sci = sci->next) { if ((statemap[sci->shadow] & STMASK) != SSTATE) { for (scc = sci->clients; scc; scc = scc->next) { for (i = 0; i < scc->nclients; i++) { clear_shadow_client(sci, scc, i); } } } } free((void *)aclbuf); aclbuf = NULL; } static void clear_shadow_client(struct shadowclientinfo *sci, struct shadowclients *scc, int client) { int suppress_update = 0; caddr_t flow; struct inodesc ldesc; struct dinode *dp; (void) printf("I=%d HAS BAD/CLEARED ACL I=%d", scc->client[client], sci->shadow); if (preen || reply("FIX") == 1) { if (preen) (void) printf("\n"); /* * If we clear the ACL, then the permissions should * be as restrictive as possible until the user can * set it to something reasonable. If we keep the * ACL, then the permissions are pretty much * irrelevant. So, just always clear the permission * bits. */ dp = ginode(scc->client[client]); dp->di_mode &= IFMT; dp->di_shadow = 0; inodirty(); /* * Decrement in-memory link count - pass1 made sure * the shadow inode # is a valid inode number. But * first, see if we're going to overflow our sixteen * bits. */ LINK_RANGE(flow, lncntp[dp->di_shadow], 1); if (flow != NULL) { LINK_CLEAR(flow, scc->client[client], dp->di_mode, &ldesc); if (statemap[scc->client[client]] == USTATE) suppress_update = 1; } /* * We don't touch the shadow's on-disk link count, * because we've already cleared its state in pass3b(). * Here we're just trying to keep lncntp[] in sync, so * we can detect spurious links. */ if (!suppress_update) TRACK_LNCNTP(sci->shadow, lncntp[sci->shadow]++); } else { iscorrupt = 1; } } /* * Collect all the (data) blocks of an acl file into a buffer. * Later we'll scan the buffer and validate the acl data. */ int pass3bcheck(struct inodesc *idesc) { struct bufarea *bp; size_t size, bsize; if (aclbufoff == idesc->id_filesize) { return (STOP); } bsize = size = sblock.fs_fsize * idesc->id_numfrags; if ((size + aclbufoff) > idesc->id_filesize) size = idesc->id_filesize - aclbufoff; if (aclbufoff + size > maxaclsize) errexit("acl size %lld exceeds maximum calculated " "size of %lld bytes", (longlong_t)aclbufoff + size, (longlong_t)maxaclsize); bp = getdatablk(idesc->id_blkno, bsize); if (bp->b_errs != 0) { brelse(bp); return (STOP); } (void) memmove((void *)(aclbuf + aclbufoff), (void *)bp->b_un.b_buf, (size_t)size); aclbufoff += size; brelse(bp); return (KEEPON); } /* * Routine to sort disk blocks. */ static int aclblksort(const void *pp1, const void *pp2) { const struct inoinfo **aclpp1 = (const struct inoinfo **)pp1; const struct inoinfo **aclpp2 = (const struct inoinfo **)pp2; return ((*aclpp1)->i_blks[0] - (*aclpp2)->i_blks[0]); } /* * Scan a chunk of a shadow file. Return zero if no ACLs were found, * or when all that were found were valid. */ static int bufchk(char *buf, int64_t len, fsck_ino_t inum) { ufs_fsd_t *fsdp; ufs_acl_t *ufsaclp = NULL; int numacls; int curacl; struct type_counts_s { int nuser_objs; int ngroup_objs; int nother_objs; int nclass_objs; int ndef_user_objs; int ndef_group_objs; int ndef_other_objs; int ndef_class_objs; int nusers; int ngroups; int ndef_users; int ndef_groups; } type_counts[3]; /* indexed by FSD_ACL and FSD_DFACL */ struct type_counts_s *tcp, *tcp_all, *tcp_def, *tcp_norm; int numdefs; caddr_t bad; caddr_t end = buf + len; int64_t recsz = 0; int64_t min_recsz = FSD_RECSZ(fsdp, sizeof (*fsdp)); struct shadowclientinfo *sci; struct shadowclients *scc; fsck_ino_t target; int numtargets = 0; /* * check we have a non-zero length for this shadow inode */ if (len == 0) { pwarn("ACL I=%d HAS ZERO LENGTH\n", inum); return (1); } (void) memset(type_counts, 0, sizeof (type_counts)); /* LINTED pointer cast alignment (aligned buffer always passed in) */ for (fsdp = (ufs_fsd_t *)buf; (caddr_t)fsdp < end; /* LINTED as per the above */ fsdp = (ufs_fsd_t *)((caddr_t)fsdp + recsz)) { recsz = FSD_RECSZ(fsdp, fsdp->fsd_size); if ((recsz < min_recsz) || (((caddr_t)fsdp + recsz) > (buf + len))) { pwarn("Bad FSD entry size %lld in shadow inode %d", recsz, inum); if (reply("CLEAR SHADOW INODE") == 1) { freeino(inum, TI_PARENT); } else { /* * Bad size can cause the kernel to * go traipsing off into never-never land. */ iscorrupt = 1; } return (0); } switch (fsdp->fsd_type) { case FSD_FREE: /* ignore empty slots */ break; case FSD_ACL: case FSD_DFACL: /* * Subtract out the two ints in the fsd_type, * leaving us just the size of fsd_data[]. */ numacls = (fsdp->fsd_size - 2 * sizeof (int)) / sizeof (ufs_acl_t); tcp = &type_counts[fsdp->fsd_type]; curacl = 0; /* LINTED pointer cast alignment */ for (ufsaclp = (ufs_acl_t *)fsdp->fsd_data; numacls; ufsaclp++, curacl++) { switch (ufsaclp->acl_tag) { case USER_OBJ: /* Owner */ tcp->nuser_objs++; break; case GROUP_OBJ: /* Group */ tcp->ngroup_objs++; break; case OTHER_OBJ: /* Other */ tcp->nother_objs++; break; case CLASS_OBJ: /* Mask */ tcp->nclass_objs++; break; case DEF_USER_OBJ: /* Default Owner */ tcp->ndef_user_objs++; break; case DEF_GROUP_OBJ: /* Default Group */ tcp->ndef_group_objs++; break; case DEF_OTHER_OBJ: /* Default Other */ tcp->ndef_other_objs++; break; case DEF_CLASS_OBJ: /* Default Mask */ tcp->ndef_class_objs++; break; case USER: /* Users */ tcp->nusers++; break; case GROUP: /* Groups */ tcp->ngroups++; break; case DEF_USER: /* Default Users */ tcp->ndef_users++; break; case DEF_GROUP: /* Default Groups */ tcp->ndef_groups++; break; default: return (1); } if ((ufsaclp->acl_perm & ~07) != 0) { /* * Caller will report inode, etc */ pwarn("Bad permission 0%o in ACL\n", ufsaclp->acl_perm); return (1); } numacls--; } break; default: if (fsdp->fsd_type >= FSD_RESERVED3 && fsdp->fsd_type <= FSD_RESERVED7) bad = "Unexpected"; else bad = "Unknown"; pwarn("%s FSD type %d in shadow inode %d", bad, fsdp->fsd_type, inum); /* * This is relatively harmless, since the * kernel will ignore any entries it doesn't * recognize. Don't bother with iscorrupt. */ if (preen) { (void) printf(" (IGNORED)\n"); } else if (reply("IGNORE") == 0) { if (reply("CLEAR SHADOW INODE") == 1) { freeino(inum, TI_PARENT); } return (0); } break; } } if ((caddr_t)fsdp != (buf + len)) { return (1); } /* If we didn't find any acls, ignore the unknown attribute */ if (ufsaclp == NULL) return (0); /* * Should only have default ACLs in FSD_DFACL records. * However, the kernel can handle it, so just report that * something odd might be going on. */ tcp = &type_counts[FSD_DFACL]; if (verbose && (tcp->nuser_objs != 0 || tcp->ngroup_objs != 0 || tcp->nother_objs != 0 || tcp->nclass_objs != 0 || tcp->nusers != 0 || tcp->ngroups != 0)) { (void) printf("NOTE: ACL I=%d has miscategorized ACLs. ", inum); (void) printf("This is harmless, but not normal.\n"); } /* * Similarly for default ACLs in FSD_ACL records. */ tcp = &type_counts[FSD_ACL]; if (verbose && (tcp->ndef_user_objs != 0 || tcp->ndef_group_objs != 0 || tcp->ndef_other_objs != 0 || tcp->ndef_class_objs != 0 || tcp->ndef_users != 0 || tcp->ndef_groups != 0)) { (void) printf("NOTE: ACL I=%d has miscategorized ACLs.", inum); (void) printf(" This is harmless, but not normal.\n"); } /* * Get consolidated totals, now that we're done with checking * the segregation above. Assumes that neither FSD_ACL nor * FSD_DFACL are zero. */ tcp_all = &type_counts[0]; tcp_norm = &type_counts[FSD_ACL]; tcp_def = &type_counts[FSD_DFACL]; tcp_all->nuser_objs = tcp_def->nuser_objs + tcp_norm->nuser_objs; tcp_all->ngroup_objs = tcp_def->ngroup_objs + tcp_norm->ngroup_objs; tcp_all->nother_objs = tcp_def->nother_objs + tcp_norm->nother_objs; tcp_all->nclass_objs = tcp_def->nclass_objs + tcp_norm->nclass_objs; tcp_all->ndef_user_objs = tcp_def->ndef_user_objs + tcp_norm->ndef_user_objs; tcp_all->ndef_group_objs = tcp_def->ndef_group_objs + tcp_norm->ndef_group_objs; tcp_all->ndef_other_objs = tcp_def->ndef_other_objs + tcp_norm->ndef_other_objs; tcp_all->ndef_class_objs = tcp_def->ndef_class_objs + tcp_norm->ndef_class_objs; tcp_all->nusers = tcp_def->nusers + tcp_norm->nusers; tcp_all->ngroups = tcp_def->ngroups + tcp_norm->ngroups; tcp_all->ndef_users = tcp_def->ndef_users + tcp_norm->ndef_users; tcp_all->ndef_groups = tcp_def->ndef_groups + tcp_norm->ndef_groups; /* * Check relationships among acls */ if (tcp_all->nuser_objs != 1 || tcp_all->ngroup_objs != 1 || tcp_all->nother_objs != 1 || tcp_all->nclass_objs > 1) { return (1); } if (tcp_all->ngroups && !tcp_all->nclass_objs) { return (1); } if (tcp_all->ndef_user_objs > 1 || tcp_all->ndef_group_objs > 1 || tcp_all->ndef_other_objs > 1 || tcp_all->ndef_class_objs > 1) { return (1); } /* * Check relationships among default acls */ numdefs = tcp_all->ndef_other_objs + tcp_all->ndef_user_objs + tcp_all->ndef_group_objs; if (numdefs != 0 && numdefs != 3) { return (1); } /* * If there are default acls, then the shadow inode's clients * must be a directory or an xattr directory. */ if (numdefs != 0) { /* This is an ACL so find it's clients */ for (sci = shadowclientinfo; sci != NULL; sci = sci->next) if (sci->shadow == inum) break; if ((sci == NULL) || (sci->clients == NULL)) return (1); /* Got shadow info, now look at clients */ for (scc = sci->clients; scc != NULL; scc = scc->next) { for (numtargets = 0; numtargets < scc->nclients; numtargets++) { target = scc->client[numtargets]; if (!INO_IS_DVALID(target)) return (1); } } } if (tcp_all->ndef_groups && !tcp_all->ndef_class_objs) { return (1); } if ((tcp_all->ndef_users || tcp_all->ndef_groups) && ((numdefs != 3) && !tcp_all->ndef_class_objs)) { return (1); } return (0); } /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include "fsck.h" void pass4(void) { fsck_ino_t inumber; struct dinode *dp; struct inodesc idesc; int n, was_dir; int need_rescan; int scan_pass = 0; /* * If we clear a directory, it may have produced orphans which * we need to go pick up. So, do this until done. It can be * proven that the loop terminates because at most there can * be lastino directories, and we only rescan if we clear a * directory. */ do { if (debug) (void) printf("pass4 scan %d\n", scan_pass++); need_rescan = 0; for (inumber = UFSROOTINO; inumber <= lastino; inumber++) { init_inodesc(&idesc); idesc.id_type = ADDR; idesc.id_func = pass4check; idesc.id_number = inumber; was_dir = (statemap[inumber] & DSTATE) == DSTATE; switch (statemap[inumber] & ~(INORPHAN | INDELAYD | INZLINK)) { case FZLINK: case DZLINK: /* * INZLINK gets set if the inode claimed zero * links when we first looked at it in pass 1. * If lncntp[] also claims it has zero links, * it really is unreferenced. However, we * could have found a link to it during one of * the other passes, so we have to check the * final count in lncntp[]. */ if (lncntp[inumber] == 0) { clri(&idesc, "UNREF", CLRI_VERBOSE, CLRI_NOP_OK); if (was_dir && (statemap[inumber] == USTATE)) need_rescan = 1; break; } /* FALLTHROUGH */ case FSTATE: case DFOUND: case SSTATE: n = lncntp[inumber]; if (n || (statemap[inumber] & (INDELAYD | INZLINK))) { /* * adjust() will clear the inode if * the link count goes to zero. If * it isn't cleared, we need to note * that we've adjusted the count * already, so we don't do it again * on a rescan. */ adjust(&idesc, n); if (was_dir && (statemap[inumber] == USTATE)) { need_rescan = 1; } else { TRACK_LNCNTP(inumber, lncntp[inumber] = 0); } } break; case DSTATE: clri(&idesc, "UNREF", CLRI_VERBOSE, CLRI_NOP_OK); if (was_dir && (statemap[inumber] == USTATE)) need_rescan = 1; break; case DCLEAR: dp = ginode(inumber); if (dp->di_size == 0) { clri(&idesc, "ZERO LENGTH", CLRI_VERBOSE, CLRI_NOP_CORRUPT); break; } /* FALLTHROUGH */ case FCLEAR: clri(&idesc, "BAD/DUP", CLRI_VERBOSE, CLRI_NOP_CORRUPT); break; case SCLEAR: clri(&idesc, "BAD", CLRI_VERBOSE, CLRI_NOP_CORRUPT); break; case USTATE: break; default: errexit("BAD STATE 0x%x FOR INODE I=%d", (int)statemap[inumber], inumber); } } } while (need_rescan); } int pass4check(struct inodesc *idesc) { int fragnum, cg_frag; int res = KEEPON; daddr32_t blkno = idesc->id_blkno; int cylno; struct cg *cgp = &cgrp; caddr_t err; if ((idesc->id_truncto >= 0) && (idesc->id_lbn < idesc->id_truncto)) { if (debug) (void) printf( "pass4check: skipping inode %d lbn %d with truncto %d\n", idesc->id_number, idesc->id_lbn, idesc->id_truncto); return (KEEPON); } for (fragnum = 0; fragnum < idesc->id_numfrags; fragnum++) { if (chkrange(blkno + fragnum, 1)) { res = SKIP; } else if (testbmap(blkno + fragnum)) { /* * The block's in use. Remove our reference * from it. * * If it wasn't a dup, or everybody's done with * it, then this is the last reference and it's * safe to actually deallocate the on-disk block. * * We depend on pass 5 resolving the on-disk bitmap * effects. */ cg_frag = blkno + fragnum; if (!find_dup_ref(cg_frag, idesc->id_number, idesc->id_lbn * sblock.fs_frag + fragnum, DB_DECR)) { if (debug) (void) printf("p4c marking %d avail\n", cg_frag); clrbmap(cg_frag); n_blks--; /* * Do the same for the on-disk bitmap, so * that we don't need another pass to figure * out what's really being used. We'll let * pass5() work out the fragment/block * accounting. */ cylno = dtog(&sblock, cg_frag); (void) getblk(&cgblk, cgtod(&sblock, cylno), (size_t)sblock.fs_cgsize); err = cg_sanity(cgp, cylno); if (err != NULL) { pfatal("CG %d: %s\n", cylno, err); free((void *)err); if (reply("REPAIR") == 0) errexit("Program terminated."); fix_cg(cgp, cylno); } clrbit(cg_blksfree(cgp), dtogd(&sblock, cg_frag)); cgdirty(); res |= ALTERED; } } } return (res); } /* * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include "fsck.h" static int check_maps(uchar_t *, uchar_t *, int, int, char *, int, int); void pass5(void) { caddr_t err; int32_t c, blk, frags; size_t basesize, sumsize, mapsize; int excessdirs; int inomapsize, blkmapsize; int update_csums, update_bitmaps; int bad_csum_sb, bad_csum_cg, bad_cgblks_cg, bad_cgblktot_cg; struct fs *fs = &sblock; struct cg *cg = &cgrp; diskaddr_t dbase, dmax; diskaddr_t d; uint64_t i, j; struct csum *cs; struct csum backup_cs; time_t now; struct csum cstotal; struct inodesc idesc; union { /* keep lint happy about alignment */ struct cg cg; /* the rest of buf has the bitmaps */ char buf[MAXBSIZE]; } u; caddr_t buf = u.buf; struct cg *newcg = &u.cg; (void) memset((void *)buf, 0, sizeof (u.buf)); newcg->cg_niblk = fs->fs_ipg; if (fs->fs_postblformat != FS_DYNAMICPOSTBLFMT) { pfatal("UNSUPPORTED ROTATIONAL TABLE FORMAT %d\n", fs->fs_postblformat); errexit("Program terminated."); /* NOTREACHED */ } /* LINTED this subtraction can't overflow and is int32-aligned */ basesize = &newcg->cg_space[0] - (uchar_t *)newcg; /* * We reserve the space for the old rotation summary * tables for the benefit of old kernels, but do not * maintain them in modern kernels. In time, they could * theoretically go away, if we wanted to deal with * changing the on-disk format. */ /* * Note that we don't use any of the cg_*() macros until * after cg_sanity() has approved of what we've got. */ newcg->cg_btotoff = basesize; newcg->cg_boff = newcg->cg_btotoff + fs->fs_cpg * sizeof (daddr32_t); newcg->cg_iusedoff = newcg->cg_boff + fs->fs_cpg * fs->fs_nrpos * sizeof (uint16_t); (void) memset(&newcg->cg_space[0], 0, newcg->cg_iusedoff - basesize); inomapsize = howmany(fs->fs_ipg, NBBY); newcg->cg_freeoff = newcg->cg_iusedoff + inomapsize; blkmapsize = howmany(fs->fs_fpg, NBBY); newcg->cg_nextfreeoff = newcg->cg_freeoff + blkmapsize; newcg->cg_magic = CG_MAGIC; sumsize = newcg->cg_iusedoff - newcg->cg_btotoff; mapsize = newcg->cg_nextfreeoff - newcg->cg_iusedoff; init_inodesc(&idesc); idesc.id_type = ADDR; (void) memset((void *)&cstotal, 0, sizeof (struct csum)); now = time(NULL); /* * If the last fragments in the file system don't make up a * full file system block, mark the bits in the blockmap * that correspond to those missing fragments as "allocated", * so that the last block doesn't get counted as a free block * and those missing fragments don't get counted as free frags. */ j = blknum(fs, (uint64_t)fs->fs_size + fs->fs_frag - 1); for (i = fs->fs_size; i < j; i++) setbmap(i); /* * The cg summaries are not always updated when using * logging. Since we're really concerned with getting a * sane filesystem, rather than in trying to debug UFS * corner cases, logically we would just always recompute * them. However, it is disconcerting to users to be asked * about updating the summaries when, from their point of * view, there's been no indication of a problem up to this * point. So, only do it if we find a discrepancy. */ update_csums = -1; update_bitmaps = 0; for (c = 0; c < fs->fs_ncg; c++) { backup_cs = cstotal; /* * cg_sanity() will catch i/o errors for us. */ (void) getblk(&cgblk, (diskaddr_t)cgtod(fs, c), (size_t)fs->fs_cgsize); err = cg_sanity(cg, c); if (err != NULL) { pfatal("CG %d: %s\n", c, err); free((void *)err); if (reply("REPAIR") == 0) errexit("Program terminated."); fix_cg(cg, c); } /* * If the on-disk timestamp is in the future, then it * by definition is wrong. Otherwise, if it's in * the past, then use that value so that we don't * declare a spurious mismatch. */ if (now > cg->cg_time) newcg->cg_time = cg->cg_time; else newcg->cg_time = now; newcg->cg_cgx = c; dbase = cgbase(fs, c); dmax = dbase + fs->fs_fpg; if (dmax > fs->fs_size) dmax = fs->fs_size; newcg->cg_ndblk = dmax - dbase; if (c == fs->fs_ncg - 1) newcg->cg_ncyl = fs->fs_ncyl - (fs->fs_cpg * c); else newcg->cg_ncyl = fs->fs_cpg; newcg->cg_niblk = sblock.fs_ipg; newcg->cg_cs.cs_ndir = 0; newcg->cg_cs.cs_nffree = 0; newcg->cg_cs.cs_nbfree = 0; newcg->cg_cs.cs_nifree = fs->fs_ipg; if ((cg->cg_rotor >= 0) && (cg->cg_rotor < newcg->cg_ndblk)) newcg->cg_rotor = cg->cg_rotor; else newcg->cg_rotor = 0; if ((cg->cg_frotor >= 0) && (cg->cg_frotor < newcg->cg_ndblk)) newcg->cg_frotor = cg->cg_frotor; else newcg->cg_frotor = 0; if ((cg->cg_irotor >= 0) && (cg->cg_irotor < newcg->cg_niblk)) newcg->cg_irotor = cg->cg_irotor; else newcg->cg_irotor = 0; (void) memset((void *)&newcg->cg_frsum[0], 0, sizeof (newcg->cg_frsum)); (void) memset((void *)cg_inosused(newcg), 0, (size_t)mapsize); /* LINTED macro is int32-aligned per newcg->cg_btotoff above */ (void) memset((void *)&cg_blktot(newcg)[0], 0, sumsize + mapsize); j = fs->fs_ipg * c; for (i = 0; i < fs->fs_ipg; j++, i++) { switch (statemap[j] & ~(INORPHAN | INDELAYD)) { case USTATE: break; case DSTATE: case DCLEAR: case DFOUND: case DZLINK: newcg->cg_cs.cs_ndir++; /* FALLTHROUGH */ case FSTATE: case FCLEAR: case FZLINK: case SSTATE: case SCLEAR: newcg->cg_cs.cs_nifree--; setbit(cg_inosused(newcg), i); break; default: if (j < UFSROOTINO) break; errexit("BAD STATE 0x%x FOR INODE I=%d", statemap[j], (int)j); } } if (c == 0) { for (i = 0; i < UFSROOTINO; i++) { setbit(cg_inosused(newcg), i); newcg->cg_cs.cs_nifree--; } } /* * Count up what fragments and blocks are free, and * reflect the relevant section of blockmap[] into * newcg's map. */ for (i = 0, d = dbase; d < dmax; d += fs->fs_frag, i += fs->fs_frag) { frags = 0; for (j = 0; j < fs->fs_frag; j++) { if (testbmap(d + j)) continue; setbit(cg_blksfree(newcg), i + j); frags++; } if (frags == fs->fs_frag) { newcg->cg_cs.cs_nbfree++; j = cbtocylno(fs, i); /* LINTED macro is int32-aligned per above */ cg_blktot(newcg)[j]++; /* LINTED cg_blks(newcg) is aligned */ cg_blks(fs, newcg, j)[cbtorpos(fs, i)]++; } else if (frags > 0) { newcg->cg_cs.cs_nffree += frags; blk = blkmap(fs, cg_blksfree(newcg), i); fragacct(fs, blk, newcg->cg_frsum, 1); } } cstotal.cs_nffree += newcg->cg_cs.cs_nffree; cstotal.cs_nbfree += newcg->cg_cs.cs_nbfree; cstotal.cs_nifree += newcg->cg_cs.cs_nifree; cstotal.cs_ndir += newcg->cg_cs.cs_ndir; /* * Note that, just like the kernel, we dynamically * allocated an array to hold the csums and stuffed * the pointer into the in-core superblock's fs_u.fs_csp * field. This means that the fs_u field contains a * random value when the disk version is examined, but * fs_cs() gives us a valid pointer nonetheless. * We need to compare the recalculated summaries to * both the superblock version and the on disk version. * If either is bad, copy the calculated version over * the corrupt values. */ cs = &fs->fs_cs(fs, c); bad_csum_sb = (memcmp((void *)cs, (void *)&newcg->cg_cs, sizeof (*cs)) != 0); bad_csum_cg = (memcmp((void *)&cg->cg_cs, (void *)&newcg->cg_cs, sizeof (struct csum)) != 0); /* * Has the user told us what to do yet? If not, find out. */ if ((bad_csum_sb || bad_csum_cg) && (update_csums == -1)) { if (preen) { update_csums = 1; (void) printf("CORRECTING BAD CG SUMMARIES" " FOR CG %d\n", c); } else if (update_csums == -1) { update_csums = (reply( "CORRECT BAD CG SUMMARIES FOR CG %d", c) == 1); } } if (bad_csum_sb && (update_csums == 1)) { (void) memmove((void *)cs, (void *)&newcg->cg_cs, sizeof (*cs)); sbdirty(); (void) printf("CORRECTED SUPERBLOCK SUMMARIES FOR" " CG %d\n", c); } if (bad_csum_cg && (update_csums == 1)) { (void) memmove((void *)cg, (void *)newcg, (size_t)basesize); /* LINTED per cg_sanity() */ (void) memmove((void *)&cg_blktot(cg)[0], /* LINTED macro aligned as above */ (void *)&cg_blktot(newcg)[0], sumsize); cgdirty(); (void) printf("CORRECTED SUMMARIES FOR CG %d\n", c); } excessdirs = cg->cg_cs.cs_ndir - newcg->cg_cs.cs_ndir; if (excessdirs < 0) { pfatal("LOST %d DIRECTORIES IN CG %d\n", -excessdirs, c); excessdirs = 0; } if (excessdirs > 0) { if (check_maps((uchar_t *)cg_inosused(newcg), (uchar_t *)cg_inosused(cg), inomapsize, cg->cg_cgx * fs->fs_ipg, "DIR", 0, excessdirs)) { if (!verbose) (void) printf("DIR BITMAP WRONG "); if (preen || update_bitmaps || reply("FIX") == 1) { (void) memmove((void *)cg_inosused(cg), (void *)cg_inosused(newcg), inomapsize); cgdirty(); if (preen || (!verbose && update_bitmaps)) (void) printf("(CORRECTED)\n"); update_bitmaps = 1; } } } if (check_maps((uchar_t *)cg_inosused(newcg), (uchar_t *)cg_inosused(cg), inomapsize, cg->cg_cgx * fs->fs_ipg, "FILE", excessdirs, fs->fs_ipg)) { if (!verbose) (void) printf("FILE BITMAP WRONG "); if (preen || update_bitmaps || reply("FIX") == 1) { (void) memmove((void *)cg_inosused(cg), (void *)cg_inosused(newcg), inomapsize); cgdirty(); if (preen || (!verbose && update_bitmaps)) (void) printf("(CORRECTED)\n"); update_bitmaps = 1; } } if (check_maps((uchar_t *)cg_blksfree(cg), (uchar_t *)cg_blksfree(newcg), blkmapsize, cg->cg_cgx * fs->fs_fpg, "FRAG", 0, fs->fs_fpg)) { if (!verbose) (void) printf("FRAG BITMAP WRONG "); if (preen || update_bitmaps || reply("FIX") == 1) { (void) memmove((void *)cg_blksfree(cg), (void *)cg_blksfree(newcg), blkmapsize); cgdirty(); if (preen || (!verbose && update_bitmaps)) (void) printf("(CORRECTED)\n"); update_bitmaps = 1; } } bad_cgblks_cg = (memcmp((void *)&cg_blks(fs, cg, 0)[0], (void *)&cg_blks(fs, newcg, 0)[0], fs->fs_cpg * fs->fs_nrpos * sizeof (int16_t)) != 0); if (bad_cgblks_cg) { if (!verbose) (void) printf("ROTATIONAL POSITIONS " "BLOCK COUNT WRONG "); if (preen || update_bitmaps || reply("FIX") == 1) { (void) memmove((void *)&cg_blks(fs, cg, 0)[0], (void *)&cg_blks(fs, newcg, 0)[0], fs->fs_cpg * fs->fs_nrpos * sizeof (int16_t)); cgdirty(); if (preen || (!verbose && update_bitmaps)) (void) printf("(CORRECTED)\n"); update_bitmaps = 1; } } bad_cgblktot_cg = (memcmp((void *)&cg_blktot(cg)[0], (void *)&cg_blktot(newcg)[0], fs->fs_cpg * sizeof (int32_t)) != 0); if (bad_cgblktot_cg) { if (!verbose) (void) printf("ROTATIONAL POSITIONS " "BLOCK TOTAL WRONG "); if (preen || update_bitmaps || reply("FIX") == 1) { (void) memmove((void *)&cg_blktot(cg)[0], (void *)&cg_blktot(newcg)[0], fs->fs_cpg * sizeof (int32_t)); cgdirty(); if (preen || (!verbose && update_bitmaps)) (void) printf("(CORRECTED)\n"); update_bitmaps = 1; } } /* * Fixing one set of problems often shows up more in the * same cg. Just to make sure, go back and check it * again if we found something this time through. */ if (cgisdirty()) { cgflush(); cstotal = backup_cs; c--; } } if ((fflag || !(islog && islogok)) && (memcmp((void *)&cstotal, (void *)&fs->fs_cstotal, sizeof (struct csum)) != 0)) { if (dofix(&idesc, "CORRECT GLOBAL SUMMARY")) { (void) memmove((void *)&fs->fs_cstotal, (void *)&cstotal, sizeof (struct csum)); fs->fs_ronly = 0; fs->fs_fmod = 0; sbdirty(); } else { iscorrupt = 1; } } } /* * Compare two allocation bitmaps, reporting any discrepancies. * * If a mismatch is found, if the bit is set in map1, it's considered * to be an indication that the corresponding resource is supposed * to be free, but isn't. Otherwise, it's considered marked as allocated * but not found to be so. In other words, if the two maps being compared * use a set bit to indicate something is free, pass the on-disk map * first. Otherwise, pass the calculated map first. */ static int check_maps( uchar_t *map1, /* map of claimed allocations */ uchar_t *map2, /* map of determined allocations */ int mapsize, /* size of above two maps */ int startvalue, /* resource value for first element in map */ char *name, /* name of resource found in maps */ int skip, /* number of entries to skip before starting to free */ int limit) /* limit on number of entries to free */ { long i, j, k, l, m, n, size; int astart, aend, ustart, uend; int mismatch; mismatch = 0; astart = ustart = aend = uend = -1; for (i = 0; i < mapsize; i++) { j = *map1++; k = *map2++; if (j == k) continue; for (m = 0, l = 1; m < NBBY; m++, l <<= 1) { if ((j & l) == (k & l)) continue; n = startvalue + i * NBBY + m; if ((j & l) != 0) { if (astart == -1) { astart = aend = n; continue; } if (aend + 1 == n) { aend = n; continue; } if (verbose) { if (astart == aend) pwarn( "ALLOCATED %s %d WAS MARKED FREE ON DISK\n", name, astart); else pwarn( "ALLOCATED %sS %d-%d WERE MARKED FREE ON DISK\n", name, astart, aend); } mismatch = 1; astart = aend = n; } else { if (ustart == -1) { ustart = uend = n; continue; } if (uend + 1 == n) { uend = n; continue; } size = uend - ustart + 1; if (size <= skip) { skip -= size; ustart = uend = n; continue; } if (skip > 0) { ustart += skip; size -= skip; skip = 0; } if (size > limit) size = limit; if (verbose) { if (size == 1) pwarn( "UNALLOCATED %s %d WAS MARKED USED ON DISK\n", name, ustart); else pwarn( "UNALLOCATED %sS %d-%ld WERE MARKED USED ON DISK\n", name, ustart, ustart + size - 1); } mismatch = 1; limit -= size; if (limit <= 0) return (mismatch); ustart = uend = n; } } } if (astart != -1) { if (verbose) { if (astart == aend) pwarn( "ALLOCATED %s %d WAS MARKED FREE ON DISK\n", name, astart); else pwarn( "ALLOCATED %sS %d-%d WERE MARKED FREE ON DISK\n", name, astart, aend); } mismatch = 1; } if (ustart != -1) { size = uend - ustart + 1; if (size <= skip) return (mismatch); if (skip > 0) { ustart += skip; size -= skip; } if (size > limit) size = limit; if (verbose) { if (size == 1) pwarn( "UNALLOCATED %s %d WAS MARKED USED ON DISK\n", name, ustart); else pwarn( "UNALLOCATED %sS %d-%ld WERE MARKED USED ON DISK\n", name, ustart, ustart + size - 1); } mismatch = 1; } return (mismatch); } /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #define DKTYPENAMES #include #include #include #include #include #include #include #include #include #include #include #include #include /* for ENDIAN defines */ #include #include #include #include #include #include #include #include #include #include #include #include #include "roll_log.h" #include "fsck.h" /* * The size of a cylinder group is calculated by CGSIZE. The maximum size * is limited by the fact that cylinder groups are at most one block. * Its size is derived from the size of the maps maintained in the * cylinder group and the (struct cg) size. */ #define CGSIZE(fs) \ /* base cg */ (sizeof (struct cg) + \ /* blktot size */ (fs)->fs_cpg * sizeof (int32_t) + \ /* blks size */ (fs)->fs_cpg * (fs)->fs_nrpos * sizeof (short) + \ /* inode map */ howmany((fs)->fs_ipg, NBBY) + \ /* block map */ howmany((fs)->fs_cpg * (fs)->fs_spc / NSPF(fs), NBBY)) #define altsblock (*asblk.b_un.b_fs) #define POWEROF2(num) (((num) & ((num) - 1)) == 0) /* * Methods of determining where alternate superblocks should * be. MAX_SB_STYLES must be the last one, and the others need * to be positive. */ typedef enum { MKFS_STYLE = 1, NEWFS_STYLE, MAX_SB_STYLES } calcsb_t; static caddr_t calcsb_names[] = { "", "MKFS", "NEWFS", "" }; fsck_ino_t lfdir; int64_t numacls, aclmax, aclplast; int64_t numdirs, listmax, inplast; char havesb; int fsreadfd; int isdirty; int pid; int secsize; size_t dev_bsize; struct bufarea sblk; static struct bufarea asblk; /* alternate superblock */ struct inoinfo **inphead, **inpsort; struct shadowclientinfo *shadowclientinfo = NULL; struct shadowclientinfo *attrclientinfo = NULL; int maxshadowclients = 1024; /* allocation size, not limit */ static void badsb(int, caddr_t); static int calcsb(calcsb_t, caddr_t, int, struct fs *); static int checksb(int); static void flush_fs(void); static void sblock_init(void); static void uncreate_maps(void); static int read_super_block(int listerr) { int fd; caddr_t err; if (mount_point != NULL) { fd = open(mount_point, O_RDONLY); if (fd == -1) { errexit("fsck: open mount point error: %s", strerror(errno)); /* NOTREACHED */ } /* get the latest super block */ if (ioctl(fd, _FIOGETSUPERBLOCK, &sblock)) { errexit("fsck: ioctl _FIOGETSUPERBLOCK error: %s", strerror(errno)); /* NOTREACHED */ } (void) close(fd); } else { (void) fsck_bread(fsreadfd, (caddr_t)&sblock, bflag != 0 ? (diskaddr_t)bflag : (diskaddr_t)SBLOCK, SBSIZE); } /* * Don't let trash from the disk trip us up later * in ungetsummaryinfo(). */ sblock.fs_u.fs_csp = NULL; /* * Rudimentary consistency checks. Can't really call * checksb() here, because there may be outstanding * deltas that still need to be applied. */ if ((sblock.fs_magic != FS_MAGIC) && (sblock.fs_magic != MTB_UFS_MAGIC)) { err = "MAGIC NUMBER WRONG"; goto fail; } if (sblock.fs_magic == FS_MAGIC && (sblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && sblock.fs_version != UFS_VERSION_MIN)) { err = "UNRECOGNIZED VERSION"; goto fail; } if (sblock.fs_magic == MTB_UFS_MAGIC && (sblock.fs_version > MTB_UFS_VERSION_1 || sblock.fs_version < MTB_UFS_VERSION_MIN)) { err = "UNRECOGNIZED VERSION"; goto fail; } if (sblock.fs_ncg < 1) { err = "NCG OUT OF RANGE"; goto fail; } if (sblock.fs_cpg < 1) { err = "CPG OUT OF RANGE"; goto fail; } if (sblock.fs_ncg * sblock.fs_cpg < sblock.fs_ncyl || (sblock.fs_ncg - 1) * sblock.fs_cpg >= sblock.fs_ncyl) { err = "NCYL IS INCONSISTENT WITH NCG*CPG"; goto fail; } if (sblock.fs_sbsize < 0 || sblock.fs_sbsize > SBSIZE) { err = "SIZE OUT OF RANGE"; goto fail; } return (1); fail: badsb(listerr, err); return (0); } static void flush_fs() { int fd; if (mount_point != NULL) { fd = open(mount_point, O_RDONLY); if (fd == -1) { errexit("fsck: open mount point error: %s", strerror(errno)); /* NOTREACHED */ } if (ioctl(fd, _FIOFFS, NULL)) { /* flush file system */ errexit("fsck: ioctl _FIOFFS error: %s", strerror(errno)); /* NOTREACHED */ } (void) close(fd); } } /* * Roll the embedded log, if any, and set up the global variables * islog and islogok. */ static int logsetup(caddr_t devstr) { void *buf; extent_block_t *ebp; ml_unit_t *ul; ml_odunit_t *ud; void *ud_buf; int badlog; islog = islogok = 0; if (bflag != 0) return (1); /* can't roll log while alternate sb specified */ /* * Roll the log, if any. A bad sb implies we'll be using * an alternate sb as far as logging goes, so just fail back * to the caller if we can't read the default sb. Suppress * complaints, because the caller will be reading the same * superblock again and running full verification on it, so * whatever is bad will be reported then. */ sblock.fs_logbno = 0; badlog = 0; if (!read_super_block(0)) return (1); /* * Roll the log in 3 cases: * 1. If it's unmounted (mount_point == NULL) and it's not marked * as fully rolled (sblock.fs_rolled != FS_ALL_ROLLED) * 2. If it's mounted and anything other than a sanity * check fsck (mflag) is being done, as we have the current * super block. Note, only a sanity check is done for * root/usr at boot. If a roll were done then the expensive * ufs_flush() gets called, leading to a slower boot. * 3. If anything other then a sanity check (mflag) is being done * to a mounted filesystem while it is in read-only state * (e.g. root during early boot stages) we have to detect this * and have to roll the log as well. NB. the read-only mount * will flip fs_clean from FSLOG to FSSTABLE and marks the * log as FS_NEED_ROLL. */ if (sblock.fs_logbno && (((mount_point == NULL) && (sblock.fs_rolled != FS_ALL_ROLLED)) || ((mount_point != NULL) && !mflag))) { int roll_log_err = 0; if (sblock.fs_ronly && (sblock.fs_clean == FSSTABLE) && (sblock.fs_state + sblock.fs_time == FSOKAY)) { /* * roll the log without a mount */ flush_fs(); } if (sblock.fs_clean == FSLOG && (sblock.fs_state + sblock.fs_time == FSOKAY)) { if (rl_roll_log(devstr) != RL_SUCCESS) roll_log_err = 1; } if (roll_log_err) { (void) printf("Can't roll the log for %s.\n", devstr); /* * There are two cases where we want to set * an error code and return: * - We're preening * - We're not on a live root and the user * chose *not* to ignore the log * Otherwise, we want to mark the log as bad * and continue to check the filesystem. This * has the side effect of destroying the log. */ if (preen || (!hotroot && reply( "DISCARDING THE LOG MAY DISCARD PENDING TRANSACTIONS.\n" "DISCARD THE LOG AND CONTINUE") == 0)) { exitstat = EXERRFATAL; return (0); } ++badlog; } } /* Logging UFS may be enabled */ if (sblock.fs_logbno) { ++islog; /* log is not okay; check the fs */ if (FSOKAY != (sblock.fs_state + sblock.fs_time)) return (1); /* * If logging or (stable and mounted) then continue */ if (!((sblock.fs_clean == FSLOG) || (sblock.fs_clean == FSSTABLE) && (mount_point != NULL))) return (1); /* get the log allocation block */ buf = malloc(dev_bsize); if (buf == NULL) { return (1); } ud_buf = malloc(dev_bsize); if (ud_buf == NULL) { free(buf); return (1); } (void) fsck_bread(fsreadfd, buf, logbtodb(&sblock, sblock.fs_logbno), dev_bsize); ebp = (extent_block_t *)buf; /* log allocation block is not okay; check the fs */ if (ebp->type != LUFS_EXTENTS) { free(buf); free(ud_buf); return (1); } /* get the log state block(s) */ if (fsck_bread(fsreadfd, ud_buf, (logbtodb(&sblock, ebp->extents[0].pbno)), dev_bsize)) { (void) fsck_bread(fsreadfd, ud_buf, (logbtodb(&sblock, ebp->extents[0].pbno)) + 1, dev_bsize); } ud = (ml_odunit_t *)ud_buf; ul = (ml_unit_t *)malloc(sizeof (*ul)); if (ul == NULL) { free(buf); free(ud_buf); return (1); } ul->un_ondisk = *ud; /* log state is okay; don't need to check the fs */ if ((ul->un_chksum == ul->un_head_ident + ul->un_tail_ident) && (ul->un_version == LUFS_VERSION_LATEST) && (ul->un_badlog == 0) && (!badlog)) { ++islogok; } free(ud_buf); free(buf); free(ul); } return (1); } /* * - given a pathname, determine the pathname to actually check * - if a directory * - if it is in mnttab, set devstr to the special (block) name * - if it is in vfstab, set devstr to the special (block) name * - if it has not been found, bail * - a file is used as-is, clear rflag * - a device is converted to block version (so can search mnttab) */ static void derive_devstr(const caddr_t dev, caddr_t devstr, size_t str_size) { mode_t mode; struct stat statb; if (stat(dev, &statb) < 0) { exitstat = EXNOSTAT; errexit("fsck: could not stat %s: %s", dev, strerror(errno)); } mode = statb.st_mode & S_IFMT; switch (mode) { case S_IFDIR: /* * The check_*() routines update devstr with the name. */ devstr[0] = '\0'; if (!(check_mnttab(dev, devstr, str_size) || check_vfstab(dev, devstr, str_size))) { exitstat = EXBADPARM; errexit( "fsck: could not find mountpoint %s in mnttab nor vfstab", dev); } break; case S_IFREG: rflag = 0; (void) strlcpy(devstr, dev, str_size); break; case S_IFCHR: case S_IFBLK: (void) strlcpy(devstr, unrawname(dev), str_size); break; default: exitstat = EXBADPARM; errexit("fsck: %s must be a mountpoint, device, or file", dev); /* NOTREACHED */ } } /* * Reports the index of the magic filesystem that mntp names. * If it does not correspond any of them, returns zero (hence * the backwards loop). */ static int which_corefs(const caddr_t mntp) { int corefs; for (corefs = MAGIC_LIMIT - 1; corefs > 0; corefs--) if (strcmp(mntp, magic_fs[corefs]) == 0) break; return (corefs); } /* * - set mount_point to NULL * - if name is mounted (search mnttab) * - if it is a device, clear rflag * - if mounted on /, /usr, or /var, set corefs * - if corefs and read-only, set hotroot and continue * - if errorlocked, continue * - if preening, bail * - ask user whether to continue, bail if not * - if it is a device and not mounted and rflag, convert * name to raw version */ static int check_mount_state(caddr_t devstr, size_t str_size) { int corefs = 0; int is_dev = 0; struct stat statb; if (stat(devstr, &statb) < 0) { exitstat = EXNOSTAT; errexit("fsck: could not stat %s: %s", devstr, strerror(errno)); } if (S_ISCHR(statb.st_mode) || S_ISBLK(statb.st_mode)) is_dev = 1; /* * mounted() will update mount_point when returning true. */ mount_point = NULL; if ((mountedfs = mounted(devstr, devstr, str_size)) != M_NOMNT) { if (is_dev) rflag = 0; corefs = which_corefs(mount_point); if (corefs && (mountedfs == M_RO)) { hotroot++; } else if (errorlocked) { goto carry_on; } else if (preen) { exitstat = EXMOUNTED; pfatal("%s IS CURRENTLY MOUNTED%s.", devstr, mountedfs == M_RW ? " READ/WRITE" : ""); } else { if (!nflag && !mflag) { pwarn("%s IS CURRENTLY MOUNTED READ/%s.", devstr, mountedfs == M_RW ? "WRITE" : "ONLY"); if (reply("CONTINUE") == 0) { exitstat = EXMOUNTED; errexit("Program terminated"); } } } } else if (is_dev && rflag) { (void) strlcpy(devstr, rawname(devstr), str_size); } carry_on: return (corefs); } static int open_and_intro(caddr_t devstr, int corefs) { int retval = 0; if ((fsreadfd = open64(devstr, O_RDONLY)) < 0) { (void) printf("Can't open %s: %s\n", devstr, strerror(errno)); exitstat = EXNOSTAT; retval = -1; goto finish; } if (!preen || debug != 0) (void) printf("** %s", devstr); if (errorlocked) { if (debug && elock_combuf != NULL) (void) printf(" error-lock comment: \"%s\" ", elock_combuf); fflag = 1; } pid = getpid(); if (nflag || (fswritefd = open64(devstr, O_WRONLY)) < 0) { fswritefd = -1; if (preen && !debug) pfatal("(NO WRITE ACCESS)\n"); (void) printf(" (NO WRITE)"); } if (!preen) (void) printf("\n"); else if (debug) (void) printf(" pid %d\n", pid); if (debug && (hotroot || (mountedfs != M_NOMNT))) { (void) printf("** %s", devstr); if (hotroot) (void) printf(" is %s fs", magic_fs[corefs]); if (mountedfs != M_NOMNT) (void) printf(" and is mounted read-%s", (mountedfs == M_RO) ? "only" : "write"); if (errorlocked) (void) printf(" and is error-locked"); (void) printf(".\n"); } finish: return (retval); } static int find_superblock(caddr_t devstr) { int cg = 0; int retval = 0; int first; int found; calcsb_t style; struct fs proto; /* * Check the superblock, looking for alternates if necessary. * In more-recent times, some UFS instances get created with * only the first ten and last ten superblock backups. Since * if we can't get the necessary information from any of those, * the odds are also against us for the ones in between, we'll * just look at those twenty to save time. */ if (!read_super_block(1) || !checksb(1)) { if (bflag || preen) { retval = -1; goto finish; } for (style = MKFS_STYLE; style < MAX_SB_STYLES; style++) { if (reply("LOOK FOR ALTERNATE SUPERBLOCKS WITH %s", calcsb_names[style]) == 0) continue; first = 1; found = 0; if (!calcsb(style, devstr, fsreadfd, &proto)) { cg = proto.fs_ncg; continue; } if (debug) { (void) printf( "debug: calcsb(%s) gave fpg %d, cgoffset %d, ", calcsb_names[style], proto.fs_fpg, proto.fs_cgoffset); (void) printf("cgmask 0x%x, sblk %d, ncg %d\n", proto.fs_cgmask, proto.fs_sblkno, proto.fs_ncg); } for (cg = 0; cg < proto.fs_ncg; cg++) { bflag = fsbtodb(&proto, cgsblock(&proto, cg)); if (debug) (void) printf( "debug: trying block %lld\n", (longlong_t)bflag); if (read_super_block(0) && checksb(0)) { (void) printf( "FOUND ALTERNATE SUPERBLOCK %d WITH %s\n", bflag, calcsb_names[style]); if (reply( "USE ALTERNATE SUPERBLOCK") == 1) { found = 1; break; } } if (first && (cg >= 9)) { first = 0; if (proto.fs_ncg <= 9) cg = proto.fs_ncg; else if (proto.fs_ncg <= 19) cg = 9; else cg = proto.fs_ncg - 10; } } if (found) break; } /* * Didn't find one? Try to fake it. */ if (style >= MAX_SB_STYLES) { pwarn("SEARCH FOR ALTERNATE SUPERBLOCKS FAILED.\n"); for (style = MKFS_STYLE; style < MAX_SB_STYLES; style++) { if (reply("USE GENERIC SUPERBLOCK FROM %s", calcsb_names[style]) == 1 && calcsb(style, devstr, fsreadfd, &sblock)) { break; } } /* * We got something from mkfs/newfs, so use it. */ if (style < MAX_SB_STYLES) { proto.fs_ncg = sblock.fs_ncg; bflag = 0; } } /* * Still no luck? Tell the user they're on their own. */ if (style >= MAX_SB_STYLES) { pwarn("SEARCH FOR ALTERNATE SUPERBLOCKS FAILED. " "YOU MUST USE THE -o b OPTION\n" "TO FSCK TO SPECIFY THE LOCATION OF A VALID " "ALTERNATE SUPERBLOCK TO\n" "SUPPLY NEEDED INFORMATION; SEE fsck(8).\n"); bflag = 0; retval = -1; goto finish; } /* * Need to make sure a human really wants us to use * this. -y mode could've gotten us this far, so * we need to ask something that has to be answered * in the negative. * * Note that we can't get here when preening. */ if (!found) { pwarn("CALCULATED GENERIC SUPERBLOCK WITH %s\n", calcsb_names[style]); } else { pwarn("FOUND ALTERNATE SUPERBLOCK AT %d USING %s\n", bflag, calcsb_names[style]); } pwarn("If filesystem was created with manually-specified "); pwarn("geometry, using\nauto-discovered superblock may "); pwarn("result in irrecoverable damage to\nfilesystem and "); pwarn("user data.\n"); if (reply("CANCEL FILESYSTEM CHECK") == 1) { if (cg >= 0) { pwarn("Please verify that the indicated block " "contains a proper\nsuperblock for the " "filesystem (see fsdb(8)).\n"); if (yflag) pwarn("\nFSCK was running in YES " "mode. If you wish to run in " "that mode using\nthe alternate " "superblock, run " "`fsck -y -o b=%d %s'.\n", bflag, devstr); } retval = -1; goto finish; } /* * Pretend we found it as an alternate, so everything * gets updated when we clean up at the end. */ if (!found) { havesb = 1; sblk.b_bno = fsbtodb(&sblock, cgsblock(&sblock, 0)); bwrite(fswritefd, (caddr_t)&sblock, SBLOCK, SBSIZE); write_altsb(fswritefd); } } finish: return (retval); } /* * Check and potentially fix certain fields in the super block. */ static void fixup_superblock(void) { /* * Kernel looks for FS_OPTTIME, and assumes that if that's not * what's there, it must be FS_OPTSPACE, so not fixing does not * require setting iscorrupt. */ if (sblock.fs_optim != FS_OPTTIME && sblock.fs_optim != FS_OPTSPACE) { pfatal("UNDEFINED OPTIMIZATION IN SUPERBLOCK"); if (reply("SET TO DEFAULT") == 1) { sblock.fs_optim = FS_OPTTIME; sbdirty(); } } if ((sblock.fs_minfree < 0 || sblock.fs_minfree > 99)) { pfatal("IMPOSSIBLE MINFREE=%d IN SUPERBLOCK", sblock.fs_minfree); if (reply("SET TO DEFAULT") == 1) { sblock.fs_minfree = 10; sbdirty(); } else if (sblock.fs_minfree < 0) { /* * Kernel uses minfree without verification, * and a negative value would do bad things. */ iscorrupt = 1; } } } static int initial_error_state_adjust(void) { int retval = 0; /* do this right away to prevent any other fscks on this fs */ switch (sblock.fs_clean) { case FSBAD: break; case FSFIX: if (preen) errexit("ERROR-LOCKED; MARKED \"FSFIX\"\n"); if (reply("marked FSFIX, CONTINUE") == 0) { retval = -1; goto finish; } break; case FSCLEAN: if (preen) errexit("ERROR-LOCKED; MARKED \"FSCLEAN\"\n"); if (reply("marked FSCLEAN, CONTINUE") == 0) { retval = -1; goto finish; } break; default: if (preen) { if (debug) pwarn("ERRORLOCKED; NOT MARKED \"FSBAD\"\n"); else errexit("ERRORLOCKED; NOT MARKED \"FSBAD\"\n"); } else { (void) printf("error-locked but not marked \"FSBAD\";"); if (reply(" CONTINUE") == 0) { retval = -1; goto finish; } } break; } if (!do_errorlock(LOCKFS_ELOCK)) { if (preen) { retval = -1; goto finish; } if (reply("error-lock reset failed; CONTINUE") == 0) { retval = -1; goto finish; } } sblock.fs_state = FSOKAY - (long)sblock.fs_time; sblock.fs_clean = FSFIX; sbdirty(); write_altsb(fswritefd); finish: return (retval); } static void getsummaryinfo(void) { size_t size; int failed; int asked; int i, j; caddr_t sip; /* * read in the summary info. */ sblock.fs_u.fs_csp = calloc(1, sblock.fs_cssize); if (sblock.fs_u.fs_csp == NULL) errexit( "cannot allocate %u bytes for cylinder group summary info\n", (unsigned)sblock.fs_cssize); sip = (caddr_t)sblock.fs_u.fs_csp; asked = 0; for (i = 0, j = 0; i < sblock.fs_cssize; i += sblock.fs_bsize, j++) { size = sblock.fs_cssize - i < sblock.fs_bsize ? sblock.fs_cssize - i : sblock.fs_bsize; failed = fsck_bread(fsreadfd, sip, fsbtodb(&sblock, sblock.fs_csaddr + j * sblock.fs_frag), size); if (failed && !asked) { pfatal("BAD SUMMARY INFORMATION"); if (reply("CONTINUE") == 0) { ckfini(); exit(EXFNDERRS); } asked = 1; } sip += size; } } /* * Reverses the effects of getsummaryinfo(). */ static void ungetsummaryinfo(void) { if ((sblk.b_un.b_fs != NULL) && (sblk.b_un.b_fs->fs_u.fs_csp != NULL)) { free(sblk.b_un.b_fs->fs_u.fs_csp); sblk.b_un.b_fs->fs_u.fs_csp = NULL; } } /* * Allocate and initialize the global tables. * It is the responsibility of the caller to clean up and allocations * if an error is returned. */ static int create_and_init_maps(void) { int64_t bmapsize; int retval = 0; maxfsblock = sblock.fs_size; maxino = sblock.fs_ncg * sblock.fs_ipg; bmapsize = roundup(howmany((uint64_t)maxfsblock, NBBY), sizeof (short)); blockmap = calloc((size_t)bmapsize, sizeof (char)); if (blockmap == NULL) { (void) printf("cannot alloc %lld bytes for blockmap\n", (longlong_t)bmapsize); retval = -1; goto finish; } statemap = calloc((size_t)(maxino + 1), sizeof (*statemap)); if (statemap == NULL) { (void) printf("cannot alloc %lld bytes for statemap\n", (longlong_t)(maxino + 1) * sizeof (*statemap)); retval = -1; goto finish; } lncntp = (short *)calloc((size_t)(maxino + 1), sizeof (short)); if (lncntp == NULL) { (void) printf("cannot alloc %lld bytes for lncntp\n", (longlong_t)(maxino + 1) * sizeof (short)); retval = -1; goto finish; } /* * If we had to fake up a superblock, it won't show that there * are any directories at all. This causes problems when we * use numdirs to calculate hash keys, so use something at least * vaguely plausible. */ numdirs = sblock.fs_cstotal.cs_ndir; if (numdirs == 0) numdirs = sblock.fs_ipg * sblock.fs_ncg / 2; listmax = numdirs + 10; inpsort = (struct inoinfo **)calloc((unsigned)listmax, sizeof (struct inoinfo *)); inphead = (struct inoinfo **)calloc((unsigned)numdirs, sizeof (struct inoinfo *)); if (inpsort == NULL || inphead == NULL) { (void) printf("cannot alloc %lld bytes for inphead\n", (longlong_t)numdirs * sizeof (struct inoinfo *)); retval = -1; goto finish; } if (debug) { if (listmax > ULONG_MAX) errexit("create_and_init_maps: listmax overflowed\n"); if (numdirs > ULONG_MAX) errexit("create_and_init_maps: numdirs overflowed\n"); } numacls = numdirs; aclmax = numdirs + 10; aclpsort = (struct inoinfo **)calloc((unsigned)aclmax, sizeof (struct inoinfo *)); aclphead = (struct inoinfo **)calloc((unsigned)numacls, sizeof (struct inoinfo *)); if (aclpsort == NULL || aclphead == NULL) { (void) printf("cannot alloc %lld bytes for aclphead\n", (longlong_t)numacls * sizeof (struct inoinfo *)); retval = -1; goto finish; } if (debug) { if (aclmax > ULONG_MAX) errexit("create_and_init_maps: aclmax overflowed\n"); if (numacls > ULONG_MAX) errexit("create_and_init_maps: numacls overflowed\n"); } aclplast = 0L; inplast = 0L; finish: return (retval); } caddr_t setup(caddr_t dev) { int corefs; static char devstr[MAXPATHLEN + 1]; havesb = 0; devname = devstr; derive_devstr(dev, devstr, sizeof (devstr)); errorlocked = is_errorlocked(devstr); corefs = check_mount_state(devstr, sizeof (devstr)); sblock_init(); if (open_and_intro(devstr, corefs) == -1) goto cleanup; if (mflag && mounted(devstr, devstr, sizeof (devstr)) == M_RW) return (devstr); /* * Check log state */ if (!logsetup(devstr)) goto cleanup; /* * Flush fs if we're going to do anything other than a sanity check. * Note, if logging then the fs was already flushed in logsetup(). */ if (!islog && !mflag) flush_fs(); if (find_superblock(devstr) == -1) goto cleanup; fixup_superblock(); if (errorlocked && (initial_error_state_adjust() == -1)) goto cleanup; /* * asblk could be dirty because we found a mismatch between * the primary superblock and one of its backups in checksb(). */ if (asblk.b_dirty && !bflag) { (void) memmove(&altsblock, &sblock, (size_t)sblock.fs_sbsize); flush(fswritefd, &asblk); } getsummaryinfo(); /* * if not error-locked, using the standard superblock, * not bad log, not forced, preening, and is clean; * stop checking */ if (!errorlocked && (bflag == 0) && ((!islog || islogok) && (fflag == 0) && preen && (FSOKAY == (sblock.fs_state + sblock.fs_time)) && ((sblock.fs_clean == FSLOG && islog) || ((sblock.fs_clean == FSCLEAN) || (sblock.fs_clean == FSSTABLE))))) { iscorrupt = 0; printclean(); goto cleanup; } if (create_and_init_maps() == -1) goto nomaps; bufinit(); return (devstr); nomaps: ckfini(); exitstat = EXERRFATAL; /* FALLTHROUGH */ cleanup: unbufinit(); uncreate_maps(); ungetsummaryinfo(); /* * Can't get rid of the superblock buffer, because our * caller references it to generate the summary statistics. */ return (NULL); } /* * Undoes the allocations in create_and_init_maps() */ static void uncreate_maps(void) { /* * No ordering dependency amongst these, so they are here in * the same order they were calculated. */ if (blockmap != NULL) free(blockmap); if (statemap != NULL) free(statemap); if (lncntp != NULL) free(lncntp); if (inpsort != NULL) free(inpsort); if (inphead != NULL) free(inphead); if (aclpsort != NULL) free(aclpsort); if (aclphead != NULL) free(aclphead); } /* * mkfs limits the size of the inode map to be no more than a third of * the cylinder group space. We'll use that value for sanity checking * the superblock's inode per group value. */ #define MAXIpG (roundup(sblock.fs_bsize * NBBY / 3, sblock.fs_inopb)) /* * Check the super block and its summary info. */ static int checksb(int listerr) { caddr_t err; /* * When the fs check is successfully completed, the alternate super * block at sblk.b_bno will be overwritten by ckfini() with the * repaired super block. */ sblk.b_bno = bflag ? bflag : (SBOFF / dev_bsize); sblk.b_size = SBSIZE; /* * Sanity-check some of the values we are going to use later * in allocation requests. */ if (sblock.fs_cstotal.cs_ndir < 1 || sblock.fs_cstotal.cs_ndir > sblock.fs_ncg * sblock.fs_ipg) { if (verbose) (void) printf( "Found %d directories, should be between 1 and %d inclusive.\n", sblock.fs_cstotal.cs_ndir, sblock.fs_ncg * sblock.fs_ipg); err = "NUMBER OF DIRECTORIES OUT OF RANGE"; goto failedsb; } if (sblock.fs_nrpos <= 0 || sblock.fs_postbloff < 0 || sblock.fs_cpc < 0 || (sblock.fs_postbloff + (sblock.fs_nrpos * sblock.fs_cpc * sizeof (short))) > sblock.fs_sbsize) { err = "ROTATIONAL POSITION TABLE SIZE OUT OF RANGE"; goto failedsb; } if (sblock.fs_cssize != fragroundup(&sblock, sblock.fs_ncg * sizeof (struct csum))) { err = "SIZE OF CYLINDER GROUP SUMMARY AREA WRONG"; goto failedsb; } if (sblock.fs_inopb != (sblock.fs_bsize / sizeof (struct dinode))) { err = "INOPB NONSENSICAL RELATIVE TO BSIZE"; goto failedsb; } if (sblock.fs_bsize > MAXBSIZE) { err = "BLOCK SIZE LARGER THAN MAXIMUM SUPPORTED"; goto failedsb; } if (sblock.fs_bsize != (sblock.fs_frag * sblock.fs_fsize)) { err = "FRAGS PER BLOCK OR FRAG SIZE WRONG"; goto failedsb; } if (sblock.fs_dsize >= sblock.fs_size) { err = "NUMBER OF DATA BLOCKS OUT OF RANGE"; goto failedsb; } #if 0 if (sblock.fs_size > (sblock.fs_nsect * sblock.fs_ntrak * sblock.fs_ncyl)) { err = "FILESYSTEM SIZE LARGER THAN DEVICE"; goto failedsb; } #endif /* * Check that the number of inodes per group isn't less than or * equal to zero. Also makes sure it isn't more than the * maximum number mkfs enforces. */ if (sblock.fs_ipg <= 0 || sblock.fs_ipg > MAXIpG) { err = "INODES PER GROUP OUT OF RANGE"; goto failedsb; } if (sblock.fs_cgsize > sblock.fs_bsize) { err = "CG HEADER LARGER THAN ONE BLOCK"; goto failedsb; } /* * Set all possible fields that could differ, then do check * of whole super block against an alternate super block. * When an alternate super-block is specified this check is skipped. */ (void) getblk(&asblk, cgsblock(&sblock, sblock.fs_ncg - 1), (size_t)sblock.fs_sbsize); if (asblk.b_errs != 0) { brelse(&asblk); return (0); } if (bflag != 0) { /* * Invalidate clean flag and state information. * Note that we couldn't return until after the * above getblk(), because we're going to want to * update asblk when everything's done. */ sblock.fs_clean = FSACTIVE; sblock.fs_state = (long)sblock.fs_time; sblock.fs_reclaim = 0; sbdirty(); havesb = 1; return (1); } altsblock.fs_link = sblock.fs_link; altsblock.fs_rolled = sblock.fs_rolled; altsblock.fs_time = sblock.fs_time; altsblock.fs_state = sblock.fs_state; altsblock.fs_cstotal = sblock.fs_cstotal; altsblock.fs_cgrotor = sblock.fs_cgrotor; altsblock.fs_fmod = sblock.fs_fmod; altsblock.fs_clean = sblock.fs_clean; altsblock.fs_ronly = sblock.fs_ronly; altsblock.fs_flags = sblock.fs_flags; altsblock.fs_maxcontig = sblock.fs_maxcontig; altsblock.fs_minfree = sblock.fs_minfree; altsblock.fs_optim = sblock.fs_optim; altsblock.fs_rotdelay = sblock.fs_rotdelay; altsblock.fs_maxbpg = sblock.fs_maxbpg; altsblock.fs_logbno = sblock.fs_logbno; altsblock.fs_reclaim = sblock.fs_reclaim; altsblock.fs_si = sblock.fs_si; (void) memmove((void *)altsblock.fs_fsmnt, (void *)sblock.fs_fsmnt, sizeof (sblock.fs_fsmnt)); /* * The following should not have to be copied. */ (void) memmove((void *)altsblock.fs_u.fs_csp_pad, (void *)sblock.fs_u.fs_csp_pad, sizeof (sblock.fs_u.fs_csp_pad)); altsblock.fs_fsbtodb = sblock.fs_fsbtodb; altsblock.fs_npsect = sblock.fs_npsect; altsblock.fs_nrpos = sblock.fs_nrpos; if (memcmp((void *)&sblock, (void *)&altsblock, (size_t)sblock.fs_sbsize) != 0) { err = "BAD VALUES IN SUPER BLOCK"; goto failedsb; } havesb = 1; return (1); failedsb: badsb(listerr, err); return (0); } static void badsb(int listerr, caddr_t s) { if (!listerr) return; if (preen) (void) printf("%s: ", devname); (void) printf("BAD SUPERBLOCK AT BLOCK %d: %s\n", bflag != 0 ? bflag : SBLOCK, s); if (preen) { pwarn( "USE AN ALTERNATE SUPERBLOCK TO SUPPLY NEEDED INFORMATION;\n"); pwarn("e.g. fsck [-F ufs] -o b=# [special ...] \n"); exitstat = EXERRFATAL; pfatal( "where # is the alternate super block. SEE fsck_ufs(8). \n"); } /* we're expected to return if not preening */ } /* * Write out the super block into each of the alternate super blocks. */ void write_altsb(int fd) { int cylno; for (cylno = 0; cylno < sblock.fs_ncg; cylno++) bwrite(fd, (caddr_t)&sblock, fsbtodb(&sblock, cgsblock(&sblock, cylno)), sblock.fs_sbsize); } static void sblock_init(void) { fsmodified = 0; if (errorlocked) isdirty = 1; lfdir = 0; initbarea(&sblk); initbarea(&asblk); /* * May have buffer left over from previous filesystem check. */ if (sblk.b_un.b_buf == NULL) sblk.b_un.b_buf = calloc(1, SBSIZE); if (asblk.b_un.b_buf == NULL) asblk.b_un.b_buf = calloc(1, SBSIZE); if (sblk.b_un.b_buf == NULL || asblk.b_un.b_buf == NULL) errexit("cannot allocate space for superblock\n"); /* * Could get the actual sector size from the device here, * but considering how much would need to change in the rest * of the system before it'd be a problem for us, it's not * worth worrying about right now. */ dev_bsize = secsize = DEV_BSIZE; } /* * Calculate a prototype superblock based on information in the disk label. * When done the cgsblock macro can be calculated and the fs_ncg field * can be used. Do NOT attempt to use other macros without verifying that * their needed information is available! * * In BSD, the disk label includes all sorts of useful information, * like cpg. Solaris doesn't have that, and deriving it (as well as * some other parameters) is difficult. Rather than duplicate the * code, just ask mkfs what it would've come up with by default. * Ideally, we'd just link in the code, but given the source base * involved, it's more practical to just get a binary dump. * * The one minor drawback to the above approach is that newfs and mkfs * will produce vastly different layouts for the same partition if * they're allowed to default everything. So, if the superblock that * mkfs gives us doesn't work for guessing where the alternates are, * we need to try newfs. */ static int calcsb(calcsb_t style, caddr_t dev, int devfd, struct fs *fs) { #define FROM_CHILD 0 #define TO_FSCK 1 #define CMD_IDX 0 #define DEV_IDX 3 #define SIZE_IDX 4 int child_pipe[2]; caddr_t mkfsline[] = { "", /* CMD_IDX */ "-o", "calcbinsb,N", NULL, /* DEV_IDX */ NULL, /* SIZE_IDX */ NULL }; caddr_t newfsline[] = { "", /* CMD_IDX */ "-B", "-N", NULL, /* DEV_IDX */ NULL }; int pending, transferred; caddr_t *cmdline; caddr_t target; caddr_t sizestr = NULL; caddr_t path_old, path_new, mkfs_dir, mkfs_path, newfs_path; caddr_t slash; diskaddr_t size; int devnull; switch (style) { case MKFS_STYLE: if (debug) (void) printf("calcsb() going with style MKFS\n"); cmdline = mkfsline; break; case NEWFS_STYLE: if (debug) (void) printf("calcsb() going with style NEWFS\n"); cmdline = newfsline; break; default: if (debug) (void) printf("calcsb() doesn't undestand style %d\n", style); return (0); } cmdline[DEV_IDX] = dev; /* * Normally, only use the stock versions of the utilities. * However, if we're debugging, the odds are that we're * using experimental versions of them as well, so allow * some flexibility. */ mkfs_path = getenv("MKFS_PATH"); if (!debug || (mkfs_path == NULL)) mkfs_path = MKFS_PATH; newfs_path = getenv("NEWFS_PATH"); if (!debug || (newfs_path == NULL)) newfs_path = NEWFS_PATH; if (style == MKFS_STYLE) { cmdline[CMD_IDX] = mkfs_path; size = getdisksize(dev, devfd); if (size == 0) return (0); (void) fsck_asprintf(&sizestr, "%lld", (longlong_t)size); cmdline[SIZE_IDX] = sizestr; } else if (style == NEWFS_STYLE) { /* * Make sure that newfs will find the right version of mkfs. */ cmdline[CMD_IDX] = newfs_path; path_old = getenv("PATH"); /* mkfs_path is always initialized, despite lint's concerns */ mkfs_dir = strdup(mkfs_path); if (mkfs_dir == NULL) return (0); /* * If no location data for mkfs, don't need to do * anything about PATH. */ slash = strrchr(mkfs_dir, '/'); if (slash != NULL) { /* * Just want the dir, so discard the executable name. */ *slash = '\0'; /* * newfs uses system() to find mkfs, so make sure * that the one we want to use is first on the * list. Don't free path_new upon success, as it * has become part of the environment. */ (void) fsck_asprintf(&path_new, "PATH=%s:%s", mkfs_dir, path_old); if (putenv(path_new) != 0) { free(mkfs_dir); free(path_new); return (0); } } free(mkfs_dir); } else { /* * Bad search style, quietly return failure. */ if (debug) { (void) printf("calcsb: got bad style number %d\n", (int)style); } return (0); } if (pipe(child_pipe) < 0) { pfatal("calcsb: could not create pipe: %s\n", strerror(errno)); if (sizestr != NULL) free(sizestr); return (0); } switch (fork()) { case -1: pfatal("calcsb: fork failed: %s\n", strerror(errno)); if (sizestr != NULL) free(sizestr); return (0); case 0: if (dup2(child_pipe[TO_FSCK], fileno(stdout)) < 0) { (void) printf( "calcsb: could not rename file descriptor: %s\n", strerror(errno)); exit(EXBADPARM); } devnull = open("/dev/null", O_WRONLY); if (devnull == -1) { (void) printf("calcsb: could not open /dev/null: %s\n", strerror(errno)); exit(EXBADPARM); } if (dup2(devnull, fileno(stderr)) < 0) { (void) printf( "calcsb: could not rename file descriptor: %s\n", strerror(errno)); exit(EXBADPARM); } (void) close(child_pipe[FROM_CHILD]); (void) execv(cmdline[CMD_IDX], cmdline); (void) printf("calcsb: could not exec %s: %s\n", cmdline[CMD_IDX], strerror(errno)); exit(EXBADPARM); /* NOTREACHED */ default: break; } (void) close(child_pipe[TO_FSCK]); if (sizestr != NULL) free(sizestr); pending = sizeof (struct fs); target = (caddr_t)fs; do { transferred = read(child_pipe[FROM_CHILD], target, pending); pending -= transferred; target += transferred; } while ((pending > 0) && (transferred > 0)); if (pending > 0) { if (transferred < 0) pfatal( "calcsb: binary read of superblock from %s failed: %s\n", (style == MKFS_STYLE) ? "mkfs" : "newfs", (transferred < 0) ? strerror(errno) : ""); else pfatal( "calcsb: short read of superblock from %s\n", (style == MKFS_STYLE) ? "mkfs" : "newfs"); return (0); } (void) close(child_pipe[FROM_CHILD]); (void) wait(NULL); if ((fs->fs_magic != FS_MAGIC) && (fs->fs_magic != MTB_UFS_MAGIC)) return (0); return (1); } /* * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright (c) 1980, 1986, 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _KERNEL #include #undef _KERNEL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fsck.h" struct bufarea *pbp; struct bufarea *pdirbp; caddr_t mount_point = NULL; static struct bufarea bufhead; /* head of list of other blks in filesys */ char *elock_combuf; char *elock_mountp; static struct lockfs *lfp; /* current lockfs status */ static int64_t diskreads, totalreads; /* Disk cache statistics */ static int log_checksum(int32_t *, int32_t *, int); static void vdirerror(fsck_ino_t, caddr_t, va_list); static struct mnttab *search_mnttab(caddr_t, caddr_t, caddr_t, size_t); static struct vfstab *search_vfstab(caddr_t, caddr_t, caddr_t, size_t); static void vpwarn(caddr_t, va_list); static int getaline(FILE *, caddr_t, int); static struct bufarea *alloc_bufarea(void); static void rwerror(caddr_t, diskaddr_t, int rval); static void debugclean(void); static void report_io_prob(caddr_t, diskaddr_t, size_t, ssize_t); static void freelogblk(daddr32_t); static void verrexit(caddr_t, va_list); static void vpfatal(caddr_t, va_list); static diskaddr_t get_device_size(int, caddr_t); static diskaddr_t brute_force_get_device_size(int); static void cg_constants(int, daddr32_t *, daddr32_t *, daddr32_t *, daddr32_t *, daddr32_t *, daddr32_t *); int ftypeok(struct dinode *dp) { switch (dp->di_mode & IFMT) { case IFDIR: case IFREG: case IFBLK: case IFCHR: case IFLNK: case IFSOCK: case IFIFO: case IFSHAD: case IFATTRDIR: return (1); default: if (debug) (void) printf("bad file type 0%o\n", dp->di_mode); return (0); } } int acltypeok(struct dinode *dp) { if (CHECK_ACL_ALLOWED(dp->di_mode & IFMT)) return (1); if (debug) (void) printf("bad file type for acl I=%d: 0%o\n", dp->di_shadow, dp->di_mode); return (0); } NOTE(PRINTFLIKE(1)) int reply(caddr_t fmt, ...) { va_list ap; char line[80]; if (preen) pfatal("INTERNAL ERROR: GOT TO reply() in preen mode"); if (mflag) { /* * We don't know what's going on, so don't potentially * make things worse by having errexit() write stuff * out to disk. */ (void) printf( "\n%s: UNEXPECTED INCONSISTENCY; RUN fsck MANUALLY.\n", devname); exit(EXERRFATAL); } va_start(ap, fmt); (void) putchar('\n'); (void) vprintf(fmt, ap); (void) putchar('?'); (void) putchar(' '); va_end(ap); if (nflag || fswritefd < 0) { (void) printf(" no\n\n"); return (0); } if (yflag) { (void) printf(" yes\n\n"); return (1); } (void) fflush(stdout); if (getaline(stdin, line, sizeof (line)) == EOF) errexit("\n"); (void) printf("\n"); if (line[0] == 'y' || line[0] == 'Y') { return (1); } else { return (0); } } int getaline(FILE *fp, caddr_t loc, int maxlen) { int n; caddr_t p, lastloc; p = loc; lastloc = &p[maxlen-1]; while ((n = getc(fp)) != '\n') { if (n == EOF) return (EOF); if (!isspace(n) && p < lastloc) *p++ = (char)n; } *p = '\0'; /* LINTED pointer difference won't overflow */ return (p - loc); } /* * Malloc buffers and set up cache. */ void bufinit(void) { struct bufarea *bp; int bufcnt, i; caddr_t bufp; bufp = malloc((size_t)sblock.fs_bsize); if (bufp == NULL) goto nomem; initbarea(&cgblk); cgblk.b_un.b_buf = bufp; bufhead.b_next = bufhead.b_prev = &bufhead; bufcnt = MAXBUFSPACE / sblock.fs_bsize; if (bufcnt < MINBUFS) bufcnt = MINBUFS; for (i = 0; i < bufcnt; i++) { bp = (struct bufarea *)malloc(sizeof (struct bufarea)); if (bp == NULL) { if (i >= MINBUFS) goto noalloc; goto nomem; } bufp = malloc((size_t)sblock.fs_bsize); if (bufp == NULL) { free((void *)bp); if (i >= MINBUFS) goto noalloc; goto nomem; } initbarea(bp); bp->b_un.b_buf = bufp; bp->b_prev = &bufhead; bp->b_next = bufhead.b_next; bufhead.b_next->b_prev = bp; bufhead.b_next = bp; } noalloc: bufhead.b_size = i; /* save number of buffers */ pbp = pdirbp = NULL; return; nomem: errexit("cannot allocate buffer pool\n"); /* NOTREACHED */ } /* * Undo a bufinit(). */ void unbufinit(void) { int cnt; struct bufarea *bp, *nbp; cnt = 0; for (bp = bufhead.b_prev; bp != NULL && bp != &bufhead; bp = nbp) { cnt++; flush(fswritefd, bp); nbp = bp->b_prev; /* * We're discarding the entire chain, so this isn't * technically necessary. However, it doesn't hurt * and lint's data flow analysis is much happier * (this prevents it from thinking there's a chance * of our using memory elsewhere after it's been released). */ nbp->b_next = bp->b_next; bp->b_next->b_prev = nbp; free((void *)bp->b_un.b_buf); free((void *)bp); } if (bufhead.b_size != cnt) errexit("Panic: cache lost %d buffers\n", bufhead.b_size - cnt); } /* * Manage a cache of directory blocks. */ struct bufarea * getdatablk(daddr32_t blkno, size_t size) { struct bufarea *bp; for (bp = bufhead.b_next; bp != &bufhead; bp = bp->b_next) if (bp->b_bno == fsbtodb(&sblock, blkno)) { goto foundit; } for (bp = bufhead.b_prev; bp != &bufhead; bp = bp->b_prev) if ((bp->b_flags & B_INUSE) == 0) break; if (bp == &bufhead) { bp = alloc_bufarea(); if (bp == NULL) { errexit("deadlocked buffer pool\n"); /* NOTREACHED */ } } /* * We're at the same logical level as getblk(), so if there * are any errors, we'll let our caller handle them. */ diskreads++; (void) getblk(bp, blkno, size); foundit: totalreads++; bp->b_cnt++; /* * Move the buffer to head of linked list if it isn't * already there. */ if (bufhead.b_next != bp) { bp->b_prev->b_next = bp->b_next; bp->b_next->b_prev = bp->b_prev; bp->b_prev = &bufhead; bp->b_next = bufhead.b_next; bufhead.b_next->b_prev = bp; bufhead.b_next = bp; } bp->b_flags |= B_INUSE; return (bp); } void brelse(struct bufarea *bp) { bp->b_cnt--; if (bp->b_cnt == 0) { bp->b_flags &= ~B_INUSE; } } struct bufarea * getblk(struct bufarea *bp, daddr32_t blk, size_t size) { diskaddr_t dblk; dblk = fsbtodb(&sblock, blk); if (bp->b_bno == dblk) return (bp); flush(fswritefd, bp); bp->b_errs = fsck_bread(fsreadfd, bp->b_un.b_buf, dblk, size); bp->b_bno = dblk; bp->b_size = size; return (bp); } void flush(int fd, struct bufarea *bp) { int i, j; caddr_t sip; long size; if (!bp->b_dirty) return; /* * It's not our buf, so if there are errors, let whoever * acquired it deal with the actual problem. */ if (bp->b_errs != 0) pfatal("WRITING ZERO'ED BLOCK %lld TO DISK\n", bp->b_bno); bp->b_dirty = 0; bp->b_errs = 0; bwrite(fd, bp->b_un.b_buf, bp->b_bno, (long)bp->b_size); if (bp != &sblk) { return; } /* * We're flushing the superblock, so make sure all the * ancillary bits go out as well. */ sip = (caddr_t)sblock.fs_u.fs_csp; for (i = 0, j = 0; i < sblock.fs_cssize; i += sblock.fs_bsize, j++) { size = sblock.fs_cssize - i < sblock.fs_bsize ? sblock.fs_cssize - i : sblock.fs_bsize; bwrite(fswritefd, sip, fsbtodb(&sblock, sblock.fs_csaddr + j * sblock.fs_frag), size); sip += size; } } static void rwerror(caddr_t mesg, diskaddr_t blk, int rval) { int olderr = errno; if (!preen) (void) printf("\n"); if (rval == -1) pfatal("CANNOT %s: DISK BLOCK %lld: %s", mesg, blk, strerror(olderr)); else pfatal("CANNOT %s: DISK BLOCK %lld", mesg, blk); if (reply("CONTINUE") == 0) { exitstat = EXERRFATAL; errexit("Program terminated\n"); } } void ckfini(void) { int64_t percentage; if (fswritefd < 0) return; flush(fswritefd, &sblk); /* * Were we using a backup superblock? */ if (havesb && sblk.b_bno != SBOFF / dev_bsize) { if (preen || reply("UPDATE STANDARD SUPERBLOCK") == 1) { sblk.b_bno = SBOFF / dev_bsize; sbdirty(); flush(fswritefd, &sblk); } } flush(fswritefd, &cgblk); if (cgblk.b_un.b_buf != NULL) { free((void *)cgblk.b_un.b_buf); cgblk.b_un.b_buf = NULL; } unbufinit(); pbp = NULL; pdirbp = NULL; if (debug) { /* * Note that we only count cache-related reads. * Anything that called fsck_bread() or getblk() * directly are explicitly not cached, so they're not * included here. */ if (totalreads != 0) percentage = diskreads * 100 / totalreads; else percentage = 0; (void) printf("cache missed %lld of %lld reads (%lld%%)\n", (longlong_t)diskreads, (longlong_t)totalreads, (longlong_t)percentage); } (void) close(fsreadfd); (void) close(fswritefd); fsreadfd = -1; fswritefd = -1; } int fsck_bread(int fd, caddr_t buf, diskaddr_t blk, size_t size) { caddr_t cp; int i; int errs; offset_t offset = ldbtob(blk); offset_t addr; /* * In our universe, nothing exists before the superblock, so * just pretend it's always zeros. This is the complement of * bwrite()'s ignoring write requests into that space. */ if (blk < SBLOCK) { if (debug) (void) printf( "WARNING: fsck_bread() passed blkno < %d (%lld)\n", SBLOCK, (longlong_t)blk); (void) memset(buf, 0, (size_t)size); return (1); } if (llseek(fd, offset, SEEK_SET) < 0) { rwerror("SEEK", blk, -1); } if ((i = read(fd, buf, size)) == size) { return (0); } rwerror("READ", blk, i); if (llseek(fd, offset, SEEK_SET) < 0) { rwerror("SEEK", blk, -1); } errs = 0; (void) memset(buf, 0, (size_t)size); pwarn("THE FOLLOWING SECTORS COULD NOT BE READ:"); for (cp = buf, i = 0; i < btodb(size); i++, cp += DEV_BSIZE) { addr = ldbtob(blk + i); if (llseek(fd, addr, SEEK_SET) < 0 || read(fd, cp, (int)secsize) < 0) { iscorrupt = 1; (void) printf(" %llu", blk + (u_longlong_t)i); errs++; } } (void) printf("\n"); return (errs); } void bwrite(int fd, caddr_t buf, diskaddr_t blk, int64_t size) { int i; int n; caddr_t cp; offset_t offset = ldbtob(blk); offset_t addr; if (fd < 0) return; if (blk < SBLOCK) { if (debug) (void) printf( "WARNING: Attempt to write illegal blkno %lld on %s\n", (longlong_t)blk, devname); return; } if (llseek(fd, offset, SEEK_SET) < 0) { rwerror("SEEK", blk, -1); } if ((i = write(fd, buf, (int)size)) == size) { fsmodified = 1; return; } rwerror("WRITE", blk, i); if (llseek(fd, offset, SEEK_SET) < 0) { rwerror("SEEK", blk, -1); } pwarn("THE FOLLOWING SECTORS COULD NOT BE WRITTEN:"); for (cp = buf, i = 0; i < btodb(size); i++, cp += DEV_BSIZE) { n = 0; addr = ldbtob(blk + i); if (llseek(fd, addr, SEEK_SET) < 0 || (n = write(fd, cp, DEV_BSIZE)) < 0) { iscorrupt = 1; (void) printf(" %llu", blk + (u_longlong_t)i); } else if (n > 0) { fsmodified = 1; } } (void) printf("\n"); } /* * Allocates the specified number of contiguous fragments. */ daddr32_t allocblk(int wantedfrags) { int block, leadfrag, tailfrag; daddr32_t selected; size_t size; struct bufarea *bp; /* * It's arguable whether we should just fail, or instead * error out here. Since we should only ever be asked for * a single fragment or an entire block (i.e., sblock.fs_frag), * we'll fail out because anything else means somebody * changed code without considering all of the ramifications. */ if (wantedfrags <= 0 || wantedfrags > sblock.fs_frag) { exitstat = EXERRFATAL; errexit("allocblk() asked for %d frags. " "Legal range is 1 to %d", wantedfrags, sblock.fs_frag); } /* * For each filesystem block, look at every possible starting * offset within the block such that we can get the number of * contiguous fragments that we need. This is a drastically * simplified version of the kernel's mapsearch() and alloc*(). * It's also correspondingly slower. */ for (block = 0; block < maxfsblock - sblock.fs_frag; block += sblock.fs_frag) { for (leadfrag = 0; leadfrag <= sblock.fs_frag - wantedfrags; leadfrag++) { /* * Is first fragment of candidate run available? */ if (testbmap(block + leadfrag)) continue; /* * Are the rest of them available? */ for (tailfrag = 1; tailfrag < wantedfrags; tailfrag++) if (testbmap(block + leadfrag + tailfrag)) break; if (tailfrag < wantedfrags) { /* * No, skip the known-unusable run. */ leadfrag += tailfrag; continue; } /* * Found what we need, so claim them. */ for (tailfrag = 0; tailfrag < wantedfrags; tailfrag++) setbmap(block + leadfrag + tailfrag); n_blks += wantedfrags; size = wantedfrags * sblock.fs_fsize; selected = block + leadfrag; bp = getdatablk(selected, size); (void) memset((void *)bp->b_un.b_buf, 0, size); dirty(bp); brelse(bp); if (debug) (void) printf( "allocblk: selected %d (in block %d), frags %d, size %d\n", selected, selected % sblock.fs_bsize, wantedfrags, (int)size); return (selected); } } return (0); } /* * Free a previously allocated block */ void freeblk(fsck_ino_t ino, daddr32_t blkno, int frags) { struct inodesc idesc; if (debug) (void) printf("debug: freeing %d fragments starting at %d\n", frags, blkno); init_inodesc(&idesc); idesc.id_number = ino; idesc.id_blkno = blkno; idesc.id_numfrags = frags; idesc.id_truncto = -1; /* * Nothing in the return status has any relevance to how * we're using pass4check(), so just ignore it. */ (void) pass4check(&idesc); } /* * Fill NAMEBUF with a path starting in CURDIR for INO. Assumes * that the given buffer is at least MAXPATHLEN + 1 characters. */ void getpathname(caddr_t namebuf, fsck_ino_t curdir, fsck_ino_t ino) { int len; caddr_t cp; struct dinode *dp; struct inodesc idesc; struct inoinfo *inp; if (debug) (void) printf("debug: getpathname(curdir %d, ino %d)\n", curdir, ino); if ((curdir == 0) || (!INO_IS_DVALID(curdir))) { (void) strcpy(namebuf, "?"); return; } if ((curdir == UFSROOTINO) && (ino == UFSROOTINO)) { (void) strcpy(namebuf, "/"); return; } init_inodesc(&idesc); idesc.id_type = DATA; cp = &namebuf[MAXPATHLEN - 1]; *cp = '\0'; /* * In the case of extended attributes, our * parent won't necessarily be a directory, so just * return what we've found with a prefix indicating * that it's an XATTR. Presumably our caller will * know what's going on and do something useful, like * work out the path of the parent and then combine * the two names. * * Can't use strcpy(), etc, because we've probably * already got some name information in the buffer and * the usual trailing \0 would lose it. */ dp = ginode(curdir); if ((dp->di_mode & IFMT) == IFATTRDIR) { idesc.id_number = curdir; idesc.id_parent = ino; idesc.id_func = findname; idesc.id_name = namebuf; idesc.id_fix = NOFIX; if ((ckinode(dp, &idesc, CKI_TRAVERSE) & FOUND) == 0) { *cp-- = '?'; } len = sizeof (XATTR_DIR_NAME) - 1; cp -= len; (void) memmove(cp, XATTR_DIR_NAME, len); goto attrname; } /* * If curdir == ino, need to get a handle on .. so we * can search it for ino's name. Otherwise, just search * the given directory for ino. Repeat until out of space * or a full path has been built. */ if (curdir != ino) { idesc.id_parent = curdir; goto namelookup; } while (ino != UFSROOTINO && ino != 0) { idesc.id_number = ino; idesc.id_func = findino; idesc.id_name = ".."; idesc.id_fix = NOFIX; if ((ckinode(ginode(ino), &idesc, CKI_TRAVERSE) & FOUND) == 0) { inp = getinoinfo(ino); if ((inp == NULL) || (inp->i_parent == 0)) { break; } idesc.id_parent = inp->i_parent; } /* * To get this far, id_parent must have the inode * number for `..' in it. By definition, that's got * to be a directory, so search it for the inode of * interest. */ namelookup: idesc.id_number = idesc.id_parent; idesc.id_parent = ino; idesc.id_func = findname; idesc.id_name = namebuf; idesc.id_fix = NOFIX; if ((ckinode(ginode(idesc.id_number), &idesc, CKI_TRAVERSE) & FOUND) == 0) { break; } /* * Prepend to what we've accumulated so far. If * there's not enough room for even one more path element * (of the worst-case length), then bail out. */ len = strlen(namebuf); cp -= len; if (cp < &namebuf[MAXNAMLEN]) break; (void) memmove(cp, namebuf, len); *--cp = '/'; /* * Corner case for a looped-to-itself directory. */ if (ino == idesc.id_number) break; /* * Climb one level of the hierarchy. In other words, * the current .. becomes the inode to search for and * its parent becomes the directory to search in. */ ino = idesc.id_number; } /* * If we hit a discontinuity in the hierarchy, indicate it by * prefixing the path so far with `?'. Otherwise, the first * character will be `/' as a side-effect of the *--cp above. * * The special case is to handle the situation where we're * trying to look something up in UFSROOTINO, but didn't find * it. */ if (ino != UFSROOTINO || cp == &namebuf[MAXPATHLEN - 1]) { if (cp > namebuf) cp--; *cp = '?'; } /* * The invariants being used for buffer integrity are: * - namebuf[] is terminated with \0 before anything else * - cp is always <= the last element of namebuf[] * - the new path element is always stored at the * beginning of namebuf[], and is no more than MAXNAMLEN-1 * characters * - cp is is decremented by the number of characters in * the new path element * - if, after the above accounting for the new element's * size, there is no longer enough room at the beginning of * namebuf[] for a full-sized path element and a slash, * terminate the loop. cp is in the range * &namebuf[0]..&namebuf[MAXNAMLEN - 1] */ attrname: /* LINTED per the above discussion */ (void) memmove(namebuf, cp, &namebuf[MAXPATHLEN] - cp); } /* ARGSUSED */ void catch(int dummy) { ckfini(); exit(EXSIGNAL); } /* * When preening, allow a single quit to signal * a special exit after filesystem checks complete * so that reboot sequence may be interrupted. */ /* ARGSUSED */ void catchquit(int dummy) { (void) printf("returning to single-user after filesystem check\n"); interrupted = 1; (void) signal(SIGQUIT, SIG_DFL); } /* * determine whether an inode should be fixed. */ NOTE(PRINTFLIKE(2)) int dofix(struct inodesc *idesc, caddr_t msg, ...) { int rval = 0; va_list ap; va_start(ap, msg); switch (idesc->id_fix) { case DONTKNOW: if (idesc->id_type == DATA) vdirerror(idesc->id_number, msg, ap); else vpwarn(msg, ap); if (preen) { idesc->id_fix = FIX; rval = ALTERED; break; } if (reply("SALVAGE") == 0) { idesc->id_fix = NOFIX; break; } idesc->id_fix = FIX; rval = ALTERED; break; case FIX: rval = ALTERED; break; case NOFIX: break; default: errexit("UNKNOWN INODESC FIX MODE %d\n", (int)idesc->id_fix); } va_end(ap); return (rval); } NOTE(PRINTFLIKE(1)) void errexit(caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); verrexit(fmt, ap); /* NOTREACHED */ } NOTE(PRINTFLIKE(1)) static void verrexit(caddr_t fmt, va_list ap) { static int recursing = 0; if (!recursing) { recursing = 1; if (errorlocked || iscorrupt) { if (havesb && fswritefd >= 0) { sblock.fs_clean = FSBAD; sblock.fs_state = FSOKAY - (long)sblock.fs_time; sblock.fs_state = -sblock.fs_state; sbdirty(); write_altsb(fswritefd); flush(fswritefd, &sblk); } } ckfini(); recursing = 0; } (void) vprintf(fmt, ap); if (fmt[strlen(fmt) - 1] != '\n') (void) putchar('\n'); exit((exitstat != 0) ? exitstat : EXERRFATAL); } /* * An unexpected inconsistency occured. * Die if preening, otherwise just print message and continue. */ NOTE(PRINTFLIKE(1)) void pfatal(caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); vpfatal(fmt, ap); va_end(ap); } NOTE(PRINTFLIKE(1)) static void vpfatal(caddr_t fmt, va_list ap) { if (preen) { if (*fmt != '\0') { (void) printf("%s: ", devname); (void) vprintf(fmt, ap); (void) printf("\n"); } (void) printf( "%s: UNEXPECTED INCONSISTENCY; RUN fsck MANUALLY.\n", devname); if (havesb && fswritefd >= 0) { sblock.fs_clean = FSBAD; sblock.fs_state = -(FSOKAY - (long)sblock.fs_time); sbdirty(); flush(fswritefd, &sblk); } /* * We're exiting, it doesn't really matter that our * caller doesn't get to call va_end(). */ if (exitstat == 0) exitstat = EXFNDERRS; exit(exitstat); } if (*fmt != '\0') { (void) vprintf(fmt, ap); } } /* * Pwarn just prints a message when not preening, * or a warning (preceded by filename) when preening. */ NOTE(PRINTFLIKE(1)) void pwarn(caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); vpwarn(fmt, ap); va_end(ap); } NOTE(PRINTFLIKE(1)) static void vpwarn(caddr_t fmt, va_list ap) { if (*fmt != '\0') { if (preen) (void) printf("%s: ", devname); (void) vprintf(fmt, ap); } } /* * Like sprintf(), except the buffer is dynamically allocated * and returned, instead of being passed in. A pointer to the * buffer is stored in *RET, and FMT is the usual format string. * The number of characters in *RET (excluding the trailing \0, * to be consistent with the other *printf() routines) is returned. * * Solaris doesn't have asprintf(3C) yet, unfortunately. */ NOTE(PRINTFLIKE(2)) int fsck_asprintf(caddr_t *ret, caddr_t fmt, ...) { int len; caddr_t buffer; va_list ap; va_start(ap, fmt); len = vsnprintf(NULL, 0, fmt, ap); va_end(ap); buffer = malloc((len + 1) * sizeof (char)); if (buffer == NULL) { errexit("Out of memory in asprintf\n"); /* NOTREACHED */ } va_start(ap, fmt); (void) vsnprintf(buffer, len + 1, fmt, ap); va_end(ap); *ret = buffer; return (len); } /* * So we can take advantage of kernel routines in ufs_subr.c. */ /* PRINTFLIKE2 */ void cmn_err(int level, caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); if (level == CE_PANIC) { (void) printf("INTERNAL INCONSISTENCY:"); verrexit(fmt, ap); } else { (void) vprintf(fmt, ap); } va_end(ap); } /* * Check to see if unraw version of name is already mounted. * Updates devstr with the device name if devstr is not NULL * and str_size is positive. */ int mounted(caddr_t name, caddr_t devstr, size_t str_size) { int found; struct mnttab *mntent; mntent = search_mnttab(NULL, unrawname(name), devstr, str_size); if (mntent == NULL) return (M_NOMNT); /* * It's mounted. With or without write access? */ if (hasmntopt(mntent, MNTOPT_RO) != 0) found = M_RO; /* mounted as RO */ else found = M_RW; /* mounted as R/W */ if (mount_point == NULL) { mount_point = strdup(mntent->mnt_mountp); if (mount_point == NULL) { errexit("fsck: memory allocation failure: %s", strerror(errno)); /* NOTREACHED */ } if (devstr != NULL && str_size > 0) (void) strlcpy(devstr, mntent->mnt_special, str_size); } return (found); } /* * Check to see if name corresponds to an entry in vfstab, and that the entry * does not have option ro. */ int writable(caddr_t name) { int rw = 1; struct vfstab vfsbuf, vfskey; FILE *vfstab; vfstab = fopen(VFSTAB, "r"); if (vfstab == NULL) { (void) printf("can't open %s\n", VFSTAB); return (1); } (void) memset((void *)&vfskey, 0, sizeof (vfskey)); vfsnull(&vfskey); vfskey.vfs_special = unrawname(name); vfskey.vfs_fstype = MNTTYPE_UFS; if ((getvfsany(vfstab, &vfsbuf, &vfskey) == 0) && (hasvfsopt(&vfsbuf, MNTOPT_RO))) { rw = 0; } (void) fclose(vfstab); return (rw); } /* * debugclean */ static void debugclean(void) { if (!debug) return; if ((iscorrupt == 0) && (isdirty == 0)) return; if ((sblock.fs_clean == FSSTABLE) || (sblock.fs_clean == FSCLEAN) || (sblock.fs_clean == FSLOG && islog && islogok) || ((FSOKAY == (sblock.fs_state + sblock.fs_time)) && !errorlocked)) return; (void) printf("WARNING: inconsistencies detected on %s filesystem %s\n", sblock.fs_clean == FSSTABLE ? "stable" : sblock.fs_clean == FSLOG ? "logging" : sblock.fs_clean == FSFIX ? "being fixed" : "clean", devname); } /* * updateclean * Carefully and transparently update the clean flag. * * `iscorrupt' has to be in its final state before this is called. */ int updateclean(void) { int freedlog = 0; struct bufarea cleanbuf; size_t size; ssize_t io_res; diskaddr_t bno; char fsclean; int fsreclaim; char fsflags; int flags_ok = 1; daddr32_t fslogbno; offset_t sblkoff; time_t t; /* * debug stuff */ debugclean(); /* * set fsclean to its appropriate value */ fslogbno = sblock.fs_logbno; fsclean = sblock.fs_clean; fsreclaim = sblock.fs_reclaim; fsflags = sblock.fs_flags; if (FSOKAY != (sblock.fs_state + sblock.fs_time) && !errorlocked) { fsclean = FSACTIVE; } /* * If ufs log is not okay, note that we need to clear it. */ examinelog(NULL); if (fslogbno && !(islog && islogok)) { fsclean = FSACTIVE; fslogbno = 0; } /* * if necessary, update fs_clean and fs_state */ switch (fsclean) { case FSACTIVE: if (!iscorrupt) { fsclean = FSSTABLE; fsreclaim = 0; } break; case FSCLEAN: case FSSTABLE: if (iscorrupt) { fsclean = FSACTIVE; } else { fsreclaim = 0; } break; case FSLOG: if (iscorrupt) { fsclean = FSACTIVE; } else if (!islog || fslogbno == 0) { fsclean = FSSTABLE; fsreclaim = 0; } else if (fflag) { fsreclaim = 0; } break; case FSFIX: fsclean = FSBAD; if (errorlocked && !iscorrupt) { fsclean = islog ? FSLOG : FSCLEAN; } break; default: if (iscorrupt) { fsclean = FSACTIVE; } else { fsclean = FSSTABLE; fsreclaim = 0; } } if (largefile_count > 0) fsflags |= FSLARGEFILES; else fsflags &= ~FSLARGEFILES; /* * There can be two discrepencies here. A) The superblock * shows no largefiles but we found some while scanning. * B) The superblock indicates the presence of largefiles, * but none are present. Note that if preening, the superblock * is silently corrected. */ if ((fsflags == FSLARGEFILES && sblock.fs_flags != FSLARGEFILES) || (fsflags != FSLARGEFILES && sblock.fs_flags == FSLARGEFILES)) flags_ok = 0; if (debug) (void) printf( "** largefile count=%d, fs.fs_flags=%x, flags_ok %d\n", largefile_count, sblock.fs_flags, flags_ok); /* * If fs is unchanged, do nothing. */ if ((!isdirty) && (flags_ok) && (fslogbno == sblock.fs_logbno) && (sblock.fs_clean == fsclean) && (sblock.fs_reclaim == fsreclaim) && (FSOKAY == (sblock.fs_state + sblock.fs_time))) { if (errorlocked) { if (!do_errorlock(LOCKFS_ULOCK)) pwarn( "updateclean(unchanged): unlock(LOCKFS_ULOCK) failed\n"); } return (freedlog); } /* * if user allows, update superblock state */ if (debug) { (void) printf( "superblock: flags 0x%x logbno %d clean %d reclaim %d state 0x%x\n", sblock.fs_flags, sblock.fs_logbno, sblock.fs_clean, sblock.fs_reclaim, sblock.fs_state + sblock.fs_time); (void) printf( "calculated: flags 0x%x logbno %d clean %d reclaim %d state 0x%x\n", fsflags, fslogbno, fsclean, fsreclaim, FSOKAY); } if (!isdirty && !preen && !rerun && (reply("FILE SYSTEM STATE IN SUPERBLOCK IS WRONG; FIX") == 0)) return (freedlog); (void) time(&t); sblock.fs_time = (time32_t)t; if (debug) printclean(); if (sblock.fs_logbno != fslogbno) { examinelog(&freelogblk); freedlog++; } sblock.fs_logbno = fslogbno; sblock.fs_clean = fsclean; sblock.fs_state = FSOKAY - (long)sblock.fs_time; sblock.fs_reclaim = fsreclaim; sblock.fs_flags = fsflags; /* * if superblock can't be written, return */ if (fswritefd < 0) return (freedlog); /* * Read private copy of superblock, update clean flag, and write it. */ bno = sblk.b_bno; size = sblk.b_size; sblkoff = ldbtob(bno); if ((cleanbuf.b_un.b_buf = malloc(size)) == NULL) errexit("out of memory"); if (llseek(fsreadfd, sblkoff, SEEK_SET) == -1) { (void) printf("COULD NOT SEEK TO SUPERBLOCK AT %lld: %s\n", (longlong_t)bno, strerror(errno)); goto out; } if ((io_res = read(fsreadfd, cleanbuf.b_un.b_buf, size)) != size) { report_io_prob("READ FROM", bno, size, io_res); goto out; } cleanbuf.b_un.b_fs->fs_logbno = sblock.fs_logbno; cleanbuf.b_un.b_fs->fs_clean = sblock.fs_clean; cleanbuf.b_un.b_fs->fs_state = sblock.fs_state; cleanbuf.b_un.b_fs->fs_time = sblock.fs_time; cleanbuf.b_un.b_fs->fs_reclaim = sblock.fs_reclaim; cleanbuf.b_un.b_fs->fs_flags = sblock.fs_flags; if (llseek(fswritefd, sblkoff, SEEK_SET) == -1) { (void) printf("COULD NOT SEEK TO SUPERBLOCK AT %lld: %s\n", (longlong_t)bno, strerror(errno)); goto out; } if ((io_res = write(fswritefd, cleanbuf.b_un.b_buf, size)) != size) { report_io_prob("WRITE TO", bno, size, io_res); goto out; } /* * 1208040 * If we had to use -b to grab an alternate superblock, then we * likely had to do so because of unacceptable differences between * the main and alternate superblocks. So, we had better update * the alternate superblock as well, or we'll just fail again * the next time we attempt to run fsck! */ if (bflag != 0) { write_altsb(fswritefd); } if (errorlocked) { if (!do_errorlock(LOCKFS_ULOCK)) pwarn( "updateclean(changed): unlock(LOCKFS_ULOCK) failed\n"); } out: if (cleanbuf.b_un.b_buf != NULL) { free((void *)cleanbuf.b_un.b_buf); } return (freedlog); } static void report_io_prob(caddr_t what, diskaddr_t bno, size_t expected, ssize_t failure) { if (failure < 0) (void) printf("COULD NOT %s SUPERBLOCK AT %d: %s\n", what, (int)bno, strerror(errno)); else if (failure == 0) (void) printf("COULD NOT %s SUPERBLOCK AT %d: EOF\n", what, (int)bno); else (void) printf("SHORT %s SUPERBLOCK AT %d: %u out of %u bytes\n", what, (int)bno, (unsigned)failure, (unsigned)expected); } /* * print out clean info */ void printclean(void) { caddr_t s; if (FSOKAY != (sblock.fs_state + sblock.fs_time) && !errorlocked) s = "unknown"; else switch (sblock.fs_clean) { case FSACTIVE: s = "active"; break; case FSCLEAN: s = "clean"; break; case FSSTABLE: s = "stable"; break; case FSLOG: s = "logging"; break; case FSBAD: s = "is bad"; break; case FSFIX: s = "being fixed"; break; default: s = "unknown"; } if (preen) pwarn("is %s.\n", s); else (void) printf("** %s is %s.\n", devname, s); } int is_errorlocked(caddr_t fs) { int retval; struct stat64 statb; caddr_t mountp; struct mnttab *mntent; retval = 0; if (!fs) return (0); if (stat64(fs, &statb) < 0) return (0); if (S_ISDIR(statb.st_mode)) { mountp = fs; } else if (S_ISBLK(statb.st_mode) || S_ISCHR(statb.st_mode)) { mntent = search_mnttab(NULL, fs, NULL, 0); if (mntent == NULL) return (0); mountp = mntent->mnt_mountp; if (mountp == NULL) /* theoretically a can't-happen */ return (0); } else { return (0); } /* * From here on, must `goto out' to avoid memory leakage. */ if (elock_combuf == NULL) elock_combuf = (caddr_t)calloc(LOCKFS_MAXCOMMENTLEN, sizeof (char)); else elock_combuf = (caddr_t)realloc(elock_combuf, LOCKFS_MAXCOMMENTLEN); if (elock_combuf == NULL) goto out; (void) memset((void *)elock_combuf, 0, LOCKFS_MAXCOMMENTLEN); if (elock_mountp != NULL) { free(elock_mountp); } elock_mountp = strdup(mountp); if (elock_mountp == NULL) goto out; if (mountfd < 0) { if ((mountfd = open64(mountp, O_RDONLY)) == -1) goto out; } if (lfp == NULL) { lfp = (struct lockfs *)malloc(sizeof (struct lockfs)); if (lfp == NULL) goto out; (void) memset((void *)lfp, 0, sizeof (struct lockfs)); } lfp->lf_comlen = LOCKFS_MAXCOMMENTLEN; lfp->lf_comment = elock_combuf; if (ioctl(mountfd, _FIOLFSS, lfp) == -1) goto out; /* * lint believes that the ioctl() (or any other function * taking lfp as an arg) could free lfp. This is not the * case, however. */ retval = LOCKFS_IS_ELOCK(lfp); out: return (retval); } /* * Given a name which is known to be a directory, see if it appears * in the vfstab. If so, return the entry's block (special) device * field via devstr. */ int check_vfstab(caddr_t name, caddr_t devstr, size_t str_size) { return (NULL != search_vfstab(name, NULL, devstr, str_size)); } /* * Given a name which is known to be a directory, see if it appears * in the mnttab. If so, return the entry's block (special) device * field via devstr. */ int check_mnttab(caddr_t name, caddr_t devstr, size_t str_size) { return (NULL != search_mnttab(name, NULL, devstr, str_size)); } /* * Search for mount point and/or special device in the given file. * The first matching entry is returned. * * If an entry is found and str_size is greater than zero, then * up to size_str bytes of the special device name from the entry * are copied to devstr. */ #define SEARCH_TAB_BODY(st_type, st_file, st_mount, st_special, \ st_nuller, st_init, st_searcher) \ { \ FILE *fp; \ struct st_type *retval = NULL; \ struct st_type key; \ static struct st_type buffer; \ \ /* LINTED ``assigned value never used'' */ \ st_nuller(&key); \ key.st_mount = mountp; \ key.st_special = special; \ st_init; \ \ if ((fp = fopen(st_file, "r")) == NULL) \ return (NULL); \ \ if (st_searcher(fp, &buffer, &key) == 0) { \ retval = &buffer; \ if (devstr != NULL && str_size > 0 && \ buffer.st_special != NULL) { \ (void) strlcpy(devstr, buffer.st_special, \ str_size); \ } \ } \ (void) fclose(fp); \ return (retval); \ } static struct vfstab * search_vfstab(caddr_t mountp, caddr_t special, caddr_t devstr, size_t str_size) SEARCH_TAB_BODY(vfstab, VFSTAB, vfs_mountp, vfs_special, vfsnull, (retval = retval), getvfsany) static struct mnttab * search_mnttab(caddr_t mountp, caddr_t special, caddr_t devstr, size_t str_size) SEARCH_TAB_BODY(mnttab, MNTTAB, mnt_mountp, mnt_special, mntnull, (key.mnt_fstype = MNTTYPE_UFS), getmntany) int do_errorlock(int lock_type) { caddr_t buf; time_t now; struct tm *local; int rc; if (elock_combuf == NULL) errexit("do_errorlock(%s, %d): unallocated elock_combuf\n", elock_mountp ? elock_mountp : "", lock_type); if ((buf = (caddr_t)calloc(LOCKFS_MAXCOMMENTLEN, sizeof (char))) == NULL) { errexit("Couldn't alloc memory for temp. lock status buffer\n"); } if (lfp == NULL) { errexit("do_errorlock(%s, %d): lockfs status unallocated\n", elock_mountp, lock_type); } (void) memmove((void *)buf, (void *)elock_combuf, LOCKFS_MAXCOMMENTLEN-1); switch (lock_type) { case LOCKFS_ELOCK: /* * Note that if it is error-locked, we won't get an * error back if we try to error-lock it again. */ if (time(&now) != (time_t)-1) { if ((local = localtime(&now)) != NULL) (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s [pid:%d fsck start:%02d/%02d/%02d %02d:%02d:%02d", elock_combuf, (int)pid, local->tm_mon + 1, local->tm_mday, (local->tm_year % 100), local->tm_hour, local->tm_min, local->tm_sec); else (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s [fsck pid %d", elock_combuf, pid); } else { (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s [fsck pid %d", elock_combuf, pid); } break; case LOCKFS_ULOCK: if (time(&now) != (time_t)-1) { if ((local = localtime(&now)) != NULL) { (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s, done:%02d/%02d/%02d %02d:%02d:%02d]", elock_combuf, local->tm_mon + 1, local->tm_mday, (local->tm_year % 100), local->tm_hour, local->tm_min, local->tm_sec); } else { (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s]", elock_combuf); } } else { (void) snprintf(buf, LOCKFS_MAXCOMMENTLEN, "%s]", elock_combuf); } if ((rc = ioctl(mountfd, _FIOLFSS, lfp)) == -1) { pwarn("do_errorlock: unlock failed: %s\n", strerror(errno)); goto out; } break; default: break; } (void) memmove((void *)elock_combuf, (void *)buf, LOCKFS_MAXCOMMENTLEN - 1); lfp->lf_lock = lock_type; lfp->lf_comlen = LOCKFS_MAXCOMMENTLEN; lfp->lf_comment = elock_combuf; lfp->lf_flags = 0; errno = 0; if ((rc = ioctl(mountfd, _FIOLFS, lfp)) == -1) { if (errno == EINVAL) { pwarn("Another fsck active?\n"); iscorrupt = 0; /* don't go away mad, just go away */ } else { pwarn("do_errorlock(lock_type:%d, %s) failed: %s\n", lock_type, elock_combuf, strerror(errno)); } } out: if (buf != NULL) { free((void *)buf); } return (rc != -1); } /* * Shadow inode support. To register a shadow with a client is to note * that an inode (the client) refers to the shadow. */ static struct shadowclients * newshadowclient(struct shadowclients *prev) { struct shadowclients *rc; rc = (struct shadowclients *)malloc(sizeof (*rc)); if (rc == NULL) errexit("newshadowclient: cannot malloc shadow client"); rc->next = prev; rc->nclients = 0; rc->client = (fsck_ino_t *)malloc(sizeof (fsck_ino_t) * maxshadowclients); if (rc->client == NULL) errexit("newshadowclient: cannot malloc client array"); return (rc); } void registershadowclient(fsck_ino_t shadow, fsck_ino_t client, struct shadowclientinfo **info) { struct shadowclientinfo *sci; struct shadowclients *scc; /* * Already have a record for this shadow? */ for (sci = *info; sci != NULL; sci = sci->next) if (sci->shadow == shadow) break; if (sci == NULL) { /* * It's a new shadow, add it to the list */ sci = (struct shadowclientinfo *)malloc(sizeof (*sci)); if (sci == NULL) errexit("registershadowclient: cannot malloc"); sci->next = *info; *info = sci; sci->shadow = shadow; sci->totalClients = 0; sci->clients = newshadowclient(NULL); } sci->totalClients++; scc = sci->clients; if (scc->nclients >= maxshadowclients) { scc = newshadowclient(sci->clients); sci->clients = scc; } scc->client[scc->nclients++] = client; } /* * Locate and discard a shadow. */ void clearshadow(fsck_ino_t shadow, struct shadowclientinfo **info) { struct shadowclientinfo *sci, *prev; /* * Do we have a record for this shadow? */ prev = NULL; for (sci = *info; sci != NULL; sci = sci->next) { if (sci->shadow == shadow) break; prev = sci; } if (sci != NULL) { /* * First, pull it off the list, since we know there * shouldn't be any future references to this one. */ if (prev == NULL) *info = sci->next; else prev->next = sci->next; deshadow(sci, clearattrref); } } /* * Discard all memory used to track clients of a shadow. */ void deshadow(struct shadowclientinfo *sci, void (*cb)(fsck_ino_t)) { struct shadowclients *clients, *discard; int idx; clients = sci->clients; while (clients != NULL) { discard = clients; clients = clients->next; if (discard->client != NULL) { if (cb != NULL) { for (idx = 0; idx < discard->nclients; idx++) (*cb)(discard->client[idx]); } free((void *)discard->client); } free((void *)discard); } free((void *)sci); } /* * Allocate more buffer as need arises but allocate one at a time. * This is done to make sure that fsck does not exit with error if it * needs more buffer to complete its task. */ static struct bufarea * alloc_bufarea(void) { struct bufarea *newbp; caddr_t bufp; bufp = malloc((unsigned int)sblock.fs_bsize); if (bufp == NULL) return (NULL); newbp = (struct bufarea *)malloc(sizeof (struct bufarea)); if (newbp == NULL) { free((void *)bufp); return (NULL); } initbarea(newbp); newbp->b_un.b_buf = bufp; newbp->b_prev = &bufhead; newbp->b_next = bufhead.b_next; bufhead.b_next->b_prev = newbp; bufhead.b_next = newbp; bufhead.b_size++; return (newbp); } /* * We length-limit in both unrawname() and rawname() to avoid * overflowing our arrays or those of our naive, trusting callers. */ caddr_t unrawname(caddr_t name) { caddr_t dp; static char fullname[MAXPATHLEN + 1]; if ((dp = getfullblkname(name)) == NULL) return (""); (void) strlcpy(fullname, dp, sizeof (fullname)); /* * Not reporting under debug, as the allocation isn't * reported by getfullblkname. The idea is that we * produce balanced alloc/free instances. */ free(dp); return (fullname); } caddr_t rawname(caddr_t name) { caddr_t dp; static char fullname[MAXPATHLEN + 1]; if ((dp = getfullrawname(name)) == NULL) return (""); (void) strlcpy(fullname, dp, sizeof (fullname)); /* * Not reporting under debug, as the allocation isn't * reported by getfullblkname. The idea is that we * produce balanced alloc/free instances. */ free(dp); return (fullname); } /* * Make sure that a cg header looks at least moderately reasonable. * We want to be able to trust the contents enough to be able to use * the standard accessor macros. So, besides looking at the obvious * such as the magic number, we verify that the offset field values * are properly aligned and not too big or small. * * Returns a NULL pointer if the cg is sane enough for our needs, else * a dynamically-allocated string describing all of its faults. */ #define Append_Error(full, full_len, addition, addition_len) \ if (full == NULL) { \ full = addition; \ full_len = addition_len; \ } else { \ /* lint doesn't think realloc() understands NULLs */ \ full = realloc(full, full_len + addition_len + 1); \ if (full == NULL) { \ errexit("Out of memory in cg_sanity"); \ /* NOTREACHED */ \ } \ (void) strcpy(full + full_len, addition); \ full_len += addition_len; \ free(addition); \ } caddr_t cg_sanity(struct cg *cgp, int cgno) { caddr_t full_err; caddr_t this_err = NULL; int full_len, this_len; daddr32_t ndblk; daddr32_t exp_btotoff, exp_boff, exp_iusedoff; daddr32_t exp_freeoff, exp_nextfreeoff; cg_constants(cgno, &exp_btotoff, &exp_boff, &exp_iusedoff, &exp_freeoff, &exp_nextfreeoff, &ndblk); full_err = NULL; full_len = 0; if (!cg_chkmagic(cgp)) { this_len = fsck_asprintf(&this_err, "BAD CG MAGIC NUMBER (0x%x should be 0x%x)\n", cgp->cg_magic, CG_MAGIC); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_cgx != cgno) { this_len = fsck_asprintf(&this_err, "WRONG CG NUMBER (%d should be %d)\n", cgp->cg_cgx, cgno); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_btotoff & 3) != 0) { this_len = fsck_asprintf(&this_err, "BLOCK TOTALS OFFSET %d NOT FOUR-BYTE ALIGNED\n", cgp->cg_btotoff); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_boff & 1) != 0) { this_len = fsck_asprintf(&this_err, "FREE BLOCK POSITIONS TABLE OFFSET %d NOT TWO-BYTE ALIGNED\n", cgp->cg_boff); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_ncyl < 1) || (cgp->cg_ncyl > sblock.fs_cpg)) { if (cgp->cg_ncyl < 1) { this_len = fsck_asprintf(&this_err, "IMPOSSIBLE NUMBER OF CYLINDERS IN GROUP (%d is less than 1)\n", cgp->cg_ncyl); } else { this_len = fsck_asprintf(&this_err, "IMPOSSIBLE NUMBER OF CYLINDERS IN GROUP (%d is greater than %d)\n", cgp->cg_ncyl, sblock.fs_cpg); } Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_niblk != sblock.fs_ipg) { this_len = fsck_asprintf(&this_err, "INCORRECT NUMBER OF INODES IN GROUP (%d should be %d)\n", cgp->cg_niblk, sblock.fs_ipg); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_ndblk != ndblk) { this_len = fsck_asprintf(&this_err, "INCORRECT NUMBER OF DATA BLOCKS IN GROUP (%d should be %d)\n", cgp->cg_ndblk, ndblk); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_rotor < 0) || (cgp->cg_rotor >= ndblk)) { this_len = fsck_asprintf(&this_err, "IMPOSSIBLE BLOCK ALLOCATION ROTOR POSITION " "(%d should be at least 0 and less than %d)\n", cgp->cg_rotor, ndblk); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_frotor < 0) || (cgp->cg_frotor >= ndblk)) { this_len = fsck_asprintf(&this_err, "IMPOSSIBLE FRAGMENT ALLOCATION ROTOR POSITION " "(%d should be at least 0 and less than %d)\n", cgp->cg_frotor, ndblk); Append_Error(full_err, full_len, this_err, this_len); } if ((cgp->cg_irotor < 0) || (cgp->cg_irotor >= sblock.fs_ipg)) { this_len = fsck_asprintf(&this_err, "IMPOSSIBLE INODE ALLOCATION ROTOR POSITION " "(%d should be at least 0 and less than %d)\n", cgp->cg_irotor, sblock.fs_ipg); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_btotoff != exp_btotoff) { this_len = fsck_asprintf(&this_err, "INCORRECT BLOCK TOTALS OFFSET (%d should be %d)\n", cgp->cg_btotoff, exp_btotoff); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_boff != exp_boff) { this_len = fsck_asprintf(&this_err, "BAD FREE BLOCK POSITIONS TABLE OFFSET (%d should %d)\n", cgp->cg_boff, exp_boff); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_iusedoff != exp_iusedoff) { this_len = fsck_asprintf(&this_err, "INCORRECT USED INODE MAP OFFSET (%d should be %d)\n", cgp->cg_iusedoff, exp_iusedoff); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_freeoff != exp_freeoff) { this_len = fsck_asprintf(&this_err, "INCORRECT FREE FRAGMENT MAP OFFSET (%d should be %d)\n", cgp->cg_freeoff, exp_freeoff); Append_Error(full_err, full_len, this_err, this_len); } if (cgp->cg_nextfreeoff != exp_nextfreeoff) { this_len = fsck_asprintf(&this_err, "END OF HEADER POSITION INCORRECT (%d should be %d)\n", cgp->cg_nextfreeoff, exp_nextfreeoff); Append_Error(full_err, full_len, this_err, this_len); } return (full_err); } #undef Append_Error /* * This is taken from mkfs, and is what is used to come up with the * original values for a struct cg. This implies that, since these * are all constants, recalculating them now should give us the same * thing as what's on disk. */ static void cg_constants(int cgno, daddr32_t *btotoff, daddr32_t *boff, daddr32_t *iusedoff, daddr32_t *freeoff, daddr32_t *nextfreeoff, daddr32_t *ndblk) { daddr32_t cbase, dmax; struct cg *cgp; (void) getblk(&cgblk, (diskaddr_t)cgtod(&sblock, cgno), (size_t)sblock.fs_cgsize); cgp = cgblk.b_un.b_cg; cbase = cgbase(&sblock, cgno); dmax = cbase + sblock.fs_fpg; if (dmax > sblock.fs_size) dmax = sblock.fs_size; /* LINTED pointer difference won't overflow */ *btotoff = &cgp->cg_space[0] - (uchar_t *)(&cgp->cg_link); *boff = *btotoff + sblock.fs_cpg * sizeof (daddr32_t); *iusedoff = *boff + sblock.fs_cpg * sblock.fs_nrpos * sizeof (int16_t); *freeoff = *iusedoff + howmany(sblock.fs_ipg, NBBY); *nextfreeoff = *freeoff + howmany(sblock.fs_cpg * sblock.fs_spc / NSPF(&sblock), NBBY); *ndblk = dmax - cbase; } /* * Corrects all fields in the cg that can be done with the available * redundant data. */ void fix_cg(struct cg *cgp, int cgno) { daddr32_t exp_btotoff, exp_boff, exp_iusedoff; daddr32_t exp_freeoff, exp_nextfreeoff; daddr32_t ndblk; cg_constants(cgno, &exp_btotoff, &exp_boff, &exp_iusedoff, &exp_freeoff, &exp_nextfreeoff, &ndblk); if (cgp->cg_cgx != cgno) { cgp->cg_cgx = cgno; } if ((cgp->cg_ncyl < 1) || (cgp->cg_ncyl > sblock.fs_cpg)) { if (cgno == (sblock.fs_ncg - 1)) { cgp->cg_ncyl = sblock.fs_ncyl - (sblock.fs_cpg * cgno); } else { cgp->cg_ncyl = sblock.fs_cpg; } } if (cgp->cg_niblk != sblock.fs_ipg) { /* * This is not used by the kernel, so it's pretty * harmless if it's wrong. */ cgp->cg_niblk = sblock.fs_ipg; } if (cgp->cg_ndblk != ndblk) { cgp->cg_ndblk = ndblk; } /* * For the rotors, any position's valid, so pick the one we know * will always exist. */ if ((cgp->cg_rotor < 0) || (cgp->cg_rotor >= cgp->cg_ndblk)) { cgp->cg_rotor = 0; } if ((cgp->cg_frotor < 0) || (cgp->cg_frotor >= cgp->cg_ndblk)) { cgp->cg_frotor = 0; } if ((cgp->cg_irotor < 0) || (cgp->cg_irotor >= sblock.fs_ipg)) { cgp->cg_irotor = 0; } /* * For btotoff and boff, if they're misaligned they won't * match the expected values, so we're catching both cases * here. Of course, if any of these are off, it seems likely * that the tables really won't be where we calculate they * should be anyway. */ if (cgp->cg_btotoff != exp_btotoff) { cgp->cg_btotoff = exp_btotoff; } if (cgp->cg_boff != exp_boff) { cgp->cg_boff = exp_boff; } if (cgp->cg_iusedoff != exp_iusedoff) { cgp->cg_iusedoff = exp_iusedoff; } if (cgp->cg_freeoff != exp_freeoff) { cgp->cg_freeoff = exp_freeoff; } if (cgp->cg_nextfreeoff != exp_nextfreeoff) { cgp->cg_nextfreeoff = exp_nextfreeoff; } /* * Reset the magic, as we've recreated this cg, also * update the cg_time, as we're writing out the cg */ cgp->cg_magic = CG_MAGIC; cgp->cg_time = time(NULL); /* * We know there was at least one correctable problem, * or else we wouldn't have been called. So instead of * marking the buffer dirty N times above, just do it * once here. */ cgdirty(); } void examinelog(void (*cb)(daddr32_t)) { struct bufarea *bp; extent_block_t *ebp; extent_t *ep; daddr32_t nfno, fno; int i; int j; /* * Since ufs stores fs_logbno as blocks and MTBufs stores it as frags * we need to translate accordingly using logbtodb() */ if (logbtodb(&sblock, sblock.fs_logbno) < SBLOCK) { if (debug) { (void) printf("fs_logbno < SBLOCK: %ld < %ld\n" \ "Aborting log examination\n", \ logbtodb(&sblock, sblock.fs_logbno), SBLOCK); } return; } /* * Read errors will return zeros, which will cause us * to do nothing harmful, so don't need to handle it. */ bp = getdatablk(logbtofrag(&sblock, sblock.fs_logbno), (size_t)sblock.fs_bsize); ebp = (void *)bp->b_un.b_buf; /* * Does it look like a log allocation table? */ /* LINTED pointer cast is aligned */ if (!log_checksum(&ebp->chksum, (int32_t *)bp->b_un.b_buf, sblock.fs_bsize)) return; if (ebp->type != LUFS_EXTENTS || ebp->nextents == 0) return; ep = &ebp->extents[0]; for (i = 0; i < ebp->nextents; ++i, ++ep) { fno = logbtofrag(&sblock, ep->pbno); nfno = dbtofsb(&sblock, ep->nbno); for (j = 0; j < nfno; ++j, ++fno) { /* * Invoke the callback first, so that pass1 can * mark the log blocks in-use. Then, if any * subsequent pass over the log shows us that a * block got freed (say, it was also claimed by * an inode that we cleared), we can safely declare * the log bad. */ if (cb != NULL) (*cb)(fno); if (!testbmap(fno)) islogok = 0; } } brelse(bp); if (cb != NULL) { fno = logbtofrag(&sblock, sblock.fs_logbno); for (j = 0; j < sblock.fs_frag; ++j, ++fno) (*cb)(fno); } } static void freelogblk(daddr32_t frag) { freeblk(sblock.fs_logbno, frag, 1); } caddr_t file_id(fsck_ino_t inum, mode_t mode) { static char name[MAXPATHLEN + 1]; if (lfdir == inum) { return (lfname); } if ((mode & IFMT) == IFDIR) { (void) strcpy(name, "DIR"); } else if ((mode & IFMT) == IFATTRDIR) { (void) strcpy(name, "ATTR DIR"); } else if ((mode & IFMT) == IFSHAD) { (void) strcpy(name, "ACL"); } else { (void) strcpy(name, "FILE"); } return (name); } /* * Simple initializer for inodesc structures, so users of only a few * fields don't have to worry about getting the right defaults for * everything out. */ void init_inodesc(struct inodesc *idesc) { /* * Most fields should be zero, just hit the special cases. */ (void) memset((void *)idesc, 0, sizeof (struct inodesc)); idesc->id_fix = DONTKNOW; idesc->id_lbn = -1; idesc->id_truncto = -1; idesc->id_firsthole = -1; } /* * Compare routine for tsearch(C) to use on ino_t instances. */ int ino_t_cmp(const void *left, const void *right) { const fsck_ino_t lino = (const fsck_ino_t)left; const fsck_ino_t rino = (const fsck_ino_t)right; return (lino - rino); } int cgisdirty(void) { return (cgblk.b_dirty); } void cgflush(void) { flush(fswritefd, &cgblk); } void dirty(struct bufarea *bp) { if (fswritefd < 0) { /* * No one should call dirty() in read only mode. * But if one does, it's not fatal issue. Just warn them. */ pwarn("WON'T SET DIRTY FLAG IN READ_ONLY MODE\n"); } else { (bp)->b_dirty = 1; isdirty = 1; } } void initbarea(struct bufarea *bp) { (bp)->b_dirty = 0; (bp)->b_bno = (diskaddr_t)-1LL; (bp)->b_flags = 0; (bp)->b_cnt = 0; (bp)->b_errs = 0; } /* * Partition-sizing routines adapted from ../newfs/newfs.c. * Needed because calcsb() needs to use mkfs to work out what the * superblock should be, and mkfs insists on being told how many * sectors to use. * * Error handling assumes we're never called while preening. * * XXX This should be extracted into a ../ufslib.{c,h}, * in the same spirit to ../../fslib.{c,h}. Once that is * done, both fsck and newfs should be modified to link * against it. */ static int label_type; #define LABEL_TYPE_VTOC 1 #define LABEL_TYPE_EFI 2 #define LABEL_TYPE_OTHER 3 #define MB (1024 * 1024) #define SECTORS_PER_TERABYTE (1LL << 31) #define FS_SIZE_UPPER_LIMIT 0x100000000000LL diskaddr_t getdisksize(caddr_t disk, int fd) { int rpm; struct dk_geom g; struct dk_cinfo ci; diskaddr_t actual_size; /* * get_device_size() determines the actual size of the * device, and also the disk's attributes, such as geometry. */ actual_size = get_device_size(fd, disk); if (label_type == LABEL_TYPE_VTOC) { if (ioctl(fd, DKIOCGGEOM, &g)) { pwarn("%s: Unable to read Disk geometry", disk); return (0); } if (sblock.fs_nsect == 0) sblock.fs_nsect = g.dkg_nsect; if (sblock.fs_ntrak == 0) sblock.fs_ntrak = g.dkg_nhead; if (sblock.fs_rps == 0) { rpm = ((int)g.dkg_rpm <= 0) ? 3600: g.dkg_rpm; sblock.fs_rps = rpm / 60; } } if (sblock.fs_bsize == 0) sblock.fs_bsize = MAXBSIZE; /* * Adjust maxcontig by the device's maxtransfer. If maxtransfer * information is not available, default to the min of a MB and * maxphys. */ if (sblock.fs_maxcontig == -1 && ioctl(fd, DKIOCINFO, &ci) == 0) { sblock.fs_maxcontig = ci.dki_maxtransfer * DEV_BSIZE; if (sblock.fs_maxcontig < 0) { int gotit, maxphys; gotit = fsgetmaxphys(&maxphys, NULL); /* * If we cannot get the maxphys value, default * to ufs_maxmaxphys (MB). */ if (gotit) { sblock.fs_maxcontig = MIN(maxphys, MB); } else { sblock.fs_maxcontig = MB; } } sblock.fs_maxcontig /= sblock.fs_bsize; } return (actual_size); } /* * Figure out how big the partition we're dealing with is. */ static diskaddr_t get_device_size(int fd, caddr_t name) { struct extvtoc vtoc; struct dk_gpt *efi_vtoc; diskaddr_t slicesize = 0; int index = read_extvtoc(fd, &vtoc); if (index >= 0) { label_type = LABEL_TYPE_VTOC; } else { if (index == VT_ENOTSUP || index == VT_ERROR) { /* it might be an EFI label */ index = efi_alloc_and_read(fd, &efi_vtoc); if (index >= 0) label_type = LABEL_TYPE_EFI; } } if (index < 0) { /* * Since both attempts to read the label failed, we're * going to fall back to a brute force approach to * determining the device's size: see how far out we can * perform reads on the device. */ slicesize = brute_force_get_device_size(fd); if (slicesize == 0) { switch (index) { case VT_ERROR: pwarn("%s: %s\n", name, strerror(errno)); break; case VT_EIO: pwarn("%s: I/O error accessing VTOC", name); break; case VT_EINVAL: pwarn("%s: Invalid field in VTOC", name); break; default: pwarn("%s: unknown error %d accessing VTOC", name, index); break; } return (0); } else { label_type = LABEL_TYPE_OTHER; } } if (label_type == LABEL_TYPE_EFI) { slicesize = efi_vtoc->efi_parts[index].p_size; efi_free(efi_vtoc); } else if (label_type == LABEL_TYPE_VTOC) { slicesize = vtoc.v_part[index].p_size; } return (slicesize); } /* * brute_force_get_device_size * * Determine the size of the device by seeing how far we can * read. Doing an llseek( , , SEEK_END) would probably work * in most cases, but we've seen at least one third-party driver * which doesn't correctly support the SEEK_END option when the * the device is greater than a terabyte. */ static diskaddr_t brute_force_get_device_size(int fd) { diskaddr_t min_fail = 0; diskaddr_t max_succeed = 0; diskaddr_t cur_db_off; char buf[DEV_BSIZE]; /* * First, see if we can read the device at all, just to * eliminate errors that have nothing to do with the * device's size. */ if (((llseek(fd, (offset_t)0, SEEK_SET)) == -1) || ((read(fd, buf, DEV_BSIZE)) == -1)) return (0); /* can't determine size */ /* * Now, go sequentially through the multiples of 4TB * to find the first read that fails (this isn't strictly * the most efficient way to find the actual size if the * size really could be anything between 0 and 2**64 bytes. * We expect the sizes to be less than 16 TB for some time, * so why do a bunch of reads that are larger than that? * However, this algorithm *will* work for sizes of greater * than 16 TB. We're just not optimizing for those sizes.) */ /* * XXX lint uses 32-bit arithmetic for doing flow analysis. * We're using > 32-bit constants here. Therefore, its flow * analysis is wrong. For the time being, ignore complaints * from it about the body of the for() being unreached. */ for (cur_db_off = SECTORS_PER_TERABYTE * 4; (min_fail == 0) && (cur_db_off < FS_SIZE_UPPER_LIMIT); cur_db_off += 4 * SECTORS_PER_TERABYTE) { if ((llseek(fd, (offset_t)(cur_db_off * DEV_BSIZE), SEEK_SET) == -1) || (read(fd, buf, DEV_BSIZE) != DEV_BSIZE)) min_fail = cur_db_off; else max_succeed = cur_db_off; } /* * XXX Same lint flow analysis problem as above. */ if (min_fail == 0) return (0); /* * We now know that the size of the device is less than * min_fail and greater than or equal to max_succeed. Now * keep splitting the difference until the actual size in * sectors in known. We also know that the difference * between max_succeed and min_fail at this time is * 4 * SECTORS_PER_TERABYTE, which is a power of two, which * simplifies the math below. */ while (min_fail - max_succeed > 1) { cur_db_off = max_succeed + (min_fail - max_succeed)/2; if (((llseek(fd, (offset_t)(cur_db_off * DEV_BSIZE), SEEK_SET)) == -1) || ((read(fd, buf, DEV_BSIZE)) != DEV_BSIZE)) min_fail = cur_db_off; else max_succeed = cur_db_off; } /* the size is the last successfully read sector offset plus one */ return (max_succeed + 1); } static void vfileerror(fsck_ino_t cwd, fsck_ino_t ino, caddr_t fmt, va_list ap) { struct dinode *dp; char pathbuf[MAXPATHLEN + 1]; vpwarn(fmt, ap); (void) putchar(' '); pinode(ino); (void) printf("\n"); getpathname(pathbuf, cwd, ino); if (ino < UFSROOTINO || ino > maxino) { pfatal("NAME=%s\n", pathbuf); return; } dp = ginode(ino); if (ftypeok(dp)) pfatal("%s=%s\n", file_id(ino, dp->di_mode), pathbuf); else pfatal("NAME=%s\n", pathbuf); } void direrror(fsck_ino_t ino, caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); vfileerror(ino, ino, fmt, ap); va_end(ap); } static void vdirerror(fsck_ino_t ino, caddr_t fmt, va_list ap) { vfileerror(ino, ino, fmt, ap); } void fileerror(fsck_ino_t cwd, fsck_ino_t ino, caddr_t fmt, ...) { va_list ap; va_start(ap, fmt); vfileerror(cwd, ino, fmt, ap); va_end(ap); } /* * Adds the given inode to the orphaned-directories list, limbo_dirs. * Assumes that the caller has set INCLEAR in the inode's statemap[] * entry. * * With INCLEAR set, the inode will get ignored by passes 2 and 3, * meaning it's effectively an orphan. It needs to be noted now, so * it will be remembered in pass 4. */ void add_orphan_dir(fsck_ino_t ino) { if (tsearch((void *)ino, &limbo_dirs, ino_t_cmp) == NULL) errexit("add_orphan_dir: out of memory"); } /* * Remove an inode from the orphaned-directories list, presumably * because it's been cleared. */ void remove_orphan_dir(fsck_ino_t ino) { (void) tdelete((void *)ino, &limbo_dirs, ino_t_cmp); } /* * log_setsum() and log_checksum() are equivalent to lufs.c:setsum() * and lufs.c:checksum(). */ static void log_setsum(int32_t *sp, int32_t *lp, int nb) { int32_t csum = 0; *sp = 0; nb /= sizeof (int32_t); while (nb--) csum += *lp++; *sp = csum; } static int log_checksum(int32_t *sp, int32_t *lp, int nb) { int32_t ssum = *sp; log_setsum(sp, lp, nb); if (ssum != *sp) { *sp = ssum; return (0); } return (1); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2000 by Sun Microsystems, Inc. # All rights reserved. # FSTYPE= ufs LIBPROG= fsckall SRCS= fsckall.sh include ../../Makefile.fstype all: $(LIBPROG) install: all clean: $(RM) $(LIBPROG) #!/sbin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # ident "%Z%%M% %I% %E% SMI" # # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T # All Rights Reserved # # # Produce a list of the file systems that are not already # mounted. # for fsckdev in $* ; do /sbin/fsck -m -F ufs $fsckdev >/dev/null 2>&1 case $? in 33) echo "$fsckdev already mounted" ;; 0) echo "$fsckdev is clean" ;; *) ufs_fscklist="$ufs_fscklist $fsckdev" ;; esac done # # Check the file systems in parallel # if [ "$ufs_fscklist" ]; then echo "checking ufs filesystems" /sbin/fsck -o p $ufs_fscklist case $? in 0|40|33) # file system OK exit 0 ;; *) # couldn't fix the filesystems - return an error exit 1 ;; esac fi exit 0 # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1989,2001 by Sun Microsystems, Inc. # All rights reserved. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= fsdb include ../../Makefile.fstype CPPFLAGS += -D_LARGEFILE64_SOURCE CERRWARN += -Wno-char-subscripts CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-parentheses CERRWARN += -Wno-unused-variable CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress duplicate case value warnings from LP64 sizeof collisions CERRWARN += -Wno-switch # not linted SMATCH=off SRCS= $(LIBPROG).c /* * Copyright 2015 Gary Mills * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright (c) 1988 Regents of the University of California. * All rights reserved. * * This code is derived from software contributed to Berkeley by * Computer Consoles Inc. * * Redistribution and use in source and binary forms are permitted * provided that: (1) source distributions retain this entire copyright * notice and comment, and (2) distributions including binaries display * the following acknowledgement: ``This product includes software * developed by the University of California, Berkeley and its contributors'' * in the documentation or other materials provided with the distribution * and in all advertising materials mentioning features or use of this * software. 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ /* * fsdb - file system debugger * * usage: fsdb [-o suboptions] special * options/suboptions: * -o * ? display usage * o override some error conditions * p="string" set prompt to string * w open for write */ #include #include #include #include #include #ifdef sun #include #include #include #include #include #include #include #include #include #include #include #include #include #include #else #include #include #include #include #endif /* sun */ #include #include #define OLD_FSDB_COMPATIBILITY /* To support the obsoleted "-z" option */ #ifndef _PATH_BSHELL #define _PATH_BSHELL "/bin/sh" #endif /* _PATH_BSHELL */ /* * Defines from the 4.3-tahoe file system, for systems with the 4.2 or 4.3 * file system. */ #ifndef FS_42POSTBLFMT #define cg_blktot(cgp) (((cgp))->cg_btot) #define cg_blks(fs, cgp, cylno) (((cgp))->cg_b[cylno]) #define cg_inosused(cgp) (((cgp))->cg_iused) #define cg_blksfree(cgp) (((cgp))->cg_free) #define cg_chkmagic(cgp) ((cgp)->cg_magic == CG_MAGIC) #endif /* * Never changing defines. */ #define OCTAL 8 /* octal base */ #define DECIMAL 10 /* decimal base */ #define HEX 16 /* hexadecimal base */ /* * Adjustable defines. */ #define NBUF 10 /* number of cache buffers */ #define PROMPTSIZE 80 /* size of user definable prompt */ #define MAXFILES 40000 /* max number of files ls can handle */ #define FIRST_DEPTH 10 /* default depth for find and ls */ #define SECOND_DEPTH 100 /* second try at depth (maximum) */ #define INPUTBUFFER 1040 /* size of input buffer */ #define BYTESPERLINE 16 /* bytes per line of /dxo output */ #define NREG 36 /* number of save registers */ #define DEVPREFIX "/dev/" /* Uninteresting part of "special" */ #if defined(OLD_FSDB_COMPATIBILITY) #define FSDB_OPTIONS "o:wp:z:" #else #define FSDB_OPTIONS "o:wp:" #endif /* OLD_FSDB_COMPATIBILITY */ /* * Values dependent on sizes of structs and such. */ #define NUMB 3 /* these three are arbitrary, */ #define BLOCK 5 /* but must be different from */ #define FRAGMENT 7 /* the rest (hence odd). */ #define BITSPERCHAR 8 /* couldn't find it anywhere */ #define CHAR (sizeof (char)) #define SHORT (sizeof (short)) #define LONG (sizeof (long)) #define U_OFFSET_T (sizeof (u_offset_t)) /* essentially "long long" */ #define INODE (sizeof (struct dinode)) #define DIRECTORY (sizeof (struct direct)) #define CGRP (sizeof (struct cg)) #define SB (sizeof (struct fs)) #define BLKSIZE (fs->fs_bsize) /* for clarity */ #define FRGSIZE (fs->fs_fsize) #define BLKSHIFT (fs->fs_bshift) #define FRGSHIFT (fs->fs_fshift) #define SHADOW_DATA (sizeof (struct ufs_fsd)) /* * Messy macros that would otherwise clutter up such glamorous code. */ #define itob(i) (((u_offset_t)itod(fs, (i)) << \ (u_offset_t)FRGSHIFT) + (u_offset_t)itoo(fs, (i)) * (u_offset_t)INODE) #define min(x, y) ((x) < (y) ? (x) : (y)) #define STRINGSIZE(d) ((long)d->d_reclen - \ ((long)&d->d_name[0] - (long)&d->d_ino)) #define letter(c) ((((c) >= 'a')&&((c) <= 'z')) ||\ (((c) >= 'A')&&((c) <= 'Z'))) #define digit(c) (((c) >= '0') && ((c) <= '9')) #define HEXLETTER(c) (((c) >= 'A') && ((c) <= 'F')) #define hexletter(c) (((c) >= 'a') && ((c) <= 'f')) #define octaldigit(c) (((c) >= '0') && ((c) <= '7')) #define uppertolower(c) ((c) - 'A' + 'a') #define hextodigit(c) ((c) - 'a' + 10) #define numtodigit(c) ((c) - '0') #if !defined(loword) #define loword(X) (((ushort_t *)&X)[1]) #endif /* loword */ #if !defined(lobyte) #define lobyte(X) (((unsigned char *)&X)[1]) #endif /* lobyte */ /* * buffer cache structure. */ static struct lbuf { struct lbuf *fwd; struct lbuf *back; char *blkaddr; short valid; u_offset_t blkno; } lbuf[NBUF], bhdr; /* * used to hold save registers (see '<' and '>'). */ struct save_registers { u_offset_t sv_addr; u_offset_t sv_value; long sv_objsz; } regs[NREG]; /* * cd, find, and ls use this to hold filenames. Each filename is broken * up by a slash. In other words, /usr/src/adm would have a len field * of 2 (starting from 0), and filenames->fname[0-2] would hold usr, * src, and adm components of the pathname. */ static struct filenames { ino_t ino; /* inode */ long len; /* number of components */ char flag; /* flag if using SECOND_DEPTH allocator */ char find; /* flag if found by find */ char **fname; /* hold components of pathname */ } *filenames, *top; enum log_enum { LOG_NDELTAS, LOG_ALLDELTAS, LOG_CHECKSCAN }; #ifdef sun struct fs *fs; static union { struct fs un_filesystem; char un_sbsize[SBSIZE]; } fs_un; #define filesystem fs_un.un_filesystem #else struct fs filesystem, *fs; /* super block */ #endif /* sun */ /* * Global data. */ static char *input_path[MAXPATHLEN]; static char *stack_path[MAXPATHLEN]; static char *current_path[MAXPATHLEN]; static char input_buffer[INPUTBUFFER]; static char *prompt; static char *buffers; static char scratch[64]; static char BASE[] = "o u x"; static char PROMPT[PROMPTSIZE]; static char laststyle = '/'; static char lastpo = 'x'; static short input_pointer; static short current_pathp; static short stack_pathp; static short input_pathp; static short cmp_level; static int nfiles; static short type = NUMB; static short dirslot; static short fd; static short c_count; static short error; static short paren; static short trapped; static short doing_cd; static short doing_find; static short find_by_name; static short find_by_inode; static short long_list; static short recursive; static short objsz = SHORT; static short override = 0; static short wrtflag = O_RDONLY; static short base = HEX; static short acting_on_inode; static short acting_on_directory; static short should_print = 1; static short clear; static short star; static u_offset_t addr; static u_offset_t bod_addr; static u_offset_t value; static u_offset_t erraddr; static long errcur_bytes; static u_offset_t errino; static long errinum; static long cur_cgrp; static u_offset_t cur_ino; static long cur_inum; static u_offset_t cur_dir; static long cur_block; static long cur_bytes; static long find_ino; static u_offset_t filesize; static u_offset_t blocksize; static long stringsize; static long count = 1; static long commands; static long read_requests; static long actual_disk_reads; static jmp_buf env; static long maxfiles; static long cur_shad; #ifndef sun extern char *malloc(), *calloc(); #endif static char getachar(); static char *getblk(), *fmtentry(); static offset_t get(short); static long bmap(); static long expr(); static long term(); static long getnumb(); static u_offset_t getdirslot(); static unsigned long *print_check(unsigned long *, long *, short, int); static void usage(char *); static void ungetachar(char); static void getnextinput(); static void eat_spaces(); static void restore_inode(ino_t); static void find(); static void ls(struct filenames *, struct filenames *, short); static void formatf(struct filenames *, struct filenames *); static void parse(); static void follow_path(long, long); static void getname(); static void freemem(struct filenames *, int); static void print_path(char **, int); static void fill(); static void put(u_offset_t, short); static void insert(struct lbuf *); static void puta(); static void fprnt(char, char); static void index(); #ifdef _LARGEFILE64_SOURCE static void printll (u_offset_t value, int fieldsz, int digits, int lead); #define print(value, fieldsz, digits, lead) \ printll((u_offset_t)value, fieldsz, digits, lead) #else /* !_LARGEFILE64_SOURCE */ static void print(long value, int fieldsz, int digits, int lead); #endif /* _LARGEFILE64_SOURCE */ static void printsb(struct fs *); static void printcg(struct cg *); static void pbits(unsigned char *, int); static void old_fsdb(int, char *) __NORETURN; /* For old fsdb functionality */ static int isnumber(char *); static int icheck(u_offset_t); static int cgrp_check(long); static int valid_addr(); static int match(char *, int); static int devcheck(short); static int bcomp(); static int compare(char *, char *, short); static int check_addr(short, short *, short *, short); static int fcmp(); static int ffcmp(); static int getshadowslot(long); static void getshadowdata(long *, int); static void syncshadowscan(int); static void log_display_header(void); static void log_show(enum log_enum); #ifdef sun static void err(); #else static int err(); #endif /* sun */ /* Suboption vector */ static char *subopt_v[] = { #define OVERRIDE 0 "o", #define NEW_PROMPT 1 "p", #define WRITE_ENABLED 2 "w", #define ALT_PROMPT 3 "prompt", NULL }; /* * main - lines are read up to the unprotected ('\') newline and * held in an input buffer. Characters may be read from the * input buffer using getachar() and unread using ungetachar(). * Reading the whole line ahead allows the use of debuggers * which would otherwise be impossible since the debugger * and fsdb could not share stdin. */ int main(int argc, char *argv[]) { char c, *cptr; short i; struct direct *dirp; struct lbuf *bp; char *progname; volatile short colon; short mode; long temp; /* Options/Suboptions processing */ int opt; char *subopts; char *optval; /* * The following are used to support the old fsdb functionality * of clearing an inode. It's better to use 'clri'. */ int inum; /* Inode number to clear */ char *special; setbuf(stdin, NULL); progname = argv[0]; prompt = &PROMPT[0]; /* * Parse options. */ while ((opt = getopt(argc, argv, FSDB_OPTIONS)) != EOF) { switch (opt) { #if defined(OLD_FSDB_COMPATIBILITY) case 'z': /* Hack - Better to use clri */ (void) fprintf(stderr, "%s\n%s\n%s\n%s\n", "Warning: The '-z' option of 'fsdb_ufs' has been declared obsolete", "and may not be supported in a future version of Solaris.", "While this functionality is currently still supported, the", "recommended procedure to clear an inode is to use clri(8)."); if (isnumber(optarg)) { inum = atoi(optarg); special = argv[optind]; /* Doesn't return */ old_fsdb(inum, special); } else { usage(progname); exit(31+1); } /* Should exit() before here */ /*NOTREACHED*/ #endif /* OLD_FSDB_COMPATIBILITY */ case 'o': /* UFS Specific Options */ subopts = optarg; while (*subopts != '\0') { switch (getsubopt(&subopts, subopt_v, &optval)) { case OVERRIDE: printf("error checking off\n"); override = 1; break; /* * Change the "-o prompt=foo" option to * "-o p=foo" to match documentation. * ALT_PROMPT continues support for the * undocumented "-o prompt=foo" option so * that we don't break anyone. */ case NEW_PROMPT: case ALT_PROMPT: if (optval == NULL) { (void) fprintf(stderr, "No prompt string\n"); usage(progname); } (void) strncpy(PROMPT, optval, PROMPTSIZE); break; case WRITE_ENABLED: /* suitable for open */ wrtflag = O_RDWR; break; default: usage(progname); /* Should exit here */ } } break; default: usage(progname); } } if ((argc - optind) != 1) { /* Should just have "special" left */ usage(progname); } special = argv[optind]; /* * Unless it's already been set, the default prompt includes the * name of the special device. */ if (*prompt == '\0') (void) sprintf(prompt, "%s > ", special); /* * Attempt to open the special file. */ if ((fd = open(special, wrtflag)) < 0) { perror(special); exit(1); } /* * Read in the super block and validate (not too picky). */ if (llseek(fd, (offset_t)(SBLOCK * DEV_BSIZE), 0) == -1) { perror(special); exit(1); } #ifdef sun if (read(fd, &filesystem, SBSIZE) != SBSIZE) { printf("%s: cannot read superblock\n", special); exit(1); } #else if (read(fd, &filesystem, sizeof (filesystem)) != sizeof (filesystem)) { printf("%s: cannot read superblock\n", special); exit(1); } #endif /* sun */ fs = &filesystem; if ((fs->fs_magic != FS_MAGIC) && (fs->fs_magic != MTB_UFS_MAGIC)) { if (!override) { printf("%s: Bad magic number in file system\n", special); exit(1); } printf("WARNING: Bad magic number in file system. "); printf("Continue? (y/n): "); (void) fflush(stdout); if (gets(input_buffer) == NULL) { exit(1); } if (*input_buffer != 'y' && *input_buffer != 'Y') { exit(1); } } if ((fs->fs_magic == FS_MAGIC && (fs->fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && fs->fs_version != UFS_VERSION_MIN)) || (fs->fs_magic == MTB_UFS_MAGIC && (fs->fs_version > MTB_UFS_VERSION_1 || fs->fs_version < MTB_UFS_VERSION_MIN))) { if (!override) { printf("%s: Unrecognized UFS version number: %d\n", special, fs->fs_version); exit(1); } printf("WARNING: Unrecognized UFS version number. "); printf("Continue? (y/n): "); (void) fflush(stdout); if (gets(input_buffer) == NULL) { exit(1); } if (*input_buffer != 'y' && *input_buffer != 'Y') { exit(1); } } #ifdef FS_42POSTBLFMT if (fs->fs_postblformat == FS_42POSTBLFMT) fs->fs_nrpos = 8; #endif printf("fsdb of %s %s -- last mounted on %s\n", special, (wrtflag == O_RDWR) ? "(Opened for write)" : "(Read only)", &fs->fs_fsmnt[0]); #ifdef sun printf("fs_clean is currently set to "); switch (fs->fs_clean) { case FSACTIVE: printf("FSACTIVE\n"); break; case FSCLEAN: printf("FSCLEAN\n"); break; case FSSTABLE: printf("FSSTABLE\n"); break; case FSBAD: printf("FSBAD\n"); break; case FSSUSPEND: printf("FSSUSPEND\n"); break; case FSLOG: printf("FSLOG\n"); break; case FSFIX: printf("FSFIX\n"); if (!override) { printf("%s: fsck may be running on this file system\n", special); exit(1); } printf("WARNING: fsck may be running on this file system. "); printf("Continue? (y/n): "); (void) fflush(stdout); if (gets(input_buffer) == NULL) { exit(1); } if (*input_buffer != 'y' && *input_buffer != 'Y') { exit(1); } break; default: printf("an unknown value (0x%x)\n", fs->fs_clean); break; } if (fs->fs_state == (FSOKAY - fs->fs_time)) { printf("fs_state consistent (fs_clean CAN be trusted)\n"); } else { printf("fs_state inconsistent (fs_clean CAN'T trusted)\n"); } #endif /* sun */ /* * Malloc buffers and set up cache. */ buffers = malloc(NBUF * BLKSIZE); bhdr.fwd = bhdr.back = &bhdr; for (i = 0; i < NBUF; i++) { bp = &lbuf[i]; bp->blkaddr = buffers + (i * BLKSIZE); bp->valid = 0; insert(bp); } /* * Malloc filenames structure. The space for the actual filenames * is allocated as it needs it. We estimate the size based on the * number of inodes(objects) in the filesystem and the number of * directories. The number of directories are padded by 3 because * each directory traversed during a "find" or "ls -R" needs 3 * entries. */ maxfiles = (long)((((u_offset_t)fs->fs_ncg * (u_offset_t)fs->fs_ipg) - (u_offset_t)fs->fs_cstotal.cs_nifree) + ((u_offset_t)fs->fs_cstotal.cs_ndir * (u_offset_t)3)); filenames = (struct filenames *)calloc(maxfiles, sizeof (struct filenames)); if (filenames == NULL) { /* * If we could not allocate memory for all of files * in the filesystem then, back off to the old fixed * value. */ maxfiles = MAXFILES; filenames = (struct filenames *)calloc(maxfiles, sizeof (struct filenames)); if (filenames == NULL) { printf("out of memory\n"); exit(1); } } restore_inode(2); /* * Malloc a few filenames (needed by pwd for example). */ for (i = 0; i < MAXPATHLEN; i++) { input_path[i] = calloc(1, MAXNAMLEN); stack_path[i] = calloc(1, MAXNAMLEN); current_path[i] = calloc(1, MAXNAMLEN); if (current_path[i] == NULL) { printf("out of memory\n"); exit(1); } } current_pathp = -1; (void) signal(2, err); (void) setjmp(env); getnextinput(); /* * Main loop and case statement. If an error condition occurs * initialization and recovery is attempted. */ for (;;) { if (error) { freemem(filenames, nfiles); nfiles = 0; c_count = 0; count = 1; star = 0; error = 0; paren = 0; acting_on_inode = 0; acting_on_directory = 0; should_print = 1; addr = erraddr; cur_ino = errino; cur_inum = errinum; cur_bytes = errcur_bytes; printf("?\n"); getnextinput(); if (error) continue; } c_count++; switch (c = getachar()) { case '\n': /* command end */ freemem(filenames, nfiles); nfiles = 0; if (should_print && laststyle == '=') { ungetachar(c); goto calc; } if (c_count == 1) { clear = 0; should_print = 1; erraddr = addr; errino = cur_ino; errinum = cur_inum; errcur_bytes = cur_bytes; switch (objsz) { case DIRECTORY: if ((addr = getdirslot( (long)dirslot+1)) == 0) should_print = 0; if (error) { ungetachar(c); continue; } break; case INODE: cur_inum++; addr = itob(cur_inum); if (!icheck(addr)) { cur_inum--; should_print = 0; } break; case CGRP: case SB: cur_cgrp++; addr = cgrp_check(cur_cgrp); if (addr == 0) { cur_cgrp--; continue; } break; case SHADOW_DATA: if ((addr = getshadowslot( (long)cur_shad + 1)) == 0) should_print = 0; if (error) { ungetachar(c); continue; } break; default: addr += objsz; cur_bytes += objsz; if (valid_addr() == 0) continue; } } if (type == NUMB) trapped = 0; if (should_print) switch (objsz) { case DIRECTORY: fprnt('?', 'd'); break; case INODE: fprnt('?', 'i'); if (!error) cur_ino = addr; break; case CGRP: fprnt('?', 'c'); break; case SB: fprnt('?', 's'); break; case SHADOW_DATA: fprnt('?', 'S'); break; case CHAR: case SHORT: case LONG: fprnt(laststyle, lastpo); } if (error) { ungetachar(c); continue; } c_count = colon = acting_on_inode = 0; acting_on_directory = 0; should_print = 1; getnextinput(); if (error) continue; erraddr = addr; errino = cur_ino; errinum = cur_inum; errcur_bytes = cur_bytes; continue; case '(': /* numeric expression or unknown command */ default: colon = 0; if (digit(c) || c == '(') { ungetachar(c); addr = expr(); type = NUMB; value = addr; continue; } printf("unknown command or bad syntax\n"); error++; continue; case '?': /* general print facilities */ case '/': fprnt(c, getachar()); continue; case ';': /* command separator and . */ case '\t': case ' ': case '.': continue; case ':': /* command indicator */ colon++; commands++; should_print = 0; stringsize = 0; trapped = 0; continue; case ',': /* count indicator */ colon = star = 0; if ((c = getachar()) == '*') { star = 1; count = BLKSIZE; } else { ungetachar(c); count = expr(); if (error) continue; if (!count) count = 1; } clear = 0; continue; case '+': /* address addition */ colon = 0; c = getachar(); ungetachar(c); if (c == '\n') temp = 1; else { temp = expr(); if (error) continue; } erraddr = addr; errcur_bytes = cur_bytes; switch (objsz) { case DIRECTORY: addr = getdirslot((long)(dirslot + temp)); if (error) continue; break; case INODE: cur_inum += temp; addr = itob(cur_inum); if (!icheck(addr)) { cur_inum -= temp; continue; } break; case CGRP: case SB: cur_cgrp += temp; if ((addr = cgrp_check(cur_cgrp)) == 0) { cur_cgrp -= temp; continue; } break; case SHADOW_DATA: addr = getshadowslot((long)(cur_shad + temp)); if (error) continue; break; default: laststyle = '/'; addr += temp * objsz; cur_bytes += temp * objsz; if (valid_addr() == 0) continue; } value = get(objsz); continue; case '-': /* address subtraction */ colon = 0; c = getachar(); ungetachar(c); if (c == '\n') temp = 1; else { temp = expr(); if (error) continue; } erraddr = addr; errcur_bytes = cur_bytes; switch (objsz) { case DIRECTORY: addr = getdirslot((long)(dirslot - temp)); if (error) continue; break; case INODE: cur_inum -= temp; addr = itob(cur_inum); if (!icheck(addr)) { cur_inum += temp; continue; } break; case CGRP: case SB: cur_cgrp -= temp; if ((addr = cgrp_check(cur_cgrp)) == 0) { cur_cgrp += temp; continue; } break; case SHADOW_DATA: addr = getshadowslot((long)(cur_shad - temp)); if (error) continue; break; default: laststyle = '/'; addr -= temp * objsz; cur_bytes -= temp * objsz; if (valid_addr() == 0) continue; } value = get(objsz); continue; case '*': /* address multiplication */ colon = 0; temp = expr(); if (error) continue; if (objsz != INODE && objsz != DIRECTORY) laststyle = '/'; addr *= temp; value = get(objsz); continue; case '%': /* address division */ colon = 0; temp = expr(); if (error) continue; if (!temp) { printf("divide by zero\n"); error++; continue; } if (objsz != INODE && objsz != DIRECTORY) laststyle = '/'; addr /= temp; value = get(objsz); continue; case '=': { /* assignment operation */ short tbase; calc: tbase = base; c = getachar(); if (c == '\n') { ungetachar(c); c = lastpo; if (acting_on_inode == 1) { if (c != 'o' && c != 'd' && c != 'x' && c != 'O' && c != 'D' && c != 'X') { switch (objsz) { case LONG: c = lastpo = 'X'; break; case SHORT: c = lastpo = 'x'; break; case CHAR: c = lastpo = 'c'; } } } else { if (acting_on_inode == 2) c = lastpo = 't'; } } else if (acting_on_inode) lastpo = c; should_print = star = 0; count = 1; erraddr = addr; errcur_bytes = cur_bytes; switch (c) { case '"': /* character string */ if (type == NUMB) { blocksize = BLKSIZE; filesize = BLKSIZE * 2; cur_bytes = blkoff(fs, addr); if (objsz == DIRECTORY || objsz == INODE) lastpo = 'X'; } puta(); continue; case '+': /* =+ operator */ temp = expr(); value = get(objsz); if (!error) put(value+temp, objsz); continue; case '-': /* =- operator */ temp = expr(); value = get(objsz); if (!error) put(value-temp, objsz); continue; case 'b': case 'c': if (objsz == CGRP) fprnt('?', c); else fprnt('/', c); continue; case 'i': addr = cur_ino; fprnt('?', 'i'); continue; case 's': fprnt('?', 's'); continue; case 't': case 'T': laststyle = '='; printf("\t\t"); { /* * Truncation is intentional so * ctime is happy. */ time_t tvalue = (time_t)value; printf("%s", ctime(&tvalue)); } continue; case 'o': base = OCTAL; goto otx; case 'd': if (objsz == DIRECTORY) { addr = cur_dir; fprnt('?', 'd'); continue; } base = DECIMAL; goto otx; case 'x': base = HEX; otx: laststyle = '='; printf("\t\t"); if (acting_on_inode) print(value & 0177777L, 12, -8, 0); else print(addr & 0177777L, 12, -8, 0); printf("\n"); base = tbase; continue; case 'O': base = OCTAL; goto OTX; case 'D': base = DECIMAL; goto OTX; case 'X': base = HEX; OTX: laststyle = '='; printf("\t\t"); if (acting_on_inode) print(value, 12, -8, 0); else print(addr, 12, -8, 0); printf("\n"); base = tbase; continue; default: /* regular assignment */ ungetachar(c); value = expr(); if (error) printf("syntax error\n"); else put(value, objsz); continue; } } case '>': /* save current address */ colon = 0; should_print = 0; c = getachar(); if (!letter(c) && !digit(c)) { printf("invalid register specification, "); printf("must be letter or digit\n"); error++; continue; } if (letter(c)) { if (c < 'a') c = uppertolower(c); c = hextodigit(c); } else c = numtodigit(c); regs[c].sv_addr = addr; regs[c].sv_value = value; regs[c].sv_objsz = objsz; continue; case '<': /* restore saved address */ colon = 0; should_print = 0; c = getachar(); if (!letter(c) && !digit(c)) { printf("invalid register specification, "); printf("must be letter or digit\n"); error++; continue; } if (letter(c)) { if (c < 'a') c = uppertolower(c); c = hextodigit(c); } else c = numtodigit(c); addr = regs[c].sv_addr; value = regs[c].sv_value; objsz = regs[c].sv_objsz; continue; case 'a': if (colon) colon = 0; else goto no_colon; if (match("at", 2)) { /* access time */ acting_on_inode = 2; should_print = 1; addr = (long)&((struct dinode *) (uintptr_t)cur_ino)->di_atime; value = get(LONG); type = 0; continue; } goto bad_syntax; case 'b': if (colon) colon = 0; else goto no_colon; if (match("block", 2)) { /* block conversion */ if (type == NUMB) { value = addr; cur_bytes = 0; blocksize = BLKSIZE; filesize = BLKSIZE * 2; } addr = value << FRGSHIFT; bod_addr = addr; value = get(LONG); type = BLOCK; dirslot = 0; trapped++; continue; } if (match("bs", 2)) { /* block size */ acting_on_inode = 1; should_print = 1; if (icheck(cur_ino) == 0) continue; addr = (long)&((struct dinode *) (uintptr_t)cur_ino)->di_blocks; value = get(LONG); type = 0; continue; } if (match("base", 2)) { /* change/show base */ showbase: if ((c = getachar()) == '\n') { ungetachar(c); printf("base =\t\t"); switch (base) { case OCTAL: printf("OCTAL\n"); continue; case DECIMAL: printf("DECIMAL\n"); continue; case HEX: printf("HEX\n"); continue; } } if (c != '=') { printf("missing '='\n"); error++; continue; } value = expr(); switch (value) { default: printf("invalid base\n"); error++; break; case OCTAL: case DECIMAL: case HEX: base = (short)value; } goto showbase; } goto bad_syntax; case 'c': if (colon) colon = 0; else goto no_colon; if (match("cd", 2)) { /* change directory */ top = filenames - 1; eat_spaces(); if ((c = getachar()) == '\n') { ungetachar(c); current_pathp = -1; restore_inode(2); continue; } ungetachar(c); temp = cur_inum; doing_cd = 1; parse(); doing_cd = 0; if (nfiles != 1) { restore_inode((ino_t)temp); if (!error) { print_path(input_path, (int)input_pathp); if (nfiles == 0) printf(" not found\n"); else printf(" ambiguous\n"); error++; } continue; } restore_inode(filenames->ino); if ((mode = icheck(addr)) == 0) continue; if ((mode & IFMT) != IFDIR) { restore_inode((ino_t)temp); print_path(input_path, (int)input_pathp); printf(" not a directory\n"); error++; continue; } for (i = 0; i <= top->len; i++) (void) strcpy(current_path[i], top->fname[i]); current_pathp = top->len; continue; } if (match("cg", 2)) { /* cylinder group */ if (type == NUMB) value = addr; if (value > fs->fs_ncg - 1) { printf("maximum cylinder group is "); print(fs->fs_ncg - 1, 8, -8, 0); printf("\n"); error++; continue; } type = objsz = CGRP; cur_cgrp = (long)value; addr = cgtod(fs, cur_cgrp) << FRGSHIFT; continue; } if (match("ct", 2)) { /* creation time */ acting_on_inode = 2; should_print = 1; addr = (long)&((struct dinode *) (uintptr_t)cur_ino)->di_ctime; value = get(LONG); type = 0; continue; } goto bad_syntax; case 'd': if (colon) colon = 0; else goto no_colon; if (match("directory", 2)) { /* directory offsets */ if (type == NUMB) value = addr; objsz = DIRECTORY; type = DIRECTORY; addr = (u_offset_t)getdirslot((long)value); continue; } if (match("db", 2)) { /* direct block */ acting_on_inode = 1; should_print = 1; if (type == NUMB) value = addr; if (value >= NDADDR) { printf("direct blocks are 0 to "); print(NDADDR - 1, 0, 0, 0); printf("\n"); error++; continue; } addr = cur_ino; if (!icheck(addr)) continue; addr = (long) &((struct dinode *)(uintptr_t)cur_ino)-> di_db[value]; bod_addr = addr; cur_bytes = (value) * BLKSIZE; cur_block = (long)value; type = BLOCK; dirslot = 0; value = get(LONG); if (!value && !override) { printf("non existent block\n"); error++; } continue; } goto bad_syntax; case 'f': if (colon) colon = 0; else goto no_colon; if (match("find", 3)) { /* find command */ find(); continue; } if (match("fragment", 2)) { /* fragment conv. */ if (type == NUMB) { value = addr; cur_bytes = 0; blocksize = FRGSIZE; filesize = FRGSIZE * 2; } if (min(blocksize, filesize) - cur_bytes > FRGSIZE) { blocksize = cur_bytes + FRGSIZE; filesize = blocksize * 2; } addr = value << FRGSHIFT; bod_addr = addr; value = get(LONG); type = FRAGMENT; dirslot = 0; trapped++; continue; } if (match("file", 4)) { /* access as file */ acting_on_inode = 1; should_print = 1; if (type == NUMB) value = addr; addr = cur_ino; if ((mode = icheck(addr)) == 0) continue; if (!override) { switch (mode & IFMT) { case IFCHR: case IFBLK: printf("special device\n"); error++; continue; } } if ((addr = (u_offset_t) (bmap((long)value) << FRGSHIFT)) == 0) continue; cur_block = (long)value; bod_addr = addr; type = BLOCK; dirslot = 0; continue; } if (match("fill", 4)) { /* fill */ if (getachar() != '=') { printf("missing '='\n"); error++; continue; } if (objsz == INODE || objsz == DIRECTORY || objsz == SHADOW_DATA) { printf( "can't fill inode or directory\n"); error++; continue; } fill(); continue; } goto bad_syntax; case 'g': if (colon) colon = 0; else goto no_colon; if (match("gid", 1)) { /* group id */ acting_on_inode = 1; should_print = 1; addr = (long)&((struct dinode *) (uintptr_t)cur_ino)->di_gid; value = get(SHORT); type = 0; continue; } goto bad_syntax; case 'i': if (colon) colon = 0; else goto no_colon; if (match("inode", 2)) { /* i# to inode conversion */ if (c_count == 2) { addr = cur_ino; value = get(INODE); type = 0; laststyle = '='; lastpo = 'i'; should_print = 1; continue; } if (type == NUMB) value = addr; addr = itob(value); if (!icheck(addr)) continue; cur_ino = addr; cur_inum = (long)value; value = get(INODE); type = 0; continue; } if (match("ib", 2)) { /* indirect block */ acting_on_inode = 1; should_print = 1; if (type == NUMB) value = addr; if (value >= NIADDR) { printf("indirect blocks are 0 to "); print(NIADDR - 1, 0, 0, 0); printf("\n"); error++; continue; } addr = (long)&((struct dinode *)(uintptr_t) cur_ino)->di_ib[value]; cur_bytes = (NDADDR - 1) * BLKSIZE; temp = 1; for (i = 0; i < value; i++) { temp *= NINDIR(fs) * BLKSIZE; cur_bytes += temp; } type = BLOCK; dirslot = 0; value = get(LONG); if (!value && !override) { printf("non existent block\n"); error++; } continue; } goto bad_syntax; case 'l': if (colon) colon = 0; else goto no_colon; if (match("log_head", 8)) { log_display_header(); should_print = 0; continue; } if (match("log_delta", 9)) { log_show(LOG_NDELTAS); should_print = 0; continue; } if (match("log_show", 8)) { log_show(LOG_ALLDELTAS); should_print = 0; continue; } if (match("log_chk", 7)) { log_show(LOG_CHECKSCAN); should_print = 0; continue; } if (match("log_otodb", 9)) { if (log_lodb((u_offset_t)addr, &temp)) { addr = temp; should_print = 1; laststyle = '='; } else error++; continue; } if (match("ls", 2)) { /* ls command */ temp = cur_inum; recursive = long_list = 0; top = filenames - 1; for (;;) { eat_spaces(); if ((c = getachar()) == '-') { if ((c = getachar()) == 'R') { recursive = 1; continue; } else if (c == 'l') { long_list = 1; } else { printf( "unknown option "); printf("'%c'\n", c); error++; break; } } else ungetachar(c); if ((c = getachar()) == '\n') { if (c_count != 2) { ungetachar(c); break; } } c_count++; ungetachar(c); parse(); restore_inode((ino_t)temp); if (error) break; } recursive = 0; if (error || nfiles == 0) { if (!error) { print_path(input_path, (int)input_pathp); printf(" not found\n"); } continue; } if (nfiles) { cmp_level = 0; qsort((char *)filenames, nfiles, sizeof (struct filenames), ffcmp); ls(filenames, filenames + (nfiles - 1), 0); } else { printf("no match\n"); error++; } restore_inode((ino_t)temp); continue; } if (match("ln", 2)) { /* link count */ acting_on_inode = 1; should_print = 1; addr = (long)&((struct dinode *) (uintptr_t)cur_ino)->di_nlink; value = get(SHORT); type = 0; continue; } goto bad_syntax; case 'm': if (colon) colon = 0; else goto no_colon; addr = cur_ino; if ((mode = icheck(addr)) == 0) continue; if (match("mt", 2)) { /* modification time */ acting_on_inode = 2; should_print = 1; addr = (long)&((struct dinode *) (uintptr_t)cur_ino)->di_mtime; value = get(LONG); type = 0; continue; } if (match("md", 2)) { /* mode */ acting_on_inode = 1; should_print = 1; addr = (long)&((struct dinode *) (uintptr_t)cur_ino)->di_mode; value = get(SHORT); type = 0; continue; } if (match("maj", 2)) { /* major device number */ acting_on_inode = 1; should_print = 1; if (devcheck(mode)) continue; addr = (uintptr_t)&((struct dinode *)(uintptr_t) cur_ino)->di_ordev; { long dvalue; dvalue = get(LONG); value = major(dvalue); } type = 0; continue; } if (match("min", 2)) { /* minor device number */ acting_on_inode = 1; should_print = 1; if (devcheck(mode)) continue; addr = (uintptr_t)&((struct dinode *)(uintptr_t) cur_ino)->di_ordev; { long dvalue; dvalue = (long)get(LONG); value = minor(dvalue); } type = 0; continue; } goto bad_syntax; case 'n': if (colon) colon = 0; else goto no_colon; if (match("nm", 1)) { /* directory name */ objsz = DIRECTORY; acting_on_directory = 1; cur_dir = addr; if ((cptr = getblk(addr)) == 0) continue; /*LINTED*/ dirp = (struct direct *)(cptr+blkoff(fs, addr)); stringsize = (long)dirp->d_reclen - ((long)&dirp->d_name[0] - (long)&dirp->d_ino); addr = (long)&((struct direct *) (uintptr_t)addr)->d_name[0]; type = 0; continue; } goto bad_syntax; case 'o': if (colon) colon = 0; else goto no_colon; if (match("override", 1)) { /* override flip flop */ override = !override; if (override) printf("error checking off\n"); else printf("error checking on\n"); continue; } goto bad_syntax; case 'p': if (colon) colon = 0; else goto no_colon; if (match("pwd", 2)) { /* print working dir */ print_path(current_path, (int)current_pathp); printf("\n"); continue; } if (match("prompt", 2)) { /* change prompt */ if ((c = getachar()) != '=') { printf("missing '='\n"); error++; continue; } if ((c = getachar()) != '"') { printf("missing '\"'\n"); error++; continue; } i = 0; prompt = &prompt[0]; while ((c = getachar()) != '"' && c != '\n') { prompt[i++] = c; if (i >= PROMPTSIZE) { printf("string too long\n"); error++; break; } } prompt[i] = '\0'; continue; } goto bad_syntax; case 'q': if (!colon) goto no_colon; if (match("quit", 1)) { /* quit */ if ((c = getachar()) != '\n') { error++; continue; } exit(0); } goto bad_syntax; case 's': if (colon) colon = 0; else goto no_colon; if (match("sb", 2)) { /* super block */ if (c_count == 2) { cur_cgrp = -1; type = objsz = SB; laststyle = '='; lastpo = 's'; should_print = 1; continue; } if (type == NUMB) value = addr; if (value > fs->fs_ncg - 1) { printf("maximum super block is "); print(fs->fs_ncg - 1, 8, -8, 0); printf("\n"); error++; continue; } type = objsz = SB; cur_cgrp = (long)value; addr = cgsblock(fs, cur_cgrp) << FRGSHIFT; continue; } if (match("shadow", 2)) { /* shadow inode data */ if (type == NUMB) value = addr; objsz = SHADOW_DATA; type = SHADOW_DATA; addr = getshadowslot(value); continue; } if (match("si", 2)) { /* shadow inode field */ acting_on_inode = 1; should_print = 1; addr = (long)&((struct dinode *) (uintptr_t)cur_ino)->di_shadow; value = get(LONG); type = 0; continue; } if (match("sz", 2)) { /* file size */ acting_on_inode = 1; should_print = 1; addr = (long)&((struct dinode *) (uintptr_t)cur_ino)->di_size; value = get(U_OFFSET_T); type = 0; objsz = U_OFFSET_T; laststyle = '='; lastpo = 'X'; continue; } goto bad_syntax; case 'u': if (colon) colon = 0; else goto no_colon; if (match("uid", 1)) { /* user id */ acting_on_inode = 1; should_print = 1; addr = (long)&((struct dinode *) (uintptr_t)cur_ino)->di_uid; value = get(SHORT); type = 0; continue; } goto bad_syntax; case 'F': /* buffer status (internal use only) */ if (colon) colon = 0; else goto no_colon; for (bp = bhdr.fwd; bp != &bhdr; bp = bp->fwd) printf("%8" PRIx64 " %d\n", bp->blkno, bp->valid); printf("\n"); printf("# commands\t\t%ld\n", commands); printf("# read requests\t\t%ld\n", read_requests); printf("# actual disk reads\t%ld\n", actual_disk_reads); continue; no_colon: printf("a colon should precede a command\n"); error++; continue; bad_syntax: printf("more letters needed to distinguish command\n"); error++; continue; } } } /* * usage - print usage and exit */ static void usage(char *progname) { printf("usage: %s [options] special\n", progname); printf("options:\n"); printf("\t-o Specify ufs filesystem sepcific options\n"); printf(" Available suboptions are:\n"); printf("\t\t? display usage\n"); printf("\t\to override some error conditions\n"); printf("\t\tp=\"string\" set prompt to string\n"); printf("\t\tw open for write\n"); exit(1); } /* * getachar - get next character from input buffer. */ static char getachar() { return (input_buffer[input_pointer++]); } /* * ungetachar - return character to input buffer. */ static void ungetachar(char c) { if (input_pointer == 0) { printf("internal problem maintaining input buffer\n"); error++; return; } input_buffer[--input_pointer] = c; } /* * getnextinput - display the prompt and read an input line. * An input line is up to 128 characters terminated by the newline * character. Handle overflow, shell escape, and eof. */ static void getnextinput() { int i; char c; short pid, rpid; int retcode; newline: i = 0; printf("%s", prompt); ignore_eol: while ((c = getc(stdin)) != '\n' && !(c == '!' && i == 0) && !feof(stdin) && i <= INPUTBUFFER - 2) input_buffer[i++] = c; if (i > 0 && input_buffer[i - 1] == '\\') { input_buffer[i++] = c; goto ignore_eol; } if (feof(stdin)) { printf("\n"); exit(0); } if (c == '!') { if ((pid = fork()) == 0) { (void) execl(_PATH_BSHELL, "sh", "-t", 0); error++; return; } while ((rpid = wait(&retcode)) != pid && rpid != -1) ; printf("!\n"); goto newline; } if (c != '\n') printf("input truncated to 128 characters\n"); input_buffer[i] = '\n'; input_pointer = 0; } /* * eat_spaces - read extraneous spaces. */ static void eat_spaces() { char c; while ((c = getachar()) == ' ') ; ungetachar(c); } /* * restore_inode - set up all inode indicators so inum is now * the current inode. */ static void restore_inode(ino_t inum) { errinum = cur_inum = inum; addr = errino = cur_ino = itob(inum); } /* * match - return false if the input does not match string up to * upto letters. Then proceed to chew up extraneous letters. */ static int match(char *string, int upto) { int i, length = strlen(string) - 1; char c; int save_upto = upto; while (--upto) { string++; if ((c = getachar()) != *string) { for (i = save_upto - upto; i; i--) { ungetachar(c); c = *--string; } return (0); } length--; } while (length--) { string++; if ((c = getachar()) != *string) { ungetachar(c); return (1); } } return (1); } /* * expr - expression evaluator. Will evaluate expressions from * left to right with no operator precedence. Parentheses may * be used. */ static long expr() { long numb = 0, temp; char c; numb = term(); for (;;) { if (error) return (~0); /* error is set so value is ignored */ c = getachar(); switch (c) { case '+': numb += term(); continue; case '-': numb -= term(); continue; case '*': numb *= term(); continue; case '%': temp = term(); if (!temp) { printf("divide by zero\n"); error++; return (~0); } numb /= temp; continue; case ')': paren--; return (numb); default: ungetachar(c); if (paren && !error) { printf("missing ')'\n"); error++; } return (numb); } } } /* * term - used by expression evaluator to get an operand. */ static long term() { char c; switch (c = getachar()) { default: ungetachar(c); /*FALLTHRU*/ case '+': return (getnumb()); case '-': return (-getnumb()); case '(': paren++; return (expr()); } } /* * getnumb - read a number from the input stream. A leading * zero signifies octal interpretation, a leading '0x' * signifies hexadecimal, and a leading '0t' signifies * decimal. If the first character is a character, * return an error. */ static long getnumb() { char c, savec; long number = 0, tbase, num; extern short error; c = getachar(); if (!digit(c)) { error++; ungetachar(c); return (-1); } if (c == '0') { tbase = OCTAL; if ((c = getachar()) == 'x') tbase = HEX; else if (c == 't') tbase = DECIMAL; else ungetachar(c); } else { tbase = base; ungetachar(c); } for (;;) { num = tbase; c = savec = getachar(); if (HEXLETTER(c)) c = uppertolower(c); switch (tbase) { case HEX: if (hexletter(c)) { num = hextodigit(c); break; } /*FALLTHRU*/ case DECIMAL: if (digit(c)) num = numtodigit(c); break; case OCTAL: if (octaldigit(c)) num = numtodigit(c); } if (num == tbase) break; number = number * tbase + num; } ungetachar(savec); return (number); } /* * find - the syntax is almost identical to the unix command. * find dir [-name pattern] [-inum number] * Note: only one of -name or -inum may be used at a time. * Also, the -print is not needed (implied). */ static void find() { struct filenames *fn; char c; long temp; short mode; eat_spaces(); temp = cur_inum; top = filenames - 1; doing_cd = 1; parse(); doing_cd = 0; if (nfiles != 1) { restore_inode((ino_t)temp); if (!error) { print_path(input_path, (int)input_pathp); if (nfiles == 0) printf(" not found\n"); else printf(" ambiguous\n"); error++; return; } } restore_inode(filenames->ino); freemem(filenames, nfiles); nfiles = 0; top = filenames - 1; if ((mode = icheck(addr)) == 0) return; if ((mode & IFMT) != IFDIR) { print_path(input_path, (int)input_pathp); printf(" not a directory\n"); error++; return; } eat_spaces(); if ((c = getachar()) != '-') { restore_inode((ino_t)temp); printf("missing '-'\n"); error++; return; } find_by_name = find_by_inode = 0; c = getachar(); if (match("name", 4)) { eat_spaces(); find_by_name = 1; } else if (match("inum", 4)) { eat_spaces(); find_ino = expr(); if (error) { restore_inode((ino_t)temp); return; } while ((c = getachar()) != '\n') ; ungetachar(c); find_by_inode = 1; } else { restore_inode((ino_t)temp); printf("use -name or -inum with find\n"); error++; return; } doing_find = 1; parse(); doing_find = 0; if (error) { restore_inode((ino_t)temp); return; } for (fn = filenames; fn <= top; fn++) { if (fn->find == 0) continue; printf("i#: "); print(fn->ino, 12, -8, 0); print_path(fn->fname, (int)fn->len); printf("\n"); } restore_inode((ino_t)temp); } /* * ls - do an ls. Should behave exactly as ls(1). * Only -R and -l is supported and -l gives different results. */ static void ls(struct filenames *fn0, struct filenames *fnlast, short level) { struct filenames *fn, *fnn; fn = fn0; for (;;) { fn0 = fn; if (fn0->len) { cmp_level = level; qsort((char *)fn0, fnlast - fn0 + 1, sizeof (struct filenames), fcmp); } for (fnn = fn, fn++; fn <= fnlast; fnn = fn, fn++) { if (fnn->len != fn->len && level == fnn->len - 1) break; if (fnn->len == 0) continue; if (strcmp(fn->fname[level], fnn->fname[level])) break; } if (fn0->len && level != fn0->len - 1) ls(fn0, fnn, level + 1); else { if (fn0 != filenames) printf("\n"); print_path(fn0->fname, (int)(fn0->len - 1)); printf(":\n"); if (fn0->len == 0) cmp_level = level; else cmp_level = level + 1; qsort((char *)fn0, fnn - fn0 + 1, sizeof (struct filenames), fcmp); formatf(fn0, fnn); nfiles -= fnn - fn0 + 1; } if (fn > fnlast) return; } } /* * formatf - code lifted from ls. */ static void formatf(struct filenames *fn0, struct filenames *fnlast) { struct filenames *fn; int width = 0, w, nentry = fnlast - fn0 + 1; int i, j, columns, lines; char *cp; if (long_list) { columns = 1; } else { for (fn = fn0; fn <= fnlast; fn++) { int len = strlen(fn->fname[cmp_level]) + 2; if (len > width) width = len; } width = (width + 8) &~ 7; columns = 80 / width; if (columns == 0) columns = 1; } lines = (nentry + columns - 1) / columns; for (i = 0; i < lines; i++) { for (j = 0; j < columns; j++) { fn = fn0 + j * lines + i; if (long_list) { printf("i#: "); print(fn->ino, 12, -8, 0); } if ((cp = fmtentry(fn)) == NULL) { printf("cannot read inode %ld\n", fn->ino); return; } printf("%s", cp); if (fn + lines > fnlast) { printf("\n"); break; } w = strlen(cp); while (w < width) { w = (w + 8) &~ 7; (void) putchar('\t'); } } } } /* * fmtentry - code lifted from ls. */ static char * fmtentry(struct filenames *fn) { static char fmtres[BUFSIZ]; struct dinode *ip; char *cptr, *cp, *dp; dp = &fmtres[0]; for (cp = fn->fname[cmp_level]; *cp; cp++) { if (*cp < ' ' || *cp >= 0177) *dp++ = '?'; else *dp++ = *cp; } addr = itob(fn->ino); if ((cptr = getblk(addr)) == 0) return (NULL); cptr += blkoff(fs, addr); /*LINTED*/ ip = (struct dinode *)cptr; switch (ip->di_mode & IFMT) { case IFDIR: *dp++ = '/'; break; case IFLNK: *dp++ = '@'; break; case IFSOCK: *dp++ = '='; break; #ifdef IFIFO case IFIFO: *dp++ = 'p'; break; #endif case IFCHR: case IFBLK: case IFREG: if (ip->di_mode & 0111) *dp++ = '*'; else *dp++ = ' '; break; default: *dp++ = '?'; } *dp++ = 0; return (fmtres); } /* * fcmp - routine used by qsort. Will sort first by name, then * then by pathname length if names are equal. Uses global * cmp_level to tell what component of the path name we are comparing. */ static int fcmp(struct filenames *f1, struct filenames *f2) { int value; if ((value = strcmp(f1->fname[cmp_level], f2->fname[cmp_level]))) return (value); return (f1->len - f2->len); } /* * ffcmp - routine used by qsort. Sort only by pathname length. */ static int ffcmp(struct filenames *f1, struct filenames *f2) { return (f1->len - f2->len); } /* * parse - set up the call to follow_path. */ static void parse() { int i; char c; stack_pathp = input_pathp = -1; if ((c = getachar()) == '/') { while ((c = getachar()) == '/') ; ungetachar(c); cur_inum = 2; c = getachar(); if ((c == '\n') || ((doing_cd) && (c == ' '))) { ungetachar(c); if (doing_cd) { top++; top->ino = 2; top->len = -1; nfiles = 1; return; } } else ungetachar(c); } else { ungetachar(c); stack_pathp = current_pathp; if (!doing_find) input_pathp = current_pathp; for (i = 0; i <= current_pathp; i++) { if (!doing_find) (void) strcpy(input_path[i], current_path[i]); (void) strcpy(stack_path[i], current_path[i]); } } getname(); follow_path((long)(stack_pathp + 1), cur_inum); } /* * follow_path - called by cd, find, and ls. * input_path holds the name typed by the user. * stack_path holds the name at the current depth. */ static void follow_path(long level, long inum) { struct direct *dirp; char **ccptr, *cptr; int i; struct filenames *tos, *bos, *fn, *fnn, *fnnn; long block; short mode; tos = top + 1; restore_inode((ino_t)inum); if ((mode = icheck(addr)) == 0) return; if ((mode & IFMT) != IFDIR) return; block = cur_bytes = 0; while (cur_bytes < filesize) { if (block == 0 || bcomp(addr)) { error = 0; if ((addr = ((u_offset_t)bmap(block++) << (u_offset_t)FRGSHIFT)) == 0) break; if ((cptr = getblk(addr)) == 0) break; cptr += blkoff(fs, addr); } /*LINTED*/ dirp = (struct direct *)cptr; if (dirp->d_ino) { if (level > input_pathp || doing_find || compare(input_path[level], &dirp->d_name[0], 1)) { if ((doing_find) && ((strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0))) goto duplicate; if (++top - filenames >= maxfiles) { printf("too many files\n"); error++; return; } top->fname = (char **)calloc(FIRST_DEPTH, sizeof (char **)); top->flag = 0; if (top->fname == 0) { printf("out of memory\n"); error++; return; } nfiles++; top->ino = dirp->d_ino; top->len = stack_pathp; top->find = 0; if (doing_find) { if (find_by_name) { if (compare(input_path[0], &dirp->d_name[0], 1)) top->find = 1; } else if (find_by_inode) if (find_ino == dirp->d_ino) top->find = 1; } if (top->len + 1 >= FIRST_DEPTH && top->flag == 0) { ccptr = (char **)calloc(SECOND_DEPTH, sizeof (char **)); if (ccptr == 0) { printf("out of memory\n"); error++; return; } for (i = 0; i < FIRST_DEPTH; i++) ccptr[i] = top->fname[i]; free((char *)top->fname); top->fname = ccptr; top->flag = 1; } if (top->len >= SECOND_DEPTH) { printf("maximum depth exceeded, try to cd lower\n"); error++; return; } /* * Copy current depth. */ for (i = 0; i <= stack_pathp; i++) { top->fname[i] = calloc(1, strlen(stack_path[i])+1); if (top->fname[i] == 0) { printf("out of memory\n"); error++; return; } (void) strcpy(top->fname[i], stack_path[i]); } /* * Check for '.' or '..' typed. */ if ((level <= input_pathp) && (strcmp(input_path[level], ".") == 0 || strcmp(input_path[level], "..") == 0)) { if (strcmp(input_path[level], "..") == 0 && top->len >= 0) { free(top->fname[top->len]); top->len -= 1; } } else { /* * Check for duplicates. */ if (!doing_cd && !doing_find) { for (fn = filenames; fn < top; fn++) { if (fn->ino == dirp->d_ino && fn->len == stack_pathp + 1) { for (i = 0; i < fn->len; i++) if (strcmp(fn->fname[i], stack_path[i])) break; if (i != fn->len || strcmp(fn->fname[i], dirp->d_name)) continue; freemem(top, 1); if (top == filenames) top = NULL; else top--; nfiles--; goto duplicate; } } } top->len += 1; top->fname[top->len] = calloc(1, strlen(&dirp->d_name[0])+1); if (top->fname[top->len] == 0) { printf("out of memory\n"); error++; return; } (void) strcpy(top->fname[top->len], &dirp->d_name[0]); } } } duplicate: addr += dirp->d_reclen; cptr += dirp->d_reclen; cur_bytes += dirp->d_reclen; } if (top < filenames) return; if ((doing_cd && level == input_pathp) || (!recursive && !doing_find && level > input_pathp)) return; bos = top; /* * Check newly added entries to determine if further expansion * is required. */ for (fn = tos; fn <= bos; fn++) { /* * Avoid '.' and '..' if beyond input. */ if ((recursive || doing_find) && (level > input_pathp) && (strcmp(fn->fname[fn->len], ".") == 0 || strcmp(fn->fname[fn->len], "..") == 0)) continue; restore_inode(fn->ino); if ((mode = icheck(cur_ino)) == 0) return; if ((mode & IFMT) == IFDIR || level < input_pathp) { /* * Set up current depth, remove current entry and * continue recursion. */ for (i = 0; i <= fn->len; i++) (void) strcpy(stack_path[i], fn->fname[i]); stack_pathp = fn->len; if (!doing_find && (!recursive || (recursive && level <= input_pathp))) { /* * Remove current entry by moving others up. */ freemem(fn, 1); fnn = fn; for (fnnn = fnn, fnn++; fnn <= top; fnnn = fnn, fnn++) { fnnn->ino = fnn->ino; fnnn->len = fnn->len; if (fnnn->len + 1 < FIRST_DEPTH) { fnnn->fname = (char **)calloc(FIRST_DEPTH, sizeof (char **)); fnnn->flag = 0; } else if (fnnn->len < SECOND_DEPTH) { fnnn->fname = (char **)calloc(SECOND_DEPTH, sizeof (char **)); fnnn->flag = 1; } else { printf("maximum depth exceeded, "); printf("try to cd lower\n"); error++; return; } for (i = 0; i <= fnn->len; i++) fnnn->fname[i] = fnn->fname[i]; } if (fn == tos) fn--; top--; bos--; nfiles--; } follow_path(level + 1, cur_inum); if (error) return; } } } /* * getname - break up the pathname entered by the user into components. */ static void getname() { int i; char c; if ((c = getachar()) == '\n') { ungetachar(c); return; } ungetachar(c); input_pathp++; clear: for (i = 0; i < MAXNAMLEN; i++) input_path[input_pathp][i] = '\0'; for (;;) { c = getachar(); if (c == '\\') { if ((int)strlen(input_path[input_pathp]) + 1 >= MAXNAMLEN) { printf("maximum name length exceeded, "); printf("truncating\n"); return; } input_path[input_pathp][strlen(input_path[input_pathp])] = c; input_path[input_pathp][strlen(input_path[input_pathp])] = getachar(); continue; } if (c == ' ' || c == '\n') { ungetachar(c); return; } if (!doing_find && c == '/') { if (++input_pathp >= MAXPATHLEN) { printf("maximum path length exceeded, "); printf("truncating\n"); input_pathp--; return; } goto clear; } if ((int)strlen(input_path[input_pathp]) >= MAXNAMLEN) { printf("maximum name length exceeded, truncating\n"); return; } input_path[input_pathp][strlen(input_path[input_pathp])] = c; } } /* * compare - check if a filename matches the pattern entered by the user. * Handles '*', '?', and '[]'. */ static int compare(char *s1, char *s2, short at_start) { char c, *s; s = s2; while ((c = *s1) != '\0') { if (c == '*') { if (at_start && s == s2 && !letter(*s2) && !digit(*s2)) return (0); if (*++s1 == 0) return (1); while (*s2) { if (compare(s1, s2, 0)) return (1); if (error) return (0); s2++; } } if (*s2 == 0) return (0); if (c == '\\') { s1++; goto compare_chars; } if (c == '?') { if (at_start && s == s2 && !letter(*s2) && !digit(*s2)) return (0); s1++; s2++; continue; } if (c == '[') { s1++; if (*s2 >= *s1++) { if (*s1++ != '-') { printf("missing '-'\n"); error++; return (0); } if (*s2 <= *s1++) { if (*s1++ != ']') { printf("missing ']'"); error++; return (0); } s2++; continue; } } } compare_chars: if (*s1++ == *s2++) continue; else return (0); } if (*s1 == *s2) return (1); return (0); } /* * freemem - free the memory allocated to the filenames structure. */ static void freemem(struct filenames *p, int numb) { int i, j; if (numb == 0) return; for (i = 0; i < numb; i++, p++) { for (j = 0; j <= p->len; j++) free(p->fname[j]); free((char *)p->fname); } } /* * print_path - print the pathname held in p. */ static void print_path(char *p[], int pntr) { int i; printf("/"); if (pntr >= 0) { for (i = 0; i < pntr; i++) printf("%s/", p[i]); printf("%s", p[pntr]); } } /* * fill - fill a section with a value or string. * addr,count:fill=[value, "string"]. */ static void fill() { char *cptr; int i; short eof_flag, end = 0, eof = 0; long temp, tcount; u_offset_t taddr; if (wrtflag == O_RDONLY) { printf("not opened for write '-w'\n"); error++; return; } temp = expr(); if (error) return; if ((cptr = getblk(addr)) == 0) return; if (type == NUMB) eof_flag = 0; else eof_flag = 1; taddr = addr; switch (objsz) { case LONG: addr &= ~(LONG - 1); break; case SHORT: addr &= ~(SHORT - 1); temp &= 0177777L; break; case CHAR: temp &= 0377; } cur_bytes -= taddr - addr; cptr += blkoff(fs, addr); tcount = check_addr(eof_flag, &end, &eof, 0); for (i = 0; i < tcount; i++) { switch (objsz) { case LONG: /*LINTED*/ *(long *)cptr = temp; break; case SHORT: /*LINTED*/ *(short *)cptr = temp; break; case CHAR: *cptr = temp; } cptr += objsz; } addr += (tcount - 1) * objsz; cur_bytes += (tcount - 1) * objsz; put((u_offset_t)temp, objsz); if (eof) { printf("end of file\n"); error++; } else if (end) { printf("end of block\n"); error++; } } /* * get - read a byte, short or long from the file system. * The entire block containing the desired item is read * and the appropriate data is extracted and returned. */ static offset_t get(short lngth) { char *bptr; u_offset_t temp = addr; objsz = lngth; if (objsz == INODE || objsz == SHORT) temp &= ~(SHORT - 1); else if (objsz == DIRECTORY || objsz == LONG || objsz == SHADOW_DATA) temp &= ~(LONG - 1); if ((bptr = getblk(temp)) == 0) return (-1); bptr += blkoff(fs, temp); switch (objsz) { case CHAR: return ((offset_t)*bptr); case SHORT: case INODE: /*LINTED*/ return ((offset_t)(*(short *)bptr)); case LONG: case DIRECTORY: case SHADOW_DATA: /*LINTED*/ return ((offset_t)(*(long *)bptr)); #ifndef _LP64 /* Hammerhead: On LP64, sizeof(long) == sizeof(u_offset_t), so this * case is identical to case LONG above and causes duplicate case error. */ case U_OFFSET_T: /*LINTED*/ return (*(offset_t *)bptr); #endif } return (0); } /* * cgrp_check - make sure that we don't bump the cylinder group * beyond the total number of cylinder groups or before the start. */ static int cgrp_check(long cgrp) { if (cgrp < 0) { if (objsz == CGRP) printf("beginning of cylinder groups\n"); else printf("beginning of super blocks\n"); error++; return (0); } if (cgrp >= fs->fs_ncg) { if (objsz == CGRP) printf("end of cylinder groups\n"); else printf("end of super blocks\n"); error++; return (0); } if (objsz == CGRP) return (cgtod(fs, cgrp) << FRGSHIFT); else return (cgsblock(fs, cgrp) << FRGSHIFT); } /* * icheck - make sure we can read the block containing the inode * and determine the filesize (0 if inode not allocated). Return * 0 if error otherwise return the mode. */ int icheck(u_offset_t address) { char *cptr; struct dinode *ip; if ((cptr = getblk(address)) == 0) return (0); cptr += blkoff(fs, address); /*LINTED*/ ip = (struct dinode *)cptr; if ((ip->di_mode & IFMT) == 0) { if (!override) { printf("inode not allocated\n"); error++; return (0); } blocksize = filesize = 0; } else { trapped++; filesize = ip->di_size; blocksize = filesize * 2; } return (ip->di_mode); } /* * getdirslot - get the address of the directory slot desired. */ static u_offset_t getdirslot(long slot) { char *cptr; struct direct *dirp; short i; char *string = &scratch[0]; short bod = 0, mode, temp; if (slot < 0) { slot = 0; bod++; } if (type != DIRECTORY) { if (type == BLOCK) string = "block"; else string = "fragment"; addr = bod_addr; if ((cptr = getblk(addr)) == 0) return (0); cptr += blkoff(fs, addr); cur_bytes = 0; /*LINTED*/ dirp = (struct direct *)cptr; for (dirslot = 0; dirslot < slot; dirslot++) { /*LINTED*/ dirp = (struct direct *)cptr; if (blocksize > filesize) { if (cur_bytes + (long)dirp->d_reclen >= filesize) { printf("end of file\n"); erraddr = addr; errcur_bytes = cur_bytes; stringsize = STRINGSIZE(dirp); error++; return (addr); } } else { if (cur_bytes + (long)dirp->d_reclen >= blocksize) { printf("end of %s\n", string); erraddr = addr; errcur_bytes = cur_bytes; stringsize = STRINGSIZE(dirp); error++; return (addr); } } cptr += dirp->d_reclen; addr += dirp->d_reclen; cur_bytes += dirp->d_reclen; } if (bod) { if (blocksize > filesize) printf("beginning of file\n"); else printf("beginning of %s\n", string); erraddr = addr; errcur_bytes = cur_bytes; error++; } stringsize = STRINGSIZE(dirp); return (addr); } else { addr = cur_ino; if ((mode = icheck(addr)) == 0) return (0); if (!override && (mode & IFDIR) == 0) { printf("inode is not a directory\n"); error++; return (0); } temp = slot; i = cur_bytes = 0; for (;;) { if (i == 0 || bcomp(addr)) { error = 0; if ((addr = (bmap((long)i++) << FRGSHIFT)) == 0) break; if ((cptr = getblk(addr)) == 0) break; cptr += blkoff(fs, addr); } /*LINTED*/ dirp = (struct direct *)cptr; value = dirp->d_ino; if (!temp--) break; if (cur_bytes + (long)dirp->d_reclen >= filesize) { printf("end of file\n"); dirslot = slot - temp - 1; objsz = DIRECTORY; erraddr = addr; errcur_bytes = cur_bytes; stringsize = STRINGSIZE(dirp); error++; return (addr); } addr += dirp->d_reclen; cptr += dirp->d_reclen; cur_bytes += dirp->d_reclen; } dirslot = slot; objsz = DIRECTORY; if (bod) { printf("beginning of file\n"); erraddr = addr; errcur_bytes = cur_bytes; error++; } stringsize = STRINGSIZE(dirp); return (addr); } } /* * getshadowslot - get the address of the shadow data desired */ static int getshadowslot(long shadow) { struct ufs_fsd fsd; short bod = 0, mode; long taddr, tcurbytes; if (shadow < 0) { shadow = 0; bod++; } if (type != SHADOW_DATA) { if (shadow < cur_shad) { printf("can't scan shadow data in reverse\n"); error++; return (0); } } else { addr = cur_ino; if ((mode = icheck(addr)) == 0) return (0); if (!override && (mode & IFMT) != IFSHAD) { printf("inode is not a shadow\n"); error++; return (0); } cur_bytes = 0; cur_shad = 0; syncshadowscan(1); /* force synchronization */ } for (; cur_shad < shadow; cur_shad++) { taddr = addr; tcurbytes = cur_bytes; getshadowdata((long *)&fsd, LONG + LONG); addr = taddr; cur_bytes = tcurbytes; if (cur_bytes + (long)fsd.fsd_size > filesize) { syncshadowscan(0); printf("end of file\n"); erraddr = addr; errcur_bytes = cur_bytes; error++; return (addr); } addr += fsd.fsd_size; cur_bytes += fsd.fsd_size; syncshadowscan(0); } if (type == SHADOW_DATA) objsz = SHADOW_DATA; if (bod) { printf("beginning of file\n"); erraddr = addr; errcur_bytes = cur_bytes; error++; } return (addr); } static void getshadowdata(long *buf, int len) { long tfsd; len /= LONG; for (tfsd = 0; tfsd < len; tfsd++) { buf[tfsd] = get(SHADOW_DATA); addr += LONG; cur_bytes += LONG; syncshadowscan(0); } } static void syncshadowscan(int force) { long curblkoff; if (type == SHADOW_DATA && (force || lblkno(fs, addr) != (bhdr.fwd)->blkno)) { curblkoff = blkoff(fs, cur_bytes); addr = bmap(lblkno(fs, cur_bytes)) << FRGSHIFT; addr += curblkoff; cur_bytes += curblkoff; (void) getblk(addr); objsz = SHADOW_DATA; } } /* * putf - print a byte as an ascii character if possible. * The exceptions are tabs, newlines, backslashes * and nulls which are printed as the standard C * language escapes. Characters which are not * recognized are printed as \?. */ static void putf(char c) { if (c <= 037 || c >= 0177 || c == '\\') { printf("\\"); switch (c) { case '\\': printf("\\"); break; case '\t': printf("t"); break; case '\n': printf("n"); break; case '\0': printf("0"); break; default: printf("?"); } } else { printf("%c", c); printf(" "); } } /* * put - write an item into the buffer for the current address * block. The value is checked to make sure that it will * fit in the size given without truncation. If successful, * the entire block is written back to the file system. */ static void put(u_offset_t item, short lngth) { char *bptr, *sbptr; long s_err, nbytes; long olditem; if (wrtflag == O_RDONLY) { printf("not opened for write '-w'\n"); error++; return; } objsz = lngth; if ((sbptr = getblk(addr)) == 0) return; bptr = sbptr + blkoff(fs, addr); switch (objsz) { case LONG: case DIRECTORY: /*LINTED*/ olditem = *(long *)bptr; /*LINTED*/ *(long *)bptr = item; break; case SHORT: case INODE: /*LINTED*/ olditem = (long)*(short *)bptr; item &= 0177777L; /*LINTED*/ *(short *)bptr = item; break; case CHAR: olditem = (long)*bptr; item &= 0377; *bptr = lobyte(loword(item)); break; default: error++; return; } if ((s_err = llseek(fd, (offset_t)(addr & fs->fs_bmask), 0)) == -1) { error++; printf("seek error : %" PRIx64 "\n", addr); return; } if ((nbytes = write(fd, sbptr, BLKSIZE)) != BLKSIZE) { error++; printf("write error : addr = %" PRIx64 "\n", addr); printf(" : s_err = %lx\n", s_err); printf(" : nbytes = %lx\n", nbytes); return; } if (!acting_on_inode && objsz != INODE && objsz != DIRECTORY) { index(base); print(olditem, 8, -8, 0); printf("\t=\t"); print(item, 8, -8, 0); printf("\n"); } else { if (objsz == DIRECTORY) { addr = cur_dir; fprnt('?', 'd'); } else { addr = cur_ino; objsz = INODE; fprnt('?', 'i'); } } } /* * getblk - check if the desired block is in the file system. * Search the incore buffers to see if the block is already * available. If successful, unlink the buffer control block * from its position in the buffer list and re-insert it at * the head of the list. If failure, use the last buffer * in the list for the desired block. Again, this control * block is placed at the head of the list. This process * will leave commonly requested blocks in the in-core buffers. * Finally, a pointer to the buffer is returned. */ static char * getblk(u_offset_t address) { struct lbuf *bp; long s_err, nbytes; unsigned long block; read_requests++; block = lblkno(fs, address); if (block >= fragstoblks(fs, fs->fs_size)) { printf("cannot read block %lu\n", block); error++; return (0); } for (bp = bhdr.fwd; bp != &bhdr; bp = bp->fwd) if (bp->valid && bp->blkno == block) goto xit; actual_disk_reads++; bp = bhdr.back; bp->blkno = block; bp->valid = 0; if ((s_err = llseek(fd, (offset_t)(address & fs->fs_bmask), 0)) == -1) { error++; printf("seek error : %" PRIx64 "\n", address); return (0); } if ((nbytes = read(fd, bp->blkaddr, BLKSIZE)) != BLKSIZE) { error++; printf("read error : addr = %" PRIx64 "\n", address); printf(" : s_err = %lx\n", s_err); printf(" : nbytes = %lx\n", nbytes); return (0); } bp->valid++; xit: bp->back->fwd = bp->fwd; bp->fwd->back = bp->back; insert(bp); return (bp->blkaddr); } /* * insert - place the designated buffer control block * at the head of the linked list of buffers. */ static void insert(struct lbuf *bp) { bp->back = &bhdr; bp->fwd = bhdr.fwd; bhdr.fwd->back = bp; bhdr.fwd = bp; } /* * err - called on interrupts. Set the current address * back to the last address stored in erraddr. Reset all * appropriate flags. A reset call is made to return * to the main loop; */ #ifdef sun /*ARGSUSED*/ static void err(int sig) #else err() #endif /* sun */ { freemem(filenames, nfiles); nfiles = 0; (void) signal(2, err); addr = erraddr; cur_ino = errino; cur_inum = errinum; cur_bytes = errcur_bytes; error = 0; c_count = 0; printf("\n?\n"); (void) fseek(stdin, 0L, 2); longjmp(env, 0); } /* * devcheck - check that the given mode represents a * special device. The IFCHR bit is on for both * character and block devices. */ static int devcheck(short md) { if (override) return (0); switch (md & IFMT) { case IFCHR: case IFBLK: return (0); } printf("not character or block device\n"); error++; return (1); } /* * nullblk - return error if address is zero. This is done * to prevent block 0 from being used as an indirect block * for a large file or as a data block for a small file. */ static int nullblk(long bn) { if (bn != 0) return (0); printf("non existent block\n"); error++; return (1); } /* * puta - put ascii characters into a buffer. The string * terminates with a quote or newline. The leading quote, * which is optional for directory names, was stripped off * by the assignment case in the main loop. */ static void puta() { char *cptr, c; int i; char *sbptr; short terror = 0; long maxchars, s_err, nbytes, temp; u_offset_t taddr = addr; long tcount = 0, item, olditem = 0; if (wrtflag == O_RDONLY) { printf("not opened for write '-w'\n"); error++; return; } if ((sbptr = getblk(addr)) == 0) return; cptr = sbptr + blkoff(fs, addr); if (objsz == DIRECTORY) { if (acting_on_directory) maxchars = stringsize - 1; else maxchars = LONG; } else if (objsz == INODE) maxchars = objsz - (addr - cur_ino); else maxchars = min(blocksize - cur_bytes, filesize - cur_bytes); while ((c = getachar()) != '"') { if (tcount >= maxchars) { printf("string too long\n"); if (objsz == DIRECTORY) addr = cur_dir; else if (acting_on_inode || objsz == INODE) addr = cur_ino; else addr = taddr; erraddr = addr; errcur_bytes = cur_bytes; terror++; break; } tcount++; if (c == '\n') { ungetachar(c); break; } temp = (long)*cptr; olditem <<= BITSPERCHAR; olditem += temp & 0xff; if (c == '\\') { switch (c = getachar()) { case 't': *cptr++ = '\t'; break; case 'n': *cptr++ = '\n'; break; case '0': *cptr++ = '\0'; break; default: *cptr++ = c; break; } } else *cptr++ = c; } if (objsz == DIRECTORY && acting_on_directory) for (i = tcount; i <= maxchars; i++) *cptr++ = '\0'; if ((s_err = llseek(fd, (offset_t)(addr & fs->fs_bmask), 0)) == -1) { error++; printf("seek error : %" PRIx64 "\n", addr); return; } if ((nbytes = write(fd, sbptr, BLKSIZE)) != BLKSIZE) { error++; printf("write error : addr = %" PRIx64 "\n", addr); printf(" : s_err = %lx\n", s_err); printf(" : nbytes = %lx\n", nbytes); return; } if (!acting_on_inode && objsz != INODE && objsz != DIRECTORY) { addr += tcount; cur_bytes += tcount; taddr = addr; if (objsz != CHAR) { addr &= ~(objsz - 1); cur_bytes -= taddr - addr; } if (addr == taddr) { addr -= objsz; taddr = addr; } tcount = LONG - (taddr - addr); index(base); if ((cptr = getblk(addr)) == 0) return; cptr += blkoff(fs, addr); switch (objsz) { case LONG: /*LINTED*/ item = *(long *)cptr; if (tcount < LONG) { olditem <<= tcount * BITSPERCHAR; temp = 1; for (i = 0; i < (tcount*BITSPERCHAR); i++) temp <<= 1; olditem += item & (temp - 1); } break; case SHORT: /*LINTED*/ item = (long)*(short *)cptr; if (tcount < SHORT) { olditem <<= tcount * BITSPERCHAR; temp = 1; for (i = 0; i < (tcount * BITSPERCHAR); i++) temp <<= 1; olditem += item & (temp - 1); } olditem &= 0177777L; break; case CHAR: item = (long)*cptr; olditem &= 0377; } print(olditem, 8, -8, 0); printf("\t=\t"); print(item, 8, -8, 0); printf("\n"); } else { if (objsz == DIRECTORY) { addr = cur_dir; fprnt('?', 'd'); } else { addr = cur_ino; objsz = INODE; fprnt('?', 'i'); } } if (terror) error++; } /* * fprnt - print data. 'count' elements are printed where '*' will * print an entire blocks worth or up to the eof, whichever * occurs first. An error will occur if crossing a block boundary * is attempted since consecutive blocks don't usually have * meaning. Current print types: * / b - print as bytes (base sensitive) * c - print as characters * o O - print as octal shorts (longs) * d D - print as decimal shorts (longs) * x X - print as hexadecimal shorts (longs) * ? c - print as cylinder groups * d - print as directories * i - print as inodes * s - print as super blocks * S - print as shadow data */ static void fprnt(char style, char po) { int i; struct fs *sb; struct cg *cg; struct direct *dirp; struct dinode *ip; int tbase; char c, *cptr, *p; long tinode, tcount, temp; u_offset_t taddr; short offset, mode, end = 0, eof = 0, eof_flag; unsigned short *sptr; unsigned long *lptr; offset_t curoff, curioff; laststyle = style; lastpo = po; should_print = 0; if (count != 1) { if (clear) { count = 1; star = 0; clear = 0; } else clear = 1; } tcount = count; offset = blkoff(fs, addr); if (style == '/') { if (type == NUMB) eof_flag = 0; else eof_flag = 1; switch (po) { case 'c': /* print as characters */ case 'b': /* or bytes */ if ((cptr = getblk(addr)) == 0) return; cptr += offset; objsz = CHAR; tcount = check_addr(eof_flag, &end, &eof, 0); if (tcount) { for (i = 0; tcount--; i++) { if (i % 16 == 0) { if (i) printf("\n"); index(base); } if (po == 'c') { putf(*cptr++); if ((i + 1) % 16) printf(" "); } else { if ((i + 1) % 16 == 0) print(*cptr++ & 0377L, 2, -2, 0); else print(*cptr++ & 0377L, 4, -2, 0); } addr += CHAR; cur_bytes += CHAR; } printf("\n"); } addr -= CHAR; erraddr = addr; cur_bytes -= CHAR; errcur_bytes = cur_bytes; if (eof) { printf("end of file\n"); error++; } else if (end) { if (type == BLOCK) printf("end of block\n"); else printf("end of fragment\n"); error++; } return; case 'o': /* print as octal shorts */ tbase = OCTAL; goto otx; case 'd': /* print as decimal shorts */ tbase = DECIMAL; goto otx; case 'x': /* print as hex shorts */ tbase = HEX; otx: if ((cptr = getblk(addr)) == 0) return; taddr = addr; addr &= ~(SHORT - 1); cur_bytes -= taddr - addr; cptr += blkoff(fs, addr); /*LINTED*/ sptr = (unsigned short *)cptr; objsz = SHORT; tcount = check_addr(eof_flag, &end, &eof, 0); if (tcount) { for (i = 0; tcount--; i++) { sptr = (unsigned short *)print_check( /*LINTED*/ (unsigned long *)sptr, &tcount, tbase, i); switch (po) { case 'o': printf("%06o ", *sptr++); break; case 'd': printf("%05d ", *sptr++); break; case 'x': printf("%04x ", *sptr++); } addr += SHORT; cur_bytes += SHORT; } printf("\n"); } addr -= SHORT; erraddr = addr; cur_bytes -= SHORT; errcur_bytes = cur_bytes; if (eof) { printf("end of file\n"); error++; } else if (end) { if (type == BLOCK) printf("end of block\n"); else printf("end of fragment\n"); error++; } return; case 'O': /* print as octal longs */ tbase = OCTAL; goto OTX; case 'D': /* print as decimal longs */ tbase = DECIMAL; goto OTX; case 'X': /* print as hex longs */ tbase = HEX; OTX: if ((cptr = getblk(addr)) == 0) return; taddr = addr; addr &= ~(LONG - 1); cur_bytes -= taddr - addr; cptr += blkoff(fs, addr); /*LINTED*/ lptr = (unsigned long *)cptr; objsz = LONG; tcount = check_addr(eof_flag, &end, &eof, 0); if (tcount) { for (i = 0; tcount--; i++) { lptr = print_check(lptr, &tcount, tbase, i); switch (po) { case 'O': printf("%011lo ", *lptr++); break; case 'D': printf("%010lu ", *lptr++); break; case 'X': printf("%08lx ", *lptr++); } addr += LONG; cur_bytes += LONG; } printf("\n"); } addr -= LONG; erraddr = addr; cur_bytes -= LONG; errcur_bytes = cur_bytes; if (eof) { printf("end of file\n"); error++; } else if (end) { if (type == BLOCK) printf("end of block\n"); else printf("end of fragment\n"); error++; } return; default: error++; printf("no such print option\n"); return; } } else switch (po) { case 'c': /* print as cylinder group */ if (type != NUMB) if (cur_cgrp + count > fs->fs_ncg) { tcount = fs->fs_ncg - cur_cgrp; if (!star) end++; } addr &= ~(LONG - 1); for (/* void */; tcount--; /* void */) { erraddr = addr; errcur_bytes = cur_bytes; if (type != NUMB) { addr = cgtod(fs, cur_cgrp) << FRGSHIFT; cur_cgrp++; } if ((cptr = getblk(addr)) == 0) { if (cur_cgrp) cur_cgrp--; return; } cptr += blkoff(fs, addr); /*LINTED*/ cg = (struct cg *)cptr; if (type == NUMB) { cur_cgrp = cg->cg_cgx + 1; type = objsz = CGRP; if (cur_cgrp + count - 1 > fs->fs_ncg) { tcount = fs->fs_ncg - cur_cgrp; if (!star) end++; } } if (! override && !cg_chkmagic(cg)) { printf("invalid cylinder group "); printf("magic word\n"); if (cur_cgrp) cur_cgrp--; error++; return; } printcg(cg); if (tcount) printf("\n"); } cur_cgrp--; if (end) { printf("end of cylinder groups\n"); error++; } return; case 'd': /* print as directories */ if ((cptr = getblk(addr)) == 0) return; if (type == NUMB) { if (fragoff(fs, addr)) { printf("address must be at the "); printf("beginning of a fragment\n"); error++; return; } bod_addr = addr; type = FRAGMENT; dirslot = 0; cur_bytes = 0; blocksize = FRGSIZE; filesize = FRGSIZE * 2; } cptr += offset; objsz = DIRECTORY; while (tcount-- && cur_bytes < filesize && cur_bytes < blocksize && !bcomp(addr)) { /*LINTED*/ dirp = (struct direct *)cptr; tinode = dirp->d_ino; printf("i#: "); if (tinode == 0) printf("free\t"); else print(tinode, 12, -8, 0); printf("%s\n", &dirp->d_name[0]); erraddr = addr; errcur_bytes = cur_bytes; addr += dirp->d_reclen; cptr += dirp->d_reclen; cur_bytes += dirp->d_reclen; dirslot++; stringsize = STRINGSIZE(dirp); } addr = erraddr; cur_dir = addr; cur_bytes = errcur_bytes; dirslot--; if (tcount >= 0 && !star) { switch (type) { case FRAGMENT: printf("end of fragment\n"); break; case BLOCK: printf("end of block\n"); break; default: printf("end of directory\n"); } error++; } else error = 0; return; case 'i': /* print as inodes */ /*LINTED*/ if ((ip = (struct dinode *)getblk(addr)) == 0) return; for (i = 1; i < fs->fs_ncg; i++) if (addr < (cgimin(fs, i) << FRGSHIFT)) break; i--; offset /= INODE; temp = (addr - (cgimin(fs, i) << FRGSHIFT)) >> FRGSHIFT; temp = (i * fs->fs_ipg) + fragstoblks(fs, temp) * INOPB(fs) + offset; if (count + offset > INOPB(fs)) { tcount = INOPB(fs) - offset; if (!star) end++; } objsz = INODE; ip += offset; for (i = 0; tcount--; ip++, temp++) { if ((mode = icheck(addr)) == 0) if (!override) continue; p = " ugtrwxrwxrwx"; switch (mode & IFMT) { case IFDIR: c = 'd'; break; case IFCHR: c = 'c'; break; case IFBLK: c = 'b'; break; case IFREG: c = '-'; break; case IFLNK: c = 'l'; break; case IFSOCK: c = 's'; break; case IFSHAD: c = 'S'; break; case IFATTRDIR: c = 'A'; break; default: c = '?'; if (!override) goto empty; } printf("i#: "); print(temp, 12, -8, 0); printf(" md: "); printf("%c", c); for (mode = mode << 4; *++p; mode = mode << 1) { if (mode & IFREG) printf("%c", *p); else printf("-"); } printf(" uid: "); print(ip->di_uid, 8, -4, 0); printf(" gid: "); print(ip->di_gid, 8, -4, 0); printf("\n"); printf("ln: "); print((long)ip->di_nlink, 8, -4, 0); printf(" bs: "); print(ip->di_blocks, 12, -8, 0); printf("c_flags : "); print(ip->di_cflags, 12, -8, 0); printf(" sz : "); #ifdef _LARGEFILE64_SOURCE printll(ip->di_size, 20, -16, 0); #else /* !_LARGEFILE64_SOURCE */ print(ip->di_size, 12, -8, 0); #endif /* _LARGEFILE64_SOURCE */ if (ip->di_shadow) { printf(" si: "); print(ip->di_shadow, 12, -8, 0); } printf("\n"); if (ip->di_oeftflag) { printf("ai: "); print(ip->di_oeftflag, 12, -8, 0); printf("\n"); } printf("\n"); switch (ip->di_mode & IFMT) { case IFBLK: case IFCHR: printf("maj: "); print(major(ip->di_ordev), 4, -2, 0); printf(" min: "); print(minor(ip->di_ordev), 4, -2, 0); printf("\n"); break; default: /* * only display blocks below the * current file size */ curoff = 0LL; for (i = 0; i < NDADDR; ) { if (ip->di_size <= curoff) break; printf("db#%x: ", i); print(ip->di_db[i], 11, -8, 0); if (++i % 4 == 0) printf("\n"); else printf(" "); curoff += fs->fs_bsize; } if (i % 4) printf("\n"); /* * curioff keeps track of the number * of bytes covered by each indirect * pointer in the inode, and is added * to curoff each time to get the * actual offset into the file. */ curioff = fs->fs_bsize * (fs->fs_bsize / sizeof (daddr_t)); for (i = 0; i < NIADDR; i++) { if (ip->di_size <= curoff) break; printf("ib#%x: ", i); print(ip->di_ib[i], 11, -8, 0); printf(" "); curoff += curioff; curioff *= (fs->fs_bsize / sizeof (daddr_t)); } if (i) printf("\n"); break; } if (count == 1) { time_t t; t = ip->di_atime; printf("\taccessed: %s", ctime(&t)); t = ip->di_mtime; printf("\tmodified: %s", ctime(&t)); t = ip->di_ctime; printf("\tcreated : %s", ctime(&t)); } if (tcount) printf("\n"); empty: if (c == '?' && !override) { printf("i#: "); print(temp, 12, -8, 0); printf(" is unallocated\n"); if (count != 1) printf("\n"); } cur_ino = erraddr = addr; errcur_bytes = cur_bytes; cur_inum++; addr = addr + INODE; } addr = erraddr; cur_bytes = errcur_bytes; cur_inum--; if (end) { printf("end of block\n"); error++; } return; case 's': /* print as super block */ if (cur_cgrp == -1) { addr = SBLOCK * DEV_BSIZE; type = NUMB; } addr &= ~(LONG - 1); if (type != NUMB) if (cur_cgrp + count > fs->fs_ncg) { tcount = fs->fs_ncg - cur_cgrp; if (!star) end++; } for (/* void */; tcount--; /* void */) { erraddr = addr; cur_bytes = errcur_bytes; if (type != NUMB) { addr = cgsblock(fs, cur_cgrp) << FRGSHIFT; cur_cgrp++; } if ((cptr = getblk(addr)) == 0) { if (cur_cgrp) cur_cgrp--; return; } cptr += blkoff(fs, addr); /*LINTED*/ sb = (struct fs *)cptr; if (type == NUMB) { for (i = 0; i < fs->fs_ncg; i++) if (addr == cgsblock(fs, i) << FRGSHIFT) break; if (i == fs->fs_ncg) cur_cgrp = 0; else cur_cgrp = i + 1; type = objsz = SB; if (cur_cgrp + count - 1 > fs->fs_ncg) { tcount = fs->fs_ncg - cur_cgrp; if (!star) end++; } } if ((sb->fs_magic != FS_MAGIC) && (sb->fs_magic != MTB_UFS_MAGIC)) { cur_cgrp = 0; if (!override) { printf("invalid super block "); printf("magic word\n"); cur_cgrp--; error++; return; } } if (sb->fs_magic == FS_MAGIC && (sb->fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && sb->fs_version != UFS_VERSION_MIN)) { cur_cgrp = 0; if (!override) { printf("invalid super block "); printf("version number\n"); cur_cgrp--; error++; return; } } if (sb->fs_magic == MTB_UFS_MAGIC && (sb->fs_version > MTB_UFS_VERSION_1 || sb->fs_version < MTB_UFS_VERSION_MIN)) { cur_cgrp = 0; if (!override) { printf("invalid super block "); printf("version number\n"); cur_cgrp--; error++; return; } } if (cur_cgrp == 0) printf("\tsuper block:\n"); else { printf("\tsuper block in cylinder "); printf("group "); print(cur_cgrp - 1, 0, 0, 0); printf(":\n"); } printsb(sb); if (tcount) printf("\n"); } cur_cgrp--; if (end) { printf("end of super blocks\n"); error++; } return; case 'S': /* print as shadow data */ if (type == NUMB) { type = FRAGMENT; cur_shad = 0; cur_bytes = fragoff(fs, addr); bod_addr = addr - cur_bytes; /* no more than two fragments */ filesize = fragroundup(fs, bod_addr + FRGSIZE + 1); } objsz = SHADOW_DATA; while (tcount-- && (cur_bytes + SHADOW_DATA) <= filesize && (type != SHADOW_DATA || (cur_bytes + SHADOW_DATA)) <= blocksize) { /*LINTED*/ struct ufs_fsd fsd; long tcur_bytes; taddr = addr; tcur_bytes = cur_bytes; index(base); getshadowdata((long *)&fsd, LONG + LONG); printf(" type: "); print((long)fsd.fsd_type, 8, -8, 0); printf(" size: "); print((long)fsd.fsd_size, 8, -8, 0); tbase = fsd.fsd_size - LONG - LONG; if (tbase > 256) tbase = 256; for (i = 0; i < tbase; i++) { if (i % LONG == 0) { if (i % 16 == 0) { printf("\n"); index(base); } else printf(" "); getshadowdata(&temp, LONG); p = (char *)&temp; } else printf(" "); printf("%02x", (int)(*p++ & 0377L)); } printf("\n"); addr = taddr; cur_bytes = tcur_bytes; erraddr = addr; errcur_bytes = cur_bytes; addr += FSD_RECSZ((&fsd), fsd.fsd_size); cur_bytes += FSD_RECSZ((&fsd), fsd.fsd_size); cur_shad++; syncshadowscan(0); } addr = erraddr; cur_bytes = errcur_bytes; cur_shad--; if (tcount >= 0 && !star) { switch (type) { case FRAGMENT: printf("end of fragment\n"); break; default: printf("end of shadow data\n"); } error++; } else error = 0; return; default: error++; printf("no such print option\n"); return; } } /* * valid_addr - call check_addr to validate the current address. */ static int valid_addr() { short end = 0, eof = 0; long tcount = count; if (!trapped) return (1); if (cur_bytes < 0) { cur_bytes = 0; if (blocksize > filesize) { printf("beginning of file\n"); } else { if (type == BLOCK) printf("beginning of block\n"); else printf("beginning of fragment\n"); } error++; return (0); } count = 1; (void) check_addr(1, &end, &eof, (filesize < blocksize)); count = tcount; if (eof) { printf("end of file\n"); error++; return (0); } if (end == 2) { if (erraddr > addr) { if (type == BLOCK) printf("beginning of block\n"); else printf("beginning of fragment\n"); error++; return (0); } } if (end) { if (type == BLOCK) printf("end of block\n"); else printf("end of fragment\n"); error++; return (0); } return (1); } /* * check_addr - check if the address crosses the end of block or * end of file. Return the proper count. */ static int check_addr(short eof_flag, short *end, short *eof, short keep_on) { long temp, tcount = count, tcur_bytes = cur_bytes; u_offset_t taddr = addr; if (bcomp(addr + count * objsz - 1) || (keep_on && taddr < (bmap(cur_block) << FRGSHIFT))) { error = 0; addr = taddr; cur_bytes = tcur_bytes; if (keep_on) { if (addr < erraddr) { if (cur_bytes < 0) { (*end) = 2; return (0); /* Value ignored */ } temp = cur_block - lblkno(fs, cur_bytes); cur_block -= temp; if ((addr = bmap(cur_block) << FRGSHIFT) == 0) { cur_block += temp; return (0); /* Value ignored */ } temp = tcur_bytes - cur_bytes; addr += temp; cur_bytes += temp; return (0); /* Value ignored */ } else { if (cur_bytes >= filesize) { (*eof)++; return (0); /* Value ignored */ } temp = lblkno(fs, cur_bytes) - cur_block; cur_block += temp; if ((addr = bmap(cur_block) << FRGSHIFT) == 0) { cur_block -= temp; return (0); /* Value ignored */ } temp = tcur_bytes - cur_bytes; addr += temp; cur_bytes += temp; return (0); /* Value ignored */ } } tcount = (blkroundup(fs, addr+1)-addr) / objsz; if (!star) (*end) = 2; } addr = taddr; cur_bytes = tcur_bytes; if (eof_flag) { if (blocksize > filesize) { if (cur_bytes >= filesize) { tcount = 0; (*eof)++; } else if (tcount > (filesize - cur_bytes) / objsz) { tcount = (filesize - cur_bytes) / objsz; if (!star || tcount == 0) (*eof)++; } } else { if (cur_bytes >= blocksize) { tcount = 0; (*end)++; } else if (tcount > (blocksize - cur_bytes) / objsz) { tcount = (blocksize - cur_bytes) / objsz; if (!star || tcount == 0) (*end)++; } } } return (tcount); } /* * print_check - check if the index needs to be printed and delete * rows of zeros from the output. */ unsigned long * print_check(unsigned long *lptr, long *tcount, short tbase, int i) { int j, k, temp = BYTESPERLINE / objsz; short first_time = 0; unsigned long *tlptr; unsigned short *tsptr, *sptr; sptr = (unsigned short *)lptr; if (i == 0) first_time = 1; if (i % temp == 0) { if (*tcount >= temp - 1) { if (objsz == SHORT) tsptr = sptr; else tlptr = lptr; k = *tcount - 1; for (j = i; k--; j++) if (objsz == SHORT) { if (*tsptr++ != 0) break; } else { if (*tlptr++ != 0) break; } if (j > (i + temp - 1)) { j = (j - i) / temp; while (j-- > 0) { if (objsz == SHORT) sptr += temp; else lptr += temp; *tcount -= temp; i += temp; addr += BYTESPERLINE; cur_bytes += BYTESPERLINE; } if (first_time) printf("*"); else printf("\n*"); } if (i) printf("\n"); index(tbase); } else { if (i) printf("\n"); index(tbase); } } if (objsz == SHORT) /*LINTED*/ return ((unsigned long *)sptr); else return (lptr); } /* * index - print a byte index for the printout in base b * with leading zeros. */ static void index(int b) { int tbase = base; base = b; print(addr, 8, 8, 1); printf(":\t"); base = tbase; } /* * print - print out the value to digits places with/without * leading zeros and right/left justified in the current base. */ static void #ifdef _LARGEFILE64_SOURCE printll(u_offset_t value, int fieldsz, int digits, int lead) #else /* !_LARGEFILE64_SOURCE */ print(long value, int fieldsz, int digits, int lead) #endif /* _LARGEFILE64_SOURCE */ { int i, left = 0; char mode = BASE[base - OCTAL]; char *string = &scratch[0]; if (digits < 0) { left = 1; digits *= -1; } if (base != HEX) if (digits) digits = digits + (digits - 1)/((base >> 1) - 1) + 1; else digits = 1; if (lead) { if (left) (void) sprintf(string, "%%%c%d%d.%d" #ifdef _LARGEFILE64_SOURCE "ll" #endif /* _LARGEFILE64_SOURCE */ "%c", '-', 0, digits, lead, mode); else (void) sprintf(string, "%%%d%d.%d" #ifdef _LARGEFILE64_SOURCE "ll" #endif /* _LARGEFILE64_SOURCE */ "%c", 0, digits, lead, mode); } else { if (left) (void) sprintf(string, "%%%c%d" #ifdef _LARGEFILE64_SOURCE "ll" #endif /* _LARGEFILE64_SOURCE */ "%c", '-', digits, mode); else (void) sprintf(string, "%%%d" #ifdef _LARGEFILE64_SOURCE "ll" #endif /* _LARGEFILE64_SOURCE */ "%c", digits, mode); } printf(string, value); for (i = 0; i < fieldsz - digits; i++) printf(" "); } /* * Print out the contents of a superblock. */ static void printsb(struct fs *fs) { int c, i, j, k, size; caddr_t sip; time_t t; t = fs->fs_time; #ifdef FS_42POSTBLFMT if (fs->fs_postblformat == FS_42POSTBLFMT) fs->fs_nrpos = 8; printf("magic\t%lx\tformat\t%s\ttime\t%s", fs->fs_magic, fs->fs_postblformat == FS_42POSTBLFMT ? "static" : "dynamic", ctime(&t)); #else printf("magic\t%x\ttime\t%s", fs->fs_magic, ctime(&t)); #endif printf("version\t%x\n", fs->fs_version); printf("nbfree\t%ld\tndir\t%ld\tnifree\t%ld\tnffree\t%ld\n", fs->fs_cstotal.cs_nbfree, fs->fs_cstotal.cs_ndir, fs->fs_cstotal.cs_nifree, fs->fs_cstotal.cs_nffree); printf("ncg\t%ld\tncyl\t%ld\tsize\t%ld\tblocks\t%ld\n", fs->fs_ncg, fs->fs_ncyl, fs->fs_size, fs->fs_dsize); printf("bsize\t%ld\tshift\t%ld\tmask\t0x%08lx\n", fs->fs_bsize, fs->fs_bshift, fs->fs_bmask); printf("fsize\t%ld\tshift\t%ld\tmask\t0x%08lx\n", fs->fs_fsize, fs->fs_fshift, fs->fs_fmask); printf("frag\t%ld\tshift\t%ld\tfsbtodb\t%ld\n", fs->fs_frag, fs->fs_fragshift, fs->fs_fsbtodb); printf("cpg\t%ld\tbpg\t%ld\tfpg\t%ld\tipg\t%ld\n", fs->fs_cpg, fs->fs_fpg / fs->fs_frag, fs->fs_fpg, fs->fs_ipg); printf("minfree\t%ld%%\toptim\t%s\tmaxcontig %ld\tmaxbpg\t%ld\n", fs->fs_minfree, fs->fs_optim == FS_OPTSPACE ? "space" : "time", fs->fs_maxcontig, fs->fs_maxbpg); #ifdef FS_42POSTBLFMT #ifdef sun printf("rotdelay %ldms\tfs_id[0] 0x%lx\tfs_id[1] 0x%lx\trps\t%ld\n", fs->fs_rotdelay, fs->fs_id[0], fs->fs_id[1], fs->fs_rps); #else printf("rotdelay %dms\theadswitch %dus\ttrackseek %dus\trps\t%d\n", fs->fs_rotdelay, fs->fs_headswitch, fs->fs_trkseek, fs->fs_rps); #endif /* sun */ printf("ntrak\t%ld\tnsect\t%ld\tnpsect\t%ld\tspc\t%ld\n", fs->fs_ntrak, fs->fs_nsect, fs->fs_npsect, fs->fs_spc); printf("trackskew %ld\n", fs->fs_trackskew); #else printf("rotdelay %ldms\trps\t%ld\n", fs->fs_rotdelay, fs->fs_rps); printf("ntrak\t%ld\tnsect\t%ld\tspc\t%ld\n", fs->fs_ntrak, fs->fs_nsect, fs->fs_spc); #endif printf("si %ld\n", fs->fs_si); printf("nindir\t%ld\tinopb\t%ld\tnspf\t%ld\n", fs->fs_nindir, fs->fs_inopb, fs->fs_nspf); printf("sblkno\t%ld\tcblkno\t%ld\tiblkno\t%ld\tdblkno\t%ld\n", fs->fs_sblkno, fs->fs_cblkno, fs->fs_iblkno, fs->fs_dblkno); printf("sbsize\t%ld\tcgsize\t%ld\tcgoffset %ld\tcgmask\t0x%08lx\n", fs->fs_sbsize, fs->fs_cgsize, fs->fs_cgoffset, fs->fs_cgmask); printf("csaddr\t%ld\tcssize\t%ld\tshift\t%ld\tmask\t0x%08lx\n", fs->fs_csaddr, fs->fs_cssize, fs->fs_csshift, fs->fs_csmask); printf("cgrotor\t%ld\tfmod\t%d\tronly\t%d\n", fs->fs_cgrotor, fs->fs_fmod, fs->fs_ronly); #ifdef FS_42POSTBLFMT if (fs->fs_cpc != 0) printf("blocks available in each of %ld rotational positions", fs->fs_nrpos); else printf("insufficient space to maintain rotational tables\n"); #endif for (c = 0; c < fs->fs_cpc; c++) { printf("\ncylinder number %d:", c); #ifdef FS_42POSTBLFMT for (i = 0; i < fs->fs_nrpos; i++) { /*LINTED*/ if (fs_postbl(fs, c)[i] == -1) continue; printf("\n position %d:\t", i); /*LINTED*/ for (j = fs_postbl(fs, c)[i], k = 1; /* void */; j += fs_rotbl(fs)[j], k++) { printf("%5d", j); if (k % 12 == 0) printf("\n\t\t"); if (fs_rotbl(fs)[j] == 0) break; } } #else for (i = 0; i < NRPOS; i++) { if (fs->fs_postbl[c][i] == -1) continue; printf("\n position %d:\t", i); for (j = fs->fs_postbl[c][i], k = 1; /* void */; j += fs->fs_rotbl[j], k++) { printf("%5d", j); if (k % 12 == 0) printf("\n\t\t"); if (fs->fs_rotbl[j] == 0) break; } } #endif } printf("\ncs[].cs_(nbfree, ndir, nifree, nffree):"); sip = calloc(1, fs->fs_cssize); fs->fs_u.fs_csp = (struct csum *)sip; for (i = 0, j = 0; i < fs->fs_cssize; i += fs->fs_bsize, j++) { size = fs->fs_cssize - i < fs->fs_bsize ? fs->fs_cssize - i : fs->fs_bsize; (void) llseek(fd, (offset_t)fsbtodb(fs, (fs->fs_csaddr + j * fs->fs_frag)) * fs->fs_fsize / fsbtodb(fs, 1), 0); if (read(fd, sip, size) != size) { free(fs->fs_u.fs_csp); return; } sip += size; } for (i = 0; i < fs->fs_ncg; i++) { struct csum *cs = &fs->fs_cs(fs, i); if (i % 4 == 0) printf("\n "); printf("%d:(%ld,%ld,%ld,%ld) ", i, cs->cs_nbfree, cs->cs_ndir, cs->cs_nifree, cs->cs_nffree); } free(fs->fs_u.fs_csp); printf("\n"); if (fs->fs_ncyl % fs->fs_cpg) { printf("cylinders in last group %d\n", i = fs->fs_ncyl % fs->fs_cpg); printf("blocks in last group %ld\n", i * fs->fs_spc / NSPB(fs)); } } /* * Print out the contents of a cylinder group. */ static void printcg(struct cg *cg) { int i, j; time_t t; printf("\ncg %ld:\n", cg->cg_cgx); t = cg->cg_time; #ifdef FS_42POSTBLFMT printf("magic\t%lx\ttell\t%llx\ttime\t%s", fs->fs_postblformat == FS_42POSTBLFMT ? ((struct ocg *)cg)->cg_magic : cg->cg_magic, fsbtodb(fs, cgtod(fs, cg->cg_cgx)) * fs->fs_fsize / fsbtodb(fs, 1), ctime(&t)); #else printf("magic\t%x\ttell\t%llx\ttime\t%s", cg->cg_magic, fsbtodb(fs, cgtod(fs, cg->cg_cgx)) * fs->fs_fsize / fsbtodb(fs, 1), ctime(&t)); #endif printf("cgx\t%ld\tncyl\t%d\tniblk\t%d\tndblk\t%ld\n", cg->cg_cgx, cg->cg_ncyl, cg->cg_niblk, cg->cg_ndblk); printf("nbfree\t%ld\tndir\t%ld\tnifree\t%ld\tnffree\t%ld\n", cg->cg_cs.cs_nbfree, cg->cg_cs.cs_ndir, cg->cg_cs.cs_nifree, cg->cg_cs.cs_nffree); printf("rotor\t%ld\tirotor\t%ld\tfrotor\t%ld\nfrsum", cg->cg_rotor, cg->cg_irotor, cg->cg_frotor); for (i = 1, j = 0; i < fs->fs_frag; i++) { printf("\t%ld", cg->cg_frsum[i]); j += i * cg->cg_frsum[i]; } printf("\nsum of frsum: %d\niused:\t", j); pbits((unsigned char *)cg_inosused(cg), fs->fs_ipg); printf("free:\t"); pbits(cg_blksfree(cg), fs->fs_fpg); printf("b:\n"); for (i = 0; i < fs->fs_cpg; i++) { /*LINTED*/ if (cg_blktot(cg)[i] == 0) continue; /*LINTED*/ printf(" c%d:\t(%ld)\t", i, cg_blktot(cg)[i]); #ifdef FS_42POSTBLFMT for (j = 0; j < fs->fs_nrpos; j++) { if (fs->fs_cpc == 0 || /*LINTED*/ fs_postbl(fs, i % fs->fs_cpc)[j] == -1) continue; /*LINTED*/ printf(" %d", cg_blks(fs, cg, i)[j]); } #else for (j = 0; j < NRPOS; j++) { if (fs->fs_cpc == 0 || fs->fs_postbl[i % fs->fs_cpc][j] == -1) continue; printf(" %d", cg->cg_b[i][j]); } #endif printf("\n"); } } /* * Print out the contents of a bit array. */ static void pbits(unsigned char *cp, int max) { int i; int count = 0, j; for (i = 0; i < max; i++) if (isset(cp, i)) { if (count) printf(",%s", count % 6 ? " " : "\n\t"); count++; printf("%d", i); j = i; while ((i+1) < max && isset(cp, i+1)) i++; if (i != j) printf("-%d", i); } printf("\n"); } /* * bcomp - used to check for block over/under flows when stepping through * a file system. */ static int bcomp(addr) u_offset_t addr; { if (override) return (0); if (lblkno(fs, addr) == (bhdr.fwd)->blkno) return (0); error++; return (1); } /* * bmap - maps the logical block number of a file into * the corresponding physical block on the file * system. */ static long bmap(long bn) { int j; struct dinode *ip; int sh; long nb; char *cptr; if ((cptr = getblk(cur_ino)) == 0) return (0); cptr += blkoff(fs, cur_ino); /*LINTED*/ ip = (struct dinode *)cptr; if (bn < NDADDR) { nb = ip->di_db[bn]; return (nullblk(nb) ? 0L : nb); } sh = 1; bn -= NDADDR; for (j = NIADDR; j > 0; j--) { sh *= NINDIR(fs); if (bn < sh) break; bn -= sh; } if (j == 0) { printf("file too big\n"); error++; return (0L); } addr = (uintptr_t)&ip->di_ib[NIADDR - j]; nb = get(LONG); if (nb == 0) return (0L); for (; j <= NIADDR; j++) { sh /= NINDIR(fs); addr = (nb << FRGSHIFT) + ((bn / sh) % NINDIR(fs)) * LONG; if (nullblk(nb = get(LONG))) return (0L); } return (nb); } #if defined(OLD_FSDB_COMPATIBILITY) /* * The following are "tacked on" to support the old fsdb functionality * of clearing an inode. (All together now...) "It's better to use clri". */ #define ISIZE (sizeof (struct dinode)) #define NI (MAXBSIZE/ISIZE) static struct dinode di_buf[NI]; static union { char dummy[SBSIZE]; struct fs sblk; } sb_un; #define sblock sb_un.sblk static void old_fsdb(int inum, char *special) { int f; /* File descriptor for "special" */ int j; int status = 0; u_offset_t off; long gen; time_t t; f = open(special, 2); if (f < 0) { perror("open"); printf("cannot open %s\n", special); exit(31+4); } (void) llseek(f, (offset_t)SBLOCK * DEV_BSIZE, 0); if (read(f, &sblock, SBSIZE) != SBSIZE) { printf("cannot read %s\n", special); exit(31+4); } if (sblock.fs_magic != FS_MAGIC) { printf("bad super block magic number\n"); exit(31+4); } if (inum == 0) { printf("%d: is zero\n", inum); exit(31+1); } off = (u_offset_t)fsbtodb(&sblock, itod(&sblock, inum)) * DEV_BSIZE; (void) llseek(f, off, 0); if (read(f, (char *)di_buf, sblock.fs_bsize) != sblock.fs_bsize) { printf("%s: read error\n", special); status = 1; } if (status) exit(31+status); /* * Update the time in superblock, so fsck will check this filesystem. */ (void) llseek(f, (offset_t)(SBLOCK * DEV_BSIZE), 0); (void) time(&t); sblock.fs_time = (time32_t)t; if (write(f, &sblock, SBSIZE) != SBSIZE) { printf("cannot update %s\n", special); exit(35); } printf("clearing %u\n", inum); off = (u_offset_t)fsbtodb(&sblock, itod(&sblock, inum)) * DEV_BSIZE; (void) llseek(f, off, 0); read(f, (char *)di_buf, sblock.fs_bsize); j = itoo(&sblock, inum); gen = di_buf[j].di_gen; (void) memset((caddr_t)&di_buf[j], 0, ISIZE); di_buf[j].di_gen = gen + 1; (void) llseek(f, off, 0); write(f, (char *)di_buf, sblock.fs_bsize); exit(31+status); } static int isnumber(char *s) { int c; if (s == NULL) return (0); while ((c = *s++) != '\0') if (c < '0' || c > '9') return (0); return (1); } #endif /* OLD_FSDB_COMPATIBILITY */ enum boolean { True, False }; extent_block_t *log_eb; ml_odunit_t *log_odi; int lufs_tid; /* last valid TID seen */ /* * no single value is safe to use to indicate * lufs_tid being invalid so we need a * seperate variable. */ enum boolean lufs_tid_valid; /* * log_get_header_info - get the basic info of the logging filesystem */ int log_get_header_info(void) { char *b; int nb; /* * Mark the global tid as invalid everytime we're called to * prevent any false positive responses. */ lufs_tid_valid = False; /* * See if we've already set up the header areas. The only problem * with this approach is we don't reread the on disk data though * it shouldn't matter since we don't operate on a live disk. */ if ((log_eb != NULL) && (log_odi != NULL)) return (1); /* * Either logging is disabled or we've not running 2.7. */ if (fs->fs_logbno == 0) { printf("Logging doesn't appear to be enabled on this disk\n"); return (0); } /* * To find the log we need to first pick up the block allocation * data. The block number for that data is fs_logbno in the * super block. */ if ((b = getblk((u_offset_t)ldbtob(logbtodb(fs, fs->fs_logbno)))) == 0) { printf("getblk() indicates an error with logging block\n"); return (0); } /* * Next we need to figure out how big the extent data structure * really is. It can't be more then fs_bsize and you could just * allocate that but, why get sloppy. * 1 is subtracted from nextents because extent_block_t contains * a single extent_t itself. */ log_eb = (extent_block_t *)b; if (log_eb->type != LUFS_EXTENTS) { printf("Extents block has invalid type (0x%x)\n", log_eb->type); return (0); } nb = sizeof (extent_block_t) + (sizeof (extent_t) * (log_eb->nextents - 1)); log_eb = (extent_block_t *)malloc(nb); if (log_eb == NULL) { printf("Failed to allocate memory for extent block log\n"); return (0); } memcpy(log_eb, b, nb); if (log_eb->nextbno != 0) /* * Currently, as of 11-Dec-1997 the field nextbno isn't * implemented. If someone starts using this sucker we'd * better warn somebody. */ printf("WARNING: extent block field nextbno is non-zero!\n"); /* * Now read in the on disk log structure. This is always in the * first block of the first extent. */ b = getblk((u_offset_t)ldbtob(logbtodb(fs, log_eb->extents[0].pbno))); log_odi = (ml_odunit_t *)malloc(sizeof (ml_odunit_t)); if (log_odi == NULL) { free(log_eb); log_eb = NULL; printf("Failed to allocate memory for ondisk structure\n"); return (0); } memcpy(log_odi, b, sizeof (ml_odunit_t)); /* * Consistency checks. */ if (log_odi->od_version != LUFS_VERSION_LATEST) { free(log_eb); log_eb = NULL; free(log_odi); log_odi = NULL; printf("Version mismatch in on-disk version of log data\n"); return (0); } else if (log_odi->od_badlog) { printf("WARNING: Log was marked as bad\n"); } return (1); } static void log_display_header(void) { int x; if (!log_get_header_info()) /* * No need to display anything here. The previous routine * has already done so. */ return; if (fs->fs_magic == FS_MAGIC) printf("Log block number: 0x%x\n------------------\n", fs->fs_logbno); else printf("Log frag number: 0x%x\n------------------\n", fs->fs_logbno); printf("Extent Info\n\t# Extents : %d\n\t# Bytes : 0x%x\n", log_eb->nextents, log_eb->nbytes); printf("\tNext Block : 0x%x\n\tExtent List\n\t--------\n", log_eb->nextbno); for (x = 0; x < log_eb->nextents; x++) printf("\t [%d] lbno 0x%08x pbno 0x%08x nbno 0x%08x\n", x, log_eb->extents[x].lbno, log_eb->extents[x].pbno, log_eb->extents[x].nbno); printf("\nOn Disk Info\n\tbol_lof : 0x%08x\n\teol_lof : 0x%08x\n", log_odi->od_bol_lof, log_odi->od_eol_lof); printf("\tlog_size : 0x%08x\n", log_odi->od_logsize); printf("\thead_lof : 0x%08x\tident : 0x%x\n", log_odi->od_head_lof, log_odi->od_head_ident); printf("\ttail_lof : 0x%08x\tident : 0x%x\n\thead_tid : 0x%08x\n", log_odi->od_tail_lof, log_odi->od_tail_ident, log_odi->od_head_tid); printf("\tcheck sum : 0x%08x\n", log_odi->od_chksum); if (log_odi->od_chksum != (log_odi->od_head_ident + log_odi->od_tail_ident)) printf("bad checksum: found 0x%08x, should be 0x%08x\n", log_odi->od_chksum, log_odi->od_head_ident + log_odi->od_tail_ident); if (log_odi->od_head_lof == log_odi->od_tail_lof) printf("\t --- Log is empty ---\n"); } /* * log_lodb -- logical log offset to disk block number */ int log_lodb(u_offset_t off, diskaddr_t *pblk) { uint32_t lblk = (uint32_t)btodb(off); int x; if (!log_get_header_info()) /* * No need to display anything here. The previous routine * has already done so. */ return (0); for (x = 0; x < log_eb->nextents; x++) if ((lblk >= log_eb->extents[x].lbno) && (lblk < (log_eb->extents[x].lbno + log_eb->extents[x].nbno))) { *pblk = (diskaddr_t)lblk - log_eb->extents[x].lbno + logbtodb(fs, log_eb->extents[x].pbno); return (1); } return (0); } /* * String names for the enumerated types. These are only used * for display purposes. */ char *dt_str[] = { "DT_NONE", "DT_SB", "DT_CG", "DT_SI", "DT_AB", "DT_ABZERO", "DT_DIR", "DT_INODE", "DT_FBI", "DT_QR", "DT_COMMIT", "DT_CANCEL", "DT_BOT", "DT_EOT", "DT_UD", "DT_SUD", "DT_SHAD", "DT_MAX" }; /* * log_read_log -- transfer information from the log and adjust offset */ int log_read_log(u_offset_t *addr, caddr_t va, int nb, uint32_t *chk) { int xfer; caddr_t bp; diskaddr_t pblk; sect_trailer_t *st; while (nb) { if (!log_lodb(*addr, &pblk)) { printf("Invalid log offset\n"); return (0); } /* * fsdb getblk() expects offsets not block number. */ if ((bp = getblk((u_offset_t)dbtob(pblk))) == NULL) return (0); xfer = MIN(NB_LEFT_IN_SECTOR(*addr), nb); if (va != NULL) { memcpy(va, bp + blkoff(fs, *addr), xfer); va += xfer; } nb -= xfer; *addr += xfer; /* * If the log offset is now at a sector trailer * run the checks if requested. */ if (NB_LEFT_IN_SECTOR(*addr) == 0) { if (chk != NULL) { st = (sect_trailer_t *) (bp + blkoff(fs, *addr)); if (*chk != st->st_ident) { printf( "Expected sector trailer id 0x%08x, but saw 0x%08x\n", *chk, st->st_ident); return (0); } else { *chk = st->st_ident + 1; /* * We update the on disk structure * transaction ID each time we see * one. By comparing this value * to the last valid DT_COMMIT record * we can determine if our log is * completely valid. */ log_odi->od_head_tid = st->st_tid; } } *addr += sizeof (sect_trailer_t); } if ((int32_t)*addr == log_odi->od_eol_lof) *addr = log_odi->od_bol_lof; } return (1); } u_offset_t log_nbcommit(u_offset_t a) { /* * Comments are straight from ufs_log.c * * log is the offset following the commit header. However, * if the commit header fell on the end-of-sector, then lof * has already been advanced to the beginning of the next * sector. So do nothgin. Otherwise, return the remaining * bytes in the sector. */ if ((a & (DEV_BSIZE - 1)) == 0) return (0); else return (NB_LEFT_IN_SECTOR(a)); } /* * log_show -- pretty print the deltas. The number of which is determined * by the log_enum arg. If LOG_ALLDELTAS the routine, as the * name implies dumps everything. If LOG_NDELTAS, the routine * will print out "count" deltas starting at "addr". If * LOG_CHECKSCAN then run through the log checking the st_ident * for valid data. */ static void log_show(enum log_enum l) { struct delta d; int32_t bol, eol; int x = 0; uint32_t chk; if (!log_get_header_info()) /* * No need to display any error messages here. The previous * routine has already done so. */ return; bol = log_odi->od_head_lof; eol = log_odi->od_tail_lof; chk = log_odi->od_head_ident; if (bol == eol) { if ((l == LOG_ALLDELTAS) || (l == LOG_CHECKSCAN)) { printf("Empty log.\n"); return; } else printf("WARNING: empty log. addr may generate bogus" " information"); } /* * Only reset the "addr" if we've been requested to show all * deltas in the log. */ if ((l == LOG_ALLDELTAS) || (l == LOG_CHECKSCAN)) addr = (u_offset_t)bol; if (l != LOG_CHECKSCAN) { printf(" Log Offset Delta Count Type\n"); printf("-----------------------------------------" "-----------------\n"); } while ((bol != eol) && ((l == LOG_ALLDELTAS) || (l == LOG_CHECKSCAN) || count--)) { if (!log_read_log(&addr, (caddr_t)&d, sizeof (d), ((l == LOG_ALLDELTAS) || (l == LOG_CHECKSCAN)) ? &chk : NULL)) /* * Two failures are possible. One from getblk() * which prints out a message or when we've hit * an invalid block which may or may not indicate * an error */ goto end_scan; if ((uint32_t)d.d_nb > log_odi->od_logsize) { printf("Bad delta entry. size out of bounds\n"); return; } if (l != LOG_CHECKSCAN) printf("[%04d] %08x %08x.%08x %08x %s\n", x++, bol, d.d_mof, d.d_nb, dt_str[d.d_typ >= DT_MAX ? DT_MAX : d.d_typ]); switch (d.d_typ) { case DT_CANCEL: case DT_ABZERO: /* * These two deltas don't have log space * associated with the entry even though * d_nb is non-zero. */ break; case DT_COMMIT: /* * Commit records have zero size yet, the * rest of the current disk block is avoided. */ addr += log_nbcommit(addr); lufs_tid = log_odi->od_head_tid; lufs_tid_valid = True; break; default: if (!log_read_log(&addr, NULL, d.d_nb, ((l == LOG_ALLDELTAS) || (l == LOG_CHECKSCAN)) ? &chk : NULL)) goto end_scan; break; } bol = (int32_t)addr; } end_scan: if (lufs_tid_valid == True) { if (lufs_tid == log_odi->od_head_tid) printf("scan -- okay\n"); else printf("scan -- some transactions have been lost\n"); } else { printf("scan -- failed to find a single valid transaction\n"); printf(" (possibly due to an empty log)\n"); } } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Copyright 2005 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # cmd/fs.d/ufs/fsirand/Makefile FSTYPE= ufs LIBPROG= fsirand OTHERINSTALL= $(ROOTUSRSBIN)/$(LIBPROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(LIBPROG) include ../../Makefile.fstype CPPFLAGS += -D_LARGEFILE64_SOURCE $(ROOTUSRSBIN)/$(LIBPROG): -$(RM) $@; $(SYMLINK) $(LINKVALUE) $@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * fsirand installs random inode generation numbers on all the inodes on * device , and also installs a file system ID in the superblock. * This helps increase the security of file systems exported by NFS. */ #include #include #include #include #include #include #include #include #include #include #include #include long fsbuf[(SBSIZE / sizeof (long))]; struct dinode dibuf[8192/sizeof (struct dinode)]; int main(int argc, char *argv[]) { struct fs *fs; int fd; char *dev; int bno; struct dinode *dip; int inum, imax; int i, n; offset_t seekaddr; int bsize; int pflag = 0; struct timeval timeval; argv++; argc--; if (argc > 0 && strcmp(*argv, "-p") == 0) { pflag++; argv++; argc--; } if (argc <= 0) { (void) fprintf(stderr, "Usage: fsirand [-p] special\n"); exit(1); } dev = *argv; fd = open64(dev, pflag ? O_RDONLY : O_RDWR); if (fd == -1) { (void) fprintf(stderr, "fsirand: Cannot open %s: %s\n", dev, strerror(errno)); exit(1); } if (llseek(fd, (offset_t)SBLOCK * DEV_BSIZE, 0) == -1) { (void) fprintf(stderr, "fsirand: Seek to superblock failed: %s\n", strerror(errno)); exit(1); } fs = (struct fs *)fsbuf; if ((n = read(fd, (char *)fs, SBSIZE)) != SBSIZE) { (void) fprintf(stderr, "fsirand: Read of superblock failed: %s\n", n == -1 ? strerror(errno) : "Short read"); exit(1); } if ((fs->fs_magic != FS_MAGIC) && (fs->fs_magic != MTB_UFS_MAGIC)) { (void) fprintf(stderr, "fsirand: Not a file system (bad magic number in superblock)\n"); exit(1); } if (fs->fs_magic == FS_MAGIC && (fs->fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && fs->fs_version != UFS_VERSION_MIN)) { (void) fprintf(stderr, "fsirand: Unrecognized UFS format version number %d (in superblock)\n", fs->fs_version); exit(1); } if (fs->fs_magic == MTB_UFS_MAGIC && (fs->fs_version > MTB_UFS_VERSION_1 || fs->fs_version < MTB_UFS_VERSION_MIN)) { (void) fprintf(stderr, "fsirand: Unrecognized UFS format version number %d (in superblock)\n", fs->fs_version); exit(1); } if (pflag) { (void) printf("fsid: %x %x\n", fs->fs_id[0], fs->fs_id[1]); } else { n = getpid(); (void) gettimeofday(&timeval, (struct timezone *)NULL); srand48((long)(timeval.tv_sec + timeval.tv_usec + n)); } bsize = INOPB(fs) * sizeof (struct dinode); inum = 0; imax = fs->fs_ipg * fs->fs_ncg; while (inum < imax) { bno = itod(fs, inum); seekaddr = (offset_t)fsbtodb(fs, bno) * DEV_BSIZE; if (llseek(fd, seekaddr, 0) == -1) { (void) fprintf(stderr, "fsirand: Seek to %ld %ld failed: %s\n", ((off_t *)&seekaddr)[0], ((off_t *)&seekaddr)[1], strerror(errno)); exit(1); } n = read(fd, (char *)dibuf, bsize); if (n != bsize) { (void) fprintf(stderr, "fsirand: Read of ilist block failed: %s\n", n == -1 ? strerror(errno) : "Short read"); exit(1); } for (dip = dibuf; dip < &dibuf[INOPB(fs)]; dip++) { if (pflag) { (void) printf("ino %d gen %x\n", inum, dip->di_gen); } else { dip->di_gen = lrand48(); } inum++; } if (!pflag) { if (llseek(fd, seekaddr, 0) == -1) { (void) fprintf(stderr, "fsirand: Seek to %ld %ld failed: %s\n", ((off_t *)&seekaddr)[0], ((off_t *)&seekaddr)[1], strerror(errno)); exit(1); } n = write(fd, (char *)dibuf, bsize); if (n != bsize) { (void) fprintf(stderr, "fsirand: Write of ilist block failed: %s\n", n == -1 ? strerror(errno) : "Short write"); exit(1); } } } if (!pflag) { (void) gettimeofday(&timeval, (struct timezone *)NULL); fs->fs_id[0] = timeval.tv_sec; fs->fs_id[1] = timeval.tv_usec + getpid(); if (llseek(fd, (offset_t)SBLOCK * DEV_BSIZE, 0) == -1) { (void) fprintf(stderr, "fsirand: Seek to superblock failed: %s\n", strerror(errno)); exit(1); } if ((n = write(fd, (char *)fs, SBSIZE)) != SBSIZE) { (void) fprintf(stderr, "fsirand: Write of superblock failed: %s\n", n == -1 ? strerror(errno) : "Short write"); exit(1); } } for (i = 0; i < fs->fs_ncg; i++) { seekaddr = (offset_t)fsbtodb(fs, cgsblock(fs, i)) * DEV_BSIZE; if (llseek(fd, seekaddr, 0) == -1) { (void) fprintf(stderr, "fsirand: Seek to alternate superblock failed: %s\n", strerror(errno)); exit(1); } if (pflag) { if ((n = read(fd, (char *)fs, SBSIZE)) != SBSIZE) { (void) fprintf(stderr, "fsirand: Read of alternate superblock failed: %s\n", n == -1 ? strerror(errno) : "Short read"); exit(1); } if ((fs->fs_magic != FS_MAGIC) && (fs->fs_magic != MTB_UFS_MAGIC)) { (void) fprintf(stderr, "fsirand: Not a valid file system (bad " "magic number in alternate superblock)\n"); exit(1); } } else { if ((n = write(fd, (char *)fs, SBSIZE)) != SBSIZE) { (void) fprintf(stderr, "fsirand: Write of alternate superblock failed: %s\n", n == -1 ? strerror(errno) : "Short write"); exit(1); } } } return (0); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2003 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= fssnap include ../../Makefile.fstype FSSNAPOBJS= fssnap.o FSSNAPSRCS= $(FSSNAPOBJS:%.o=%.c) TOPDIR= ../.. TOPOBJS= fssnapsup.o TOPSRCS= $(TOPOBJS:%.o=%.c) OBJS= $(FSSNAPOBJS) $(TOPOBJS) SRCS= $(FSSNAPSRCS) $(TOPSRCS) CPPFLAGS += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE CERRWARN += -Wno-type-limits CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-unused-variable CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress pointer/int cast warnings in legacy fssnap code CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast # not linted SMATCH=off LDLIBS += -lkstat -ldevinfo # message catalog POFILE= fssnap.po catalog: $(POFILE) $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) %.o: $(TOPDIR)/%.c $(COMPILE.c) $< clean: $(RM) $(FSSNAPOBJS) $(TOPOBJS) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SNAP_CTL_PATH "/dev/" SNAP_CTL_NAME #define MAX_SUFFIX 6 /* '.' + 4 chars of number + trailing '\0' */ #define POWEROF2(num) (((num) & ((num) - 1)) == 0) static int max_uniq = 9999; void create_snap(int, char *, u_offset_t, uint_t, int, int); void delete_snap(int); void stats_snap(char *, char *); int open_backpath(int, u_offset_t, char **, char **, int **); u_offset_t spec_to_bytes(char *); void gen_backing_store_path(char *basepath, int num, char **outpath); void unlink_all(char *, int); void close_all(char *, int, int *); int open_multi_backfile(char *, int, int **, int); void die_perror(char *); void die_errno(int, char *, ...); void die_create_error(int error); void die_usage(void); void die(char *, ...); void warn_errno(int, char *, ...); void usage(void); static char *subopts[] = { #define BACKPATH (0) "backing-store", #define BACKPATH2 (1) "bs", #define BACKPATH3 (2) "bf", #define MAXSIZE (3) "maxsize", #define CHUNKSIZE (4) "chunksize", #define RAWFILE (5) "raw", #define UNLINK (6) "unlink", NULL }; static jmp_buf err_main; static char *progname = NULL; static int backout_snap_fd = -1; extern void fssnap_show_status(char *mountpoint, char *opts, int labels, int brief); /* in ../../fssnapsup.c */ int main(int argc, char *argv[]) { int c; char *suboptions = NULL; char *value; int longjmp_return; char *volatile mountpoint = NULL; int volatile mountfd = -1; char *volatile backpath = NULL; int volatile delete = 0; int volatile stats = 0; u_offset_t volatile maxsize = 0; uint_t volatile chunksize = 0; int volatile rawfile = 0; int volatile dounlink = 0; if ((progname = strrchr(argv[0], '/')) != NULL) ++progname; else progname = argv[0]; if ((longjmp_return = setjmp(err_main)) != 0) { if (backout_snap_fd >= 0) { mountfd = backout_snap_fd; backout_snap_fd = -1; /* prevent infinite loop */ delete_snap(mountfd); } return (longjmp_return); } while ((c = getopt(argc, argv, "dio:")) != EOF) { switch (c) { case 'd': ++delete; break; case 'i': ++stats; break; case 'o': suboptions = optarg; break; default: die_usage(); } } /* if -i or -d are not specified then interpret the create options */ if ((stats == 0) && (delete == 0) && (suboptions != NULL)) { while (*suboptions != '\0') { switch ((getsubopt(&suboptions, subopts, &value))) { case BACKPATH: case BACKPATH2: case BACKPATH3: if (value == NULL) die_usage(); backpath = strdup(value); if (backpath == NULL) { die_perror("strdup"); } break; case MAXSIZE: maxsize = spec_to_bytes(value); break; case CHUNKSIZE: chunksize = spec_to_bytes(value); break; case RAWFILE: ++rawfile; break; case UNLINK: ++dounlink; break; default: die_usage(); } } } /* -d and -i can not be specified together or more than once each */ if ((delete + stats) > 1) die_usage(); /* If no mount point is specified then -i is the only valid option. */ if ((optind >= argc) && (stats == 0)) die_usage(); /* * If anything but the mount point or device is specified at the end * it's an error. */ if (optind != (argc - 1)) { if (!stats) die_usage(); } else { /* Otherwise, the last option is the mountpoint. */ mountpoint = argv[optind]; if ((mountfd = open(mountpoint, O_RDONLY)) < 0) die_perror(mountpoint); } if (stats != 0) { stats_snap(mountpoint, suboptions); } else if (delete != 0) { delete_snap(mountfd); } else { /* * backpath may be invalid upon return of create_snap call. */ create_snap(mountfd, backpath, maxsize, chunksize, rawfile, dounlink); } return (0); } void create_snap(int mountfd, char *backpath, u_offset_t maxsize, uint_t chunksize, int rawfile, int dounlink) { struct fiosnapcreate_multi *enable; int backcount; int ctlfd; char *unlinkpath = NULL; di_devlink_handle_t hdl; int *fd_array; u_offset_t max_bf_size; int save_errno; /* * If chunksize is not a power of 2, the maximum size of a * backing store file might not be UFS_MAX_SNAPBACKFILESIZE, * since the size of the backing store files must be an * integral number of chunks (except for the last one). So * calculate the actual maximum backing store file size. * (It would be nice if we could assume that the chunksize * was a power of 2, but we can't.) */ if (chunksize != 0 && !POWEROF2(chunksize)) max_bf_size = (UFS_MAX_SNAPBACKFILESIZE/chunksize) * chunksize; else max_bf_size = UFS_MAX_SNAPBACKFILESIZE; /* * open_backpath() only returns on success, and * can change the value of backpath when backpath * references a directory. */ if (backpath == NULL) die(gettext("No backing store path specified.\n")); backcount = open_backpath(mountfd, max_bf_size, &backpath, &unlinkpath, &fd_array); /* * Only need backcount - 1 spaces for fd's since * fiosnapcreate_multi struct contains space for the * first one. */ if ((enable = calloc(1, sizeof (struct fiosnapcreate_multi) + (backcount - 1) * sizeof (int))) == NULL) die(gettext("Insufficient memory.\n")); enable->backfilecount = backcount; bcopy(fd_array, &(enable->backfiledesc), backcount * sizeof (int)); enable->rootfiledesc = mountfd; enable->maxsize = maxsize; enable->chunksize = chunksize; enable->backfilesize = max_bf_size; /* * enable.backfilename is advisory only. So, we don't overflow * the buffer, but we don't give an error if the backpath does not * fit. Instead, it is truncated, and the kstat shows all it can. */ if (backpath != NULL) { if (dounlink) (void) snprintf(enable->backfilename, sizeof (enable->backfilename) - 1, "%s ", backpath); else (void) strncpy(enable->backfilename, backpath, sizeof (enable->backfilename) - 1); enable->backfilename[sizeof (enable->backfilename)-1] = '\0'; } if ((ctlfd = open(SNAP_CTL_PATH, O_RDONLY | O_EXCL)) == -1) { unlink_all(unlinkpath, backcount); die_perror(SNAP_CTL_PATH); } if (ioctl(ctlfd, _FIOSNAPSHOTCREATE_MULTI, enable) == -1) { unlink_all(unlinkpath, backcount); if (enable->error != 0) { die_create_error(enable->error); } else { die_perror("ioctl"); } } backout_snap_fd = mountfd; if (dounlink != 0) unlink_all(unlinkpath, backcount); if (close(ctlfd) != 0) { save_errno = errno; die_errno(save_errno, gettext("close of control file (%s)"), SNAP_CTL_PATH); } close_all(unlinkpath, backcount, fd_array); if ((hdl = di_devlink_init("fssnap", DI_MAKE_LINK)) == NULL) { save_errno = errno; warn_errno(save_errno, gettext("/dev/%s/%d may not be immediately available\n"), (rawfile) ? SNAP_CHAR_NAME : SNAP_BLOCK_NAME, enable->snapshotnumber); } else { (void) di_devlink_fini(&hdl); } /* intentionally not internationalized */ printf("/dev/%s/%d\n", (rawfile) ? SNAP_CHAR_NAME : SNAP_BLOCK_NAME, enable->snapshotnumber); free(enable); } void delete_snap(int mountfd) { struct fiosnapdelete disable; int ctlfd; int save_errno; bzero(&disable, sizeof (disable)); if ((ctlfd = open(SNAP_CTL_PATH, O_RDONLY | O_EXCL)) == -1) die_perror(SNAP_CTL_PATH); disable.rootfiledesc = mountfd; if (ioctl(ctlfd, _FIOSNAPSHOTDELETE, &disable) == -1) { if (disable.error) { die(gettext("error %d"), disable.error); } else { die_perror("ioctl"); } } if (close(ctlfd) != 0) { save_errno = errno; die_errno(save_errno, gettext("close of control file (%s)"), SNAP_CTL_PATH); } printf(gettext("Deleted snapshot %d.\n"), disable.snapshotnumber); } void stats_snap(char *mountpath, char *opts) { fssnap_show_status(mountpath, opts, ((opts != NULL) ? 0 : 1), 0); } /* * Open as many backing files as necessary for this snapshot. * There will be one backing file for each max_bf_size * number of bytes in the file system being snapped. * The array of file descriptors for the backing files is returned in * fd_array. The number of backing files is the return value of the * function. The name of the first backing file is returned in * unlinkpath. The subsequent backing files are assumed to have the * same name as the first, but with suffixes, .2, .3, etc. */ int open_backpath(int mountfd, u_offset_t max_bf_size, char **path, char **unlinkpath, int **fd_array) { struct stat st; struct statvfs vfs; int fd, uniq, len; int ret_errno, i, num_back_files; offset_t fssize, backfilesize; char *locpath = NULL; int save_errno; *unlinkpath = NULL; /* determine size of the file system to be snapped */ if (fstatvfs(mountfd, &vfs) == -1) die_perror("statvfs"); fssize = vfs.f_blocks * vfs.f_frsize; num_back_files = howmany(fssize, max_bf_size); if (stat(*path, &st) < 0) { /* * Since we set the file_exists_is_fatal argument to 1, * if we return at all, it will be with all the backing * files successfully created and opened. */ (void) open_multi_backfile(*path, num_back_files, fd_array, 1); *unlinkpath = strdup(*path); if (unlinkpath == NULL) die_perror("strdup"); } else if (S_ISDIR(st.st_mode)) { char temppath[MAXPATHLEN]; /* remove a trailing slash from the name */ len = strlen(*path) - 1; if ((*path)[len] == '/') (*path)[len] = '\0'; /* find a unique name */ for (uniq = 0; uniq <= max_uniq; uniq++) { /* cannot use tempnam, since TMPDIR overrides path */ (void) snprintf(temppath, MAXPATHLEN, "%s/snapshot%d", *path, uniq); ret_errno = open_multi_backfile(temppath, num_back_files, fd_array, 0); if (ret_errno == 0) break; } if (uniq > max_uniq) { die(gettext("Could not find unique name in %s"), *path); } *unlinkpath = strdup(temppath); free(*path); *path = *unlinkpath; } else if (S_ISREG(st.st_mode)) { die(gettext("%s already exists."), *path); } else { die(gettext("%s: must be either the name of a file to create " "or a directory."), *path); } /* * write a block to the end to bump up the file size and ensure the * entire range needed can be written to. */ for (i = 0; i < num_back_files; i++) { fd = (*fd_array)[i]; if (i == num_back_files - 1 && fssize % max_bf_size != 0) backfilesize = fssize % max_bf_size; else backfilesize = max_bf_size; if (llseek(fd, backfilesize - 1, SEEK_SET) == -1) { unlink_all(*unlinkpath, num_back_files); die_perror("llseek"); } if (write(fd, "0", 1) == -1) { save_errno = errno; unlink_all(*unlinkpath, num_back_files); if (save_errno == EFBIG) die(gettext("File system %s " "does not support large files.\n"), *path); else die_perror("write"); } } return (num_back_files); } u_offset_t spec_to_bytes(char *spec) { u_offset_t base; base = strtoull(spec, NULL, 10); if ((base == 0LL) && (spec[0] != '0')) die(gettext("Numeric option value expected")); spec += strspn(spec, "0123456789"); if ((spec == NULL) || strlen(spec) != 1) die(gettext("Only one of b, k, m, or g may be used")); switch (spec[0]) { case 'B': case 'b': base *= 512; break; case 'K': case 'k': base *= 1024; break; case 'M': case 'm': base *= 1024 * 1024; break; case 'G': case 'g': base *= 1024 * 1024 * 1024; break; default: die(gettext("Must specify one of b, k, m, or g on size")); } return (base); } /* * Make sure that the first call to gen_backing_store() in a loop * starts with a null pointer in the outpath argument * and continues to pass in that same argument until * the loop is complete, at which point the string * pointed to by that argument must be freed by the caller. */ void gen_backing_store_path(char *basepath, int num, char **outpath) { if (*outpath == NULL) { *outpath = malloc(strlen(basepath) + MAX_SUFFIX); if (*outpath == NULL) die_perror("malloc"); } /* * Security note: We use strcpy here, instead of the safer * strncpy, because the string pointed to by outpath has * been generated by THIS code, above. Hence it is impossible * for the strcpy to overrun the buffer. */ if (num == 1) (void) strcpy(*outpath, basepath); else (void) sprintf(*outpath, "%s.%d", basepath, num); } void unlink_all(char *unlinkpath, int count) { char *bspath = NULL; int i; int save_errno; for (i = 1; i <= count; i++) { /* * Make sure that the first call to gen_backing_store() * starts with a null pointer in the third argument * and continues to pass in that same argument until * the loop is complete, at which point the string * pointed to by that argument must be freed. */ gen_backing_store_path(unlinkpath, i, &bspath); if (unlink(bspath) < 0) { save_errno = errno; warn_errno(save_errno, gettext("could not unlink %s"), bspath); } } free(bspath); } void close_all(char *closepath, int count, int *fd_array) { char *bspath = NULL; int i; int save_errno; for (i = 1; i <= count; i++) { if (close(fd_array[i - 1]) != 0) { save_errno = errno; /* * Make sure that the first call to gen_backing_store() * starts with a null pointer in the third argument * and continues to pass in that same argument until * the loop is complete, at which point the string * pointed to by that argument must be freed. */ gen_backing_store_path(closepath, i, &bspath); die_errno(save_errno, gettext( "close of backing-store (%s)"), bspath); } } if (bspath != NULL) free(bspath); } /* * Create "count" files starting with name backpath ("backpath", * "backpath".2, "backpath".3, etc. When this function returns, * either all of the files will exist and be opened (and their * file descriptors will be in fd_array), or NONE of will exist * (if they had to be created) and opened (that is, if we created a file, * and then failed to create a later file, the earlier files will * be closed and unlinked.) * * If file_exists_is_fatal is set, it is a fatal error (resulting in * an error message and termination) if any of the backing files to * be created already exists. Otherwise, if one of the backing * files already exists, we close and unlink all the files we already * created, and return an error to the caller, but we don't print * an error or terminate. * * If there is any failure other than EEXIST when attempting to * create the file, the routine prints an error and terminates the * program, regardless of the setting of file_exists_is_fatal. */ int open_multi_backfile(char *backpath, int count, int **fd_array, int file_exists_is_fatal) { char *wpath = NULL; /* working path */ int i, j, fd; struct stat st; int stat_succeeded = 0; int save_errno; *fd_array = (int *)malloc(count * sizeof (int)); if (*fd_array == NULL) die_perror("malloc"); for (i = 0; i < count; i++) { /* * Make sure that the first call to gen_backing_store() * starts with a null pointer in the third argument * and continues to pass in that same argument until * the loop is complete, at which point the string * pointed to by that argument must be freed. */ gen_backing_store_path(backpath, i + 1, &wpath); if (stat(wpath, &st) == 0) stat_succeeded = 1; else fd = open(wpath, O_RDWR | O_CREAT | O_EXCL, 0600); if (stat_succeeded || fd < 0) { if (i > 0) { for (j = 0; j < i - 1; j++) (void) close((*fd_array)[j]); /* * unlink_all's second argument is the number * of files to be removed, NOT the offset * into the array of fd's of the last * successfully created file. */ unlink_all(backpath, i); } if (stat_succeeded || errno == EEXIST) { if (file_exists_is_fatal) die(gettext("%s exists, please specify" " a nonexistent backing store."), wpath); else return (1); } else { save_errno = errno; die_errno(save_errno, gettext("Could not create" " backing file %s"), wpath); } } (*fd_array)[i] = fd; } if (wpath != NULL) free(wpath); return (0); } void die_perror(char *string) { int en = errno; char *errstr; if (string == NULL) { string = gettext("Fatal"); } errstr = strerror(en); if (errstr == NULL) { errstr = gettext("Unknown error"); } fprintf(stderr, gettext("%s: %s: error %d: %s\n"), progname, string, en, errstr); longjmp(err_main, 2); } void die_usage(void) { usage(); longjmp(err_main, 1); } void warn_errno(int en, char *fmt, ...) { va_list ap; char *errstr; errstr = strerror(en); if (errstr == NULL) { errstr = gettext("Unknown error"); } va_start(ap, fmt); fprintf(stderr, gettext("%s: Warning: "), progname); vfprintf(stderr, fmt, ap); fprintf(stderr, ": %s\n", errstr); va_end(ap); } void die_errno(int en, char *fmt, ...) { va_list ap; char *errstr; errstr = strerror(en); if (errstr == NULL) { errstr = gettext("Unknown error"); } va_start(ap, fmt); fprintf(stderr, gettext("%s: Fatal: "), progname); vfprintf(stderr, fmt, ap); fprintf(stderr, ": %s\n", errstr); va_end(ap); longjmp(err_main, 2); } void die_create_error(int error) { fprintf(stderr, gettext("snapshot error: ")); switch (error) { case FIOCOW_EREADONLY: fprintf(stderr, gettext("Read only file system\n")); break; case FIOCOW_EBUSY: fprintf(stderr, gettext("Snapshot already enabled\n")); break; case FIOCOW_EULOCK: fprintf(stderr, gettext("File system is locked\n")); break; case FIOCOW_EWLOCK: fprintf(stderr, gettext("File system could not be write locked\n")); break; case FIOCOW_EFLUSH: fprintf(stderr, gettext("File system could not be flushed\n")); break; case FIOCOW_ECLEAN: fprintf(stderr, gettext("File system may not be stable\n")); break; case FIOCOW_ENOULOCK: fprintf(stderr, gettext("File system could not be unlocked\n")); break; case FIOCOW_ECHUNKSZ: fprintf(stderr, gettext("Chunk size must be a multiple of the " "fragment size\n")); break; case FIOCOW_ECREATE: fprintf(stderr, gettext("Could not allocate or create " "a new snapshot\n")); break; case FIOCOW_EBITMAP: fprintf(stderr, gettext("Error scanning file system bitmaps\n")); break; case FIOCOW_EBACKFILE: fprintf(stderr, gettext("Invalid backing file path\n")); break; default: fprintf(stderr, gettext("Unknown create error\n")); break; } longjmp(err_main, 2); } void die(char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, gettext("%s: Fatal: "), progname); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); longjmp(err_main, 2); } void usage(void) { int i; char *use_str[] = { " %s [-F ufs] [-V] -o backing-store=path,[special_options] " "/mount/point\n", " %s -d [-F ufs] [-V] /mount/point | dev\n", " %s -i [-F ufS] [-V] [-o special-options] /mount/point " "| dev\n", NULL }; fprintf(stderr, gettext("Usage:\n")); for (i = 0; use_str[i] != NULL; i++) fprintf(stderr, gettext(use_str[i]), progname); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # FSTYP_VERS=1 FSTYPE= ufs LIBPROG= fstyp.so.${FSTYP_VERS} include ../../../../lib/Makefile.lib include ../../Makefile.fstype # There should be a mapfile here MAPFILES = CFLAGS += $(C_PICFLAGS) DYNLIB= $(LIBPROG) LDLIBS += -lnvpair -lc # # Override PMAP dependency # PMAP= # # Rules for making shared objects out of .c files. Works well if # we have a one-to-one mapping. Applies in all cases so far. # .SUFFIXES: .so.${FSTYP_VERS} ${SUFFIXES} .c.so.${FSTYP_VERS}: $(CC) $(CPPFLAGS) $(CFLAGS) $(DYNFLAGS) $(GSHARED) -o $@ $< $(LDLIBS) $(POST_PROCESS_SO) SRCS= ${LIBPROG:%.so.$(FSTYP_VERS)=%.c} CPPFLAGS += -DFSTYP_VERS=${FSTYP_VERS} -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 .KEEP_STATE: all: $(LIBPROG) install: all $(RM) $(ROOTLIBFSTYPE)/fstyp $(LN) $(ROOTUSRSBIN)/fstyp $(ROOTLIBFSTYPE)/fstyp cstyle: $(CSTYLE) $(SRCS) clean: $(RM) $(LIBPROG) clobber: clean /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * libfstyp module for ufs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct fstyp_ufs { int fd; nvlist_t *attr; union { struct fs fs; char pad[MAXBSIZE]; } fsun; union { struct cg cg; char pad[MAXBSIZE]; } cgun; char eg[MAXBSIZE]; } fstyp_ufs_t; #define afs fsun.fs #define acg cgun.cg #define MAXLABELS 20 #define LINEMAX 256 #define NRPOS 8 /* for pre FFFS compatibility */ static int is_ufs(fstyp_ufs_t *h); static int get_attr(fstyp_ufs_t *h); static int dumpfs(fstyp_ufs_t *h, FILE *fout, FILE *ferr); static void dumplog(fstyp_ufs_t *h, FILE *fout, FILE *ferr); static void dumpcg(fstyp_ufs_t *h, FILE *fout, FILE *ferr, const int c); static void pbits(FILE *out, const void *cp, const int max); int fstyp_mod_init(int fd, off_t offset, fstyp_mod_handle_t *handle); void fstyp_mod_fini(fstyp_mod_handle_t handle); int fstyp_mod_ident(fstyp_mod_handle_t handle); int fstyp_mod_get_attr(fstyp_mod_handle_t handle, nvlist_t **attrp); int fstyp_mod_dump(fstyp_mod_handle_t handle, FILE *fout, FILE *ferr); int fstyp_mod_init(int fd, off_t offset, fstyp_mod_handle_t *handle) { struct fstyp_ufs *h; if (offset != 0) { return (FSTYP_ERR_OFFSET); } if ((h = calloc(1, sizeof (struct fstyp_ufs))) == NULL) { return (FSTYP_ERR_NOMEM); } h->fd = fd; *handle = (fstyp_mod_handle_t)h; return (0); } void fstyp_mod_fini(fstyp_mod_handle_t handle) { struct fstyp_ufs *h = (struct fstyp_ufs *)handle; if (h->attr == NULL) { nvlist_free(h->attr); h->attr = NULL; } free(h); } int fstyp_mod_ident(fstyp_mod_handle_t handle) { struct fstyp_ufs *h = (struct fstyp_ufs *)handle; return (is_ufs(h)); } int fstyp_mod_get_attr(fstyp_mod_handle_t handle, nvlist_t **attrp) { struct fstyp_ufs *h = (struct fstyp_ufs *)handle; int error; if (h->attr == NULL) { if (nvlist_alloc(&h->attr, NV_UNIQUE_NAME_TYPE, 0)) { return (FSTYP_ERR_NOMEM); } if ((error = get_attr(h)) != 0) { nvlist_free(h->attr); h->attr = NULL; return (error); } } *attrp = h->attr; return (0); } int fstyp_mod_dump(fstyp_mod_handle_t handle, FILE *fout, FILE *ferr) { struct fstyp_ufs *h = (struct fstyp_ufs *)handle; return (dumpfs(h, fout, ferr)); } static int is_ufs(fstyp_ufs_t *h) { (void) llseek(h->fd, (offset_t)SBLOCK * DEV_BSIZE, 0); if (read(h->fd, &h->afs, SBSIZE) != SBSIZE) { return (FSTYP_ERR_IO); } if ((h->afs.fs_magic != FS_MAGIC) && (h->afs.fs_magic != MTB_UFS_MAGIC)) { return (FSTYP_ERR_NO_MATCH); } if ((h->afs.fs_magic == FS_MAGIC) && (h->afs.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && h->afs.fs_version != UFS_VERSION_MIN)) { return (FSTYP_ERR_NO_MATCH); } if ((h->afs.fs_magic == MTB_UFS_MAGIC) && (h->afs.fs_version > MTB_UFS_VERSION_1 || h->afs.fs_version < MTB_UFS_VERSION_MIN)) { return (FSTYP_ERR_NO_MATCH); } return (0); } #define ADD_STRING(h, name, value) \ if (nvlist_add_string(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } #define ADD_INT32(h, name, value) \ if (nvlist_add_int32(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } #define ADD_BOOL(h, name, value) \ if (nvlist_add_boolean_value(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } static int get_attr(fstyp_ufs_t *h) { struct fs *fsp = &h->afs; char s[128]; time_t t; ADD_INT32(h, "magic", fsp->fs_magic); ADD_STRING(h, "format", fsp->fs_postblformat == FS_42POSTBLFMT ? "static" : "dynamic"); t = (time_t)fsp->fs_time; (void) snprintf(s, sizeof (s), "%s", ctime(&t)); s[strlen(s) - 1] = '\0'; ADD_STRING(h, "time", s); ADD_INT32(h, "sblkno", fsp->fs_sblkno); ADD_INT32(h, "cblkno", fsp->fs_cblkno); ADD_INT32(h, "iblkno", fsp->fs_iblkno); ADD_INT32(h, "dblkno", fsp->fs_dblkno); ADD_INT32(h, "sbsize", fsp->fs_sbsize); ADD_INT32(h, "cgsize", fsp->fs_cgsize); ADD_INT32(h, "cgoffset", fsp->fs_cgoffset); ADD_INT32(h, "cgmask", fsp->fs_cgmask); ADD_INT32(h, "ncg", fsp->fs_ncg); ADD_INT32(h, "size", fsp->fs_size); ADD_INT32(h, "blocks", fsp->fs_dsize); ADD_INT32(h, "bsize", fsp->fs_bsize); ADD_INT32(h, "bshift", fsp->fs_bshift); ADD_INT32(h, "bmask", fsp->fs_bmask); ADD_INT32(h, "fsize", fsp->fs_fsize); ADD_INT32(h, "fshift", fsp->fs_fshift); ADD_INT32(h, "fmask", fsp->fs_fmask); ADD_INT32(h, "frag", fsp->fs_frag); ADD_INT32(h, "fragshift", fsp->fs_fragshift); ADD_INT32(h, "fsbtodb", fsp->fs_fsbtodb); ADD_INT32(h, "minfree", fsp->fs_minfree); ADD_INT32(h, "maxbpg", fsp->fs_maxbpg); ADD_STRING(h, "optim", fsp->fs_optim == FS_OPTSPACE ? "space" : "time"); ADD_INT32(h, "maxcontig", fsp->fs_maxcontig); ADD_INT32(h, "rotdelay", fsp->fs_rotdelay); ADD_INT32(h, "rps", fsp->fs_rps); ADD_INT32(h, "csaddr", fsp->fs_csaddr); ADD_INT32(h, "cssize", fsp->fs_cssize); ADD_INT32(h, "csshift", fsp->fs_csshift); ADD_INT32(h, "csmask", fsp->fs_csmask); ADD_INT32(h, "ntrak", fsp->fs_ntrak); ADD_INT32(h, "nsect", fsp->fs_nsect); ADD_INT32(h, "spc", fsp->fs_spc); ADD_INT32(h, "ncyl", fsp->fs_ncyl); ADD_INT32(h, "cpg", fsp->fs_cpg); ADD_INT32(h, "bpg", fsp->fs_fpg / fsp->fs_frag); ADD_INT32(h, "fpg", fsp->fs_fpg); ADD_INT32(h, "ipg", fsp->fs_ipg); ADD_INT32(h, "nindir", fsp->fs_nindir); ADD_INT32(h, "inopb", fsp->fs_inopb); ADD_INT32(h, "nspf", fsp->fs_nspf); ADD_INT32(h, "nbfree", fsp->fs_cstotal.cs_nbfree); ADD_INT32(h, "ndir", fsp->fs_cstotal.cs_ndir); ADD_INT32(h, "nifree", fsp->fs_cstotal.cs_nifree); ADD_INT32(h, "nffree", fsp->fs_cstotal.cs_nffree); ADD_INT32(h, "cgrotor", fsp->fs_cgrotor); ADD_INT32(h, "fmod", fsp->fs_fmod); ADD_INT32(h, "ronly", fsp->fs_ronly); ADD_INT32(h, "logbno", fsp->fs_logbno); ADD_INT32(h, "rolled", fsp->fs_rolled); ADD_INT32(h, "si", fsp->fs_si); ADD_INT32(h, "flags", fsp->fs_flags); ADD_INT32(h, "version", fsp->fs_version); if (fsp->fs_reclaim & (FS_RECLAIM | FS_RECLAIMING)) { (void) snprintf(s, sizeof (s), "%s%s", (fsp->fs_reclaim & FS_RECLAIM) ? " FS_RECLAIM" : "", (fsp->fs_reclaim & FS_RECLAIMING) ? " FS_RECLAIMING" : ""); ADD_STRING(h, "fs_reclaim", s); } ADD_INT32(h, "clean", fsp->fs_clean); if ((fsp->fs_state + (long)fsp->fs_time == FSOKAY) && (fsp->fs_clean == FSCLEAN || fsp->fs_clean == FSSTABLE || (fsp->fs_clean == FSLOG))) { ADD_BOOL(h, "gen_clean", B_TRUE); } else { ADD_BOOL(h, "gen_clean", B_FALSE); } (void) snprintf(s, sizeof (s), "%d", fsp->fs_version); ADD_STRING(h, "gen_version", s); return (0); } static int dumpfs(fstyp_ufs_t *h, FILE *fout, FILE *ferr) { int c, i, j, k, size, nrpos; struct fs *fsp = &h->afs; offset_t offset; caddr_t sip; time_t t; t = (time_t)fsp->fs_time; (void) fprintf(fout, "magic\t%x\tformat\t%s\ttime\t%s", fsp->fs_magic, fsp->fs_postblformat == FS_42POSTBLFMT ? "static" : "dynamic", ctime(&t)); (void) fprintf(fout, "sblkno\t%d\tcblkno\t%d\tiblkno\t%d\tdblkno\t%d\n", fsp->fs_sblkno, fsp->fs_cblkno, fsp->fs_iblkno, fsp->fs_dblkno); (void) fprintf(fout, "sbsize\t%d\tcgsize\t%d\tcgoffset %d\tcgmask\t0x%08x\n", fsp->fs_sbsize, fsp->fs_cgsize, fsp->fs_cgoffset, fsp->fs_cgmask); (void) fprintf(fout, "ncg\t%d\tsize\t%d\tblocks\t%d\n", fsp->fs_ncg, fsp->fs_size, fsp->fs_dsize); (void) fprintf(fout, "bsize\t%d\tshift\t%d\tmask\t0x%08x\n", fsp->fs_bsize, fsp->fs_bshift, fsp->fs_bmask); (void) fprintf(fout, "fsize\t%d\tshift\t%d\tmask\t0x%08x\n", fsp->fs_fsize, fsp->fs_fshift, fsp->fs_fmask); (void) fprintf(fout, "frag\t%d\tshift\t%d\tfsbtodb\t%d\n", fsp->fs_frag, fsp->fs_fragshift, fsp->fs_fsbtodb); (void) fprintf(fout, "minfree\t%d%%\tmaxbpg\t%d\toptim\t%s\n", fsp->fs_minfree, fsp->fs_maxbpg, fsp->fs_optim == FS_OPTSPACE ? "space" : "time"); (void) fprintf(fout, "maxcontig %d\trotdelay %dms\trps\t%d\n", fsp->fs_maxcontig, fsp->fs_rotdelay, fsp->fs_rps); (void) fprintf(fout, "csaddr\t%d\tcssize\t%d\tshift\t%d\tmask\t0x%08x\n", fsp->fs_csaddr, fsp->fs_cssize, fsp->fs_csshift, fsp->fs_csmask); (void) fprintf(fout, "ntrak\t%d\tnsect\t%d\tspc\t%d\tncyl\t%d\n", fsp->fs_ntrak, fsp->fs_nsect, fsp->fs_spc, fsp->fs_ncyl); (void) fprintf(fout, "cpg\t%d\tbpg\t%d\tfpg\t%d\tipg\t%d\n", fsp->fs_cpg, fsp->fs_fpg / fsp->fs_frag, fsp->fs_fpg, fsp->fs_ipg); (void) fprintf(fout, "nindir\t%d\tinopb\t%d\tnspf\t%d\n", fsp->fs_nindir, fsp->fs_inopb, fsp->fs_nspf); (void) fprintf(fout, "nbfree\t%d\tndir\t%d\tnifree\t%d\tnffree\t%d\n", fsp->fs_cstotal.cs_nbfree, fsp->fs_cstotal.cs_ndir, fsp->fs_cstotal.cs_nifree, fsp->fs_cstotal.cs_nffree); (void) fprintf(fout, "cgrotor\t%d\tfmod\t%d\tronly\t%d\tlogbno\t%d\n", fsp->fs_cgrotor, fsp->fs_fmod, fsp->fs_ronly, fsp->fs_logbno); (void) fprintf(fout, "rolled\t%d\tsi\t%d\tflags\t%x\n", fsp->fs_rolled, fsp->fs_si, fsp->fs_flags); (void) fprintf(fout, "version\t%d\n", fsp->fs_version); if (fsp->fs_reclaim & (FS_RECLAIM | FS_RECLAIMING)) { (void) fprintf(fout, "fs_reclaim%s%s\n", (fsp->fs_reclaim & FS_RECLAIM) ? " FS_RECLAIM" : "", (fsp->fs_reclaim & FS_RECLAIMING) ? " FS_RECLAIMING" : ""); } else { (void) fprintf(fout, "fs_reclaim is not set\n"); } if (fsp->fs_state + (long)fsp->fs_time == FSOKAY) { (void) fprintf(fout, gettext( "file system state is valid, fsclean is %d\n"), fsp->fs_clean); } else { (void) fprintf(fout, gettext("file system state is not valid\n")); } if (fsp->fs_cpc != 0) { (void) fprintf(fout, gettext( "blocks available in each rotational position")); } else { (void) fprintf(fout, gettext( "insufficient space to maintain rotational tables\n")); } for (c = 0; c < fsp->fs_cpc; c++) { (void) fprintf(fout, gettext("\ncylinder number %d:"), c); nrpos = (((fsp)->fs_postblformat == FS_DYNAMICPOSTBLFMT) ? (fsp)->fs_nrpos : NRPOS); for (i = 0; i < nrpos; i++) { if (fs_postbl(fsp, c)[i] == -1) continue; (void) fprintf(fout, gettext("\n position %d:\t"), i); /*CSTYLED*/ for (j = fs_postbl(fsp, c)[i], k = 1; ; j += fs_rotbl(fsp)[j], k++) { (void) fprintf(fout, "%5d", j); if (k % 12 == 0) (void) fprintf(fout, "\n\t\t"); if ((fs_rotbl(fsp))[j] == 0) break; } } } (void) fprintf(fout, "\ncs[].cs_(nbfree,ndir,nifree,nffree):\n\t"); sip = calloc(1, fsp->fs_cssize); /* void * cast is to convince lint that sip really is aligned */ fsp->fs_u.fs_csp = (struct csum *)(void *)sip; for (i = 0, j = 0; i < fsp->fs_cssize; i += fsp->fs_bsize, j++) { size = fsp->fs_cssize - i < fsp->fs_bsize ? fsp->fs_cssize - i : fsp->fs_bsize; offset = (offset_t)fsbtodb( fsp, (fsp->fs_csaddr + j * fsp->fs_frag)) * DEV_BSIZE; (void) llseek(h->fd, offset, 0); if (read(h->fd, sip, size) != size) { return (FSTYP_ERR_IO); } sip += size; } for (i = 0; i < fsp->fs_ncg; i++) { struct csum *cs = &fsp->fs_cs(fsp, i); if (i && i % 4 == 0) (void) fprintf(fout, "\n\t"); (void) fprintf(fout, "(%d,%d,%d,%d) ", cs->cs_nbfree, cs->cs_ndir, cs->cs_nifree, cs->cs_nffree); } (void) fprintf(fout, "\n"); if (fsp->fs_ncyl % fsp->fs_cpg) { (void) fprintf(fout, gettext("cylinders in last group %d\n"), i = fsp->fs_ncyl % fsp->fs_cpg); (void) fprintf(fout, gettext("blocks in last group %d\n"), i * fsp->fs_spc / NSPB(fsp)); } (void) fprintf(fout, "\n"); for (i = 0; i < fsp->fs_ncg; i++) dumpcg(h, fout, ferr, i); if (fsp->fs_logbno) dumplog(h, fout, ferr); return (0); } static void setsum(int32_t *sp, int32_t *lp, int nb) { int32_t csum = 0; *sp = 0; nb /= sizeof (int32_t); while (nb--) csum += *lp++; *sp = csum; } static int checksum(int32_t *sp, int32_t *lp, int nb) { int32_t ssum = *sp; setsum(sp, lp, nb); if (ssum != *sp) { *sp = ssum; return (0); } return (1); } /* ARGSUSED */ static void dumplog(fstyp_ufs_t *h, FILE *fout, FILE *ferr) { int i; long tb = 0; extent_block_t *ebp; extent_t *ep; ml_odunit_t *ud; struct fs *fsp = &h->afs; (void) fprintf(fout, "\nlog\n"); if (fsp->fs_magic == FS_MAGIC) (void) fprintf(fout, "log allocation block %d\n", fsp->fs_logbno); else (void) fprintf(fout, "log allocation block (in frags) %d\n", fsp->fs_logbno); (void) llseek(h->fd, (offset_t)logbtodb(fsp, fsp->fs_logbno) * DEV_BSIZE, 0); if (read(h->fd, (char *)&h->eg, fsp->fs_bsize) != fsp->fs_bsize) { (void) fprintf(fout, gettext( "dumplog: error reading log allocation\n")); return; } ebp = (void *)h->eg; if (ebp->type != LUFS_EXTENTS) (void) fprintf(fout, gettext("Invalid log allocation type %x\n"), ebp->type); if (!checksum(&ebp->chksum, (int32_t *)ebp, fsp->fs_bsize)) (void) fprintf(fout, gettext("Invalid log checksum\n")); for (i = 0, ep = &ebp->extents[0]; i < ebp->nextents; ++i, ++ep) { (void) fprintf(fout, "\tlogical block\t%" PRId32 "\tphysical block\t%" PRId32 "\tblocks\t%" PRId32 "\n", ep->lbno, ep->pbno, ep->nbno); tb += dbtob(ep->nbno); } (void) fprintf(fout, "log size %" PRIu32 " bytes (%ld calculated)\n", ebp->nbytes, tb); (void) fprintf(fout, "\n"); ep = &ebp->extents[0]; (void) llseek(h->fd, (offset_t)logbtodb(fsp, ep->pbno) * DEV_BSIZE, 0); if (read(h->fd, &h->eg, dbtob(LS_SECTORS)) != dbtob(LS_SECTORS)) { (void) fprintf(fout, gettext( "dumplog: error reading log state\n")); return; } ud = (void *)&h->eg; (void) fprintf(fout, "version\t\t%" PRIu32 "\t\t", ud->od_version); if (ud->od_badlog) (void) fprintf(fout, "logstate\tError\n"); else (void) fprintf(fout, "logstate\tOkay\n"); (void) fprintf(fout, "bol\t\t%" PRId32 "\t\teol\t\t%" PRId32 "\n", ud->od_bol_lof, ud->od_eol_lof); (void) fprintf(fout, "requestsize\t%" PRIu32 "\n", ud->od_requestsize); (void) fprintf(fout, "statesize\t%" PRIu32 "\n", ud->od_statesize); (void) fprintf(fout, "logsize\t\t%" PRIu32 "\n", ud->od_logsize); (void) fprintf(fout, "maxtransfer\t%" PRIu32 "\t\tdevbsize\t%" PRIu32 "\n", ud->od_maxtransfer, ud->od_devbsize); (void) fprintf(fout, "head\t\t%" PRId32 "\t\thead ident\t%#" PRIx32 "\n", ud->od_head_lof, ud->od_head_ident); (void) fprintf(fout, "tail\t\t%" PRId32 "\t\ttail ident\t%#" PRIx32 "\n", ud->od_tail_lof, ud->od_tail_ident); (void) fprintf(fout, "\t\t\t\tdebug\t\t%#" PRIx32 "\n", ud->od_debug); if (ud->od_head_ident + ud->od_tail_ident != ud->od_chksum) (void) fprintf(fout, "Bad chksum\t%#" PRIx32 "\n", ud->od_chksum); else (void) fprintf(fout, "Good chksum\t%#" PRIx32 "\n", ud->od_chksum); } /* ARGSUSED */ static void dumpcg(fstyp_ufs_t *h, FILE *fout, FILE *ferr, const int c) { int i, j; offset_t off; struct cg *cgp; struct ocg *ocgp; struct fs *fsp = &h->afs; time_t t; (void) fprintf(fout, "\ncg %d:\n", c); off = llseek(h->fd, (offset_t)fsbtodb(fsp, cgtod(fsp, c)) * DEV_BSIZE, 0); if (read(h->fd, (char *)&h->acg, fsp->fs_bsize) != fsp->fs_bsize) { (void) fprintf(fout, gettext("dumpfs: error reading cg\n")); return; } cgp = (struct cg *)&h->acg; ocgp = (struct ocg *)&h->acg; if (!cg_chkmagic(cgp)) (void) fprintf(fout, gettext( "Invalid Cylinder grp magic fffs:%x 4.2 fs:%x\n"), cgp->cg_magic, ocgp->cg_magic); if (cgp->cg_magic == CG_MAGIC) { /* print FFFS 4.3 cyl grp format. */ t = (time_t)cgp->cg_time; (void) fprintf(fout, "magic\t%x\ttell\t%llx\ttime\t%s", cgp->cg_magic, off, ctime(&t)); /* *** */ (void) fprintf(fout, "cgx\t%d\tncyl\t%d\tniblk\t%d\tndblk\t%d\n", cgp->cg_cgx, cgp->cg_ncyl, cgp->cg_niblk, cgp->cg_ndblk); (void) fprintf(fout, "nbfree\t%d\tndir\t%d\tnifree\t%d\tnffree\t%d\n", cgp->cg_cs.cs_nbfree, cgp->cg_cs.cs_ndir, cgp->cg_cs.cs_nifree, cgp->cg_cs.cs_nffree); (void) fprintf(fout, "rotor\t%d\tirotor\t%d\tfrotor\t%d\nfrsum", cgp->cg_rotor, cgp->cg_irotor, cgp->cg_frotor); for (i = 1, j = 0; i < fsp->fs_frag; i++) { (void) fprintf(fout, "\t%d", cgp->cg_frsum[i]); j += i * cgp->cg_frsum[i]; } (void) fprintf(fout, gettext("\nsum of frsum: %d\niused:\t"), j); pbits(fout, cg_inosused(cgp), fsp->fs_ipg); (void) fprintf(fout, gettext("free:\t")); pbits(fout, cg_blksfree(cgp), fsp->fs_fpg); (void) fprintf(fout, "b:\n"); for (i = 0; i < fsp->fs_cpg; i++) { (void) fprintf(fout, " c%d:\t(%d)\t", i, cg_blktot(cgp)[i]); for (j = 0; j < fsp->fs_nrpos; j++) /* ****** */ (void) fprintf(fout, " %d", cg_blks(fsp, cgp, i)[j]); (void) fprintf(fout, "\n"); } } else if (ocgp->cg_magic == CG_MAGIC) { /* print Old cyl grp format. */ t = (time_t)ocgp->cg_time; (void) fprintf(fout, "magic\t%x\ttell\t%llx\ttime\t%s", ocgp->cg_magic, off, ctime(&t)); (void) fprintf(fout, "cgx\t%d\tncyl\t%d\tniblk\t%d\tndblk\t%d\n", ocgp->cg_cgx, ocgp->cg_ncyl, ocgp->cg_niblk, ocgp->cg_ndblk); (void) fprintf(fout, "nbfree\t%d\tndir\t%d\tnifree\t%d\tnffree\t%d\n", ocgp->cg_cs.cs_nbfree, ocgp->cg_cs.cs_ndir, ocgp->cg_cs.cs_nifree, ocgp->cg_cs.cs_nffree); (void) fprintf(fout, "rotor\t%d\tirotor\t%d\tfrotor\t%d\nfrsum", ocgp->cg_rotor, ocgp->cg_irotor, ocgp->cg_frotor); for (i = 1, j = 0; i < fsp->fs_frag; i++) { (void) fprintf(fout, "\t%d", ocgp->cg_frsum[i]); j += i * ocgp->cg_frsum[i]; } (void) fprintf(fout, gettext("\nsum of frsum: %d\niused:\t"), j); pbits(fout, ocgp->cg_iused, fsp->fs_ipg); (void) fprintf(fout, gettext("free:\t")); pbits(fout, ocgp->cg_free, fsp->fs_fpg); (void) fprintf(fout, "b:\n"); for (i = 0; i < fsp->fs_cpg; i++) { (void) fprintf(fout, " c%d:\t(%d)\t", i, ocgp->cg_btot[i]); for (j = 0; j < NRPOS; j++) (void) fprintf(fout, " %d", ocgp->cg_b[i][j]); (void) fprintf(fout, "\n"); } } } static void pbits(FILE *fout, const void *p, const int max) { int i; int count = 0, j; unsigned char *cp = (unsigned char *)p; for (i = 0; i < max; i++) { if (isset(cp, i)) { if (count) (void) fprintf(fout, ",%s", (count % 9 == 8) ? "\n\t" : " "); count++; (void) fprintf(fout, "%d", i); j = i; while ((i + 1) < max && isset(cp, i+1)) i++; if (i != j) (void) fprintf(fout, "-%d", i); } } (void) fprintf(fout, "\n"); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1989,1996,2001 by Sun Microsystems, Inc. # All rights reserved. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= labelit ATTMK= $(LIBPROG) include ../../Makefile.fstype CPPFLAGS += -D_LARGEFILE64_SOURCE # not linted SMATCH=off /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Label a file system volume. */ #include #include #include #include #include #include #include #include #define bcopy(f, t, n) (void) memcpy(t, f, n) #define bzero(s, n) memset(s, 0, n) #define bcmp(s, d, n) memcmp(s, d, n) #define index(s, r) strchr(s, r) #define rindex(s, r) strrchr(s, r) #include #include #include #include static void usage(); static void label(char *, char *, char *); static union sbtag { char dummy[SBSIZE]; struct fs sblk; } sb_un, altsb_un; #define sblock sb_un.sblk #define altsblock altsb_un.sblk extern int optind; extern char *optarg; int main(int argc, char *argv[]) { int opt; char *special = NULL; char *fsname = NULL; char *volume = NULL; while ((opt = getopt(argc, argv, "o:")) != EOF) { switch (opt) { case 'o': /* specific options (none defined yet) */ break; case '?': usage(); } } if (optind > (argc - 1)) { usage(); } argc -= optind; argv = &argv[optind]; special = argv[0]; if (argc > 1) { fsname = argv[1]; if (strlen(fsname) > 6) { (void) fprintf(stderr, gettext("labelit: ")); (void) fprintf(stderr, gettext("fsname can not be longer than 6 characters\n")); exit(31+1); } } if (argc > 2) { volume = argv[2]; if (strlen(volume) > 6) { (void) fprintf(stderr, gettext("labelit: ")); (void) fprintf(stderr, gettext("volume can not be longer than 6 characters\n")); exit(31+1); } } label(special, fsname, volume); return (0); } void usage() { (void) fprintf(stderr, gettext( "ufs usage: labelit [-F ufs] [gen opts] special [fsname volume]\n")); exit(31+1); } void label(char *special, char *fsname, char *volume) { int f; int blk; int i; char *p; offset_t offset; struct fs *fsp, *altfsp; if (fsname == NULL) { f = open64(special, O_RDONLY); } else { f = open64(special, O_RDWR); } if (f < 0) { (void) fprintf(stderr, gettext("labelit: ")); perror("open"); exit(31+1); } if (llseek(f, (offset_t)SBLOCK * DEV_BSIZE, 0) < 0) { (void) fprintf(stderr, gettext("labelit: ")); perror("llseek"); exit(31+1); } if (read(f, &sblock, SBSIZE) != SBSIZE) { (void) fprintf(stderr, gettext("labelit: ")); perror("read"); exit(31+1); } if ((sblock.fs_magic != FS_MAGIC) && (sblock.fs_magic != MTB_UFS_MAGIC)) { (void) fprintf(stderr, gettext("labelit: ")); (void) fprintf(stderr, gettext("bad super block magic number\n")); exit(31+1); } if ((sblock.fs_magic == FS_MAGIC) && ((sblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2) && (sblock.fs_version != UFS_VERSION_MIN))) { (void) fprintf(stderr, gettext("labelit: ")); (void) fprintf(stderr, gettext("unrecognized UFS format version: %d\n"), sblock.fs_version); exit(31+1); } if ((sblock.fs_magic == MTB_UFS_MAGIC) && ((sblock.fs_version > MTB_UFS_VERSION_1) || (sblock.fs_version < MTB_UFS_VERSION_MIN))) { (void) fprintf(stderr, gettext("labelit: ")); (void) fprintf(stderr, gettext("unrecognized UFS format version: %d\n"), sblock.fs_version); exit(31+1); } fsp = &sblock; /* * Is block layout available? */ if (sblock.fs_cpc <= 0 && (fsname || volume)) { (void) fprintf(stderr, gettext("labelit: ")); (void) fprintf(stderr, gettext("insufficient superblock space for file system label\n")); return; } /* * calculate the available blocks for each rotational position */ blk = sblock.fs_spc * sblock.fs_cpc / NSPF(&sblock); for (i = 0; i < blk; i += sblock.fs_frag) /* void */; i -= sblock.fs_frag; blk = i / sblock.fs_frag; p = (char *)&(fs_rotbl(fsp)[blk]); if (fsname != NULL) { for (i = 0; i < 14; i++) p[i] = '\0'; for (i = 0; (i < 6) && (fsname[i]); i++, p++) *p = fsname[i]; p++; } if (volume != NULL) { for (i = 0; (i < 6) && (volume[i]); i++, p++) *p = volume[i]; } if (fsname != NULL) { if (llseek(f, (offset_t)SBLOCK * DEV_BSIZE, 0) < 0) { (void) fprintf(stderr, gettext("labelit: ")); perror("llseek"); exit(31+1); } if (write(f, &sblock, SBSIZE) != SBSIZE) { (void) fprintf(stderr, gettext("labelit: ")); perror("write"); exit(31+1); } for (i = 0; i < sblock.fs_ncg; i++) { /* * In the case of multi-terabyte ufs file * systems, only the first ten and last ten * cylinder groups have copies of the superblock. */ if (sblock.fs_magic == MTB_UFS_MAGIC && sblock.fs_ncg > 20 && (i >= 10 && i < sblock.fs_ncg - 10)) continue; offset = (offset_t)cgsblock(&sblock, i) * sblock.fs_fsize; if (llseek(f, offset, 0) < 0) { (void) fprintf(stderr, gettext("labelit: ")); perror("lseek"); exit(31+1); } altfsp = &altsblock; if (read(f, &altsblock, SBSIZE) != SBSIZE) { (void) fprintf(stderr, gettext("labelit: ")); perror("read"); exit(31+1); } if ((altsblock.fs_magic != FS_MAGIC) && (altsblock.fs_magic != MTB_UFS_MAGIC)) { (void) fprintf(stderr, gettext("labelit: ")); (void) fprintf(stderr, gettext("bad alternate super block(%i) magic number\n"), i); exit(31+1); } if ((altsblock.fs_magic == FS_MAGIC) && ((altsblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2) && (altsblock.fs_version != UFS_VERSION_MIN))) { (void) fprintf(stderr, gettext("labelit: ")); (void) fprintf(stderr, gettext("bad alternate super block UFS format version: %d\n"), altsblock.fs_version); exit(31+1); } if ((altsblock.fs_magic == MTB_UFS_MAGIC) && ((altsblock.fs_version > MTB_UFS_VERSION_1) || (altsblock.fs_version < MTB_UFS_VERSION_MIN))) { (void) fprintf(stderr, gettext("labelit: ")); (void) fprintf(stderr, gettext("bad alternate super block UFS format version: %d\n"), altsblock.fs_version); exit(31+1); } bcopy((char *)&(fs_rotbl(fsp)[blk]), (char *)&(fs_rotbl(altfsp)[blk]), 14); if (llseek(f, offset, 0) < 0) { (void) fprintf(stderr, gettext("labelit: ")); perror("llseek"); exit(31+1); } if (write(f, &altsblock, SBSIZE) != SBSIZE) { (void) fprintf(stderr, gettext("labelit: ")); perror("write"); exit(31+1); } } } p = (char *)&(fs_rotbl(fsp)[blk]); (void) fprintf(stderr, gettext("fsname: ")); for (i = 0; (i < 6) && (*p); i++, p++) { (void) fprintf(stderr, "%c", *p); } (void) fprintf(stderr, "\n"); (void) fprintf(stderr, gettext("volume: ")); p++; for (i = 0; (i < 6); i++, p++) { (void) fprintf(stderr, "%c", *p); } (void) fprintf(stderr, "\n"); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1991,1996,2001 by Sun Microsystems, Inc. # All rights reserved. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= lockfs ATTMK= $(LIBPROG) OTHERINSTALL= $(ROOTUSRSBIN)/$(LIBPROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(LIBPROG) include ../../Makefile.fstype CPPFLAGS += -D_LARGEFILE64_SOURCE SMOFF += all_func_returns $(ROOTUSRSBIN)/$(LIBPROG): $(RM) $@; $(SYMLINK) $(LINKVALUE) $@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * lockfs * user interface to lockfs functionality */ #include #include #include #include #include #include #include #include #include #include #define bzero(s, n) memset(s, 0, n); /* * command line processing */ extern char *optarg; extern int optind; extern int opterr; extern void exit(); static void exitusage(); static void printstatusline(char *, char *, char *); static void printstatus(char *); static void flushfs(char *); static void lockfs(char *); static void getmntnames(); static void getcmdnames(int, char **, int); /* * -a = all * -v = verbose */ int all = 0; int verbose = 0; /* * exitstatus * 0 all ok * 1 internal error * 2 system call error */ int exitstatus = 0; /* * list of filenames */ struct filename { struct filename *fn_next; char *fn_name; }; struct filename *fnanchor = 0; /* * default request is `file system lock status' * default lock type is `unlock' * -wnduhfe changes them */ int request = _FIOLFSS; ushort_t lock = LOCKFS_ULOCK; /* * default comment is null * -c changes it */ caddr_t comment = 0; ulong_t comlen = 0; /* * for prettyprint */ int firsttime = 0; /* * no unlocks printed */ int no_unlocks_printed = 0; /* * file system was modified during hlock/wlock/elock */ #define LOCKWARN(FN, S) \ { \ if (verbose) \ printf("WARNING: %s was modified while %s locked\n", FN, S); \ exitstatus = 2; \ } /* * forward reference */ char *malloc(); int main(int argc, char *argv[]) { int c; struct filename *fnp; exitstatus = 0; /* * process command line */ opterr = 0; optarg = 0; while ((c = getopt(argc, argv, "vfwnduheac:")) != -1) switch (c) { case 'v': verbose = 1; break; case 'f': request = _FIOFFS; break; case 'w': lock = LOCKFS_WLOCK; request = _FIOLFS; break; case 'n': lock = LOCKFS_NLOCK; request = _FIOLFS; break; case 'd': lock = LOCKFS_DLOCK; request = _FIOLFS; break; case 'h': lock = LOCKFS_HLOCK; request = _FIOLFS; break; case 'e': lock = LOCKFS_ELOCK; request = _FIOLFS; break; case 'u': lock = LOCKFS_ULOCK; request = _FIOLFS; break; case 'a': all = 1; break; case 'c': comment = optarg; comlen = strlen(optarg)+1; request = _FIOLFS; break; default: exitusage(); break; } if (argc == 1) { no_unlocks_printed = 1; all = 1; } if (all) /* * use /etc/mtab */ getmntnames(); else /* * use command line */ getcmdnames(argc, argv, optind); /* * for each filename, doit */ for (fnp = fnanchor; fnp; fnp = fnp->fn_next) { switch (request) { case _FIOLFSS: printstatus(fnp->fn_name); break; case _FIOLFS: lockfs(fnp->fn_name); break; case _FIOFFS: flushfs(fnp->fn_name); break; default: break; } } /* * all done */ return (exitstatus); } /* * exitusage * bad command line, give hint */ void exitusage() { printf("usage: lockfs [-dfhnuw] [-c string] [-a] [file system ...]\n"); exit(1); } /* * printstatusline * prettyprint the status line */ void printstatusline(char *fn, char *locktype, char *comment) { if (firsttime++ == 0) printf("%-20s %-10s %s\n", "Filesystem", "Locktype", "Comment"); printf("%-20s %-10s %s\n", fn, locktype, comment); } /* * printstatus * get and prettyprint file system lock status */ void printstatus(char *fn) { int fd; int fsmod = 0; char *locktype; char commentbuffer[LOCKFS_MAXCOMMENTLEN+1]; struct lockfs lf; fd = open64(fn, O_RDONLY); if (fd == -1) { if (errno == EIO) printstatusline(fn, "EIO", "May be hard locked"); else perror(fn); exitstatus = 2; return; } bzero((caddr_t)&lf, sizeof (struct lockfs)); lf.lf_flags = LOCKFS_MOD; lf.lf_comlen = LOCKFS_MAXCOMMENTLEN; lf.lf_comment = commentbuffer; if (ioctl(fd, _FIOLFSS, &lf) == -1) { perror(fn); close(fd); exitstatus = 2; return; } switch (lf.lf_lock) { case LOCKFS_ULOCK: if (no_unlocks_printed) goto out; if (LOCKFS_IS_BUSY(&lf)) locktype = "(unlock)"; else locktype = "unlock"; break; case LOCKFS_WLOCK: if (LOCKFS_IS_BUSY(&lf)) locktype = "(write)"; else { locktype = "write"; fsmod = LOCKFS_IS_MOD(&lf); } break; case LOCKFS_NLOCK: if (LOCKFS_IS_BUSY(&lf)) locktype = "(name)"; else locktype = "name"; break; case LOCKFS_DLOCK: locktype = "delete"; if (LOCKFS_IS_BUSY(&lf)) locktype = "(delete)"; else locktype = "delete"; break; case LOCKFS_HLOCK: if (LOCKFS_IS_BUSY(&lf)) locktype = "(hard)"; else { locktype = "hard"; fsmod = LOCKFS_IS_MOD(&lf); } break; case LOCKFS_ELOCK: if (LOCKFS_IS_BUSY(&lf)) locktype = "(error)"; else { locktype = "error"; fsmod = LOCKFS_IS_MOD(&lf); } break; default: if (LOCKFS_IS_BUSY(&lf)) locktype = "(unknown)"; else locktype = "unknown"; break; } lf.lf_comment[lf.lf_comlen] = '\0'; printstatusline(fn, locktype, lf.lf_comment); if (fsmod) LOCKWARN(fn, locktype); out: close(fd); } /* * flushfs * push and invalidate at least the data that is *currently* dirty */ void flushfs(char *fn) { int fd; fd = open64(fn, O_RDONLY); if (fd == -1) { perror(fn); exitstatus = 2; return; } if (ioctl(fd, _FIOFFS, NULL) == -1) { perror(fn); close(fd); exitstatus = 2; return; } close(fd); } /* * lockfs * lock the file system */ void lockfs(char *fn) { int fd; struct lockfs lf; fd = open64(fn, O_RDONLY); if (fd == -1) { perror(fn); exitstatus = 2; return; } bzero((caddr_t)&lf, sizeof (struct lockfs)); lf.lf_flags = LOCKFS_MOD; if (ioctl(fd, _FIOLFSS, &lf) == -1) { perror(fn); close(fd); exitstatus = 2; return; } if (!LOCKFS_IS_BUSY(&lf) && LOCKFS_IS_MOD(&lf)) { if (LOCKFS_IS_HLOCK(&lf)) LOCKWARN(fn, "hard"); if (LOCKFS_IS_ELOCK(&lf)) LOCKWARN(fn, "error"); if (LOCKFS_IS_WLOCK(&lf)) LOCKWARN(fn, "write"); } lf.lf_lock = lock; lf.lf_flags = 0; lf.lf_key = lf.lf_key; lf.lf_comment = comment; lf.lf_comlen = (comment) ? strlen(comment)+1 : 0; if (ioctl(fd, _FIOLFS, &lf) == -1) { perror(fn); close(fd); exitstatus = 2; return; } close(fd); } /* * getmntnames * file names from /etc/mtab */ void getmntnames() { int fnlen; struct filename *fnp; struct filename *fnpc; FILE *mnttab; struct mnttab mnt, *mntp = &mnt; fnpc = fnanchor; if ((mnttab = fopen(MNTTAB, "r")) == NULL) { fprintf(stderr, "Can't open %s\n", MNTTAB); perror(MNTTAB); exit(32); } while ((getmntent(mnttab, mntp)) == 0) { if (strcmp(mntp->mnt_fstype, MNTTYPE_UFS) != 0) continue; fnlen = strlen(mntp->mnt_mountp) + 1; fnp = (struct filename *)malloc(sizeof (struct filename)); fnp->fn_name = malloc((uint_t)fnlen); strcpy(fnp->fn_name, mntp->mnt_mountp); fnp->fn_next = NULL; if (fnpc) fnpc->fn_next = fnp; else fnanchor = fnp; fnpc = fnp; } fclose(mnttab); } /* * getcmdnames * file names from command line */ void getcmdnames(int argc, char **argv, int i) { struct filename *fnp; struct filename *fnpc; for (fnpc = fnanchor; i < argc; ++i) { fnp = (struct filename *)malloc(sizeof (struct filename)); fnp->fn_name = *(argv+i); fnp->fn_next = NULL; if (fnpc) fnpc->fn_next = fnp; else fnanchor = fnp; fnpc = fnp; } } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2019, Joyent, Inc. # FSTYPE= ufs LIBPROG= mkfs ATTMK= $(LIBPROG) include ../../Makefile.fstype include ../Makefile.roll CPPFLAGS += -I../../ OBJS= $(LIBPROG).o $(ROLLOBJS) $(FSLIB) SRCS= $(LIBPROG).c $(ROLLSRCS) $(FSLIBSRC) MKFSOBJS= mkfs.o CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-unused-variable CERRWARN += -Wno-empty-body CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress range_check_64 pointer type mismatch warning CERRWARN += -Wno-incompatible-pointer-types # can't hack main() ! SMATCH = off # for messaging catalog # POFILE= mkfs.po catalog: $(POFILE) $(POFILE): $(SRCS) $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) $(POFILE).i messages.po CPPFLAGS += -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 LDLIBS += -ladm -lefi $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(MKFSOBJS) $(FSLIB) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Copyright (c) 2018, Joyent, Inc. */ /* * The maximum supported file system size (in sectors) is the * number of frags that can be represented in an int32_t field * (INT_MAX) times the maximum number of sectors per frag. Since * the maximum frag size is MAXBSIZE, the maximum number of sectors * per frag is MAXBSIZE/DEV_BSIZE. */ #define FS_MAX (((diskaddr_t)INT_MAX) * (MAXBSIZE/DEV_BSIZE)) /* * make file system for cylinder-group style file systems * * usage: * * mkfs [-F FSType] [-V] [-G [-P]] [-M dirname] [-m] [options] * [-o specific_options] special size * [nsect ntrack bsize fsize cpg minfree rps nbpi opt apc rotdelay * 2 3 4 5 6 7 8 9 10 11 12 * nrpos maxcontig mtb] * 13 14 15 * * where specific_options are: * N - no create * nsect - The number of sectors per track * ntrack - The number of tracks per cylinder * bsize - block size * fragsize - fragment size * cgsize - The number of disk cylinders per cylinder group. * free - minimum free space * rps - rotational speed (rev/sec). * nbpi - number of data bytes per allocated inode * opt - optimization (space, time) * apc - number of alternates * gap - gap size * nrpos - number of rotational positions * maxcontig - maximum number of logical blocks that will be * allocated contiguously before inserting rotational delay * mtb - if "y", set up file system for eventual growth to over a * a terabyte * -P Do not grow the file system, but print on stdout the maximal * size in sectors to which the file system can be increased. The calculated * size is limited by the value provided by the operand size. * * Note that -P is a project-private interface and together with -G intended * to be used only by the growfs script. It is therefore purposely not * documented in the man page. * The -P option is covered by PSARC case 2003/422. */ /* * The following constants set the defaults used for the number * of sectors/track (fs_nsect), and number of tracks/cyl (fs_ntrak). * * NSECT NTRAK * 72MB CDC 18 9 * 30MB CDC 18 5 * 720KB Diskette 9 2 * * However the defaults will be different for disks larger than CHSLIMIT. */ #define DFLNSECT 32 #define DFLNTRAK 16 /* * The following default sectors and tracks values are used for * non-efi disks that are larger than the CHS addressing limit. The * existing default cpg of 16 (DESCPG) holds good for larger disks too. */ #define DEF_SECTORS_EFI 128 #define DEF_TRACKS_EFI 48 /* * The maximum number of cylinders in a group depends upon how much * information can be stored on a single cylinder. The default is to * use 16 cylinders per group. This is effectively tradition - it was * the largest value acceptable under SunOs 4.1 */ #define DESCPG 16 /* desired fs_cpg */ /* * The following two constants set the default block and fragment sizes. * Both constants must be a power of 2 and meet the following constraints: * MINBSIZE <= DESBLKSIZE <= MAXBSIZE * DEV_BSIZE <= DESFRAGSIZE <= DESBLKSIZE * DESBLKSIZE / DESFRAGSIZE <= 8 */ #define DESBLKSIZE 8192 #define DESFRAGSIZE 1024 /* * MINFREE gives the minimum acceptable percentage of file system * blocks which may be free. If the freelist drops below this level * only the superuser may continue to allocate blocks. This may * be set to 0 if no reserve of free blocks is deemed necessary, * however throughput drops by fifty percent if the file system * is run at between 90% and 100% full; thus the default value of * fs_minfree is 10%. With 10% free space, fragmentation is not a * problem, so we choose to optimize for time. */ #define MINFREE 10 #define DEFAULTOPT FS_OPTTIME /* * ROTDELAY gives the minimum number of milliseconds to initiate * another disk transfer on the same cylinder. It is no longer used * and will always default to 0. */ #define ROTDELAY 0 /* * MAXBLKPG determines the maximum number of data blocks which are * placed in a single cylinder group. The default is one indirect * block worth of data blocks. */ #define MAXBLKPG(bsize) ((bsize) / sizeof (daddr32_t)) /* * Each file system has a number of inodes statically allocated. * We allocate one inode slot per NBPI bytes, expecting this * to be far more than we will ever need. */ #define NBPI 2048 /* Number Bytes Per Inode */ #define MTB_NBPI (MB) /* Number Bytes Per Inode for multi-terabyte */ /* * Disks are assumed to rotate at 60HZ, unless otherwise specified. */ #define DEFHZ 60 /* * Cylinder group related limits. * * For each cylinder we keep track of the availability of blocks at different * rotational positions, so that we can lay out the data to be picked * up with minimum rotational latency. NRPOS is the number of rotational * positions which we distinguish. With NRPOS 8 the resolution of our * summary information is 2ms for a typical 3600 rpm drive. */ #define NRPOS 8 /* number distinct rotational positions */ #ifdef DEBUG #define dbgprintf(x) printf x #else #define dbgprintf(x) #endif /* * For the -N option, when calculating the backup superblocks, do not print * them if we are not really sure. We may have to try an alternate method of * arriving at the superblocks. So defer printing till a handful of superblocks * look good. */ #define tprintf(x) if (Nflag && retry) \ (void) strncat(tmpbuf, x, strlen(x)); \ else \ (void) fprintf(stderr, x); #define ALTSB 32 /* Location of first backup superblock */ /* * range_check "user_supplied" flag values. */ #define RC_DEFAULT 0 #define RC_KEYWORD 1 #define RC_POSITIONAL 2 /* * ufs hole */ #define UFS_HOLE -1 #ifndef STANDALONE #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "roll_log.h" #define bcopy(f, t, n) (void) memcpy(t, f, n) #define bzero(s, n) (void) memset(s, 0, n) #define bcmp(s, d, n) memcmp(s, d, n) #define index(s, r) strchr(s, r) #define rindex(s, r) strrchr(s, r) #include #include #include #include #include /* for ENDIAN defines */ #include #include #include extern offset_t llseek(); extern char *getfullblkname(); extern long lrand48(); extern int optind; extern char *optarg; /* * The size of a cylinder group is calculated by CGSIZE. The maximum size * is limited by the fact that cylinder groups are at most one block. * Its size is derived from the size of the maps maintained in the * cylinder group and the (struct cg) size. */ #define CGSIZE(fs) \ /* base cg */ (sizeof (struct cg) + \ /* blktot size */ (fs)->fs_cpg * sizeof (long) + \ /* blks size */ (fs)->fs_cpg * (fs)->fs_nrpos * sizeof (short) + \ /* inode map */ howmany((fs)->fs_ipg, NBBY) + \ /* block map */ howmany((fs)->fs_cpg * (fs)->fs_spc / NSPF(fs), NBBY)) /* * We limit the size of the inode map to be no more than a * third of the cylinder group space, since we must leave at * least an equal amount of space for the block map. * * N.B.: MAXIpG must be a multiple of INOPB(fs). */ #define MAXIpG(fs) roundup((fs)->fs_bsize * NBBY / 3, INOPB(fs)) /* * Same as MAXIpG, but parameterized by the block size (b) and the * cylinder group divisor (d), which is the reciprocal of the fraction of the * cylinder group overhead block that is used for the inode map. So for * example, if d = 5, the macro's computation assumes that 1/5 of the * cylinder group overhead block can be dedicated to the inode map. */ #define MAXIpG_B(b, d) roundup((b) * NBBY / (d), (b) / sizeof (struct dinode)) #define UMASK 0755 #define MAXINOPB (MAXBSIZE / sizeof (struct dinode)) #define POWEROF2(num) (((num) & ((num) - 1)) == 0) #define MB (1024*1024) #define BETWEEN(x, l, h) ((x) >= (l) && (x) <= (h)) /* * Used to set the inode generation number. Since both inodes and dinodes * are dealt with, we really need a pointer to an icommon here. */ #define IRANDOMIZE(icp) (icp)->ic_gen = lrand48(); /* * Flags for number() */ #define ALLOW_PERCENT 0x01 /* allow trailing `%' on number */ #define ALLOW_MS1 0x02 /* allow trailing `ms', state 1 */ #define ALLOW_MS2 0x04 /* allow trailing `ms', state 2 */ #define ALLOW_END_ONLY 0x08 /* must be at end of number & suffixes */ #define MAXAIO 1000 /* maximum number of outstanding I/O's we'll manage */ #define BLOCK 1 /* block in aiowait */ #define NOBLOCK 0 /* don't block in aiowait */ #define RELEASE 1 /* free an aio buffer after use */ #define SAVE 0 /* don't free the buffer */ typedef struct aio_trans { aio_result_t resultbuf; diskaddr_t bno; char *buffer; int size; int release; struct aio_trans *next; } aio_trans; typedef struct aio_results { int max; int outstanding; int maxpend; aio_trans *trans; } aio_results; int aio_inited = 0; aio_results results; /* * Allow up to MAXBUF aio requests that each have a unique buffer. * More aio's might be done, but not using memory through the getbuf() * interface. This can be raised, but you run into the potential of * using more memory than is physically available on the machine, * and if you start swapping, you can forget about performance. * To prevent this, we also limit the total memory used for a given * type of buffer to MAXBUFMEM. * * Tests indicate a cylinder group's worth of inodes takes: * * NBPI Size of Inode Buffer * 2k 1688k * 8k 424k * * initcg() stores all the inodes for a cylinder group in one buffer, * so allowing 20 buffers could take 32 MB if not limited by MAXBUFMEM. */ #define MAXBUF 20 #define MAXBUFMEM (8 * 1024 * 1024) /* * header information for buffers managed by getbuf() and freebuf() */ typedef struct bufhdr { struct bufhdr *head; struct bufhdr *next; } bufhdr; int bufhdrsize; bufhdr inodebuf = { NULL, NULL }; bufhdr cgsumbuf = { NULL, NULL }; #define SECTORS_PER_TERABYTE (1LL << 31) /* * The following constant specifies an upper limit for file system size * that is actually a lot bigger than we expect to support with UFS. (Since * it's specified in sectors, the file system size would be 2**44 * 512, * which is 2**53, which is 8192 Terabytes.) However, it's useful * for checking the basic sanity of a size value that is input on the * command line. */ #define FS_SIZE_UPPER_LIMIT 0x100000000000LL /* * Forward declarations */ static char *getbuf(bufhdr *bufhead, int size); static void freebuf(char *buf); static void freetrans(aio_trans *transp); static aio_trans *get_aiop(); static aio_trans *wait_for_write(int block); static void initcg(int cylno); static void fsinit(); static int makedir(struct direct *protodir, int entries); static void iput(struct inode *ip); static void rdfs(diskaddr_t bno, int size, char *bf); static void wtfs(diskaddr_t bno, int size, char *bf); static void awtfs(diskaddr_t bno, int size, char *bf, int release); static void wtfs_breakup(diskaddr_t bno, int size, char *bf); static int isblock(struct fs *fs, unsigned char *cp, int h); static void clrblock(struct fs *fs, unsigned char *cp, int h); static void setblock(struct fs *fs, unsigned char *cp, int h); static void usage(void) __NORETURN; static void dump_fscmd(char *fsys, int fsi); static uint64_t number(uint64_t d_value, char *param, int flags); static int match(char *s); static char checkopt(char *optim); static char checkmtb(char *mtbarg); static void range_check(long *varp, char *name, long minimum, long maximum, long def_val, int user_supplied); static void range_check_64(uint64_t *varp, char *name, uint64_t minimum, uint64_t maximum, uint64_t def_val, int user_supplied); static daddr32_t alloc(int size, int mode); static diskaddr_t get_max_size(int fd); static long get_max_track_size(int fd); static void block_sigint(sigset_t *old_mask); static void unblock_sigint(sigset_t *old_mask); static void recover_from_sigint(int signum); static int confirm_abort(void); static int getaline(FILE *fp, char *loc, int maxlen); static void flush_writes(void); static long compute_maxcpg(long, long, long, long, long); static int in_64bit_mode(void); static int validate_size(int fd, diskaddr_t size); static void dump_sblock(void); /* * Workaround for mkfs to function properly on disks attached to XMIT 2.X * controller. If the address is not aligned at 8 byte boundary, mkfs on * disks attached to XMIT 2.X controller exhibts un-predictable behaviour. */ #define XMIT_2_X_ALIGN 8 #pragma align XMIT_2_X_ALIGN(fsun, altfsun, cgun) union { struct fs fs; char pad[SBSIZE]; } fsun, altfsun; #define sblock fsun.fs #define altsblock altfsun.fs struct csum *fscs; union cgun { struct cg cg; char pad[MAXBSIZE]; } cgun; #define acg cgun.cg /* * Size of screen in cols in which to fit output */ #define WIDTH 80 struct dinode zino[MAXBSIZE / sizeof (struct dinode)]; /* * file descriptors used for rdfs(fsi) and wtfs(fso). * Initialized to an illegal file descriptor number. */ int fsi = -1; int fso = -1; /* * The BIG parameter is machine dependent. It should be a longlong integer * constant that can be used by the number parser to check the validity * of numeric parameters. */ #define BIG 0x7fffffffffffffffLL /* Used to indicate to number() that a bogus value should cause us to exit */ #define NO_DEFAULT LONG_MIN /* * INVALIDSBLIMIT is the number of bad backup superblocks that will be * tolerated before we decide to try arriving at a different set of them * using a different logic. This is applicable for non-EFI disks only. */ #define INVALIDSBLIMIT 10 /* * The *_flag variables are used to indicate that the user specified * the values, rather than that we made them up ourselves. We can * complain about the user giving us bogus values. */ /* semi-constants */ long sectorsize = DEV_BSIZE; /* bytes/sector from param.h */ long bbsize = BBSIZE; /* boot block size */ long sbsize = SBSIZE; /* superblock size */ /* parameters */ diskaddr_t fssize_db; /* file system size in disk blocks */ diskaddr_t fssize_frag; /* file system size in frags */ long cpg; /* cylinders/cylinder group */ int cpg_flag = RC_DEFAULT; long rotdelay = -1; /* rotational delay between blocks */ int rotdelay_flag = RC_DEFAULT; long maxcontig; /* max contiguous blocks to allocate */ int maxcontig_flag = RC_DEFAULT; long nsect = DFLNSECT; /* sectors per track */ int nsect_flag = RC_DEFAULT; long ntrack = DFLNTRAK; /* tracks per cylinder group */ int ntrack_flag = RC_DEFAULT; long bsize = DESBLKSIZE; /* filesystem block size */ int bsize_flag = RC_DEFAULT; long fragsize = DESFRAGSIZE; /* filesystem fragment size */ int fragsize_flag = RC_DEFAULT; long minfree = MINFREE; /* fs_minfree */ int minfree_flag = RC_DEFAULT; long rps = DEFHZ; /* revolutions/second of drive */ int rps_flag = RC_DEFAULT; long nbpi = NBPI; /* number of bytes per inode */ int nbpi_flag = RC_DEFAULT; long nrpos = NRPOS; /* number of rotational positions */ int nrpos_flag = RC_DEFAULT; long apc = 0; /* alternate sectors per cylinder */ int apc_flag = RC_DEFAULT; char opt = 't'; /* optimization style, `t' or `s' */ char mtb = 'n'; /* multi-terabyte format, 'y' or 'n' */ #define DEFAULT_SECT_TRAK_CPG (nsect_flag == RC_DEFAULT && \ ntrack_flag == RC_DEFAULT && \ cpg_flag == RC_DEFAULT) long debug = 0; /* enable debugging output */ int spc_flag = 0; /* alternate sectors specified or */ /* found */ /* global state */ int Nflag; /* do not write to disk */ int mflag; /* return the command line used to create this FS */ int rflag; /* report the superblock in an easily-parsed form */ int Rflag; /* dump the superblock in binary */ char *fsys; time_t mkfstime; char *string; int label_type; /* * logging support */ int islog; /* true if ufs logging is enabled */ int islogok; /* true if ufs log state is good */ int waslog; /* true when ufs logging disabled during grow */ /* * growfs defines, globals, and forward references */ #define NOTENOUGHSPACE 33 int grow; #define GROW_WITH_DEFAULT_TRAK (grow && ntrack_flag == RC_DEFAULT) static int Pflag; /* probe to which size the fs can be grown */ int ismounted; char *directory; diskaddr_t grow_fssize; long grow_fs_size; long grow_fs_ncg; diskaddr_t grow_fs_csaddr; long grow_fs_cssize; int grow_fs_clean; struct csum *grow_fscs; diskaddr_t grow_sifrag; int test; int testforce; diskaddr_t testfrags; int inlockexit; int isbad; void lockexit(int) __NORETURN; void randomgeneration(void); void checksummarysize(void); int checksblock(struct fs, int); void growinit(char *); void checkdev(char *, char *); void checkmount(struct mnttab *, char *); struct dinode *gdinode(ino_t); int csfraginrange(daddr32_t); struct csfrag *findcsfrag(daddr32_t, struct csfrag **); void checkindirect(ino_t, daddr32_t *, daddr32_t, int); void addcsfrag(ino_t, daddr32_t, struct csfrag **); void delcsfrag(daddr32_t, struct csfrag **); void checkdirect(ino_t, daddr32_t *, daddr32_t *, int); void findcsfragino(void); void fixindirect(daddr32_t, int); void fixdirect(caddr_t, daddr32_t, daddr32_t *, int); void fixcsfragino(void); void extendsummaryinfo(void); int notenoughspace(void); void unalloccsfragino(void); void unalloccsfragfree(void); void findcsfragfree(void); void copycsfragino(void); void rdcg(long); void wtcg(void); void flcg(void); void allocfrags(long, daddr32_t *, long *); void alloccsfragino(void); void alloccsfragfree(void); void freefrags(daddr32_t, long, long); int findfreerange(long *, long *); void resetallocinfo(void); void extendcg(long); void ulockfs(void); void wlockfs(void); void clockfs(void); void wtsb(void); static int64_t checkfragallocated(daddr32_t); static struct csum *read_summaryinfo(struct fs *); static diskaddr_t probe_summaryinfo(); int main(int argc, char *argv[]) { long i, mincpc, mincpg, ibpcl; long cylno, rpos, blk, j, warn = 0; long mincpgcnt, maxcpg; uint64_t used, bpcg, inospercg; long mapcramped, inodecramped; long postblsize, rotblsize, totalsbsize; FILE *mnttab; struct mnttab mntp; char *special; struct statvfs64 fs; struct dk_geom dkg; struct dk_minfo dkminfo; char pbuf[sizeof (uint64_t) * 3 + 1]; char *tmpbuf; int width, plen; uint64_t num; int c, saverr; diskaddr_t max_fssize; long tmpmaxcontig = -1; struct sigaction sigact; uint64_t nbytes64; int remaining_cg; int do_dot = 0; int use_efi_dflts = 0, retry = 0, isremovable = 0, ishotpluggable = 0; int invalid_sb_cnt, ret, skip_this_sb, cg_too_small; int geom_nsect, geom_ntrack, geom_cpg; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); while ((c = getopt(argc, argv, "F:bmo:VPGM:T:t:")) != EOF) { switch (c) { case 'F': string = optarg; if (strcmp(string, "ufs") != 0) usage(); break; case 'm': /* return command line used to create this FS */ mflag++; break; case 'o': /* * ufs specific options. */ string = optarg; while (*string != '\0') { if (match("nsect=")) { nsect = number(DFLNSECT, "nsect", 0); nsect_flag = RC_KEYWORD; } else if (match("ntrack=")) { ntrack = number(DFLNTRAK, "ntrack", 0); ntrack_flag = RC_KEYWORD; } else if (match("bsize=")) { bsize = number(DESBLKSIZE, "bsize", 0); bsize_flag = RC_KEYWORD; } else if (match("fragsize=")) { fragsize = number(DESFRAGSIZE, "fragsize", 0); fragsize_flag = RC_KEYWORD; } else if (match("cgsize=")) { cpg = number(DESCPG, "cgsize", 0); cpg_flag = RC_KEYWORD; } else if (match("free=")) { minfree = number(MINFREE, "free", ALLOW_PERCENT); minfree_flag = RC_KEYWORD; } else if (match("maxcontig=")) { tmpmaxcontig = number(-1, "maxcontig", 0); maxcontig_flag = RC_KEYWORD; } else if (match("nrpos=")) { nrpos = number(NRPOS, "nrpos", 0); nrpos_flag = RC_KEYWORD; } else if (match("rps=")) { rps = number(DEFHZ, "rps", 0); rps_flag = RC_KEYWORD; } else if (match("nbpi=")) { nbpi = number(NBPI, "nbpi", 0); nbpi_flag = RC_KEYWORD; } else if (match("opt=")) { opt = checkopt(string); } else if (match("mtb=")) { mtb = checkmtb(string); } else if (match("apc=")) { apc = number(0, "apc", 0); apc_flag = RC_KEYWORD; } else if (match("gap=")) { (void) number(0, "gap", ALLOW_MS1); rotdelay = ROTDELAY; rotdelay_flag = RC_DEFAULT; } else if (match("debug=")) { debug = number(0, "debug", 0); } else if (match("N")) { Nflag++; } else if (match("calcsb")) { rflag++; Nflag++; } else if (match("calcbinsb")) { rflag++; Rflag++; Nflag++; } else if (*string == '\0') { break; } else { (void) fprintf(stderr, gettext( "illegal option: %s\n"), string); usage(); } if (*string == ',') string++; if (*string == ' ') string++; } break; case 'V': { char *opt_text; int opt_count; (void) fprintf(stdout, gettext("mkfs -F ufs ")); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) fprintf(stdout, " %s ", opt_text); } (void) fprintf(stdout, "\n"); } break; case 'b': /* do nothing for this */ break; case 'M': /* grow the mounted file system */ directory = optarg; /* FALLTHROUGH */ case 'G': /* grow the file system */ grow = 1; break; case 'P': /* probe the file system growing size */ Pflag = 1; grow = 1; /* probe mode implies fs growing */ break; case 'T': /* For testing */ testforce = 1; /* FALLTHROUGH */ case 't': test = 1; string = optarg; testfrags = number(NO_DEFAULT, "testfrags", 0); break; case '?': usage(); break; } } #ifdef MKFS_DEBUG /* * Turning on MKFS_DEBUG causes mkfs to produce a filesystem * that can be reproduced by setting the time to 0 and seeding * the random number generator to a constant. */ mkfstime = 0; /* reproducible results */ #else (void) time(&mkfstime); #endif if (optind >= (argc - 1)) { if (optind > (argc - 1)) { (void) fprintf(stderr, gettext("special not specified\n")); usage(); } else if (mflag == 0) { (void) fprintf(stderr, gettext("size not specified\n")); usage(); } } argc -= optind; argv = &argv[optind]; fsys = argv[0]; fsi = open64(fsys, O_RDONLY); if (fsi < 0) { (void) fprintf(stderr, gettext("%s: cannot open\n"), fsys); lockexit(32); } if (mflag) { dump_fscmd(fsys, fsi); lockexit(0); } /* * The task of setting all of the configuration parameters for a * UFS file system is basically a matter of solving n equations * in m variables. Typically, m is greater than n, so there is * usually more than one valid solution. Since this is usually * an under-constrained problem, it's not always obvious what the * "best" configuration is. * * In general, the approach is to * 1. Determine the values for the file system parameters * that are externally contrained and therefore not adjustable * by mkfs (such as the device's size and maxtransfer size). * 2. Acquire the user's requested setting for all configuration * values that can be set on the command line. * 3. Determine the final value of all configuration values, by * the following approach: * - set the file system block size (fs_bsize). Although * this could be regarded as an adjustable parameter, in * fact, it's pretty much a constant. At this time, it's * generally set to 8k (with older hardware, it can * sometimes make sense to set it to 4k, but those * situations are pretty rare now). * - re-adjust the maximum file system size based on the * value of the file system block size. Since the * frag size can't be any larger than a file system * block, and the number of frags in the file system * has to fit into 31 bits, the file system block size * affects the maximum file system size. * - now that the real maximum file system is known, set the * actual size of the file system to be created to * MIN(requested size, maximum file system size). * - now validate, and if necessary, adjust the following * values: * rotdelay * nsect * maxcontig * apc * frag_size * rps * minfree * nrpos * nrack * nbpi * - calculate maxcpg (the maximum value of the cylinders-per- * cylinder-group configuration parameters). There are two * algorithms for calculating maxcpg: an old one, which is * used for file systems of less than 1 terabyte, and a * new one, implemented in the function compute_maxcpg(), * which is used for file systems of greater than 1 TB. * The difference between them is that compute_maxcpg() * really tries to maximize the cpg value. The old * algorithm fails to take advantage of smaller frags and * lower inode density when determining the maximum cpg, * and thus comes up with much lower numbers in some * configurations. At some point, we might use the * new algorithm for determining maxcpg for all file * systems, but at this time, the changes implemented for * multi-terabyte UFS are NOT being automatically applied * to UFS file systems of less than a terabyte (in the * interest of not changing existing UFS policy too much * until the ramifications of the changes are well-understood * and have been evaluated for their effects on performance.) * - check the current values of the configuration parameters * against the various constraints imposed by UFS. These * include: * * There must be at least one inode in each * cylinder group. * * The cylinder group overhead block, which * contains the inode and frag bigmaps, must fit * within one file system block. * * The space required for inode maps should * occupy no more than a third of the cylinder * group overhead block. * * The rotational position tables have to fit * within the available space in the super block. * Adjust the configuration values that can be adjusted * so that these constraints are satisfied. The * configuration values that are adjustable are: * * frag size * * cylinders per group * * inode density (can be increased) * * number of rotational positions (the rotational * position tables are eliminated altogether if * there isn't enough room for them.) * 4. Set the values for all the dependent configuration * values (those that aren't settable on the command * line and which are completely dependent on the * adjustable parameters). This include cpc (cycles * per cylinder, spc (sectors-per-cylinder), and many others. */ /* * Figure out the partition size and initialize the label_type. */ max_fssize = get_max_size(fsi); /* * Get and check positional arguments, if any. */ switch (argc - 1) { default: usage(); /*NOTREACHED*/ case 15: mtb = checkmtb(argv[15]); /* FALLTHROUGH */ case 14: string = argv[14]; tmpmaxcontig = number(-1, "maxcontig", 0); maxcontig_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 13: string = argv[13]; nrpos = number(NRPOS, "nrpos", 0); nrpos_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 12: string = argv[12]; rotdelay = ROTDELAY; rotdelay_flag = RC_DEFAULT; /* FALLTHROUGH */ case 11: string = argv[11]; apc = number(0, "apc", 0); apc_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 10: opt = checkopt(argv[10]); /* FALLTHROUGH */ case 9: string = argv[9]; nbpi = number(NBPI, "nbpi", 0); nbpi_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 8: string = argv[8]; rps = number(DEFHZ, "rps", 0); rps_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 7: string = argv[7]; minfree = number(MINFREE, "free", ALLOW_PERCENT); minfree_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 6: string = argv[6]; cpg = number(DESCPG, "cgsize", 0); cpg_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 5: string = argv[5]; fragsize = number(DESFRAGSIZE, "fragsize", 0); fragsize_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 4: string = argv[4]; bsize = number(DESBLKSIZE, "bsize", 0); bsize_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 3: string = argv[3]; ntrack = number(DFLNTRAK, "ntrack", 0); ntrack_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 2: string = argv[2]; nsect = number(DFLNSECT, "nsect", 0); nsect_flag = RC_POSITIONAL; /* FALLTHROUGH */ case 1: string = argv[1]; fssize_db = number(max_fssize, "size", 0); } /* * Initialize the parameters in the same way as newfs so that * newfs and mkfs would result in the same file system layout * for EFI labelled disks. Do this only in the absence of user * specified values for these parameters. */ if (label_type == LABEL_TYPE_EFI) { if (apc_flag == RC_DEFAULT) apc = 0; if (nrpos_flag == RC_DEFAULT) nrpos = 1; if (ntrack_flag == RC_DEFAULT) ntrack = DEF_TRACKS_EFI; if (rps_flag == RC_DEFAULT) rps = DEFHZ; if (nsect_flag == RC_DEFAULT) nsect = DEF_SECTORS_EFI; } if ((maxcontig_flag == RC_DEFAULT) || (tmpmaxcontig == -1) || (maxcontig == -1)) { long maxtrax = get_max_track_size(fsi); maxcontig = maxtrax / bsize; } else { maxcontig = tmpmaxcontig; } dbgprintf(("DeBuG maxcontig : %ld\n", maxcontig)); if (rotdelay == -1) { /* default by newfs and mkfs */ rotdelay = ROTDELAY; } if (cpg_flag == RC_DEFAULT) { /* If not explicity set, use default */ cpg = DESCPG; } dbgprintf(("DeBuG cpg : %ld\n", cpg)); /* * Now that we have the semi-sane args, either positional, via -o, * or by defaulting, handle inter-dependencies and range checks. */ /* * Settle the file system block size first, since it's a fixed * parameter once set and so many other parameters, including * max_fssize, depend on it. */ range_check(&bsize, "bsize", MINBSIZE, MAXBSIZE, DESBLKSIZE, bsize_flag); if (!POWEROF2(bsize)) { (void) fprintf(stderr, gettext("block size must be a power of 2, not %ld\n"), bsize); bsize = DESBLKSIZE; (void) fprintf(stderr, gettext("mkfs: bsize reset to default %ld\n"), bsize); } if (fssize_db > max_fssize && validate_size(fsi, fssize_db)) { (void) fprintf(stderr, gettext( "Warning: the requested size of this file system\n" "(%lld sectors) is greater than the size of the\n" "device reported by the driver (%lld sectors).\n" "However, a read of the device at the requested size\n" "does succeed, so the requested size will be used.\n"), fssize_db, max_fssize); max_fssize = fssize_db; } /* * Since the maximum allocatable unit (the frag) must be less than * or equal to bsize, and the number of frags must be less than or * equal to INT_MAX, the total size of the file system (in * bytes) must be less than or equal to bsize * INT_MAX. */ if (max_fssize > ((diskaddr_t)bsize/DEV_BSIZE) * INT_MAX) max_fssize = ((diskaddr_t)bsize/DEV_BSIZE) * INT_MAX; range_check_64(&fssize_db, "size", 1024LL, max_fssize, max_fssize, 1); if (fssize_db >= SECTORS_PER_TERABYTE) { mtb = 'y'; if (!in_64bit_mode()) { (void) fprintf(stderr, gettext( "mkfs: Warning: Creating a file system greater than 1 terabyte on a\n" " system running a 32-bit kernel. This file system will not be\n" " accessible until the system is rebooted with a 64-bit kernel.\n")); } } dbgprintf(("DeBuG mtb : %c\n", mtb)); /* * With newer and much larger disks, the newfs(8) and mkfs_ufs(8) * commands had problems in correctly handling the "native" geometries * for various storage devices. * * To handle the new age disks, mkfs_ufs(8) will use the EFI style * for non-EFI disks that are larger than the CHS addressing limit * ( > 8GB approx ) and ignore the disk geometry information for * these drives. This is what is currently done for multi-terrabyte * filesystems on EFI disks. * * However if the user asked for a specific layout by supplying values * for even one of the three parameters (nsect, ntrack, cpg), honour * the user supplied parameters. * * Choosing EFI style or native geometry style can make a lot of * difference, because the size of a cylinder group is dependent on * this choice. This in turn means that the position of alternate * superblocks varies depending on the style chosen. It is not * necessary that all disks of size > CHSLIMIT have EFI style layout. * There can be disks which are > CHSLIMIT size, but have native * geometry style layout, thereby warranting the need for alternate * logic in superblock detection. */ if (mtb != 'y' && (ntrack == -1 || GROW_WITH_DEFAULT_TRAK || DEFAULT_SECT_TRAK_CPG)) { /* * "-1" indicates that we were called from newfs and ntracks * was not specified in newfs command line. Calculate nsect * and ntrack in the same manner as newfs. * * This is required because, the defaults for nsect and ntrack * is hardcoded in mkfs, whereas to generate the alternate * superblock locations for the -N option, there is a need for * the geometry based values that newfs would have arrived at. * Newfs would have arrived at these values as below. */ if (label_type == LABEL_TYPE_EFI || label_type == LABEL_TYPE_OTHER) { use_efi_dflts = 1; retry = 1; } else if (ioctl(fsi, DKIOCGGEOM, &dkg)) { dbgprintf(("%s: Unable to read Disk geometry", fsys)); perror(gettext("Unable to read Disk geometry")); lockexit(32); } else { nsect = dkg.dkg_nsect; ntrack = dkg.dkg_nhead; #ifdef i386 /* Bug 1170182 */ if (ntrack > 32 && (ntrack % 16) != 0) { ntrack -= (ntrack % 16); } #endif if (ioctl(fsi, DKIOCREMOVABLE, &isremovable)) { dbgprintf(("DeBuG Unable to determine if %s is" " Removable Media. Proceeding with system" " determined parameters.\n", fsys)); isremovable = 0; } if (ioctl(fsi, DKIOCHOTPLUGGABLE, &ishotpluggable)) { dbgprintf(("DeBuG Unable to determine if %s is" " Hotpluggable Media. Proceeding with " "system determined parameters.\n", fsys)); ishotpluggable = 0; } if ((((diskaddr_t)dkg.dkg_ncyl * dkg.dkg_nhead * dkg.dkg_nsect) > CHSLIMIT) || isremovable || ishotpluggable) { use_efi_dflts = 1; retry = 1; } } } dbgprintf(("DeBuG CHSLIMIT = %d geom = %llu\n", CHSLIMIT, (diskaddr_t)dkg.dkg_ncyl * dkg.dkg_nhead * dkg.dkg_nsect)); dbgprintf(("DeBuG label_type = %d isremovable = %d ishotpluggable = %d " "use_efi_dflts = %d\n", label_type, isremovable, ishotpluggable, use_efi_dflts)); /* * For the newfs -N case, even if the disksize is > CHSLIMIT, do not * blindly follow EFI style. If the fs_version indicates a geometry * based layout, try that one first. If it fails we can always try the * other logic. * * If we were called from growfs, we will have a problem if we mix * and match the filesystem creation and growth styles. For example, * if we create using EFI style, we have to also grow using EFI * style. So follow the style indicated by the fs_version. * * Read and verify the primary superblock. If it looks sane, use the * fs_version from the superblock. If the primary superblock does * not look good, read and verify the first alternate superblock at * ALTSB. Use the fs_version to decide whether to use the * EFI style logic or the old geometry based logic to calculate * the alternate superblock locations. */ if ((Nflag && use_efi_dflts) || (grow)) { if (grow && ntrack_flag != RC_DEFAULT) goto start_fs_creation; rdfs((diskaddr_t)(SBOFF / sectorsize), (int)sbsize, (char *)&altsblock); ret = checksblock(altsblock, 1); if (!ret) { if (altsblock.fs_magic == MTB_UFS_MAGIC) { mtb = 'y'; goto start_fs_creation; } use_efi_dflts = (altsblock.fs_version == UFS_EFISTYLE4NONEFI_VERSION_2) ? 1 : 0; } else { /* * The primary superblock didn't help in determining * the fs_version. Try the first alternate superblock. */ dbgprintf(("DeBuG checksblock() failed - error : %d" " for sb : %d\n", ret, SBOFF/sectorsize)); rdfs((diskaddr_t)ALTSB, (int)sbsize, (char *)&altsblock); ret = checksblock(altsblock, 1); if (!ret) { if (altsblock.fs_magic == MTB_UFS_MAGIC) { mtb = 'y'; goto start_fs_creation; } use_efi_dflts = (altsblock.fs_version == UFS_EFISTYLE4NONEFI_VERSION_2) ? 1 : 0; } dbgprintf(("DeBuG checksblock() returned : %d" " for sb : %d\n", ret, ALTSB)); } } geom_nsect = nsect; geom_ntrack = ntrack; geom_cpg = cpg; dbgprintf(("DeBuG geom_nsect=%d, geom_ntrack=%d, geom_cpg=%d\n", geom_nsect, geom_ntrack, geom_cpg)); start_fs_creation: retry_alternate_logic: invalid_sb_cnt = 0; cg_too_small = 0; if (use_efi_dflts) { nsect = DEF_SECTORS_EFI; ntrack = DEF_TRACKS_EFI; cpg = DESCPG; dbgprintf(("\nDeBuG Using EFI defaults\n")); } else { nsect = geom_nsect; ntrack = geom_ntrack; cpg = geom_cpg; dbgprintf(("\nDeBuG Using Geometry\n")); /* * 32K based on max block size of 64K, and rotational layout * test of nsect <= (256 * sectors/block). Current block size * limit is not 64K, but it's growing soon. */ range_check(&nsect, "nsect", 1, 32768, DFLNSECT, nsect_flag); /* * ntrack is the number of tracks per cylinder. * The ntrack value must be between 1 and the total number of * sectors in the file system. */ range_check(&ntrack, "ntrack", 1, fssize_db > INT_MAX ? INT_MAX : (uint32_t)fssize_db, DFLNTRAK, ntrack_flag); } range_check(&apc, "apc", 0, nsect - 1, 0, apc_flag); if (mtb == 'y') fragsize = bsize; range_check(&fragsize, "fragsize", sectorsize, bsize, MAX(bsize / MAXFRAG, MIN(DESFRAGSIZE, bsize)), fragsize_flag); if ((bsize / MAXFRAG) > fragsize) { (void) fprintf(stderr, gettext( "fragment size %ld is too small, minimum with block size %ld is %ld\n"), fragsize, bsize, bsize / MAXFRAG); (void) fprintf(stderr, gettext("mkfs: fragsize reset to minimum %ld\n"), bsize / MAXFRAG); fragsize = bsize / MAXFRAG; } if (!POWEROF2(fragsize)) { (void) fprintf(stderr, gettext("fragment size must be a power of 2, not %ld\n"), fragsize); fragsize = MAX(bsize / MAXFRAG, MIN(DESFRAGSIZE, bsize)); (void) fprintf(stderr, gettext("mkfs: fragsize reset to %ld\n"), fragsize); } /* At this point, bsize must be >= fragsize, so no need to check it */ if (bsize < PAGESIZE) { (void) fprintf(stderr, gettext( "WARNING: filesystem block size (%ld) is smaller than " "memory page size (%ld).\nResulting filesystem can not be " "mounted on this system.\n\n"), bsize, (long)PAGESIZE); } range_check(&rps, "rps", 1, 1000, DEFHZ, rps_flag); range_check(&minfree, "free", 0, 99, MINFREE, minfree_flag); range_check(&nrpos, "nrpos", 1, nsect, MIN(nsect, NRPOS), nrpos_flag); /* * nbpi is variable, but 2MB seems a reasonable upper limit, * as 4MB tends to cause problems (using otherwise-default * parameters). The true limit is where we end up with one * inode per cylinder group. If this file system is being * configured for multi-terabyte access, nbpi must be at least 1MB. */ if (mtb == 'y' && nbpi < MTB_NBPI) { if (nbpi_flag != RC_DEFAULT) (void) fprintf(stderr, gettext("mkfs: bad value for " "nbpi: must be at least 1048576 for multi-terabyte," " nbpi reset to default 1048576\n")); nbpi = MTB_NBPI; } if (mtb == 'y') range_check(&nbpi, "nbpi", MTB_NBPI, 2 * MB, MTB_NBPI, nbpi_flag); else range_check(&nbpi, "nbpi", DEV_BSIZE, 2 * MB, NBPI, nbpi_flag); /* * maxcpg is another variably-limited parameter. Calculate * the limit based on what we've got for its dependent * variables. Effectively, it's how much space is left in the * superblock after all the other bits are accounted for. We * only fill in sblock fields so we can use MAXIpG. * * If the calculation of maxcpg below (for the mtb == 'n' * case) is changed, update newfs as well. * * For old-style, non-MTB format file systems, use the old * algorithm for calculating the maximum cylinder group size, * even though it limits the cylinder group more than necessary. * Since layout can affect performance, we don't want to change * the default layout for non-MTB file systems at this time. * However, for MTB file systems, use the new maxcpg calculation, * which really maxes out the cylinder group size. */ sblock.fs_bsize = bsize; sblock.fs_inopb = sblock.fs_bsize / sizeof (struct dinode); if (mtb == 'n') { maxcpg = (bsize - sizeof (struct cg) - howmany(MAXIpG(&sblock), NBBY)) / (sizeof (long) + nrpos * sizeof (short) + nsect / (MAXFRAG * NBBY)); } else { maxcpg = compute_maxcpg(bsize, fragsize, nbpi, nrpos, nsect * ntrack); } dbgprintf(("DeBuG cpg : %ld\n", cpg)); /* * Increase the cpg to maxcpg if either newfs was invoked * with -T option or if mkfs wants to create a mtb file system * and if the user has not specified the cpg. */ if (cpg == -1 || (mtb == 'y' && cpg_flag == RC_DEFAULT)) cpg = maxcpg; dbgprintf(("DeBuG cpg : %ld\n", cpg)); /* * mincpg is variable in complex ways, so we really can't * do a sane lower-end limit check at this point. */ range_check(&cpg, "cgsize", 1, maxcpg, MIN(maxcpg, DESCPG), cpg_flag); /* * get the controller info */ islog = 0; islogok = 0; waslog = 0; /* * Do not grow the file system, but print on stdout the maximum * size in sectors to which the file system can be increased. * The calculated size is limited by fssize_db. * Note that we don't lock the filesystem and therefore under rare * conditions (the filesystem is mounted, the free block count is * almost zero, and the superuser is still changing it) the calculated * size can be imprecise. */ if (Pflag) { (void) printf("%llu\n", probe_summaryinfo()); exit(0); } /* * If we're growing an existing filesystem, then we're about * to start doing things that can require recovery efforts if * we get interrupted, so make sure we get a chance to do so. */ if (grow) { sigact.sa_handler = recover_from_sigint; sigemptyset(&sigact.sa_mask); sigact.sa_flags = SA_RESTART; if (sigaction(SIGINT, &sigact, (struct sigaction *)NULL) < 0) { perror(gettext("Could not register SIGINT handler")); lockexit(3); } } if (!Nflag) { /* * Check if MNTTAB is trustable */ if (statvfs64(MNTTAB, &fs) < 0) { (void) fprintf(stderr, gettext("can't statvfs %s\n"), MNTTAB); exit(32); } if (strcmp(MNTTYPE_MNTFS, fs.f_basetype) != 0) { (void) fprintf(stderr, gettext( "%s file system type is not %s, can't mkfs\n"), MNTTAB, MNTTYPE_MNTFS); exit(32); } special = getfullblkname(fsys); checkdev(fsys, special); /* * If we found the block device name, * then check the mount table. * if mounted, and growing write lock the file system * */ if ((special != NULL) && (*special != '\0')) { if ((mnttab = fopen(MNTTAB, "r")) == NULL) { (void) fprintf(stderr, gettext( "can't open %s\n"), MNTTAB); exit(32); } while ((getmntent(mnttab, &mntp)) == 0) { if (grow) { checkmount(&mntp, special); continue; } if (strcmp(special, mntp.mnt_special) == 0) { (void) fprintf(stderr, gettext( "%s is mounted, can't mkfs\n"), special); exit(32); } } (void) fclose(mnttab); } if (directory && (ismounted == 0)) { (void) fprintf(stderr, gettext("%s is not mounted\n"), special); lockexit(32); } fso = (grow) ? open64(fsys, O_WRONLY) : creat64(fsys, 0666); if (fso < 0) { saverr = errno; (void) fprintf(stderr, gettext("%s: cannot create: %s\n"), fsys, strerror(saverr)); lockexit(32); } } else { /* * For the -N case, a file descriptor is needed for the llseek() * in wtfs(). See the comment in wtfs() for more information. * * Get a file descriptor that's read-only so that this code * doesn't accidentally write to the file. */ fso = open64(fsys, O_RDONLY); if (fso < 0) { saverr = errno; (void) fprintf(stderr, gettext("%s: cannot open: %s\n"), fsys, strerror(saverr)); lockexit(32); } } /* * Check the media sector size */ if (ioctl(fso, DKIOCGMEDIAINFO, &dkminfo) != -1) { if (dkminfo.dki_lbsize != 0 && POWEROF2(dkminfo.dki_lbsize / DEV_BSIZE) && dkminfo.dki_lbsize != DEV_BSIZE) { fprintf(stderr, gettext("The device sector size %u is not " "supported by ufs!\n"), dkminfo.dki_lbsize); (void) close(fso); exit(1); } } /* * seed random # generator (for ic_generation) */ #ifdef MKFS_DEBUG srand48(12962); /* reproducible results */ #else srand48((long)(time((time_t *)NULL) + getpid())); #endif if (grow) { growinit(fsys); goto grow00; } /* * Validate the given file system size. * Verify that its last block can actually be accessed. * * Note: it's ok to use sblock as a buffer because it is immediately * overwritten by the rdfs() of the superblock in the next line. * * ToDo: Because the size checking is done in rdfs()/wtfs(), the * error message for specifying an illegal size is very unfriendly. * In the future, one could replace the rdfs()/wtfs() calls * below with in-line calls to read() or write(). This allows better * error messages to be put in place. */ rdfs(fssize_db - 1, (int)sectorsize, (char *)&sblock); /* * make the fs unmountable */ rdfs((diskaddr_t)(SBOFF / sectorsize), (int)sbsize, (char *)&sblock); sblock.fs_magic = -1; sblock.fs_clean = FSBAD; sblock.fs_state = FSOKAY - sblock.fs_time; wtfs((diskaddr_t)(SBOFF / sectorsize), (int)sbsize, (char *)&sblock); bzero(&sblock, (size_t)sbsize); sblock.fs_nsect = nsect; sblock.fs_ntrak = ntrack; /* * Validate specified/determined spc * and calculate minimum cylinders per group. */ /* * sectors/cyl = tracks/cyl * sectors/track */ sblock.fs_spc = sblock.fs_ntrak * sblock.fs_nsect; grow00: if (apc_flag) { sblock.fs_spc -= apc; } /* * Have to test for this separately from apc_flag, due to * the growfs case.... */ if (sblock.fs_spc != sblock.fs_ntrak * sblock.fs_nsect) { spc_flag = 1; } if (grow) goto grow10; sblock.fs_nrpos = nrpos; sblock.fs_bsize = bsize; sblock.fs_fsize = fragsize; sblock.fs_minfree = minfree; grow10: if (nbpi < sblock.fs_fsize) { (void) fprintf(stderr, gettext( "warning: wasteful data byte allocation / inode (nbpi):\n")); (void) fprintf(stderr, gettext( "%ld smaller than allocatable fragment size of %d\n"), nbpi, sblock.fs_fsize); } if (grow) goto grow20; if (opt == 's') sblock.fs_optim = FS_OPTSPACE; else sblock.fs_optim = FS_OPTTIME; sblock.fs_bmask = ~(sblock.fs_bsize - 1); sblock.fs_fmask = ~(sblock.fs_fsize - 1); /* * Planning now for future expansion. */ #if defined(_BIG_ENDIAN) sblock.fs_qbmask.val[0] = 0; sblock.fs_qbmask.val[1] = ~sblock.fs_bmask; sblock.fs_qfmask.val[0] = 0; sblock.fs_qfmask.val[1] = ~sblock.fs_fmask; #endif #if defined(_LITTLE_ENDIAN) sblock.fs_qbmask.val[0] = ~sblock.fs_bmask; sblock.fs_qbmask.val[1] = 0; sblock.fs_qfmask.val[0] = ~sblock.fs_fmask; sblock.fs_qfmask.val[1] = 0; #endif for (sblock.fs_bshift = 0, i = sblock.fs_bsize; i > 1; i >>= 1) sblock.fs_bshift++; for (sblock.fs_fshift = 0, i = sblock.fs_fsize; i > 1; i >>= 1) sblock.fs_fshift++; sblock.fs_frag = numfrags(&sblock, sblock.fs_bsize); for (sblock.fs_fragshift = 0, i = sblock.fs_frag; i > 1; i >>= 1) sblock.fs_fragshift++; if (sblock.fs_frag > MAXFRAG) { (void) fprintf(stderr, gettext( "fragment size %d is too small, minimum with block size %d is %d\n"), sblock.fs_fsize, sblock.fs_bsize, sblock.fs_bsize / MAXFRAG); lockexit(32); } sblock.fs_nindir = sblock.fs_bsize / sizeof (daddr32_t); sblock.fs_inopb = sblock.fs_bsize / sizeof (struct dinode); sblock.fs_nspf = sblock.fs_fsize / sectorsize; for (sblock.fs_fsbtodb = 0, i = NSPF(&sblock); i > 1; i >>= 1) sblock.fs_fsbtodb++; /* * Compute the super-block, cylinder group, and inode blocks. * Note that these "blkno" are really fragment addresses. * For example, on an 8K/1K (block/fragment) system, fs_sblkno is 16, * fs_cblkno is 24, and fs_iblkno is 32. This is why CGSIZE is so * important: only 1 FS block is allocated for the cg struct (fragment * numbers 24 through 31). */ sblock.fs_sblkno = roundup(howmany(bbsize + sbsize, sblock.fs_fsize), sblock.fs_frag); sblock.fs_cblkno = (daddr32_t)(sblock.fs_sblkno + roundup(howmany(sbsize, sblock.fs_fsize), sblock.fs_frag)); sblock.fs_iblkno = sblock.fs_cblkno + sblock.fs_frag; sblock.fs_cgoffset = roundup( howmany(sblock.fs_nsect, NSPF(&sblock)), sblock.fs_frag); for (sblock.fs_cgmask = -1, i = sblock.fs_ntrak; i > 1; i >>= 1) sblock.fs_cgmask <<= 1; if (!POWEROF2(sblock.fs_ntrak)) sblock.fs_cgmask <<= 1; /* * Validate specified/determined spc * and calculate minimum cylinders per group. */ for (sblock.fs_cpc = NSPB(&sblock), i = sblock.fs_spc; sblock.fs_cpc > 1 && (i & 1) == 0; sblock.fs_cpc >>= 1, i >>= 1) /* void */; mincpc = sblock.fs_cpc; /* if these calculations are changed, check dump_fscmd also */ bpcg = (uint64_t)sblock.fs_spc * sectorsize; inospercg = (uint64_t)roundup(bpcg / sizeof (struct dinode), INOPB(&sblock)); if (inospercg > MAXIpG(&sblock)) inospercg = MAXIpG(&sblock); used = (uint64_t)(sblock.fs_iblkno + inospercg / INOPF(&sblock)) * NSPF(&sblock); mincpgcnt = (long)howmany((uint64_t)sblock.fs_cgoffset * (~sblock.fs_cgmask) + used, sblock.fs_spc); mincpg = roundup(mincpgcnt, mincpc); /* * Insure that cylinder group with mincpg has enough space * for block maps */ sblock.fs_cpg = mincpg; sblock.fs_ipg = (int32_t)inospercg; mapcramped = 0; /* * Make sure the cg struct fits within the file system block. * Use larger block sizes until it fits */ while (CGSIZE(&sblock) > sblock.fs_bsize) { mapcramped = 1; if (sblock.fs_bsize < MAXBSIZE) { sblock.fs_bsize <<= 1; if ((i & 1) == 0) { i >>= 1; } else { sblock.fs_cpc <<= 1; mincpc <<= 1; mincpg = roundup(mincpgcnt, mincpc); sblock.fs_cpg = mincpg; } sblock.fs_frag <<= 1; sblock.fs_fragshift += 1; if (sblock.fs_frag <= MAXFRAG) continue; } /* * Looped far enough. The fragment is now as large as the * filesystem block! */ if (sblock.fs_fsize == sblock.fs_bsize) { (void) fprintf(stderr, gettext( "There is no block size that can support this disk\n")); lockexit(32); } /* * Try a larger fragment. Double the fragment size. */ sblock.fs_frag >>= 1; sblock.fs_fragshift -= 1; sblock.fs_fsize <<= 1; sblock.fs_nspf <<= 1; } /* * Insure that cylinder group with mincpg has enough space for inodes */ inodecramped = 0; used *= sectorsize; nbytes64 = (uint64_t)mincpg * bpcg - used; inospercg = (uint64_t)roundup((nbytes64 / nbpi), INOPB(&sblock)); sblock.fs_ipg = (int32_t)inospercg; while (inospercg > MAXIpG(&sblock)) { inodecramped = 1; if (mincpc == 1 || sblock.fs_frag == 1 || sblock.fs_bsize == MINBSIZE) break; nbytes64 = (uint64_t)mincpg * bpcg - used; (void) fprintf(stderr, gettext("With a block size of %d %s %lu\n"), sblock.fs_bsize, gettext("minimum bytes per inode is"), (uint32_t)(nbytes64 / MAXIpG(&sblock) + 1)); sblock.fs_bsize >>= 1; sblock.fs_frag >>= 1; sblock.fs_fragshift -= 1; mincpc >>= 1; sblock.fs_cpg = roundup(mincpgcnt, mincpc); if (CGSIZE(&sblock) > sblock.fs_bsize) { sblock.fs_bsize <<= 1; break; } mincpg = sblock.fs_cpg; nbytes64 = (uint64_t)mincpg * bpcg - used; inospercg = (uint64_t)roundup((nbytes64 / nbpi), INOPB(&sblock)); sblock.fs_ipg = (int32_t)inospercg; } if (inodecramped) { if (inospercg > MAXIpG(&sblock)) { nbytes64 = (uint64_t)mincpg * bpcg - used; (void) fprintf(stderr, gettext( "Minimum bytes per inode is %d\n"), (uint32_t)(nbytes64 / MAXIpG(&sblock) + 1)); } else if (!mapcramped) { (void) fprintf(stderr, gettext( "With %ld bytes per inode, minimum cylinders per group is %ld\n"), nbpi, mincpg); } } if (mapcramped) { (void) fprintf(stderr, gettext( "With %d sectors per cylinder, minimum cylinders " "per group is %ld\n"), sblock.fs_spc, mincpg); } if (inodecramped || mapcramped) { /* * To make this at least somewhat comprehensible in * the world of i18n, figure out what we're going to * say and then say it all at one time. The days of * needing to scrimp on string space are behind us.... */ if ((sblock.fs_bsize != bsize) && (sblock.fs_fsize != fragsize)) { (void) fprintf(stderr, gettext( "This requires the block size to be changed from %ld to %d\n" "and the fragment size to be changed from %ld to %d\n"), bsize, sblock.fs_bsize, fragsize, sblock.fs_fsize); } else if (sblock.fs_bsize != bsize) { (void) fprintf(stderr, gettext( "This requires the block size to be changed from %ld to %d\n"), bsize, sblock.fs_bsize); } else if (sblock.fs_fsize != fragsize) { (void) fprintf(stderr, gettext( "This requires the fragment size to be changed from %ld to %d\n"), fragsize, sblock.fs_fsize); } else { (void) fprintf(stderr, gettext( "Unable to make filesystem fit with the given constraints\n")); } (void) fprintf(stderr, gettext( "Please re-run mkfs with corrected parameters\n")); lockexit(32); } /* * Calculate the number of cylinders per group */ sblock.fs_cpg = cpg; if (sblock.fs_cpg % mincpc != 0) { (void) fprintf(stderr, gettext( "Warning: cylinder groups must have a multiple " "of %ld cylinders with the given\n parameters\n"), mincpc); sblock.fs_cpg = roundup(sblock.fs_cpg, mincpc); (void) fprintf(stderr, gettext("Rounded cgsize up to %d\n"), sblock.fs_cpg); } /* * Must insure there is enough space for inodes */ /* if these calculations are changed, check dump_fscmd also */ nbytes64 = (uint64_t)sblock.fs_cpg * bpcg - used; sblock.fs_ipg = roundup((uint32_t)(nbytes64 / nbpi), INOPB(&sblock)); /* * Slim down cylinders per group, until the inodes can fit. */ while (sblock.fs_ipg > MAXIpG(&sblock)) { inodecramped = 1; sblock.fs_cpg -= mincpc; nbytes64 = (uint64_t)sblock.fs_cpg * bpcg - used; sblock.fs_ipg = roundup((uint32_t)(nbytes64 / nbpi), INOPB(&sblock)); } /* * Must insure there is enough space to hold block map. * Cut down on cylinders per group, until the cg struct fits in a * filesystem block. */ while (CGSIZE(&sblock) > sblock.fs_bsize) { mapcramped = 1; sblock.fs_cpg -= mincpc; nbytes64 = (uint64_t)sblock.fs_cpg * bpcg - used; sblock.fs_ipg = roundup((uint32_t)(nbytes64 / nbpi), INOPB(&sblock)); } sblock.fs_fpg = (sblock.fs_cpg * sblock.fs_spc) / NSPF(&sblock); if ((sblock.fs_cpg * sblock.fs_spc) % NSPB(&sblock) != 0) { (void) fprintf(stderr, gettext("newfs: panic (fs_cpg * fs_spc) %% NSPF != 0\n")); lockexit(32); } if (sblock.fs_cpg < mincpg) { (void) fprintf(stderr, gettext( "With the given parameters, cgsize must be at least %ld; please re-run mkfs\n"), mincpg); lockexit(32); } sblock.fs_cgsize = fragroundup(&sblock, CGSIZE(&sblock)); grow20: /* * Now have size for file system and nsect and ntrak. * Determine number of cylinders and blocks in the file system. */ fssize_frag = (int64_t)dbtofsb(&sblock, fssize_db); if (fssize_frag > INT_MAX) { (void) fprintf(stderr, gettext( "There are too many fragments in the system, increase fragment size\n"), mincpg); lockexit(32); } sblock.fs_size = (int32_t)fssize_frag; sblock.fs_ncyl = (int32_t)(fssize_frag * NSPF(&sblock) / sblock.fs_spc); if (fssize_frag * NSPF(&sblock) > (uint64_t)sblock.fs_ncyl * sblock.fs_spc) { sblock.fs_ncyl++; warn = 1; } if (sblock.fs_ncyl < 1) { (void) fprintf(stderr, gettext( "file systems must have at least one cylinder\n")); lockexit(32); } if (grow) goto grow30; /* * Determine feasability/values of rotational layout tables. * * The size of the rotational layout tables is limited by the size * of the file system block, fs_bsize. The amount of space * available for tables is calculated as (fs_bsize - sizeof (struct * fs)). The size of these tables is inversely proportional to the * block size of the file system. The size increases if sectors per * track are not powers of two, because more cylinders must be * described by the tables before the rotational pattern repeats * (fs_cpc). */ sblock.fs_postblformat = FS_DYNAMICPOSTBLFMT; sblock.fs_sbsize = fragroundup(&sblock, sizeof (struct fs)); sblock.fs_npsect = sblock.fs_nsect; if (sblock.fs_ntrak == 1) { sblock.fs_cpc = 0; goto next; } postblsize = sblock.fs_nrpos * sblock.fs_cpc * sizeof (short); rotblsize = sblock.fs_cpc * sblock.fs_spc / NSPB(&sblock); totalsbsize = sizeof (struct fs) + rotblsize; /* do static allocation if nrpos == 8 and fs_cpc == 16 */ if (sblock.fs_nrpos == 8 && sblock.fs_cpc <= 16) { /* use old static table space */ sblock.fs_postbloff = (char *)(&sblock.fs_opostbl[0][0]) - (char *)(&sblock.fs_link); sblock.fs_rotbloff = &sblock.fs_space[0] - (uchar_t *)(&sblock.fs_link); } else { /* use 4.3 dynamic table space */ sblock.fs_postbloff = &sblock.fs_space[0] - (uchar_t *)(&sblock.fs_link); sblock.fs_rotbloff = sblock.fs_postbloff + postblsize; totalsbsize += postblsize; } if (totalsbsize > sblock.fs_bsize || sblock.fs_nsect > (1 << NBBY) * NSPB(&sblock)) { (void) fprintf(stderr, gettext( "Warning: insufficient space in super block for\n" "rotational layout tables with nsect %d, ntrack %d, " "and nrpos %d.\nOmitting tables - file system " "performance may be impaired.\n"), sblock.fs_nsect, sblock.fs_ntrak, sblock.fs_nrpos); /* * Setting fs_cpc to 0 tells alloccgblk() in ufs_alloc.c to * ignore the positional layout table and rotational * position table. */ sblock.fs_cpc = 0; goto next; } sblock.fs_sbsize = fragroundup(&sblock, totalsbsize); /* * calculate the available blocks for each rotational position */ for (cylno = 0; cylno < sblock.fs_cpc; cylno++) for (rpos = 0; rpos < sblock.fs_nrpos; rpos++) fs_postbl(&sblock, cylno)[rpos] = -1; for (i = (rotblsize - 1) * sblock.fs_frag; i >= 0; i -= sblock.fs_frag) { cylno = cbtocylno(&sblock, i); rpos = cbtorpos(&sblock, i); blk = fragstoblks(&sblock, i); if (fs_postbl(&sblock, cylno)[rpos] == -1) fs_rotbl(&sblock)[blk] = 0; else fs_rotbl(&sblock)[blk] = fs_postbl(&sblock, cylno)[rpos] - blk; fs_postbl(&sblock, cylno)[rpos] = blk; } next: grow30: /* * Compute/validate number of cylinder groups. * Note that if an excessively large filesystem is specified * (e.g., more than 16384 cylinders for an 8K filesystem block), it * does not get detected until checksummarysize() */ sblock.fs_ncg = sblock.fs_ncyl / sblock.fs_cpg; if (sblock.fs_ncyl % sblock.fs_cpg) sblock.fs_ncg++; sblock.fs_dblkno = sblock.fs_iblkno + sblock.fs_ipg / INOPF(&sblock); i = MIN(~sblock.fs_cgmask, sblock.fs_ncg - 1); ibpcl = cgdmin(&sblock, i) - cgbase(&sblock, i); if (ibpcl >= sblock.fs_fpg) { (void) fprintf(stderr, gettext( "inode blocks/cyl group (%d) >= data blocks (%d)\n"), cgdmin(&sblock, i) - cgbase(&sblock, i) / sblock.fs_frag, sblock.fs_fpg / sblock.fs_frag); if ((ibpcl < 0) || (sblock.fs_fpg < 0)) { (void) fprintf(stderr, gettext( "number of cylinders per cylinder group (%d) must be decreased.\n"), sblock.fs_cpg); } else { (void) fprintf(stderr, gettext( "number of cylinders per cylinder group (%d) must be increased.\n"), sblock.fs_cpg); } (void) fprintf(stderr, gettext( "Note that cgsize may have been adjusted to allow struct cg to fit.\n")); lockexit(32); } j = sblock.fs_ncg - 1; if ((i = fssize_frag - j * sblock.fs_fpg) < sblock.fs_fpg && cgdmin(&sblock, j) - cgbase(&sblock, j) > i) { (void) fprintf(stderr, gettext( "Warning: inode blocks/cyl group (%d) >= data " "blocks (%ld) in last\n cylinder group. This " "implies %ld sector(s) cannot be allocated.\n"), (cgdmin(&sblock, j) - cgbase(&sblock, j)) / sblock.fs_frag, i / sblock.fs_frag, i * NSPF(&sblock)); /* * If there is only one cylinder group and that is not even * big enough to hold the inodes, exit. */ if (sblock.fs_ncg == 1) cg_too_small = 1; sblock.fs_ncg--; sblock.fs_ncyl = sblock.fs_ncg * sblock.fs_cpg; sblock.fs_size = fssize_frag = (int64_t)sblock.fs_ncyl * (int64_t)sblock.fs_spc / (int64_t)NSPF(&sblock); warn = 0; } if (warn && !spc_flag) { (void) fprintf(stderr, gettext( "Warning: %d sector(s) in last cylinder unallocated\n"), sblock.fs_spc - (uint32_t)(fssize_frag * NSPF(&sblock) - (uint64_t)(sblock.fs_ncyl - 1) * sblock.fs_spc)); } /* * fill in remaining fields of the super block */ /* * The csum records are stored in cylinder group 0, starting at * cgdmin, the first data block. */ sblock.fs_csaddr = cgdmin(&sblock, 0); sblock.fs_cssize = fragroundup(&sblock, sblock.fs_ncg * sizeof (struct csum)); i = sblock.fs_bsize / sizeof (struct csum); sblock.fs_csmask = ~(i - 1); for (sblock.fs_csshift = 0; i > 1; i >>= 1) sblock.fs_csshift++; fscs = (struct csum *)calloc(1, sblock.fs_cssize); checksummarysize(); if (mtb == 'y') { sblock.fs_magic = MTB_UFS_MAGIC; sblock.fs_version = MTB_UFS_VERSION_1; } else { sblock.fs_magic = FS_MAGIC; if (use_efi_dflts) sblock.fs_version = UFS_EFISTYLE4NONEFI_VERSION_2; else sblock.fs_version = UFS_VERSION_MIN; } if (grow) { bcopy((caddr_t)grow_fscs, (caddr_t)fscs, (int)grow_fs_cssize); extendsummaryinfo(); goto grow40; } sblock.fs_rotdelay = rotdelay; sblock.fs_maxcontig = maxcontig; sblock.fs_maxbpg = MAXBLKPG(sblock.fs_bsize); sblock.fs_rps = rps; sblock.fs_cgrotor = 0; sblock.fs_cstotal.cs_ndir = 0; sblock.fs_cstotal.cs_nbfree = 0; sblock.fs_cstotal.cs_nifree = 0; sblock.fs_cstotal.cs_nffree = 0; sblock.fs_fmod = 0; sblock.fs_ronly = 0; sblock.fs_time = mkfstime; sblock.fs_state = FSOKAY - sblock.fs_time; sblock.fs_clean = FSCLEAN; grow40: /* * If all that's needed is a dump of the superblock we * would use by default, we've got it now. So, splat it * out and leave. */ if (rflag) { dump_sblock(); lockexit(0); } /* * Dump out summary information about file system. */ (void) fprintf(stderr, gettext( "%s:\t%lld sectors in %d cylinders of %d tracks, %d sectors\n"), fsys, (uint64_t)sblock.fs_size * NSPF(&sblock), sblock.fs_ncyl, sblock.fs_ntrak, sblock.fs_nsect); (void) fprintf(stderr, gettext( "\t%.1fMB in %d cyl groups (%d c/g, %.2fMB/g, %d i/g)\n"), (float)sblock.fs_size * sblock.fs_fsize / MB, sblock.fs_ncg, sblock.fs_cpg, (float)sblock.fs_fpg * sblock.fs_fsize / MB, sblock.fs_ipg); tmpbuf = calloc(sblock.fs_ncg / 50 + 500, 1); if (tmpbuf == NULL) { perror("calloc"); lockexit(32); } if (cg_too_small) { (void) fprintf(stderr, gettext("File system creation failed. " "There is only one cylinder group and\nthat is " "not even big enough to hold the inodes.\n")); lockexit(32); } /* * Now build the cylinders group blocks and * then print out indices of cylinder groups. */ tprintf(gettext( "super-block backups (for fsck -F ufs -o b=#) at:\n")); for (width = cylno = 0; cylno < sblock.fs_ncg && cylno < 10; cylno++) { if ((grow == 0) || (cylno >= grow_fs_ncg)) initcg(cylno); num = fsbtodb(&sblock, (uint64_t)cgsblock(&sblock, cylno)); /* * If Nflag and if the disk is larger than the CHSLIMIT, * then sanity test the superblocks before reporting. If there * are too many superblocks which look insane, we have * to retry with alternate logic. If both methods have * failed, then our efforts to arrive at alternate * superblocks failed, so complain and exit. */ if (Nflag && retry) { skip_this_sb = 0; rdfs((diskaddr_t)num, sbsize, (char *)&altsblock); ret = checksblock(altsblock, 1); if (ret) { skip_this_sb = 1; invalid_sb_cnt++; dbgprintf(("DeBuG checksblock() failed - error" " : %d for sb : %llu invalid_sb_cnt : %d\n", ret, num, invalid_sb_cnt)); } else { /* * Though the superblock looks sane, verify if * the fs_version in the superblock and the * logic that we are using to arrive at the * superblocks match. */ if (use_efi_dflts && altsblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2) { skip_this_sb = 1; invalid_sb_cnt++; } } if (invalid_sb_cnt >= INVALIDSBLIMIT) { if (retry > 1) { (void) fprintf(stderr, gettext( "Error determining alternate " "superblock locations\n")); free(tmpbuf); lockexit(32); } retry++; use_efi_dflts = !use_efi_dflts; free(tmpbuf); goto retry_alternate_logic; } if (skip_this_sb) continue; } (void) sprintf(pbuf, " %llu,", num); plen = strlen(pbuf); if ((width + plen) > (WIDTH - 1)) { width = plen; tprintf("\n"); } else { width += plen; } if (Nflag && retry) (void) strncat(tmpbuf, pbuf, strlen(pbuf)); else (void) fprintf(stderr, "%s", pbuf); } tprintf("\n"); remaining_cg = sblock.fs_ncg - cylno; /* * If there are more than 300 cylinder groups still to be * initialized, print a "." for every 50 cylinder groups. */ if (remaining_cg > 300) { tprintf(gettext("Initializing cylinder groups:\n")); do_dot = 1; } /* * Now initialize all cylinder groups between the first ten * and the last ten. * * If the number of cylinder groups was less than 10, all of the * cylinder group offsets would have printed in the last loop * and cylno will already be equal to sblock.fs_ncg and so this * loop will not be entered. If there are less than 20 cylinder * groups, cylno is already less than fs_ncg - 10, so this loop * won't be entered in that case either. */ i = 0; for (; cylno < sblock.fs_ncg - 10; cylno++) { if ((grow == 0) || (cylno >= grow_fs_ncg)) initcg(cylno); if (do_dot && cylno % 50 == 0) { tprintf("."); i++; if (i == WIDTH - 1) { tprintf("\n"); i = 0; } } } /* * Now print the cylinder group offsets for the last 10 * cylinder groups, if any are left. */ if (do_dot) { tprintf(gettext( "\nsuper-block backups for last 10 cylinder groups at:\n")); } for (width = 0; cylno < sblock.fs_ncg; cylno++) { if ((grow == 0) || (cylno >= grow_fs_ncg)) initcg(cylno); num = fsbtodb(&sblock, (uint64_t)cgsblock(&sblock, cylno)); if (Nflag && retry) { skip_this_sb = 0; rdfs((diskaddr_t)num, sbsize, (char *)&altsblock); ret = checksblock(altsblock, 1); if (ret) { skip_this_sb = 1; invalid_sb_cnt++; dbgprintf(("DeBuG checksblock() failed - error" " : %d for sb : %llu invalid_sb_cnt : %d\n", ret, num, invalid_sb_cnt)); } else { /* * Though the superblock looks sane, verify if * the fs_version in the superblock and the * logic that we are using to arrive at the * superblocks match. */ if (use_efi_dflts && altsblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2) { skip_this_sb = 1; invalid_sb_cnt++; } } if (invalid_sb_cnt >= INVALIDSBLIMIT) { if (retry > 1) { (void) fprintf(stderr, gettext( "Error determining alternate " "superblock locations\n")); free(tmpbuf); lockexit(32); } retry++; use_efi_dflts = !use_efi_dflts; free(tmpbuf); goto retry_alternate_logic; } if (skip_this_sb) continue; } /* Don't print ',' for the last superblock */ if (cylno == sblock.fs_ncg-1) (void) sprintf(pbuf, " %llu", num); else (void) sprintf(pbuf, " %llu,", num); plen = strlen(pbuf); if ((width + plen) > (WIDTH - 1)) { width = plen; tprintf("\n"); } else { width += plen; } if (Nflag && retry) (void) strncat(tmpbuf, pbuf, strlen(pbuf)); else (void) fprintf(stderr, "%s", pbuf); } tprintf("\n"); if (Nflag) { if (retry) (void) fprintf(stderr, "%s", tmpbuf); free(tmpbuf); lockexit(0); } free(tmpbuf); if (grow) goto grow50; /* * Now construct the initial file system, * then write out the super-block. */ fsinit(); grow50: /* * write the superblock and csum information */ wtsb(); /* * extend the last cylinder group in the original file system */ if (grow) { extendcg(grow_fs_ncg-1); wtsb(); } /* * Write out the duplicate super blocks to the first 10 * cylinder groups (or fewer, if there are fewer than 10 * cylinder groups). */ for (cylno = 0; cylno < sblock.fs_ncg && cylno < 10; cylno++) awtfs(fsbtodb(&sblock, (uint64_t)cgsblock(&sblock, cylno)), (int)sbsize, (char *)&sblock, SAVE); /* * Now write out duplicate super blocks to the remaining * cylinder groups. In the case of multi-terabyte file * systems, just write out the super block to the last ten * cylinder groups (or however many are left). */ if (mtb == 'y') { if (sblock.fs_ncg <= 10) cylno = sblock.fs_ncg; else if (sblock.fs_ncg <= 20) cylno = 10; else cylno = sblock.fs_ncg - 10; } for (; cylno < sblock.fs_ncg; cylno++) awtfs(fsbtodb(&sblock, (uint64_t)cgsblock(&sblock, cylno)), (int)sbsize, (char *)&sblock, SAVE); /* * Flush out all the AIO writes we've done. It's not * necessary to do this explicitly, but it's the only * way to report any errors from those writes. */ flush_writes(); /* * set clean flag */ if (grow) sblock.fs_clean = grow_fs_clean; else sblock.fs_clean = FSCLEAN; sblock.fs_time = mkfstime; sblock.fs_state = FSOKAY - sblock.fs_time; wtfs((diskaddr_t)(SBOFF / sectorsize), sbsize, (char *)&sblock); isbad = 0; if (fsync(fso) == -1) { saverr = errno; (void) fprintf(stderr, gettext("mkfs: fsync failed on write disk: %s\n"), strerror(saverr)); /* we're just cleaning up, so keep going */ } if (close(fsi) == -1) { saverr = errno; (void) fprintf(stderr, gettext("mkfs: close failed on read disk: %s\n"), strerror(saverr)); /* we're just cleaning up, so keep going */ } if (close(fso) == -1) { saverr = errno; (void) fprintf(stderr, gettext("mkfs: close failed on write disk: %s\n"), strerror(saverr)); /* we're just cleaning up, so keep going */ } fsi = fso = -1; #ifndef STANDALONE lockexit(0); #endif return (0); } static diskaddr_t get_device_size(int fd) { struct dk_minfo disk_info; if ((ioctl(fd, DKIOCGMEDIAINFO, (caddr_t)&disk_info)) == -1) return (0); return (disk_info.dki_capacity); } /* * Figure out how big the partition we're dealing with is. * The value returned is in disk blocks (sectors); */ static diskaddr_t get_max_size(int fd) { struct extvtoc vtoc; dk_gpt_t *efi_vtoc; diskaddr_t slicesize; int index = read_extvtoc(fd, &vtoc); if (index >= 0) { label_type = LABEL_TYPE_VTOC; } else { if (index == VT_ENOTSUP || index == VT_ERROR) { /* it might be an EFI label */ index = efi_alloc_and_read(fd, &efi_vtoc); label_type = LABEL_TYPE_EFI; } } if (index < 0) { /* * Since both attempts to read the label failed, we're * going to use DKIOCGMEDIAINFO to get device size. */ label_type = LABEL_TYPE_OTHER; slicesize = get_device_size(fd); if (slicesize == 0) { switch (index) { case VT_ERROR: break; case VT_EIO: errno = EIO; break; case VT_EINVAL: errno = EINVAL; } perror(gettext("Can not determine partition size")); lockexit(32); } } if (label_type == LABEL_TYPE_EFI) { slicesize = efi_vtoc->efi_parts[index].p_size; efi_free(efi_vtoc); } else if (label_type == LABEL_TYPE_VTOC) { /* * In the vtoc struct, p_size is a 32-bit signed quantity. * In the dk_gpt struct (efi's version of the vtoc), p_size * is an unsigned 64-bit quantity. By casting the vtoc's * psize to an unsigned 32-bit quantity, it will be copied * to 'slicesize' (an unsigned 64-bit diskaddr_t) without * sign extension. */ slicesize = (uint32_t)vtoc.v_part[index].p_size; } dbgprintf(("DeBuG get_max_size index = %d, p_size = %lld, " "dolimit = %d\n", index, slicesize, (slicesize > FS_MAX))); /* * The next line limits a UFS file system to the maximum * supported size. */ if (slicesize > FS_MAX) return (FS_MAX); return (slicesize); } static long get_max_track_size(int fd) { struct dk_cinfo ci; long track_size = -1; if (ioctl(fd, DKIOCINFO, &ci) == 0) { track_size = ci.dki_maxtransfer * DEV_BSIZE; } if ((track_size < 0)) { int error = 0; int maxphys; int gotit = 0; gotit = fsgetmaxphys(&maxphys, &error); if (gotit) { track_size = MIN(MB, maxphys); } else { (void) fprintf(stderr, gettext( "Warning: Could not get system value for maxphys. The value for\n" "maxcontig will default to 1MB.\n")); track_size = MB; } } return (track_size); } /* * Initialize a cylinder group. */ static void initcg(int cylno) { diskaddr_t cbase, d; diskaddr_t dlower; /* last data block before cg metadata */ diskaddr_t dupper; /* first data block after cg metadata */ diskaddr_t dmax; int64_t i; struct csum *cs; struct dinode *inode_buffer; int size; /* * Variables used to store intermediate results as a part of * the internal implementation of the cbtocylno() macros. */ diskaddr_t bno; /* UFS block number (not sector number) */ int cbcylno; /* current cylinder number */ int cbcylno_sect; /* sector offset within cylinder */ int cbsect_incr; /* amount to increment sector offset */ /* * Variables used to store intermediate results as a part of * the internal implementation of the cbtorpos() macros. */ short *cgblks; /* pointer to array of free blocks in cg */ int trackrpos; /* tmp variable for rotation position */ int trackoff; /* offset within a track */ int trackoff_incr; /* amount to increment trackoff */ int rpos; /* rotation position of current block */ int rpos_incr; /* amount to increment rpos per block */ union cgun *icgun; /* local pointer to a cg summary block */ #define icg (icgun->cg) icgun = (union cgun *)getbuf(&cgsumbuf, sizeof (union cgun)); /* * Determine block bounds for cylinder group. * Allow space for super block summary information in first * cylinder group. */ cbase = cgbase(&sblock, cylno); dmax = cbase + sblock.fs_fpg; if (dmax > sblock.fs_size) /* last cg may be smaller than normal */ dmax = sblock.fs_size; dlower = cgsblock(&sblock, cylno) - cbase; dupper = cgdmin(&sblock, cylno) - cbase; if (cylno == 0) dupper += howmany(sblock.fs_cssize, sblock.fs_fsize); cs = fscs + cylno; icg.cg_time = mkfstime; icg.cg_magic = CG_MAGIC; icg.cg_cgx = cylno; /* last one gets whatever's left */ if (cylno == sblock.fs_ncg - 1) icg.cg_ncyl = sblock.fs_ncyl - (sblock.fs_cpg * cylno); else icg.cg_ncyl = sblock.fs_cpg; icg.cg_niblk = sblock.fs_ipg; icg.cg_ndblk = dmax - cbase; icg.cg_cs.cs_ndir = 0; icg.cg_cs.cs_nffree = 0; icg.cg_cs.cs_nbfree = 0; icg.cg_cs.cs_nifree = 0; icg.cg_rotor = 0; icg.cg_frotor = 0; icg.cg_irotor = 0; icg.cg_btotoff = &icg.cg_space[0] - (uchar_t *)(&icg.cg_link); icg.cg_boff = icg.cg_btotoff + sblock.fs_cpg * sizeof (long); icg.cg_iusedoff = icg.cg_boff + sblock.fs_cpg * sblock.fs_nrpos * sizeof (short); icg.cg_freeoff = icg.cg_iusedoff + howmany(sblock.fs_ipg, NBBY); icg.cg_nextfreeoff = icg.cg_freeoff + howmany(sblock.fs_cpg * sblock.fs_spc / NSPF(&sblock), NBBY); for (i = 0; i < sblock.fs_frag; i++) { icg.cg_frsum[i] = 0; } bzero((caddr_t)cg_inosused(&icg), icg.cg_freeoff - icg.cg_iusedoff); icg.cg_cs.cs_nifree += sblock.fs_ipg; if (cylno == 0) for (i = 0; i < UFSROOTINO; i++) { setbit(cg_inosused(&icg), i); icg.cg_cs.cs_nifree--; } /* * Initialize all the inodes in the cylinder group using * random numbers. */ size = sblock.fs_ipg * sizeof (struct dinode); inode_buffer = (struct dinode *)getbuf(&inodebuf, size); for (i = 0; i < sblock.fs_ipg; i++) { IRANDOMIZE(&(inode_buffer[i].di_ic)); } /* * Write all inodes in a single write for performance. */ awtfs(fsbtodb(&sblock, (uint64_t)cgimin(&sblock, cylno)), (int)size, (char *)inode_buffer, RELEASE); bzero((caddr_t)cg_blktot(&icg), icg.cg_boff - icg.cg_btotoff); bzero((caddr_t)cg_blks(&sblock, &icg, 0), icg.cg_iusedoff - icg.cg_boff); bzero((caddr_t)cg_blksfree(&icg), icg.cg_nextfreeoff - icg.cg_freeoff); if (cylno > 0) { for (d = 0; d < dlower; d += sblock.fs_frag) { setblock(&sblock, cg_blksfree(&icg), d/sblock.fs_frag); icg.cg_cs.cs_nbfree++; cg_blktot(&icg)[cbtocylno(&sblock, d)]++; cg_blks(&sblock, &icg, cbtocylno(&sblock, d)) [cbtorpos(&sblock, d)]++; } sblock.fs_dsize += dlower; } sblock.fs_dsize += icg.cg_ndblk - dupper; if ((i = dupper % sblock.fs_frag) != 0) { icg.cg_frsum[sblock.fs_frag - i]++; for (d = dupper + sblock.fs_frag - i; dupper < d; dupper++) { setbit(cg_blksfree(&icg), dupper); icg.cg_cs.cs_nffree++; } } /* * WARNING: The following code is somewhat confusing, but * results in a substantial performance improvement in mkfs. * * Instead of using cbtocylno() and cbtorpos() macros, we * keep track of all the intermediate state of those macros * in some variables. This allows simple addition to be * done to calculate the results as we step through the * blocks in an orderly fashion instead of the slower * multiplication and division the macros are forced to * used so they can support random input. (Multiplication, * division, and remainder operations typically take about * 10x as many processor cycles as other operations.) * * The basic idea is to take code: * * for (x = starting_x; x < max; x++) * y = (x * c) / z * * and rewrite it to take advantage of the fact that * the variable x is incrementing in an orderly way: * * intermediate = starting_x * c * yval = intermediate / z * for (x = starting_x; x < max; x++) { * y = yval; * intermediate += c * if (intermediate > z) { * yval++; * intermediate -= z * } * } * * Performance has improved as much as 4X using this code. */ /* * Initialize the starting points for all the cbtocylno() * macro variables and figure out the increments needed each * time through the loop. */ cbcylno_sect = dupper * NSPF(&sblock); cbsect_incr = sblock.fs_frag * NSPF(&sblock); cbcylno = cbcylno_sect / sblock.fs_spc; cbcylno_sect %= sblock.fs_spc; cgblks = cg_blks(&sblock, &icg, cbcylno); bno = dupper / sblock.fs_frag; /* * Initialize the starting points for all the cbtorpos() * macro variables and figure out the increments needed each * time through the loop. * * It's harder to simplify the cbtorpos() macro if there were * alternate sectors specified (or if they previously existed * in the growfs case). Since this is rare, we just revert to * using the macros in this case and skip the variable setup. */ if (!spc_flag) { trackrpos = (cbcylno_sect % sblock.fs_nsect) * sblock.fs_nrpos; rpos = trackrpos / sblock.fs_nsect; trackoff = trackrpos % sblock.fs_nsect; trackoff_incr = cbsect_incr * sblock.fs_nrpos; rpos_incr = (trackoff_incr / sblock.fs_nsect) % sblock.fs_nrpos; trackoff_incr = trackoff_incr % sblock.fs_nsect; } /* * Loop through all the blocks, marking them free and * updating totals kept in the superblock and cg summary. */ for (d = dupper; d + sblock.fs_frag <= dmax - cbase; ) { setblock(&sblock, cg_blksfree(&icg), bno); icg.cg_cs.cs_nbfree++; cg_blktot(&icg)[cbcylno]++; if (!spc_flag) cgblks[rpos]++; else cg_blks(&sblock, &icg, cbtocylno(&sblock, d)) [cbtorpos(&sblock, d)]++; d += sblock.fs_frag; bno++; /* * Increment the sector offset within the cylinder * for the cbtocylno() macro reimplementation. If * we're beyond the end of the cylinder, update the * cylinder number, calculate the offset in the * new cylinder, and update the cgblks pointer * to the next rotational position. */ cbcylno_sect += cbsect_incr; if (cbcylno_sect >= sblock.fs_spc) { cbcylno++; cbcylno_sect -= sblock.fs_spc; cgblks += sblock.fs_nrpos; } /* * If there aren't alternate sectors, increment the * rotational position variables for the cbtorpos() * reimplementation. Note that we potentially * increment rpos twice. Once by rpos_incr, and one * more time when we wrap to a new track because * trackoff >= fs_nsect. */ if (!spc_flag) { trackoff += trackoff_incr; rpos += rpos_incr; if (trackoff >= sblock.fs_nsect) { trackoff -= sblock.fs_nsect; rpos++; } if (rpos >= sblock.fs_nrpos) rpos -= sblock.fs_nrpos; } } if (d < dmax - cbase) { icg.cg_frsum[dmax - cbase - d]++; for (; d < dmax - cbase; d++) { setbit(cg_blksfree(&icg), d); icg.cg_cs.cs_nffree++; } } sblock.fs_cstotal.cs_ndir += icg.cg_cs.cs_ndir; sblock.fs_cstotal.cs_nffree += icg.cg_cs.cs_nffree; sblock.fs_cstotal.cs_nbfree += icg.cg_cs.cs_nbfree; sblock.fs_cstotal.cs_nifree += icg.cg_cs.cs_nifree; *cs = icg.cg_cs; awtfs(fsbtodb(&sblock, (uint64_t)cgtod(&sblock, cylno)), sblock.fs_bsize, (char *)&icg, RELEASE); } /* * initialize the file system */ struct inode node; #define LOSTDIR #ifdef LOSTDIR #define PREDEFDIR 3 #else #define PREDEFDIR 2 #endif struct direct root_dir[] = { { UFSROOTINO, sizeof (struct direct), 1, "." }, { UFSROOTINO, sizeof (struct direct), 2, ".." }, #ifdef LOSTDIR { LOSTFOUNDINO, sizeof (struct direct), 10, "lost+found" }, #endif }; #ifdef LOSTDIR struct direct lost_found_dir[] = { { LOSTFOUNDINO, sizeof (struct direct), 1, "." }, { UFSROOTINO, sizeof (struct direct), 2, ".." }, { 0, DIRBLKSIZ, 0, 0 }, }; #endif char buf[MAXBSIZE]; static void fsinit() { int i; /* * initialize the node */ node.i_atime = mkfstime; node.i_mtime = mkfstime; node.i_ctime = mkfstime; #ifdef LOSTDIR /* * create the lost+found directory */ (void) makedir(lost_found_dir, 2); for (i = DIRBLKSIZ; i < sblock.fs_bsize; i += DIRBLKSIZ) { bcopy(&lost_found_dir[2], &buf[i], DIRSIZ(&lost_found_dir[2])); } node.i_number = LOSTFOUNDINO; node.i_smode = IFDIR | 0700; node.i_nlink = 2; node.i_size = sblock.fs_bsize; node.i_db[0] = alloc((int)node.i_size, node.i_mode); node.i_blocks = btodb(fragroundup(&sblock, (int)node.i_size)); IRANDOMIZE(&node.i_ic); wtfs(fsbtodb(&sblock, (uint64_t)node.i_db[0]), (int)node.i_size, buf); iput(&node); #endif /* * create the root directory */ node.i_number = UFSROOTINO; node.i_mode = IFDIR | UMASK; node.i_nlink = PREDEFDIR; node.i_size = makedir(root_dir, PREDEFDIR); node.i_db[0] = alloc(sblock.fs_fsize, node.i_mode); /* i_size < 2GB because we are initializing the file system */ node.i_blocks = btodb(fragroundup(&sblock, (int)node.i_size)); IRANDOMIZE(&node.i_ic); wtfs(fsbtodb(&sblock, (uint64_t)node.i_db[0]), sblock.fs_fsize, buf); iput(&node); } /* * construct a set of directory entries in "buf". * return size of directory. */ static int makedir(struct direct *protodir, int entries) { char *cp; int i; ushort_t spcleft; spcleft = DIRBLKSIZ; for (cp = buf, i = 0; i < entries - 1; i++) { protodir[i].d_reclen = DIRSIZ(&protodir[i]); bcopy(&protodir[i], cp, protodir[i].d_reclen); cp += protodir[i].d_reclen; spcleft -= protodir[i].d_reclen; } protodir[i].d_reclen = spcleft; bcopy(&protodir[i], cp, DIRSIZ(&protodir[i])); return (DIRBLKSIZ); } /* * allocate a block or frag */ static daddr32_t alloc(int size, int mode) { int i, frag; daddr32_t d; rdfs(fsbtodb(&sblock, (uint64_t)cgtod(&sblock, 0)), sblock.fs_cgsize, (char *)&acg); if (acg.cg_magic != CG_MAGIC) { (void) fprintf(stderr, gettext("cg 0: bad magic number\n")); lockexit(32); } if (acg.cg_cs.cs_nbfree == 0) { (void) fprintf(stderr, gettext("first cylinder group ran out of space\n")); lockexit(32); } for (d = 0; d < acg.cg_ndblk; d += sblock.fs_frag) if (isblock(&sblock, cg_blksfree(&acg), d / sblock.fs_frag)) goto goth; (void) fprintf(stderr, gettext("internal error: can't find block in cyl 0\n")); lockexit(32); goth: clrblock(&sblock, cg_blksfree(&acg), d / sblock.fs_frag); acg.cg_cs.cs_nbfree--; sblock.fs_cstotal.cs_nbfree--; fscs[0].cs_nbfree--; if (mode & IFDIR) { acg.cg_cs.cs_ndir++; sblock.fs_cstotal.cs_ndir++; fscs[0].cs_ndir++; } cg_blktot(&acg)[cbtocylno(&sblock, d)]--; cg_blks(&sblock, &acg, cbtocylno(&sblock, d))[cbtorpos(&sblock, d)]--; if (size != sblock.fs_bsize) { frag = howmany(size, sblock.fs_fsize); fscs[0].cs_nffree += sblock.fs_frag - frag; sblock.fs_cstotal.cs_nffree += sblock.fs_frag - frag; acg.cg_cs.cs_nffree += sblock.fs_frag - frag; acg.cg_frsum[sblock.fs_frag - frag]++; for (i = frag; i < sblock.fs_frag; i++) setbit(cg_blksfree(&acg), d + i); } wtfs(fsbtodb(&sblock, (uint64_t)cgtod(&sblock, 0)), sblock.fs_cgsize, (char *)&acg); return (d); } /* * Allocate an inode on the disk */ static void iput(struct inode *ip) { struct dinode buf[MAXINOPB]; diskaddr_t d; rdfs(fsbtodb(&sblock, (uint64_t)cgtod(&sblock, 0)), sblock.fs_cgsize, (char *)&acg); if (acg.cg_magic != CG_MAGIC) { (void) fprintf(stderr, gettext("cg 0: bad magic number\n")); lockexit(32); } acg.cg_cs.cs_nifree--; setbit(cg_inosused(&acg), ip->i_number); wtfs(fsbtodb(&sblock, (uint64_t)cgtod(&sblock, 0)), sblock.fs_cgsize, (char *)&acg); sblock.fs_cstotal.cs_nifree--; fscs[0].cs_nifree--; if ((int)ip->i_number >= sblock.fs_ipg * sblock.fs_ncg) { (void) fprintf(stderr, gettext("fsinit: inode value out of range (%d).\n"), ip->i_number); lockexit(32); } d = fsbtodb(&sblock, (uint64_t)itod(&sblock, (int)ip->i_number)); rdfs(d, sblock.fs_bsize, (char *)buf); buf[itoo(&sblock, (int)ip->i_number)].di_ic = ip->i_ic; wtfs(d, sblock.fs_bsize, (char *)buf); } /* * getbuf() -- Get a buffer for use in an AIO operation. Buffer * is zero'd the first time returned, left with whatever * was in memory after that. This function actually gets * enough memory the first time it's called to support * MAXBUF buffers like a slab allocator. When all the * buffers are in use, it waits for an aio to complete * and make a buffer available. * * Never returns an error. Either succeeds or exits. */ static char * getbuf(bufhdr *bufhead, int size) { bufhdr *pbuf; bufhdr *prev; int i; int buf_size, max_bufs; /* * Initialize all the buffers */ if (bufhead->head == NULL) { /* * round up the size of our buffer header to a * 16 byte boundary so the address we return to * the caller is "suitably aligned". */ bufhdrsize = (sizeof (bufhdr) + 15) & ~15; /* * Add in our header to the buffer and round it all up to * a 16 byte boundry so each member of the slab is aligned. */ buf_size = (size + bufhdrsize + 15) & ~15; /* * Limit number of buffers to lesser of MAXBUFMEM's worth * or MAXBUF, whichever is less. */ max_bufs = MAXBUFMEM / buf_size; if (max_bufs > MAXBUF) max_bufs = MAXBUF; pbuf = (bufhdr *)calloc(max_bufs, buf_size); if (pbuf == NULL) { perror("calloc"); lockexit(32); } bufhead->head = bufhead; prev = bufhead; for (i = 0; i < max_bufs; i++) { pbuf->head = bufhead; prev->next = pbuf; prev = pbuf; pbuf = (bufhdr *)((char *)pbuf + buf_size); } } /* * Get an available buffer, waiting for I/O if necessary */ wait_for_write(NOBLOCK); while (bufhead->next == NULL) wait_for_write(BLOCK); /* * Take the buffer off the list */ pbuf = bufhead->next; bufhead->next = pbuf->next; pbuf->next = NULL; /* * return the empty buffer space just past the header */ return ((char *)pbuf + bufhdrsize); } /* * freebuf() -- Free a buffer gotten previously through getbuf. * Puts the buffer back on the appropriate list for * later use. Never calls free(). * * Assumes that SIGINT is blocked. */ static void freebuf(char *buf) { bufhdr *pbuf; bufhdr *bufhead; /* * get the header for this buffer */ pbuf = (bufhdr *)(buf - bufhdrsize); /* * Put it back on the list of available buffers */ bufhead = pbuf->head; pbuf->next = bufhead->next; bufhead->next = pbuf; } /* * freetrans() -- Free a transaction gotten previously through getaiop. * Puts the transaction struct back on the appropriate list for * later use. Never calls free(). * * Assumes that SIGINT is blocked. */ static void freetrans(aio_trans *transp) { /* * free the buffer associated with this AIO if needed */ if (transp->release == RELEASE) freebuf(transp->buffer); /* * Put transaction on the free list */ transp->next = results.trans; results.trans = transp; } /* * wait_for_write() -- Wait for an aio write to complete. Return * the transaction structure for that write. * * Blocks SIGINT if necessary. */ aio_trans * wait_for_write(int block) { aio_trans *transp; aio_result_t *resultp; static struct timeval zero_wait = { 0, 0 }; sigset_t old_mask; /* * If we know there aren't any outstanding transactions, just return */ if (results.outstanding == 0) return ((aio_trans *) 0); block_sigint(&old_mask); resultp = aiowait(block ? NULL : &zero_wait); if (resultp == NULL || (resultp == (aio_result_t *)-1 && errno == EINVAL)) { unblock_sigint(&old_mask); return ((aio_trans *) 0); } results.outstanding--; transp = (aio_trans *)resultp; if (resultp->aio_return != transp->size) { if (resultp->aio_return == -1) { /* * The aiowrite() may have failed because the * kernel didn't have enough memory to do the job. * Flush all pending writes and try a normal * write(). wtfs_breakup() will call exit if it * fails, so we don't worry about errors here. */ flush_writes(); wtfs_breakup(transp->bno, transp->size, transp->buffer); } else { (void) fprintf(stderr, gettext( "short write (%d of %d bytes) on sector %lld\n"), resultp->aio_return, transp->size, transp->bno); /* * Don't unblock SIGINT, to avoid potential * looping due to queued interrupts and * error handling. */ lockexit(32); } } resultp->aio_return = 0; freetrans(transp); unblock_sigint(&old_mask); return (transp); } /* * flush_writes() -- flush all the outstanding aio writes. */ static void flush_writes(void) { while (wait_for_write(BLOCK)) ; } /* * get_aiop() -- find and return an aio_trans structure on which a new * aio can be done. Blocks on aiowait() if needed. Reaps * all outstanding completed aio's. * * Assumes that SIGINT is blocked. */ aio_trans * get_aiop() { int i; aio_trans *transp; aio_trans *prev; /* * initialize aio stuff */ if (!aio_inited) { aio_inited = 1; results.maxpend = 0; results.outstanding = 0; results.max = MAXAIO; results.trans = (aio_trans *)calloc(results.max, sizeof (aio_trans)); if (results.trans == NULL) { perror("calloc"); lockexit(32); } /* * Initialize the linked list of aio transaction * structures. Note that the final "next" pointer * will be NULL since we got the buffer from calloc(). */ prev = results.trans; for (i = 1; i < results.max; i++) { prev->next = &(results.trans[i]); prev = prev->next; } } wait_for_write(NOBLOCK); while (results.trans == NULL) wait_for_write(BLOCK); transp = results.trans; results.trans = results.trans->next; transp->next = 0; transp->resultbuf.aio_return = AIO_INPROGRESS; return (transp); } /* * read a block from the file system */ static void rdfs(diskaddr_t bno, int size, char *bf) { int n, saverr; /* * In case we need any data that's pending in an aiowrite(), * we wait for them all to complete before doing a read. */ flush_writes(); /* * Note: the llseek() can succeed, even if the offset is out of range. * It's not until the file i/o operation (the read()) that one knows * for sure if the raw device can handle the offset. */ if (llseek(fsi, (offset_t)bno * sectorsize, 0) < 0) { saverr = errno; (void) fprintf(stderr, gettext("seek error on sector %lld: %s\n"), bno, strerror(saverr)); lockexit(32); } n = read(fsi, bf, size); if (n != size) { saverr = errno; if (n == -1) (void) fprintf(stderr, gettext("read error on sector %lld: %s\n"), bno, strerror(saverr)); else (void) fprintf(stderr, gettext( "short read (%d of %d bytes) on sector %lld\n"), n, size, bno); lockexit(32); } } /* * write a block to the file system */ static void wtfs(diskaddr_t bno, int size, char *bf) { int n, saverr; if (fso == -1) return; /* * Note: the llseek() can succeed, even if the offset is out of range. * It's not until the file i/o operation (the write()) that one knows * for sure if the raw device can handle the offset. */ if (llseek(fso, (offset_t)bno * sectorsize, 0) < 0) { saverr = errno; (void) fprintf(stderr, gettext("seek error on sector %lld: %s\n"), bno, strerror(saverr)); lockexit(32); } if (Nflag) return; n = write(fso, bf, size); if (n != size) { saverr = errno; if (n == -1) (void) fprintf(stderr, gettext("write error on sector %lld: %s\n"), bno, strerror(saverr)); else (void) fprintf(stderr, gettext( "short write (%d of %d bytes) on sector %lld\n"), n, size, bno); lockexit(32); } } /* * write a block to the file system -- buffered with aio */ static void awtfs(diskaddr_t bno, int size, char *bf, int release) { int n; aio_trans *transp; sigset_t old_mask; if (fso == -1) return; /* * We need to keep things consistent if we get interrupted, * so defer any expected interrupts for the time being. */ block_sigint(&old_mask); if (Nflag) { if (release == RELEASE) freebuf(bf); } else { transp = get_aiop(); transp->bno = bno; transp->buffer = bf; transp->size = size; transp->release = release; n = aiowrite(fso, bf, size, (off_t)bno * sectorsize, SEEK_SET, &transp->resultbuf); if (n < 0) { /* * The aiowrite() may have failed because the * kernel didn't have enough memory to do the job. * Flush all pending writes and try a normal * write(). wtfs_breakup() will call exit if it * fails, so we don't worry about errors here. */ flush_writes(); wtfs_breakup(transp->bno, transp->size, transp->buffer); freetrans(transp); } else { /* * Keep track of our pending writes. */ results.outstanding++; if (results.outstanding > results.maxpend) results.maxpend = results.outstanding; } } unblock_sigint(&old_mask); } /* * write a block to the file system, but break it up into sbsize * chunks to avoid forcing a large amount of memory to be locked down. * Only used as a fallback when an aio write has failed. */ static void wtfs_breakup(diskaddr_t bno, int size, char *bf) { int n, saverr; int wsize; int block_incr = sbsize / sectorsize; if (size < sbsize) wsize = size; else wsize = sbsize; n = 0; while (size) { /* * Note: the llseek() can succeed, even if the offset is * out of range. It's not until the file i/o operation * (the write()) that one knows for sure if the raw device * can handle the offset. */ if (llseek(fso, (offset_t)bno * sectorsize, 0) < 0) { saverr = errno; (void) fprintf(stderr, gettext("seek error on sector %lld: %s\n"), bno, strerror(saverr)); lockexit(32); } n = write(fso, bf, wsize); if (n == -1) { saverr = errno; (void) fprintf(stderr, gettext("write error on sector %lld: %s\n"), bno, strerror(saverr)); lockexit(32); } if (n != wsize) { saverr = errno; (void) fprintf(stderr, gettext( "short write (%d of %d bytes) on sector %lld\n"), n, size, bno); lockexit(32); } bno += block_incr; bf += wsize; size -= wsize; if (size < wsize) wsize = size; } } /* * check if a block is available */ static int isblock(struct fs *fs, unsigned char *cp, int h) { unsigned char mask; switch (fs->fs_frag) { case 8: return (cp[h] == 0xff); case 4: mask = 0x0f << ((h & 0x1) << 2); return ((cp[h >> 1] & mask) == mask); case 2: mask = 0x03 << ((h & 0x3) << 1); return ((cp[h >> 2] & mask) == mask); case 1: mask = 0x01 << (h & 0x7); return ((cp[h >> 3] & mask) == mask); default: (void) fprintf(stderr, "isblock bad fs_frag %d\n", fs->fs_frag); return (0); } } /* * take a block out of the map */ static void clrblock(struct fs *fs, unsigned char *cp, int h) { switch ((fs)->fs_frag) { case 8: cp[h] = 0; return; case 4: cp[h >> 1] &= ~(0x0f << ((h & 0x1) << 2)); return; case 2: cp[h >> 2] &= ~(0x03 << ((h & 0x3) << 1)); return; case 1: cp[h >> 3] &= ~(0x01 << (h & 0x7)); return; default: (void) fprintf(stderr, gettext("clrblock: bad fs_frag value %d\n"), fs->fs_frag); return; } } /* * put a block into the map */ static void setblock(struct fs *fs, unsigned char *cp, int h) { switch (fs->fs_frag) { case 8: cp[h] = 0xff; return; case 4: cp[h >> 1] |= (0x0f << ((h & 0x1) << 2)); return; case 2: cp[h >> 2] |= (0x03 << ((h & 0x3) << 1)); return; case 1: cp[h >> 3] |= (0x01 << (h & 0x7)); return; default: (void) fprintf(stderr, gettext("setblock: bad fs_frag value %d\n"), fs->fs_frag); return; } } static void usage(void) { (void) fprintf(stderr, gettext("ufs usage: mkfs [-F FSType] [-V] [-m] [-o options] " "special " /* param 0 */ "size(sectors) \\ \n")); /* param 1 */ (void) fprintf(stderr, "[nsect " /* param 2 */ "ntrack " /* param 3 */ "bsize " /* param 4 */ "fragsize " /* param 5 */ "cpg " /* param 6 */ "free " /* param 7 */ "rps " /* param 8 */ "nbpi " /* param 9 */ "opt " /* param 10 */ "apc " /* param 11 */ "gap " /* param 12 */ "nrpos " /* param 13 */ "maxcontig " /* param 14 */ "mtb]\n"); /* param 15 */ (void) fprintf(stderr, gettext(" -m : dump fs cmd line used to make this partition\n" " -V :print this command line and return\n" " -o :ufs options: :nsect=%d,ntrack=%d,bsize=%d,fragsize=%d\n" " -o :ufs options: :cgsize=%d,free=%d,rps=%d,nbpi=%d,opt=%c\n" " -o :ufs options: :apc=%d,gap=%d,nrpos=%d,maxcontig=%d\n" " -o :ufs options: :mtb=%c,calcsb,calcbinsb\n" "NOTE that all -o suboptions: must be separated only by commas so as to\n" "be parsed as a single argument\n"), nsect, ntrack, bsize, fragsize, cpg, sblock.fs_minfree, rps, nbpi, opt, apc, (rotdelay == -1) ? 0 : rotdelay, sblock.fs_nrpos, maxcontig, mtb); lockexit(32); } /*ARGSUSED*/ static void dump_fscmd(char *fsys, int fsi) { int64_t used, bpcg, inospercg; int64_t nbpi; uint64_t nbytes64; bzero((char *)&sblock, sizeof (sblock)); rdfs((diskaddr_t)SBLOCK, SBSIZE, (char *)&sblock); /* * ensure a valid file system and if not, exit with error or else * we will end up computing block numbers etc and dividing by zero * which will cause floating point errors in this routine. */ if ((sblock.fs_magic != FS_MAGIC) && (sblock.fs_magic != MTB_UFS_MAGIC)) { (void) fprintf(stderr, gettext( "[not currently a valid file system - bad superblock]\n")); lockexit(32); } if (sblock.fs_magic == FS_MAGIC && (sblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && sblock.fs_version != UFS_VERSION_MIN)) { (void) fprintf(stderr, gettext( "Unknown version of UFS format: %d\n"), sblock.fs_version); lockexit(32); } if (sblock.fs_magic == MTB_UFS_MAGIC && (sblock.fs_version > MTB_UFS_VERSION_1 || sblock.fs_version < MTB_UFS_VERSION_MIN)) { (void) fprintf(stderr, gettext( "Unknown version of UFS format: %d\n"), sblock.fs_version); lockexit(32); } /* * Compute a reasonable nbpi value. * The algorithm for "used" is copied from code * in main() verbatim. * The nbpi equation is taken from main where the * fs_ipg value is set for the last time. The INOPB(...) - 1 * is used to account for the roundup. * The problem is that a range of nbpi values map to * the same file system layout. So it is not possible * to calculate the exact value specified when the file * system was created. So instead we determine the top * end of the range of values. */ bpcg = sblock.fs_spc * sectorsize; inospercg = (int64_t)roundup(bpcg / sizeof (struct dinode), INOPB(&sblock)); if (inospercg > MAXIpG(&sblock)) inospercg = MAXIpG(&sblock); used = (int64_t) (sblock.fs_iblkno + inospercg / INOPF(&sblock)) * NSPF(&sblock); used *= sectorsize; nbytes64 = (uint64_t)sblock.fs_cpg * bpcg - used; /* * The top end of the range of values for nbpi may not be * a valid command line value for mkfs. Report the bottom * end instead. */ nbpi = (int64_t)(nbytes64 / (sblock.fs_ipg)); (void) fprintf(stdout, gettext("mkfs -F ufs -o "), fsys); (void) fprintf(stdout, "nsect=%d,ntrack=%d,", sblock.fs_nsect, sblock.fs_ntrak); (void) fprintf(stdout, "bsize=%d,fragsize=%d,cgsize=%d,free=%d,", sblock.fs_bsize, sblock.fs_fsize, sblock.fs_cpg, sblock.fs_minfree); (void) fprintf(stdout, "rps=%d,nbpi=%lld,opt=%c,apc=%d,gap=%d,", sblock.fs_rps, nbpi, (sblock.fs_optim == FS_OPTSPACE) ? 's' : 't', (sblock.fs_ntrak * sblock.fs_nsect) - sblock.fs_spc, sblock.fs_rotdelay); (void) fprintf(stdout, "nrpos=%d,maxcontig=%d,mtb=%c ", sblock.fs_nrpos, sblock.fs_maxcontig, ((sblock.fs_magic == MTB_UFS_MAGIC) ? 'y' : 'n')); (void) fprintf(stdout, "%s %lld\n", fsys, fsbtodb(&sblock, sblock.fs_size)); bzero((char *)&sblock, sizeof (sblock)); } /* number ************************************************************* */ /* */ /* Convert a numeric string arg to binary */ /* */ /* Args: d_value - default value, if have parse error */ /* param - the name of the argument, for error messages */ /* flags - parser state and what's allowed in the arg */ /* Global arg: string - pointer to command arg */ /* */ /* Valid forms: 123 | 123k | 123*123 | 123x123 */ /* */ /* Return: converted number */ /* */ /* ******************************************************************** */ static uint64_t number(uint64_t d_value, char *param, int flags) { char *cs; uint64_t n, t; uint64_t cut = BIG / 10; /* limit to avoid overflow */ int minus = 0; cs = string; if (*cs == '-') { minus = 1; cs += 1; } if ((*cs < '0') || (*cs > '9')) { goto bail_out; } n = 0; while ((*cs >= '0') && (*cs <= '9') && (n <= cut)) { n = n*10 + *cs++ - '0'; } if (minus) n = -n; for (;;) { switch (*cs++) { case 'k': if (flags & ALLOW_END_ONLY) goto bail_out; if (n > (BIG / 1024)) goto overflow; n *= 1024; continue; case '*': case 'x': if (flags & ALLOW_END_ONLY) goto bail_out; string = cs; t = number(d_value, param, flags); if (n > (BIG / t)) goto overflow; n *= t; cs = string + 1; /* adjust for -- below */ /* recursion has read rest of expression */ /* FALLTHROUGH */ case ',': case '\0': cs--; string = cs; return (n); case '%': if (flags & ALLOW_END_ONLY) goto bail_out; if (flags & ALLOW_PERCENT) { flags &= ~ALLOW_PERCENT; flags |= ALLOW_END_ONLY; continue; } goto bail_out; case 'm': if (flags & ALLOW_END_ONLY) goto bail_out; if (flags & ALLOW_MS1) { flags &= ~ALLOW_MS1; flags |= ALLOW_MS2; continue; } goto bail_out; case 's': if (flags & ALLOW_END_ONLY) goto bail_out; if (flags & ALLOW_MS2) { flags &= ~ALLOW_MS2; flags |= ALLOW_END_ONLY; continue; } goto bail_out; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': overflow: (void) fprintf(stderr, gettext("mkfs: value for %s overflowed\n"), param); while ((*cs != '\0') && (*cs != ',')) cs++; string = cs; return (BIG); default: bail_out: (void) fprintf(stderr, gettext( "mkfs: bad numeric arg for %s: \"%s\"\n"), param, string); while ((*cs != '\0') && (*cs != ',')) cs++; string = cs; if (d_value != NO_DEFAULT) { (void) fprintf(stderr, gettext("mkfs: %s reset to default %lld\n"), param, d_value); return (d_value); } lockexit(2); } } /* never gets here */ } /* match ************************************************************** */ /* */ /* Compare two text strings for equality */ /* */ /* Arg: s - pointer to string to match with a command arg */ /* Global arg: string - pointer to command arg */ /* */ /* Return: 1 if match, 0 if no match */ /* If match, also reset `string' to point to the text */ /* that follows the matching text. */ /* */ /* ******************************************************************** */ static int match(char *s) { char *cs; cs = string; while (*cs++ == *s) { if (*s++ == '\0') { goto true; } } if (*s != '\0') { return (0); } true: cs--; string = cs; return (1); } /* * GROWFS ROUTINES */ /* ARGSUSED */ void lockexit(int exitstatus) { if (Pflag) { /* the probe mode neither changes nor locks the filesystem */ exit(exitstatus); } /* * flush the dirty cylinder group */ if (inlockexit == 0) { inlockexit = 1; flcg(); } if (aio_inited) { flush_writes(); } /* * make sure the file system is unlocked before exiting */ if ((inlockexit == 1) && (!isbad)) { inlockexit = 2; ulockfs(); /* * if logging was enabled, then re-enable it */ if (waslog) { if (rl_log_control(fsys, _FIOLOGENABLE) != RL_SUCCESS) { (void) fprintf(stderr, gettext( "failed to re-enable logging\n")); } } } else if (grow) { if (isbad) { (void) fprintf(stderr, gettext( "Filesystem is currently inconsistent. It " "must be repaired with fsck(8)\nbefore being " "used. Use the following command to " "do this:\n\n\tfsck %s\n\n"), fsys); if (ismounted) { (void) fprintf(stderr, gettext( "You will be told that the filesystem " "is already mounted, and asked if you\n" "wish to continue. Answer `yes' to " "this question.\n\n")); } (void) fprintf(stderr, gettext( "One problem should be reported, that the summary " "information is bad.\nYou will then be asked if it " "should be salvaged. Answer `yes' to\nthis " "question.\n\n")); } if (ismounted) { /* * In theory, there's no way to get here without * isbad also being set, but be robust in the * face of future code changes. */ (void) fprintf(stderr, gettext( "The filesystem is currently mounted " "read-only and write-locked. ")); if (isbad) { (void) fprintf(stderr, gettext( "After\nrunning fsck, unlock the " "filesystem and ")); } else { (void) fprintf(stderr, gettext( "Unlock the filesystem\nand ")); } (void) fprintf(stderr, gettext( "re-enable writing with\nthe following " "command:\n\n\tlockfs -u %s\n\n"), directory); } } exit(exitstatus); } void randomgeneration() { int i; struct dinode *dp; /* * always perform fsirand(8) function... newfs will notice that * the inodes have been randomized and will not call fsirand itself */ for (i = 0, dp = zino; i < sblock.fs_inopb; ++i, ++dp) IRANDOMIZE(&dp->di_ic); } /* * Check the size of the summary information. * Fields in sblock are not changed in this function. * * For an 8K filesystem block, the maximum number of cylinder groups is 16384. * MAXCSBUFS {32} * 8K {FS block size} * divided by (sizeof csum) {16} * * Note that MAXCSBUFS is not used in the kernel; as of Solaris 2.6 build 32, * this is the only place where it's referenced. */ void checksummarysize() { diskaddr_t dmax; diskaddr_t dmin; int64_t cg0frags; int64_t cg0blocks; int64_t maxncg; int64_t maxfrags; uint64_t fs_size; uint64_t maxfs_blocks; /* filesystem blocks for max filesystem size */ /* * compute the maximum summary info size */ dmin = cgdmin(&sblock, 0); dmax = cgbase(&sblock, 0) + sblock.fs_fpg; fs_size = (grow) ? grow_fs_size : sblock.fs_size; if (dmax > fs_size) dmax = fs_size; cg0frags = dmax - dmin; cg0blocks = cg0frags / sblock.fs_frag; cg0frags = cg0blocks * sblock.fs_frag; maxncg = (longlong_t)cg0blocks * (longlong_t)(sblock.fs_bsize / sizeof (struct csum)); maxfs_blocks = FS_MAX; if (maxncg > ((longlong_t)maxfs_blocks / (longlong_t)sblock.fs_fpg) + 1) maxncg = ((longlong_t)maxfs_blocks / (longlong_t)sblock.fs_fpg) + 1; maxfrags = maxncg * (longlong_t)sblock.fs_fpg; if (maxfrags > maxfs_blocks) maxfrags = maxfs_blocks; /* * remember for later processing in extendsummaryinfo() */ if (test) grow_sifrag = dmin + (cg0blocks * sblock.fs_frag); if (testfrags == 0) testfrags = cg0frags; if (testforce) if (testfrags > cg0frags) { (void) fprintf(stderr, gettext("Too many test frags (%lld); " "try %lld\n"), testfrags, cg0frags); lockexit(32); } /* * if summary info is too large (too many cg's) tell the user and exit */ if ((longlong_t)sblock.fs_size > maxfrags) { (void) fprintf(stderr, gettext( "Too many cylinder groups with %llu sectors;\n try " "increasing cgsize, or decreasing fssize to %llu\n"), fsbtodb(&sblock, (uint64_t)sblock.fs_size), fsbtodb(&sblock, (uint64_t)maxfrags)); lockexit(32); } } /* * checksblock() has two uses: * - One is to sanity test the superblock and is used when newfs(8) * is invoked with the "-N" option. If any discrepancy was found, * just return whatever error was found and do not exit. * - the other use of it is in places where you expect the superblock * to be sane, and if it isn't, then we exit. * Which of the above two actions to take is indicated with the second argument. */ int checksblock(struct fs sb, int proceed) { int err = 0; char *errmsg; if ((sb.fs_magic != FS_MAGIC) && (sb.fs_magic != MTB_UFS_MAGIC)) { err = 1; errmsg = gettext("Bad superblock; magic number wrong\n"); } else if ((sb.fs_magic == FS_MAGIC && (sb.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && sb.fs_version != UFS_VERSION_MIN)) || (sb.fs_magic == MTB_UFS_MAGIC && (sb.fs_version > MTB_UFS_VERSION_1 || sb.fs_version < MTB_UFS_VERSION_MIN))) { err = 2; errmsg = gettext("Unrecognized version of UFS\n"); } else if (sb.fs_ncg < 1) { err = 3; errmsg = gettext("Bad superblock; ncg out of range\n"); } else if (sb.fs_cpg < 1) { err = 4; errmsg = gettext("Bad superblock; cpg out of range\n"); } else if (sb.fs_ncg * sb.fs_cpg < sb.fs_ncyl || (sb.fs_ncg - 1) * sb.fs_cpg >= sb.fs_ncyl) { err = 5; errmsg = gettext("Bad superblock; ncyl out of range\n"); } else if (sb.fs_sbsize <= 0 || sb.fs_sbsize > sb.fs_bsize) { err = 6; errmsg = gettext("Bad superblock; superblock size out of " "range\n"); } if (proceed) { if (err) dbgprintf(("%s", errmsg)); return (err); } if (err) { fprintf(stderr, "%s", errmsg); lockexit(32); } return (32); } /* * Roll the embedded log, if any, and set up the global variables * islog and islogok. */ static void logsetup(char *devstr) { void *buf, *ud_buf; extent_block_t *ebp; ml_unit_t *ul; ml_odunit_t *ud; /* * Does the superblock indicate that we are supposed to have a log ? */ if (sblock.fs_logbno == 0) { /* * No log present, nothing to do. */ islog = 0; islogok = 0; return; } else { /* * There's a log in a yet unknown state, attempt to roll it. */ islogok = 0; /* * We failed to roll the log, bail out. */ if (rl_roll_log(devstr) != RL_SUCCESS) return; islog = 1; /* log is not okay; check the fs */ if ((FSOKAY != (sblock.fs_state + sblock.fs_time)) || (sblock.fs_clean != FSLOG)) return; /* get the log allocation block */ buf = (void *)malloc(DEV_BSIZE); if (buf == (void *) NULL) return; ud_buf = (void *)malloc(DEV_BSIZE); if (ud_buf == (void *) NULL) { free(buf); return; } rdfs((diskaddr_t)logbtodb(&sblock, sblock.fs_logbno), DEV_BSIZE, buf); ebp = (extent_block_t *)buf; /* log allocation block is not okay; check the fs */ if (ebp->type != LUFS_EXTENTS) { free(buf); free(ud_buf); return; } /* get the log state block(s) */ rdfs((diskaddr_t)logbtodb(&sblock, ebp->extents[0].pbno), DEV_BSIZE, ud_buf); ud = (ml_odunit_t *)ud_buf; ul = (ml_unit_t *)malloc(sizeof (*ul)); ul->un_ondisk = *ud; /* log state is okay */ if ((ul->un_chksum == ul->un_head_ident + ul->un_tail_ident) && (ul->un_version == LUFS_VERSION_LATEST) && (ul->un_badlog == 0)) islogok = 1; free(ud_buf); free(buf); free(ul); } } void growinit(char *devstr) { int i; char buf[DEV_BSIZE]; /* * Read and verify the superblock */ rdfs((diskaddr_t)(SBOFF / sectorsize), (int)sbsize, (char *)&sblock); (void) checksblock(sblock, 0); if (sblock.fs_postblformat != FS_DYNAMICPOSTBLFMT) { (void) fprintf(stderr, gettext("old file system format; can't growfs\n")); lockexit(32); } /* * can't shrink a file system */ grow_fssize = fsbtodb(&sblock, (uint64_t)sblock.fs_size); if (fssize_db < grow_fssize) { (void) fprintf(stderr, gettext("%lld sectors < current size of %lld sectors\n"), fssize_db, grow_fssize); lockexit(32); } /* * can't grow a system to over a terabyte unless it was set up * as an MTB UFS file system. */ if (mtb == 'y' && sblock.fs_magic != MTB_UFS_MAGIC) { if (fssize_db >= SECTORS_PER_TERABYTE) { (void) fprintf(stderr, gettext( "File system was not set up with the multi-terabyte format.\n")); (void) fprintf(stderr, gettext( "Its size cannot be increased to a terabyte or more.\n")); } else { (void) fprintf(stderr, gettext( "Cannot convert file system to multi-terabyte format.\n")); } lockexit(32); } logsetup(devstr); /* * can't growfs when logging device has errors */ if ((islog && !islogok) || ((FSOKAY == (sblock.fs_state + sblock.fs_time)) && (sblock.fs_clean == FSLOG && !islog))) { (void) fprintf(stderr, gettext("logging device has errors; can't growfs\n")); lockexit(32); } /* * disable ufs logging for growing */ if (islog) { if (rl_log_control(devstr, _FIOLOGDISABLE) != RL_SUCCESS) { (void) fprintf(stderr, gettext( "failed to disable logging\n")); lockexit(32); } islog = 0; waslog = 1; } /* * if mounted write lock the file system to be grown */ if (ismounted) wlockfs(); /* * refresh dynamic superblock state - disabling logging will have * changed the amount of free space available in the file system */ rdfs((diskaddr_t)(SBOFF / sectorsize), sbsize, (char *)&sblock); /* * make sure device is big enough */ rdfs((diskaddr_t)fssize_db - 1, DEV_BSIZE, buf); wtfs((diskaddr_t)fssize_db - 1, DEV_BSIZE, buf); /* * read current summary information */ grow_fscs = read_summaryinfo(&sblock); /* * save some current size related fields from the superblock * These are used in extendsummaryinfo() */ grow_fs_size = sblock.fs_size; grow_fs_ncg = sblock.fs_ncg; grow_fs_csaddr = (diskaddr_t)sblock.fs_csaddr; grow_fs_cssize = sblock.fs_cssize; /* * save and reset the clean flag */ if (FSOKAY == (sblock.fs_state + sblock.fs_time)) grow_fs_clean = sblock.fs_clean; else grow_fs_clean = FSBAD; sblock.fs_clean = FSBAD; sblock.fs_state = FSOKAY - sblock.fs_time; isbad = 1; wtfs((diskaddr_t)(SBOFF / sectorsize), sbsize, (char *)&sblock); } void checkdev(char *rdev, char *bdev) { struct stat64 statarea; if (stat64(bdev, &statarea) < 0) { (void) fprintf(stderr, gettext("can't check mount point; ")); (void) fprintf(stderr, gettext("can't stat %s\n"), bdev); lockexit(32); } if ((statarea.st_mode & S_IFMT) != S_IFBLK) { (void) fprintf(stderr, gettext( "can't check mount point; %s is not a block device\n"), bdev); lockexit(32); } if (stat64(rdev, &statarea) < 0) { (void) fprintf(stderr, gettext("can't stat %s\n"), rdev); lockexit(32); } if ((statarea.st_mode & S_IFMT) != S_IFCHR) { (void) fprintf(stderr, gettext("%s is not a character device\n"), rdev); lockexit(32); } } void checkmount(struct mnttab *mntp, char *bdevname) { struct stat64 statdir; struct stat64 statdev; if (strcmp(bdevname, mntp->mnt_special) == 0) { if (stat64(mntp->mnt_mountp, &statdir) == -1) { (void) fprintf(stderr, gettext("can't stat %s\n"), mntp->mnt_mountp); lockexit(32); } if (stat64(mntp->mnt_special, &statdev) == -1) { (void) fprintf(stderr, gettext("can't stat %s\n"), mntp->mnt_special); lockexit(32); } if (statdir.st_dev != statdev.st_rdev) { (void) fprintf(stderr, gettext( "%s is not mounted on %s; mnttab(5) wrong\n"), mntp->mnt_special, mntp->mnt_mountp); lockexit(32); } ismounted = 1; if (directory) { if (strcmp(mntp->mnt_mountp, directory) != 0) { (void) fprintf(stderr, gettext("%s is mounted on %s, not %s\n"), bdevname, mntp->mnt_mountp, directory); lockexit(32); } } else { if (grow) (void) fprintf(stderr, gettext( "%s is mounted on %s; can't growfs\n"), bdevname, mntp->mnt_mountp); else (void) fprintf(stderr, gettext("%s is mounted, can't mkfs\n"), bdevname); lockexit(32); } } } struct dinode *dibuf = 0; diskaddr_t difrag = 0; struct dinode * gdinode(ino_t ino) { /* * read the block of inodes containing inode number ino */ if (dibuf == 0) dibuf = (struct dinode *)malloc((unsigned)sblock.fs_bsize); if (itod(&sblock, ino) != difrag) { difrag = itod(&sblock, ino); rdfs(fsbtodb(&sblock, (uint64_t)difrag), (int)sblock.fs_bsize, (char *)dibuf); } return (dibuf + (ino % INOPB(&sblock))); } /* * structure that manages the frags we need for extended summary info * These frags can be: * free * data block * alloc block */ struct csfrag { struct csfrag *next; /* next entry */ daddr32_t ofrag; /* old frag */ daddr32_t nfrag; /* new frag */ long cylno; /* cylno of nfrag */ long frags; /* number of frags */ long size; /* size in bytes */ ino_t ino; /* inode number */ long fixed; /* Boolean - Already fixed? */ }; struct csfrag *csfrag; /* state unknown */ struct csfrag *csfragino; /* frags belonging to an inode */ struct csfrag *csfragfree; /* frags that are free */ daddr32_t maxcsfrag = 0; /* maximum in range */ daddr32_t mincsfrag = 0x7fffffff; /* minimum in range */ int csfraginrange(daddr32_t frag) { return ((frag >= mincsfrag) && (frag <= maxcsfrag)); } struct csfrag * findcsfrag(daddr32_t frag, struct csfrag **cfap) { struct csfrag *cfp; if (!csfraginrange(frag)) return (NULL); for (cfp = *cfap; cfp; cfp = cfp->next) if (cfp->ofrag == frag) return (cfp); return (NULL); } void checkindirect(ino_t ino, daddr32_t *fragsp, daddr32_t frag, int level) { int i; int ne = sblock.fs_bsize / sizeof (daddr32_t); daddr32_t fsb[MAXBSIZE / sizeof (daddr32_t)]; if (frag == 0) return; rdfs(fsbtodb(&sblock, frag), (int)sblock.fs_bsize, (char *)fsb); checkdirect(ino, fragsp, fsb, sblock.fs_bsize / sizeof (daddr32_t)); if (level) for (i = 0; i < ne && *fragsp; ++i) checkindirect(ino, fragsp, fsb[i], level-1); } void addcsfrag(ino_t ino, daddr32_t frag, struct csfrag **cfap) { struct csfrag *cfp, *curr, *prev; /* * establish a range for faster checking in csfraginrange() */ if (frag > maxcsfrag) maxcsfrag = frag; if (frag < mincsfrag) mincsfrag = frag; /* * if this frag belongs to an inode and is not the start of a block * then see if it is part of a frag range for this inode */ if (ino && (frag % sblock.fs_frag)) for (cfp = *cfap; cfp; cfp = cfp->next) { if (ino != cfp->ino) continue; if (frag != cfp->ofrag + cfp->frags) continue; cfp->frags++; cfp->size += sblock.fs_fsize; return; } /* * allocate a csfrag entry and insert it in an increasing order into the * specified list */ cfp = (struct csfrag *)calloc(1, sizeof (struct csfrag)); cfp->ino = ino; cfp->ofrag = frag; cfp->frags = 1; cfp->size = sblock.fs_fsize; for (prev = NULL, curr = *cfap; curr != NULL; prev = curr, curr = curr->next) { if (frag < curr->ofrag) { cfp->next = curr; if (prev) prev->next = cfp; /* middle element */ else *cfap = cfp; /* first element */ break; } if (curr->next == NULL) { curr->next = cfp; /* last element */ break; } } if (*cfap == NULL) /* will happen only once */ *cfap = cfp; } void delcsfrag(daddr32_t frag, struct csfrag **cfap) { struct csfrag *cfp; struct csfrag **cfpp; /* * free up entry whose beginning frag matches */ for (cfpp = cfap; *cfpp; cfpp = &(*cfpp)->next) { if (frag == (*cfpp)->ofrag) { cfp = *cfpp; *cfpp = (*cfpp)->next; free((char *)cfp); return; } } } /* * See whether any of the direct blocks in the array pointed by "db" and of * length "ne" are within the range of frags needed to extend the cylinder * summary. If so, remove those frags from the "as-yet-unclassified" list * (csfrag) and add them to the "owned-by-inode" list (csfragino). * For each such frag found, decrement the frag count pointed to by fragsp. * "ino" is the inode that contains (either directly or indirectly) the frags * being checked. */ void checkdirect(ino_t ino, daddr32_t *fragsp, daddr32_t *db, int ne) { int i; int j; int found; diskaddr_t frag; /* * scan for allocation within the new summary info range */ for (i = 0; i < ne && *fragsp; ++i) { if ((frag = *db++) != 0) { found = 0; for (j = 0; j < sblock.fs_frag && *fragsp; ++j) { if (found || (found = csfraginrange(frag))) { addcsfrag(ino, frag, &csfragino); delcsfrag(frag, &csfrag); } ++frag; --(*fragsp); } } } } void findcsfragino() { int i; int j; daddr32_t frags; struct dinode *dp; /* * scan all old inodes looking for allocations in the new * summary info range. Move the affected frag from the * generic csfrag list onto the `owned-by-inode' list csfragino. */ for (i = UFSROOTINO; i < grow_fs_ncg*sblock.fs_ipg && csfrag; ++i) { dp = gdinode((ino_t)i); switch (dp->di_mode & IFMT) { case IFSHAD : case IFLNK : case IFDIR : case IFREG : break; default : continue; } frags = dbtofsb(&sblock, dp->di_blocks); checkdirect((ino_t)i, &frags, &dp->di_db[0], NDADDR+NIADDR); for (j = 0; j < NIADDR && frags; ++j) { /* Negate the block if its an fallocate'd block */ if (dp->di_ib[j] < 0 && dp->di_ib[j] != UFS_HOLE) checkindirect((ino_t)i, &frags, -(dp->di_ib[j]), j); else checkindirect((ino_t)i, &frags, dp->di_ib[j], j); } } } void fixindirect(daddr32_t frag, int level) { int i; int ne = sblock.fs_bsize / sizeof (daddr32_t); daddr32_t fsb[MAXBSIZE / sizeof (daddr32_t)]; if (frag == 0) return; rdfs(fsbtodb(&sblock, (uint64_t)frag), (int)sblock.fs_bsize, (char *)fsb); fixdirect((caddr_t)fsb, frag, fsb, ne); if (level) for (i = 0; i < ne; ++i) fixindirect(fsb[i], level-1); } void fixdirect(caddr_t bp, daddr32_t frag, daddr32_t *db, int ne) { int i; struct csfrag *cfp; for (i = 0; i < ne; ++i, ++db) { if (*db == 0) continue; if ((cfp = findcsfrag(*db, &csfragino)) == NULL) continue; *db = cfp->nfrag; cfp->fixed = 1; wtfs(fsbtodb(&sblock, (uint64_t)frag), (int)sblock.fs_bsize, bp); } } void fixcsfragino() { int i; struct dinode *dp; struct csfrag *cfp; for (cfp = csfragino; cfp; cfp = cfp->next) { if (cfp->fixed) continue; dp = gdinode((ino_t)cfp->ino); fixdirect((caddr_t)dibuf, difrag, dp->di_db, NDADDR+NIADDR); for (i = 0; i < NIADDR; ++i) fixindirect(dp->di_ib[i], i); } } /* * Read the cylinders summary information specified by settings in the * passed 'fs' structure into a new allocated array of csum structures. * The caller is responsible for freeing the returned array. * Return a pointer to an array of csum structures. */ static struct csum * read_summaryinfo(struct fs *fsp) { struct csum *csp; int i; if ((csp = malloc((size_t)fsp->fs_cssize)) == NULL) { (void) fprintf(stderr, gettext("cannot create csum list," " not enough memory\n")); exit(32); } for (i = 0; i < fsp->fs_cssize; i += fsp->fs_bsize) { rdfs(fsbtodb(fsp, (uint64_t)(fsp->fs_csaddr + numfrags(fsp, i))), (int)(fsp->fs_cssize - i < fsp->fs_bsize ? fsp->fs_cssize - i : fsp->fs_bsize), ((caddr_t)csp) + i); } return (csp); } /* * Check the allocation of fragments that are to be made part of a csum block. * A fragment is allocated if it is either in the csfragfree list or, it is * in the csfragino list and has new frags associated with it. * Return the number of allocated fragments. */ int64_t checkfragallocated(daddr32_t frag) { struct csfrag *cfp; /* * Since the lists are sorted we can break the search if the asked * frag is smaller then the one in the list. */ for (cfp = csfragfree; cfp != NULL && frag >= cfp->ofrag; cfp = cfp->next) { if (frag == cfp->ofrag) return (1); } for (cfp = csfragino; cfp != NULL && frag >= cfp->ofrag; cfp = cfp->next) { if (frag == cfp->ofrag && cfp->nfrag != 0) return (cfp->frags); } return (0); } /* * Figure out how much the filesystem can be grown. The limiting factor is * the available free space needed to extend the cg summary info block. * The free space is determined in three steps: * - Try to extend the cg summary block to the required size. * - Find free blocks in last cg. * - Find free space in the last already allocated fragment of the summary info * block, and use it for additional csum structures. * Return the maximum size of the new filesystem or 0 if it can't be grown. * Please note that this function leaves the global list pointers csfrag, * csfragfree, and csfragino initialized, and the caller is responsible for * freeing the lists. */ diskaddr_t probe_summaryinfo() { /* fragments by which the csum block can be extended. */ int64_t growth_csum_frags = 0; /* fragments by which the filesystem can be extended. */ int64_t growth_fs_frags = 0; int64_t new_fs_cssize; /* size of csum blk in the new FS */ int64_t new_fs_ncg; /* number of cg in the new FS */ int64_t spare_csum; daddr32_t oldfrag_daddr; daddr32_t newfrag_daddr; daddr32_t daddr; int i; /* * read and verify the superblock */ rdfs((diskaddr_t)(SBOFF / sectorsize), (int)sbsize, (char *)&sblock); (void) checksblock(sblock, 0); /* * check how much we can extend the cg summary info block */ /* * read current summary information */ fscs = read_summaryinfo(&sblock); /* * build list of frags needed for cg summary info block extension */ oldfrag_daddr = howmany(sblock.fs_cssize, sblock.fs_fsize) + sblock.fs_csaddr; new_fs_ncg = howmany(dbtofsb(&sblock, fssize_db), sblock.fs_fpg); new_fs_cssize = fragroundup(&sblock, new_fs_ncg * sizeof (struct csum)); newfrag_daddr = howmany(new_fs_cssize, sblock.fs_fsize) + sblock.fs_csaddr; /* * add all of the frags that are required to grow the cyl summary to the * csfrag list, which is the generic/unknown list, since at this point * we don't yet know the state of those frags. */ for (daddr = oldfrag_daddr; daddr < newfrag_daddr; daddr++) addcsfrag((ino_t)0, daddr, &csfrag); /* * filter free fragments and allocate them. Note that the free frags * must be allocated first otherwise they could be grabbed by * alloccsfragino() for data frags. */ findcsfragfree(); alloccsfragfree(); /* * filter fragments owned by inodes and allocate them */ grow_fs_ncg = sblock.fs_ncg; /* findcsfragino() needs this glob. var. */ findcsfragino(); alloccsfragino(); if (notenoughspace()) { /* * check how many consecutive fragments could be allocated * in both lists. */ int64_t tmp_frags; for (daddr = oldfrag_daddr; daddr < newfrag_daddr; daddr += tmp_frags) { if ((tmp_frags = checkfragallocated(daddr)) > 0) growth_csum_frags += tmp_frags; else break; } } else { /* * We have all we need for the new desired size, * so clean up and report back. */ return (fssize_db); } /* * given the number of fragments by which the csum block can be grown * compute by how many new fragments the FS can be increased. * It is the number of csum instances per fragment multiplied by * `growth_csum_frags' and the number of fragments per cylinder group. */ growth_fs_frags = howmany(sblock.fs_fsize, sizeof (struct csum)) * growth_csum_frags * sblock.fs_fpg; /* * compute free fragments in the last cylinder group */ rdcg(sblock.fs_ncg - 1); growth_fs_frags += sblock.fs_fpg - acg.cg_ndblk; /* * compute how many csum instances are unused in the old csum block. * For each unused csum instance the FS can be grown by one cylinder * group without extending the csum block. */ spare_csum = howmany(sblock.fs_cssize, sizeof (struct csum)) - sblock.fs_ncg; if (spare_csum > 0) growth_fs_frags += spare_csum * sblock.fs_fpg; /* * recalculate the new filesystem size in sectors, shorten it by * the requested size `fssize_db' if necessary. */ if (growth_fs_frags > 0) { diskaddr_t sect; sect = (sblock.fs_size + growth_fs_frags) * sblock.fs_nspf; return ((sect > fssize_db) ? fssize_db : sect); } return (0); } void extendsummaryinfo() { int64_t i; int localtest = test; int64_t frags; daddr32_t oldfrag; daddr32_t newfrag; /* * if no-write (-N), don't bother */ if (Nflag) return; again: flcg(); /* * summary info did not change size -- do nothing unless in test mode */ if (grow_fs_cssize == sblock.fs_cssize) if (!localtest) return; /* * build list of frags needed for additional summary information */ oldfrag = howmany(grow_fs_cssize, sblock.fs_fsize) + grow_fs_csaddr; newfrag = howmany(sblock.fs_cssize, sblock.fs_fsize) + grow_fs_csaddr; /* * add all of the frags that are required to grow the cyl summary to the * csfrag list, which is the generic/unknown list, since at this point * we don't yet know the state of those frags. */ for (i = oldfrag, frags = 0; i < newfrag; ++i, ++frags) addcsfrag((ino_t)0, (diskaddr_t)i, &csfrag); /* * reduce the number of data blocks in the file system (fs_dsize) by * the number of frags that need to be added to the cyl summary */ sblock.fs_dsize -= (newfrag - oldfrag); /* * In test mode, we move more data than necessary from * cylinder group 0. The lookup/allocate/move code can be * better stressed without having to create HUGE file systems. */ if (localtest) for (i = newfrag; i < grow_sifrag; ++i) { if (frags >= testfrags) break; frags++; addcsfrag((ino_t)0, (diskaddr_t)i, &csfrag); } /* * move frags to free or inode lists, depending on owner */ findcsfragfree(); findcsfragino(); /* * if not all frags can be located, file system must be inconsistent */ if (csfrag) { isbad = 1; /* should already be set, but make sure */ lockexit(32); } /* * allocate the free frags. Note that the free frags must be allocated * first otherwise they could be grabbed by alloccsfragino() for data * frags. */ alloccsfragfree(); /* * allocate extra space for inode frags */ alloccsfragino(); /* * not enough space */ if (notenoughspace()) { unalloccsfragfree(); unalloccsfragino(); if (localtest && !testforce) { localtest = 0; goto again; } (void) fprintf(stderr, gettext("Not enough free space\n")); lockexit(NOTENOUGHSPACE); } /* * copy the data from old frags to new frags */ copycsfragino(); /* * fix the inodes to point to the new frags */ fixcsfragino(); /* * We may have moved more frags than we needed. Free them. */ rdcg((long)0); for (i = newfrag; i <= maxcsfrag; ++i) setbit(cg_blksfree(&acg), i-cgbase(&sblock, 0)); wtcg(); flcg(); } /* * Check if all fragments in the `csfragino' list were reallocated. */ int notenoughspace() { struct csfrag *cfp; /* * If any element in the csfragino array has a "new frag location" * of 0, the allocfrags() function was unsuccessful in allocating * space for moving the frag represented by this array element. */ for (cfp = csfragino; cfp; cfp = cfp->next) if (cfp->nfrag == 0) return (1); return (0); } void unalloccsfragino() { struct csfrag *cfp; while ((cfp = csfragino) != NULL) { if (cfp->nfrag) freefrags(cfp->nfrag, cfp->frags, cfp->cylno); delcsfrag(cfp->ofrag, &csfragino); } } void unalloccsfragfree() { struct csfrag *cfp; while ((cfp = csfragfree) != NULL) { freefrags(cfp->ofrag, cfp->frags, cfp->cylno); delcsfrag(cfp->ofrag, &csfragfree); } } /* * For each frag in the "as-yet-unclassified" list (csfrag), see if * it's free (i.e., its bit is set in the free frag bit map). If so, * move it from the "as-yet-unclassified" list to the csfragfree list. */ void findcsfragfree() { struct csfrag *cfp; struct csfrag *cfpnext; /* * move free frags onto the free-frag list */ rdcg((long)0); for (cfp = csfrag; cfp; cfp = cfpnext) { cfpnext = cfp->next; if (isset(cg_blksfree(&acg), cfp->ofrag - cgbase(&sblock, 0))) { addcsfrag(cfp->ino, cfp->ofrag, &csfragfree); delcsfrag(cfp->ofrag, &csfrag); } } } void copycsfragino() { struct csfrag *cfp; char buf[MAXBSIZE]; /* * copy data from old frags to newly allocated frags */ for (cfp = csfragino; cfp; cfp = cfp->next) { rdfs(fsbtodb(&sblock, (uint64_t)cfp->ofrag), (int)cfp->size, buf); wtfs(fsbtodb(&sblock, (uint64_t)cfp->nfrag), (int)cfp->size, buf); } } long curcylno = -1; int cylnodirty = 0; void rdcg(long cylno) { if (cylno != curcylno) { flcg(); curcylno = cylno; rdfs(fsbtodb(&sblock, (uint64_t)cgtod(&sblock, curcylno)), (int)sblock.fs_cgsize, (char *)&acg); } } void flcg() { if (cylnodirty) { if (debug && Pflag) { (void) fprintf(stderr, "Assert: cylnodirty set in probe mode\n"); return; } resetallocinfo(); wtfs(fsbtodb(&sblock, (uint64_t)cgtod(&sblock, curcylno)), (int)sblock.fs_cgsize, (char *)&acg); cylnodirty = 0; } curcylno = -1; } void wtcg() { if (!Pflag) { /* probe mode should never write to disk */ cylnodirty = 1; } } void allocfrags(long frags, daddr32_t *fragp, long *cylnop) { int i; int j; long bits; long bit; /* * Allocate a free-frag range in an old cylinder group */ for (i = 0, *fragp = 0; i < grow_fs_ncg; ++i) { if (((fscs+i)->cs_nffree < frags) && ((fscs+i)->cs_nbfree == 0)) continue; rdcg((long)i); bit = bits = 0; while (findfreerange(&bit, &bits)) { if (frags <= bits) { for (j = 0; j < frags; ++j) clrbit(cg_blksfree(&acg), bit+j); wtcg(); *cylnop = i; *fragp = bit + cgbase(&sblock, i); return; } bit += bits; } } } /* * Allocate space for frags that need to be moved in order to free up space for * expanding the cylinder summary info. * For each frag that needs to be moved (each frag or range of frags in * the csfragino list), allocate a new location and store the frag number * of that new location in the nfrag field of the csfrag struct. * If a new frag can't be allocated for any element in the csfragino list, * set the new frag number for that element to 0 and return immediately. * The notenoughspace() function will detect this condition. */ void alloccsfragino() { struct csfrag *cfp; /* * allocate space for inode frag ranges */ for (cfp = csfragino; cfp; cfp = cfp->next) { allocfrags(cfp->frags, &cfp->nfrag, &cfp->cylno); if (cfp->nfrag == 0) break; } } void alloccsfragfree() { struct csfrag *cfp; /* * allocate the free frags needed for extended summary info */ rdcg((long)0); for (cfp = csfragfree; cfp; cfp = cfp->next) clrbit(cg_blksfree(&acg), cfp->ofrag - cgbase(&sblock, 0)); wtcg(); } void freefrags(daddr32_t frag, long frags, long cylno) { int i; /* * free frags */ rdcg(cylno); for (i = 0; i < frags; ++i) { setbit(cg_blksfree(&acg), (frag+i) - cgbase(&sblock, cylno)); } wtcg(); } int findfreerange(long *bitp, long *bitsp) { long bit; /* * find a range of free bits in a cylinder group bit map */ for (bit = *bitp, *bitsp = 0; bit < acg.cg_ndblk; ++bit) if (isset(cg_blksfree(&acg), bit)) break; if (bit >= acg.cg_ndblk) return (0); *bitp = bit; *bitsp = 1; for (++bit; bit < acg.cg_ndblk; ++bit, ++(*bitsp)) { if ((bit % sblock.fs_frag) == 0) break; if (isclr(cg_blksfree(&acg), bit)) break; } return (1); } void resetallocinfo() { long cno; long bit; long bits; /* * Compute the free blocks/frags info and update the appropriate * inmemory superblock, summary info, and cylinder group fields */ sblock.fs_cstotal.cs_nffree -= acg.cg_cs.cs_nffree; sblock.fs_cstotal.cs_nbfree -= acg.cg_cs.cs_nbfree; acg.cg_cs.cs_nffree = 0; acg.cg_cs.cs_nbfree = 0; bzero((caddr_t)acg.cg_frsum, sizeof (acg.cg_frsum)); bzero((caddr_t)cg_blktot(&acg), (int)(acg.cg_iusedoff-acg.cg_btotoff)); bit = bits = 0; while (findfreerange(&bit, &bits)) { if (bits == sblock.fs_frag) { acg.cg_cs.cs_nbfree++; cno = cbtocylno(&sblock, bit); cg_blktot(&acg)[cno]++; cg_blks(&sblock, &acg, cno)[cbtorpos(&sblock, bit)]++; } else { acg.cg_cs.cs_nffree += bits; acg.cg_frsum[bits]++; } bit += bits; } *(fscs + acg.cg_cgx) = acg.cg_cs; sblock.fs_cstotal.cs_nffree += acg.cg_cs.cs_nffree; sblock.fs_cstotal.cs_nbfree += acg.cg_cs.cs_nbfree; } void extendcg(long cylno) { int i; diskaddr_t dupper; diskaddr_t cbase; diskaddr_t dmax; /* * extend the cylinder group at the end of the old file system * if it was partially allocated becase of lack of space */ flcg(); rdcg(cylno); dupper = acg.cg_ndblk; if (cylno == sblock.fs_ncg - 1) acg.cg_ncyl = sblock.fs_ncyl - (sblock.fs_cpg * cylno); else acg.cg_ncyl = sblock.fs_cpg; cbase = cgbase(&sblock, cylno); dmax = cbase + sblock.fs_fpg; if (dmax > sblock.fs_size) dmax = sblock.fs_size; acg.cg_ndblk = dmax - cbase; for (i = dupper; i < acg.cg_ndblk; ++i) setbit(cg_blksfree(&acg), i); sblock.fs_dsize += (acg.cg_ndblk - dupper); wtcg(); flcg(); } struct lockfs lockfs; int lockfd; int islocked; int lockfskey; char lockfscomment[128]; void ulockfs() { /* * if the file system was locked, unlock it before exiting */ if (islocked == 0) return; /* * first, check if the lock held */ lockfs.lf_flags = LOCKFS_MOD; if (ioctl(lockfd, _FIOLFSS, &lockfs) == -1) { perror(directory); lockexit(32); } if (LOCKFS_IS_MOD(&lockfs)) { (void) fprintf(stderr, gettext("FILE SYSTEM CHANGED DURING GROWFS!\n")); (void) fprintf(stderr, gettext(" See lockfs(8), umount(8), and fsck(8)\n")); lockexit(32); } /* * unlock the file system */ lockfs.lf_lock = LOCKFS_ULOCK; lockfs.lf_flags = 0; lockfs.lf_key = lockfskey; clockfs(); if (ioctl(lockfd, _FIOLFS, &lockfs) == -1) { perror(directory); lockexit(32); } } void wlockfs() { /* * if no-write (-N), don't bother */ if (Nflag) return; /* * open the mountpoint, and write lock the file system */ if ((lockfd = open64(directory, O_RDONLY)) == -1) { perror(directory); lockexit(32); } /* * check if it is already locked */ if (ioctl(lockfd, _FIOLFSS, &lockfs) == -1) { perror(directory); lockexit(32); } if (lockfs.lf_lock != LOCKFS_WLOCK) { lockfs.lf_lock = LOCKFS_WLOCK; lockfs.lf_flags = 0; lockfs.lf_key = 0; clockfs(); if (ioctl(lockfd, _FIOLFS, &lockfs) == -1) { perror(directory); lockexit(32); } } islocked = 1; lockfskey = lockfs.lf_key; } void clockfs() { time_t t; char *ct; (void) time(&t); ct = ctime(&t); ct[strlen(ct)-1] = '\0'; (void) sprintf(lockfscomment, "%s -- mkfs pid %d", ct, getpid()); lockfs.lf_comlen = strlen(lockfscomment)+1; lockfs.lf_comment = lockfscomment; } /* * Write the csum records and the superblock */ void wtsb() { long i; /* * write summary information */ for (i = 0; i < sblock.fs_cssize; i += sblock.fs_bsize) wtfs(fsbtodb(&sblock, (uint64_t)(sblock.fs_csaddr + numfrags(&sblock, i))), (int)(sblock.fs_cssize - i < sblock.fs_bsize ? sblock.fs_cssize - i : sblock.fs_bsize), ((char *)fscs) + i); /* * write superblock */ sblock.fs_time = mkfstime; wtfs((diskaddr_t)(SBOFF / sectorsize), sbsize, (char *)&sblock); } /* * Verify that the optimization selection is reasonable, and advance * the global "string" appropriately. */ static char checkopt(char *optim) { char opt; int limit = strcspn(optim, ","); switch (limit) { case 0: /* missing indicator (have comma or nul) */ (void) fprintf(stderr, gettext( "mkfs: missing optimization flag reset to `t' (time)\n")); opt = 't'; break; case 1: /* single-character indicator */ opt = *optim; if ((opt != 's') && (opt != 't')) { (void) fprintf(stderr, gettext( "mkfs: bad optimization value `%c' reset to `t' (time)\n"), opt); opt = 't'; } break; default: /* multi-character indicator */ (void) fprintf(stderr, gettext( "mkfs: bad optimization value `%*.*s' reset to `t' (time)\n"), limit, limit, optim); opt = 't'; break; } string += limit; return (opt); } /* * Verify that the mtb selection is reasonable, and advance * the global "string" appropriately. */ static char checkmtb(char *mtbarg) { char mtbc; int limit = strcspn(mtbarg, ","); switch (limit) { case 0: /* missing indicator (have comma or nul) */ (void) fprintf(stderr, gettext( "mkfs: missing mtb flag reset to `n' (no mtb support)\n")); mtbc = 'n'; break; case 1: /* single-character indicator */ mtbc = tolower(*mtbarg); if ((mtbc != 'y') && (mtbc != 'n')) { (void) fprintf(stderr, gettext( "mkfs: bad mtb value `%c' reset to `n' (no mtb support)\n"), mtbc); mtbc = 'n'; } break; default: /* multi-character indicator */ (void) fprintf(stderr, gettext( "mkfs: bad mtb value `%*.*s' reset to `n' (no mtb support)\n"), limit, limit, mtbarg); opt = 'n'; break; } string += limit; return (mtbc); } /* * Verify that a value is in a range. If it is not, resets it to * its default value if one is supplied, exits otherwise. * * When testing, can compare user_supplied to RC_KEYWORD or RC_POSITIONAL. */ static void range_check(long *varp, char *name, long minimum, long maximum, long def_val, int user_supplied) { dbgprintf(("DeBuG %s : %ld (%ld %ld %ld)\n", name, *varp, minimum, maximum, def_val)); if ((*varp < minimum) || (*varp > maximum)) { if (user_supplied != RC_DEFAULT) { (void) fprintf(stderr, gettext( "mkfs: bad value for %s: %ld must be between %ld and %ld\n"), name, *varp, minimum, maximum); } if (def_val != NO_DEFAULT) { if (user_supplied) { (void) fprintf(stderr, gettext("mkfs: %s reset to default %ld\n"), name, def_val); } *varp = def_val; dbgprintf(("DeBuG %s : %ld\n", name, *varp)); return; } lockexit(2); /*NOTREACHED*/ } } /* * Verify that a value is in a range. If it is not, resets it to * its default value if one is supplied, exits otherwise. * * When testing, can compare user_supplied to RC_KEYWORD or RC_POSITIONAL. */ static void range_check_64(uint64_t *varp, char *name, uint64_t minimum, uint64_t maximum, uint64_t def_val, int user_supplied) { if ((*varp < minimum) || (*varp > maximum)) { if (user_supplied != RC_DEFAULT) { (void) fprintf(stderr, gettext( "mkfs: bad value for %s: %lld must be between %lld and %lld\n"), name, *varp, minimum, maximum); } if (def_val != NO_DEFAULT) { if (user_supplied) { (void) fprintf(stderr, gettext("mkfs: %s reset to default %lld\n"), name, def_val); } *varp = def_val; return; } lockexit(2); /*NOTREACHED*/ } } /* * Blocks SIGINT from delivery. Returns the previous mask in the * buffer provided, so that mask may be later restored. */ static void block_sigint(sigset_t *old_mask) { sigset_t block_mask; if (sigemptyset(&block_mask) < 0) { fprintf(stderr, gettext("Could not clear signal mask\n")); lockexit(3); } if (sigaddset(&block_mask, SIGINT) < 0) { fprintf(stderr, gettext("Could not set signal mask\n")); lockexit(3); } if (sigprocmask(SIG_BLOCK, &block_mask, old_mask) < 0) { fprintf(stderr, gettext("Could not block SIGINT\n")); lockexit(3); } } /* * Restores the signal mask that was in force before a call * to block_sigint(). This may actually still have SIGINT blocked, * if we've been recursively invoked. */ static void unblock_sigint(sigset_t *old_mask) { if (sigprocmask(SIG_UNBLOCK, old_mask, (sigset_t *)NULL) < 0) { fprintf(stderr, gettext("Could not restore signal mask\n")); lockexit(3); } } /* * Attempt to be somewhat graceful about being interrupted, rather than * just silently leaving the filesystem in an unusable state. * * The kernel has blocked SIGINT upon entry, so we don't have to worry * about recursion if the user starts pounding on the keyboard. */ static void recover_from_sigint(int signum) { if (fso > -1) { if ((Nflag != 0) || confirm_abort()) { lockexit(4); } } } static int confirm_abort(void) { char line[80]; printf(gettext("\n\nAborting at this point will leave the filesystem " "in an inconsistent\nstate. If you do choose to stop, " "you will be given instructions on how to\nrecover " "the filesystem. Do you wish to cancel the filesystem " "grow\noperation (y/n)?")); if (getaline(stdin, line, sizeof (line)) == EOF) line[0] = 'y'; printf("\n"); if (line[0] == 'y' || line[0] == 'Y') return (1); else { return (0); } } static int getaline(FILE *fp, char *loc, int maxlen) { int n; char *p, *lastloc; p = loc; lastloc = &p[maxlen-1]; while ((n = getc(fp)) != '\n') { if (n == EOF) return (EOF); if (!isspace(n) && p < lastloc) *p++ = n; } *p = 0; return (p - loc); } /* * Calculate the maximum value of cylinders-per-group for a file * system with the characteristics: * * bsize - file system block size * fragsize - frag size * nbpi - number of bytes of disk space per inode * nrpos - number of rotational positions * spc - sectors per cylinder * * These five characteristic are not adjustable (by this function). * The only attribute of the file system which IS adjusted by this * function in order to maximize cylinders-per-group is the proportion * of the cylinder group overhead block used for the inode map. The * inode map cannot occupy more than one-third of the cylinder group * overhead block, but it's OK for it to occupy less than one-third * of the overhead block. * * The setting of nbpi determines one possible value for the maximum * size of a cylinder group. It does so because it determines the total * number of inodes in the file system (file system size is fixed, and * nbpi is fixed, so the total number of inodes is fixed too). The * cylinder group has to be small enough so that the number of inodes * in the cylinder group is less than or equal to the number of bits * in one-third (or whatever proportion is assumed) of a file system * block. The details of the calculation are: * * The macro MAXIpG_B(bsize, inode_divisor) determines the maximum * number of inodes that can be in a cylinder group, given the * proportion of the cylinder group overhead block used for the * inode bitmaps (an inode_divisor of 3 means that 1/3 of the * block is used for inode bitmaps; an inode_divisor of 12 means * that 1/12 of the block is used for inode bitmaps.) * * Once the number of inodes per cylinder group is known, the * maximum value of cylinders-per-group (determined by nbpi) * is calculated by the formula * * maxcpg_given_nbpi = (size of a cylinder group)/(size of a cylinder) * * = (inodes-per-cg * nbpi)/(spc * DEV_BSIZE) * * (Interestingly, the size of the file system never enters * into this calculation.) * * Another possible value for the maximum cylinder group size is determined * by frag_size and nrpos. The frags in the cylinder group must be * representable in the frag bitmaps in the cylinder overhead block and the * rotational positions for each cylinder must be represented in the * rotational position tables. The calculation of the maximum cpg * value, given the frag and nrpos vales, is: * * maxcpg_given_fragsize = * (available space in the overhead block) / (size of per-cylinder data) * * The available space in the overhead block = * bsize - sizeof (struct cg) - space_used_for_inode_bitmaps * * The size of the per-cylinder data is: * sizeof(long) # for the "blocks avail per cylinder" field * + nrpos * sizeof(short) # for the rotational position table entry * + frags-per-cylinder/NBBY # number of bytes to represent this * # cylinder in the frag bitmap * * The two calculated maximum values of cylinder-per-group will typically * turn out to be different, since they are derived from two different * constraints. Usually, maxcpg_given_nbpi is much bigger than * maxcpg_given_fragsize. But they can be brought together by * adjusting the proportion of the overhead block dedicated to * the inode bitmaps. Decreasing the proportion of the cylinder * group overhead block used for inode maps will decrease * maxcpg_given_nbpi and increase maxcpg_given_fragsize. * * This function calculates the initial values of maxcpg_given_nbpi * and maxcpg_given_fragsize assuming that 1/3 of the cg overhead * block is used for inode bitmaps. Then it decreases the proportion * of the cg overhead block used for inode bitmaps (by increasing * the value of inode_divisor) until maxcpg_given_nbpi and * maxcpg_given_fragsize are the same, or stop changing, or * maxcpg_given_nbpi is less than maxcpg_given_fragsize. * * The loop terminates when any of the following occur: * * maxcpg_given_fragsize is greater than or equal to * maxcpg_given_nbpi * * neither maxcpg_given_fragsize nor maxcpg_given_nbpi * change in the expected direction * * The loop is guaranteed to terminate because it only continues * while maxcpg_given_fragsize and maxcpg_given_nbpi are approaching * each other. As soon they cross each other, or neither one changes * in the direction of the other, or one of them moves in the wrong * direction, the loop completes. */ static long compute_maxcpg(long bsize, long fragsize, long nbpi, long nrpos, long spc) { int maxcpg_given_nbpi; /* in cylinders */ int maxcpg_given_fragsize; /* in cylinders */ int spf; /* sectors per frag */ int inode_divisor; int old_max_given_frag = 0; int old_max_given_nbpi = INT_MAX; spf = fragsize / DEV_BSIZE; inode_divisor = 3; while (1) { maxcpg_given_nbpi = (((int64_t)(MAXIpG_B(bsize, inode_divisor))) * nbpi) / (DEV_BSIZE * ((int64_t)spc)); maxcpg_given_fragsize = (bsize - (sizeof (struct cg)) - (bsize / inode_divisor)) / (sizeof (long) + nrpos * sizeof (short) + (spc / spf) / NBBY); if (maxcpg_given_fragsize >= maxcpg_given_nbpi) return (maxcpg_given_nbpi); /* * If neither value moves toward the other, return the * least of the old values (we use the old instead of the * new because: if the old is the same as the new, it * doesn't matter which ones we use. If one of the * values changed, but in the wrong direction, the * new values are suspect. Better use the old. This * shouldn't happen, but it's best to check. */ if (!(maxcpg_given_nbpi < old_max_given_nbpi) && !(maxcpg_given_fragsize > old_max_given_frag)) return (MIN(old_max_given_nbpi, old_max_given_frag)); /* * This is probably impossible, but if one of the maxcpg * values moved in the "right" direction and one moved * in the "wrong" direction (that is, the two values moved * in the same direction), the previous conditional won't * recognize that the values aren't converging (since at * least one value moved in the "right" direction, the * last conditional says "keep going"). * * Just to make absolutely certain that the loop terminates, * check for one of the values moving in the "wrong" direction * and terminate the loop if it happens. */ if (maxcpg_given_nbpi > old_max_given_nbpi || maxcpg_given_fragsize < old_max_given_frag) return (MIN(old_max_given_nbpi, old_max_given_frag)); old_max_given_nbpi = maxcpg_given_nbpi; old_max_given_frag = maxcpg_given_fragsize; inode_divisor++; } } static int in_64bit_mode(void) { /* cmd must be an absolute path, for security */ char *cmd = "/usr/bin/isainfo -b"; char buf[BUFSIZ]; FILE *ptr; int retval = 0; putenv("IFS= \t"); if ((ptr = popen(cmd, "r")) != NULL) { if (fgets(buf, BUFSIZ, ptr) != NULL && strncmp(buf, "64", 2) == 0) retval = 1; (void) pclose(ptr); } return (retval); } /* * validate_size * * Return 1 if the device appears to be at least "size" sectors long. * Return 0 if it's shorter or we can't read it. */ static int validate_size(int fd, diskaddr_t size) { char buf[DEV_BSIZE]; int rc; if ((llseek(fd, (offset_t)((size - 1) * DEV_BSIZE), SEEK_SET) == -1) || (read(fd, buf, DEV_BSIZE)) != DEV_BSIZE) rc = 0; else rc = 1; return (rc); } /* * Print every field of the calculated superblock, along with * its value. To make parsing easier on the caller, the value * is printed first, then the name. Additionally, there's only * one name/value pair per line. All values are reported in * hexadecimal (with the traditional 0x prefix), as that's slightly * easier for humans to read. Not that they're expected to, but * debugging happens. */ static void dump_sblock(void) { int row, column, pending, written; caddr_t source; if (Rflag) { pending = sizeof (sblock); source = (caddr_t)&sblock; do { written = write(fileno(stdout), source, pending); pending -= written; source += written; } while ((pending > 0) && (written > 0)); if (written < 0) { perror(gettext("Binary dump of superblock failed")); lockexit(1); } return; } else { printf("0x%x sblock.fs_link\n", sblock.fs_link); printf("0x%x sblock.fs_rolled\n", sblock.fs_rolled); printf("0x%x sblock.fs_sblkno\n", sblock.fs_sblkno); printf("0x%x sblock.fs_cblkno\n", sblock.fs_cblkno); printf("0x%x sblock.fs_iblkno\n", sblock.fs_iblkno); printf("0x%x sblock.fs_dblkno\n", sblock.fs_dblkno); printf("0x%x sblock.fs_cgoffset\n", sblock.fs_cgoffset); printf("0x%x sblock.fs_cgmask\n", sblock.fs_cgmask); printf("0x%x sblock.fs_time\n", sblock.fs_time); printf("0x%x sblock.fs_size\n", sblock.fs_size); printf("0x%x sblock.fs_dsize\n", sblock.fs_dsize); printf("0x%x sblock.fs_ncg\n", sblock.fs_ncg); printf("0x%x sblock.fs_bsize\n", sblock.fs_bsize); printf("0x%x sblock.fs_fsize\n", sblock.fs_fsize); printf("0x%x sblock.fs_frag\n", sblock.fs_frag); printf("0x%x sblock.fs_minfree\n", sblock.fs_minfree); printf("0x%x sblock.fs_rotdelay\n", sblock.fs_rotdelay); printf("0x%x sblock.fs_rps\n", sblock.fs_rps); printf("0x%x sblock.fs_bmask\n", sblock.fs_bmask); printf("0x%x sblock.fs_fmask\n", sblock.fs_fmask); printf("0x%x sblock.fs_bshift\n", sblock.fs_bshift); printf("0x%x sblock.fs_fshift\n", sblock.fs_fshift); printf("0x%x sblock.fs_maxcontig\n", sblock.fs_maxcontig); printf("0x%x sblock.fs_maxbpg\n", sblock.fs_maxbpg); printf("0x%x sblock.fs_fragshift\n", sblock.fs_fragshift); printf("0x%x sblock.fs_fsbtodb\n", sblock.fs_fsbtodb); printf("0x%x sblock.fs_sbsize\n", sblock.fs_sbsize); printf("0x%x sblock.fs_csmask\n", sblock.fs_csmask); printf("0x%x sblock.fs_csshift\n", sblock.fs_csshift); printf("0x%x sblock.fs_nindir\n", sblock.fs_nindir); printf("0x%x sblock.fs_inopb\n", sblock.fs_inopb); printf("0x%x sblock.fs_nspf\n", sblock.fs_nspf); printf("0x%x sblock.fs_optim\n", sblock.fs_optim); #ifdef _LITTLE_ENDIAN printf("0x%x sblock.fs_state\n", sblock.fs_state); #else printf("0x%x sblock.fs_npsect\n", sblock.fs_npsect); #endif printf("0x%x sblock.fs_si\n", sblock.fs_si); printf("0x%x sblock.fs_trackskew\n", sblock.fs_trackskew); printf("0x%x sblock.fs_id[0]\n", sblock.fs_id[0]); printf("0x%x sblock.fs_id[1]\n", sblock.fs_id[1]); printf("0x%x sblock.fs_csaddr\n", sblock.fs_csaddr); printf("0x%x sblock.fs_cssize\n", sblock.fs_cssize); printf("0x%x sblock.fs_cgsize\n", sblock.fs_cgsize); printf("0x%x sblock.fs_ntrak\n", sblock.fs_ntrak); printf("0x%x sblock.fs_nsect\n", sblock.fs_nsect); printf("0x%x sblock.fs_spc\n", sblock.fs_spc); printf("0x%x sblock.fs_ncyl\n", sblock.fs_ncyl); printf("0x%x sblock.fs_cpg\n", sblock.fs_cpg); printf("0x%x sblock.fs_ipg\n", sblock.fs_ipg); printf("0x%x sblock.fs_fpg\n", sblock.fs_fpg); printf("0x%x sblock.fs_cstotal\n", sblock.fs_cstotal); printf("0x%x sblock.fs_fmod\n", sblock.fs_fmod); printf("0x%x sblock.fs_clean\n", sblock.fs_clean); printf("0x%x sblock.fs_ronly\n", sblock.fs_ronly); printf("0x%x sblock.fs_flags\n", sblock.fs_flags); printf("0x%x sblock.fs_fsmnt\n", sblock.fs_fsmnt); printf("0x%x sblock.fs_cgrotor\n", sblock.fs_cgrotor); printf("0x%x sblock.fs_u.fs_csp\n", sblock.fs_u.fs_csp); printf("0x%x sblock.fs_cpc\n", sblock.fs_cpc); /* * No macros are defined for the dimensions of the * opostbl array. */ for (row = 0; row < 16; row++) { for (column = 0; column < 8; column++) { printf("0x%x sblock.fs_opostbl[%d][%d]\n", sblock.fs_opostbl[row][column], row, column); } } /* * Ditto the size of sparecon. */ for (row = 0; row < 51; row++) { printf("0x%x sblock.fs_sparecon[%d]\n", sblock.fs_sparecon[row], row); } printf("0x%x sblock.fs_version\n", sblock.fs_version); printf("0x%x sblock.fs_logbno\n", sblock.fs_logbno); printf("0x%x sblock.fs_reclaim\n", sblock.fs_reclaim); printf("0x%x sblock.fs_sparecon2\n", sblock.fs_sparecon2); #ifdef _LITTLE_ENDIAN printf("0x%x sblock.fs_npsect\n", sblock.fs_npsect); #else printf("0x%x sblock.fs_state\n", sblock.fs_state); #endif printf("0x%llx sblock.fs_qbmask\n", sblock.fs_qbmask); printf("0x%llx sblock.fs_qfmask\n", sblock.fs_qfmask); printf("0x%x sblock.fs_postblformat\n", sblock.fs_postblformat); printf("0x%x sblock.fs_nrpos\n", sblock.fs_nrpos); printf("0x%x sblock.fs_postbloff\n", sblock.fs_postbloff); printf("0x%x sblock.fs_rotbloff\n", sblock.fs_rotbloff); printf("0x%x sblock.fs_magic\n", sblock.fs_magic); /* * fs_space isn't of much use in this context, so we'll * just ignore it for now. */ } } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # FSTYPE= ufs PROG= mount LIBPROG= $(PROG) ROOTFS_PROG= $(PROG) # duplicate ROOTLIBFSTYPE value needed for installation rule # we must define this before including Makefile.fstype ROOTLIBFSTYPE = $(ROOT)/usr/lib/fs/$(FSTYPE) $(ROOTLIBFSTYPE)/%: $(ROOTLIBFSTYPE) % $(RM) $@; $(SYMLINK) ../../../../etc/fs/$(FSTYPE)/$(PROG) $@ include ../../Makefile.fstype include ../../Makefile.mount CPPFLAGS += -D_LARGEFILE64_SOURCE CERRWARN += -Wno-parentheses include ../../Makefile.mount.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * mount */ #include #include #include #include #include #include #include #include #include #include #define bcopy(f, t, n) memcpy(t, f, n) #define bzero(s, n) memset(s, 0, n) #define bcmp(s, d, n) memcmp(s, d, n) #define index(s, r) strchr(s, r) #define rindex(s, r) strrchr(s, r) #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int ro = 0; static int largefiles = 0; /* flag - add default nolargefiles to mnttab */ static int gflg = 0; static int mflg = 0; static int Oflg = 0; static int qflg = 0; #define NAME_MAX 64 /* sizeof "fstype myname" */ static int checkislog(char *); static void disable_logging(char *, char *); static int eatmntopt(struct mnttab *, char *); static void enable_logging(char *, char *); static void fixopts(struct mnttab *, char *); static void mountfs(struct mnttab *); static void replace_opts(char *, int, char *, char *); static int replace_opts_dflt(char *, int, const char *, const char *); static void rmopt(struct mnttab *, char *); static void rpterr(char *, char *); static void usage(void); static char fstype[] = MNTTYPE_UFS; static char opts[MAX_MNTOPT_STR]; static char typename[NAME_MAX], *myname; static char *fop_subopts[] = { MNTOPT_ONERROR, NULL }; #define NOMATCH (-1) #define ONERROR (0) /* index within fop_subopts */ static struct fop_subopt { char *str; int flag; } fop_subopt_list[] = { { UFSMNT_ONERROR_PANIC_STR, UFSMNT_ONERROR_PANIC }, { UFSMNT_ONERROR_LOCK_STR, UFSMNT_ONERROR_LOCK }, { UFSMNT_ONERROR_UMOUNT_STR, UFSMNT_ONERROR_UMOUNT }, { NULL, UFSMNT_ONERROR_DEFAULT } }; /* * Check if the specified filesystem is already mounted. */ static boolean_t in_mnttab(char *mountp) { FILE *file; int found = B_FALSE; struct mnttab mntent; if ((file = fopen(MNTTAB, "r")) == NULL) return (B_FALSE); while (getmntent(file, &mntent) == 0) { if (mntent.mnt_mountp != NULL && strcmp(mntent.mnt_mountp, mountp) == 0 && mntent.mnt_fstype != NULL && strcmp(mntent.mnt_fstype, MNTTYPE_UFS) == 0) { found = B_TRUE; break; } } (void) fclose(file); return (found); } /* * Find opt in mntopt */ static char * findopt(char *mntopt, char *opt) { int nc, optlen = strlen(opt); while (*mntopt) { nc = strcspn(mntopt, ", ="); if (strncmp(mntopt, opt, nc) == 0) if (optlen == nc) return (mntopt); mntopt += nc; mntopt += strspn(mntopt, ", ="); } return (NULL); } int main(int argc, char *argv[]) { struct mnttab mnt; int c; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); myname = strrchr(argv[0], '/'); if (myname) myname++; else myname = argv[0]; (void) snprintf(typename, sizeof (typename), "%s %s", fstype, myname); argv[0] = typename; opts[0] = '\0'; /* * Set options */ while ((c = getopt(argc, argv, "gmo:pqrVO")) != EOF) { switch (c) { case 'g': gflg++; break; case 'o': if (strlcpy(opts, optarg, sizeof (opts)) >= sizeof (opts)) { (void) fprintf(stderr, gettext("option string " "argument too long\n")); } break; case 'O': Oflg++; break; case 'r': ro++; break; case 'm': mflg++; break; case 'q': qflg++; break; default: usage(); } } if ((argc - optind) != 2) usage(); mnt.mnt_special = argv[optind]; mnt.mnt_mountp = argv[optind+1]; mnt.mnt_fstype = fstype; /* * Process options. The resulting options string overwrites the * original. * * XXX: This code doesn't do a good job of resolving options that are * specified multiple times or that are given in conflicting * forms (e.g., both "largefiles" and "nolargefiles"). It also * doesn't produce well defined behavior for options that may * also be specified as flags (e.g, "-r" and "ro"/"rw") when both * are present. * * The proper way to deal with such conflicts is to start with * the default value (i.e., the one if no flag or option is * specified), override it with the last mentioned option pair * in the -o option string, and finally, override that with * the flag value. This allows "mount -r" command to mount a * file system read only that is listed rw in /etc/vfstab. */ mnt.mnt_mntopts = opts; if (findopt(mnt.mnt_mntopts, "m")) mflg++; if ((gflg || findopt(mnt.mnt_mntopts, MNTOPT_GLOBAL)) && findopt(mnt.mnt_mntopts, MNTOPT_NBMAND)) { (void) fprintf(stderr, gettext("NBMAND option not supported on" " global filesystem\n")); exit(32); } replace_opts(opts, ro, MNTOPT_RO, MNTOPT_RW); replace_opts(opts, largefiles, MNTOPT_NOLARGEFILES, MNTOPT_LARGEFILES); gflg = replace_opts_dflt(opts, gflg, MNTOPT_GLOBAL, MNTOPT_NOGLOBAL); if (findopt(mnt.mnt_mntopts, MNTOPT_RQ)) { rmopt(&mnt, MNTOPT_RQ); replace_opts(opts, 1, MNTOPT_QUOTA, MNTOPT_NOQUOTA); } mountfs(&mnt); return (0); } static void reportlogerror(int ret, char *mp, char *special, char *cmd, fiolog_t *flp) { /* No error */ if ((ret != -1) && (flp->error == FIOLOG_ENONE)) return; /* logging was not enabled/disabled */ if (ret == -1 || flp->error != FIOLOG_ENONE) (void) fprintf(stderr, gettext("Could not %s logging" " for %s on %s.\n"), cmd, mp, special); /* ioctl returned error */ if (ret == -1) return; /* Some more info */ switch (flp->error) { case FIOLOG_ENONE : if (flp->nbytes_requested && (flp->nbytes_requested != flp->nbytes_actual)) { (void) fprintf(stderr, gettext("The log has been" " resized from %d bytes to %d bytes.\n"), flp->nbytes_requested, flp->nbytes_actual); } return; case FIOLOG_ETRANS : (void) fprintf(stderr, gettext("Solaris Volume Manager logging" " is already enabled.\n")); (void) fprintf(stderr, gettext("Please see the" " commands metadetach(8)" " or metaclear(8).\n")); break; case FIOLOG_EROFS : (void) fprintf(stderr, gettext("File system is mounted read " "only.\n")); (void) fprintf(stderr, gettext("Please see the remount " "option described in mount_ufs(8).\n")); break; case FIOLOG_EULOCK : (void) fprintf(stderr, gettext("File system is locked.\n")); (void) fprintf(stderr, gettext("Please see the -u option " "described in lockfs(8).\n")); break; case FIOLOG_EWLOCK : (void) fprintf(stderr, gettext("The file system could not be" " write locked.\n")); (void) fprintf(stderr, gettext("Please see the -w option " "described in lockfs(8).\n")); break; case FIOLOG_ECLEAN : (void) fprintf(stderr, gettext("The file system may not be" " stable.\n")); (void) fprintf(stderr, gettext("Please see the -n option" " for fsck(8).\n")); break; case FIOLOG_ENOULOCK : (void) fprintf(stderr, gettext("The file system could not be" " unlocked.\n")); (void) fprintf(stderr, gettext("Please see the -u option " "described in lockfs(8).\n")); break; default : (void) fprintf(stderr, gettext("Unknown internal error" " %d.\n"), flp->error); break; } } static int checkislog(char *mp) { int fd; uint32_t islog; fd = open(mp, O_RDONLY); islog = 0; (void) ioctl(fd, _FIOISLOG, &islog); (void) close(fd); return ((int)islog); } static void enable_logging(char *mp, char *special) { int fd, ret, islog; fiolog_t fl; fd = open(mp, O_RDONLY); if (fd == -1) { perror(mp); return; } fl.nbytes_requested = 0; fl.nbytes_actual = 0; fl.error = FIOLOG_ENONE; ret = ioctl(fd, _FIOLOGENABLE, &fl); if (ret == -1) perror(mp); (void) close(fd); /* is logging enabled? */ islog = checkislog(mp); /* report errors, if any */ if (ret == -1 || !islog) reportlogerror(ret, mp, special, "enable", &fl); } static void disable_logging(char *mp, char *special) { int fd, ret, islog; fiolog_t fl; fd = open(mp, O_RDONLY); if (fd == -1) { perror(mp); return; } fl.error = FIOLOG_ENONE; ret = ioctl(fd, _FIOLOGDISABLE, &fl); if (ret == -1) perror(mp); (void) close(fd); /* is logging enabled? */ islog = checkislog(mp); /* report errors, if any */ if (ret == -1 || islog) reportlogerror(ret, mp, special, "disable", &fl); } /* * attempt to mount file system, return errno or 0 */ void mountfs(struct mnttab *mnt) { char opt[MAX_MNTOPT_STR]; char opt2[MAX_MNTOPT_STR]; char *opts = opt; int flags = MS_OPTIONSTR; struct ufs_args args; int need_separator = 0; int mount_attempts = 5; (void) bzero((char *)&args, sizeof (args)); (void) strcpy(opts, mnt->mnt_mntopts); opt2[0] = '\0'; flags |= Oflg ? MS_OVERLAY : 0; flags |= eatmntopt(mnt, MNTOPT_RO) ? MS_RDONLY : 0; flags |= eatmntopt(mnt, MNTOPT_REMOUNT) ? MS_REMOUNT : 0; flags |= eatmntopt(mnt, MNTOPT_GLOBAL) ? MS_GLOBAL : 0; if (eatmntopt(mnt, MNTOPT_NOINTR)) args.flags |= UFSMNT_NOINTR; if (eatmntopt(mnt, MNTOPT_INTR)) args.flags &= ~UFSMNT_NOINTR; if (eatmntopt(mnt, MNTOPT_SYNCDIR)) args.flags |= UFSMNT_SYNCDIR; if (eatmntopt(mnt, MNTOPT_FORCEDIRECTIO)) { args.flags |= UFSMNT_FORCEDIRECTIO; args.flags &= ~UFSMNT_NOFORCEDIRECTIO; } if (eatmntopt(mnt, MNTOPT_NOFORCEDIRECTIO)) { args.flags |= UFSMNT_NOFORCEDIRECTIO; args.flags &= ~UFSMNT_FORCEDIRECTIO; } if (eatmntopt(mnt, MNTOPT_NOSETSEC)) args.flags |= UFSMNT_NOSETSEC; if (eatmntopt(mnt, MNTOPT_LARGEFILES)) args.flags |= UFSMNT_LARGEFILES; if (eatmntopt(mnt, MNTOPT_NOLARGEFILES)) args.flags &= ~UFSMNT_LARGEFILES; args.flags |= UFSMNT_LOGGING; /* default is logging */ (void) eatmntopt(mnt, MNTOPT_LOGGING); if (eatmntopt(mnt, MNTOPT_NOLOGGING)) args.flags &= ~UFSMNT_LOGGING; if (eatmntopt(mnt, MNTOPT_NOATIME)) args.flags |= UFSMNT_NOATIME; if (eatmntopt(mnt, MNTOPT_DFRATIME)) args.flags &= ~UFSMNT_NODFRATIME; if (eatmntopt(mnt, MNTOPT_NODFRATIME)) args.flags |= UFSMNT_NODFRATIME; while (*opts != '\0') { char *argval; switch (getsubopt(&opts, fop_subopts, &argval)) { case ONERROR: if (argval) { struct fop_subopt *s; int found = 0; for (s = fop_subopt_list; s->str && !found; s++) { if (strcmp(argval, s->str) == 0) { args.flags |= s->flag; found = 1; } } if (!found) { usage(); } if (need_separator) (void) strcat(opt2, ","); (void) strcat(opt2, MNTOPT_ONERROR); (void) strcat(opt2, "="); (void) strcat(opt2, argval); need_separator = 1; } else { args.flags |= UFSMNT_ONERROR_DEFAULT; } break; case NOMATCH: default: if (argval) { if (need_separator) (void) strcat(opt2, ","); (void) strcat(opt2, argval); need_separator = 1; } break; } } if (*opt2 != '\0') (void) strcpy(opt, opt2); opts = opt; if ((args.flags & UFSMNT_ONERROR_FLGMASK) == 0) args.flags |= UFSMNT_ONERROR_DEFAULT; (void) signal(SIGHUP, SIG_IGN); (void) signal(SIGQUIT, SIG_IGN); (void) signal(SIGINT, SIG_IGN); errno = 0; flags |= MS_DATA | MS_OPTIONSTR; if (mflg) flags |= MS_NOMNTTAB; if (flags & MS_REMOUNT) { replace_opts(mnt->mnt_mntopts, 1, MNTOPT_RW, MNTOPT_RO); } fixopts(mnt, opts); /* * For global filesystems we want to pass in logging option * so that it shows up in the mnttab of all nodes. We add * logging option if its not specified. */ if (gflg || findopt(mnt->mnt_mntopts, MNTOPT_GLOBAL)) { if (!(flags & MS_RDONLY)) { if (mnt->mnt_mntopts[0] != '\0') (void) strcat(mnt->mnt_mntopts, ","); (void) strcat(mnt->mnt_mntopts, MNTOPT_LOGGING); args.flags |= UFSMNT_LOGGING; } else { /* * Turn off logging for read only global mounts. * It was set to logging as default above. */ if (mnt->mnt_mntopts[0] != '\0') (void) strcat(mnt->mnt_mntopts, ","); (void) strcat(mnt->mnt_mntopts, MNTOPT_NOLOGGING); args.flags &= ~UFSMNT_LOGGING; } } again: if (mount(mnt->mnt_special, mnt->mnt_mountp, flags, fstype, &args, sizeof (args), mnt->mnt_mntopts, MAX_MNTOPT_STR) != 0) { if (errno == EBUSY && !(flags & MS_OVERLAY)) { /* * Because of bug 6176743, any attempt to mount any * filesystem could fail for reasons described in that * bug. We're trying to detect that situation here by * checking that the filesystem we're mounting is not * in /etc/mnttab yet. When that bug is fixed, this * code can be removed. */ if (!in_mnttab(mnt->mnt_mountp) && mount_attempts-- > 0) { (void) poll(NULL, 0, 50); goto again; } } rpterr(mnt->mnt_special, mnt->mnt_mountp); exit(32); } if (!(flags & MS_RDONLY)) { if (args.flags & UFSMNT_LOGGING) enable_logging(mnt->mnt_mountp, mnt->mnt_special); else disable_logging(mnt->mnt_mountp, mnt->mnt_special); } if (!qflg) { cmp_requested_to_actual_options(opts, mnt->mnt_mntopts, mnt->mnt_special, mnt->mnt_mountp); } if (checkislog(mnt->mnt_mountp)) { /* update mnttab file if necessary */ if (!mflg) { struct stat64 statb; struct mnttagdesc mtdesc; int fd; if (stat64(mnt->mnt_mountp, &statb) != 0) exit(32); /* do tag ioctl */ mtdesc.mtd_major = major(statb.st_dev); mtdesc.mtd_minor = minor(statb.st_dev); mtdesc.mtd_mntpt = mnt->mnt_mountp; mtdesc.mtd_tag = MNTOPT_LOGGING; if ((fd = open(MNTTAB, O_RDONLY, 0)) < 0) exit(32); if (ioctl(fd, MNTIOC_SETTAG, &mtdesc) != 0) { (void) close(fd); exit(32); } (void) close(fd); } } exit(0); } /* * same as findopt but remove the option from the option string and return * true or false */ static int eatmntopt(struct mnttab *mnt, char *opt) { int has; has = (findopt(mnt->mnt_mntopts, opt) != NULL); rmopt(mnt, opt); return (has); } /* * remove an option string from the option list */ static void rmopt(struct mnttab *mnt, char *opt) { char *str; char *optstart; while (optstart = findopt(mnt->mnt_mntopts, opt)) { for (str = optstart; *str != ',' && *str != '\0' && *str != ' '; str++) /* NULL */; if (*str == ',') { str++; } else if (optstart != mnt->mnt_mntopts) { optstart--; } while (*optstart++ = *str++) ; } } /* * mnt->mnt_ops has un-eaten opts, opts is the original opts list. * Set mnt->mnt_opts to the original, the kernel will then remove * the ones it cannot deal with. * Set "opts" to the the original options for later comparison in * cmp_....(). But strip the options which aren't returned by * the kernel: "noglobal", "global" and "quota". * And strip the options which aren't set through mount: "logging", * "nologging" from those passed to mount(2). */ static void fixopts(struct mnttab *mnt, char *opts) { struct mnttab omnt; omnt.mnt_mntopts = opts; /* * Options not passed to the kernel and possibly not returned; * these are dealt with using ioctl; and the ioctl may fail. */ rmopt(&omnt, MNTOPT_LOGGING); rmopt(&omnt, MNTOPT_NOLOGGING); /* * Set the options for ``/etc/mnttab'' to be the original * options from main(); except for the option "f" and "remount". */ (void) strlcpy(mnt->mnt_mntopts, opts, MAX_MNTOPT_STR); rmopt(mnt, "f"); rmopt(mnt, MNTOPT_REMOUNT); rmopt(&omnt, MNTOPT_GLOBAL); rmopt(&omnt, MNTOPT_NOGLOBAL); rmopt(&omnt, MNTOPT_QUOTA); } static void usage(void) { (void) fprintf(stdout, gettext( "ufs usage:\n" "mount [-F ufs] [generic options] [-o suboptions] {special | mount_point}\n")); (void) fprintf(stdout, gettext( "\tsuboptions are: \n" "\t ro,rw,nosuid,remount,f,m,\n" "\t global,noglobal,\n" "\t largefiles,nolargefiles,\n" "\t forcedirectio,noforcedirectio\n" "\t logging,nologging,\n" "\t nbmand,nonbmand,\n" "\t onerror[={panic | lock | umount}]\n")); exit(32); } /* * Returns the next option in the option string. */ static char * getnextopt(char **p) { char *cp = *p; char *retstr; while (*cp && isspace(*cp)) cp++; retstr = cp; while (*cp && *cp != ',') cp++; /* strip empty options */ while (*cp == ',') { *cp = '\0'; cp++; } *p = cp; return (retstr); } /* * "trueopt" and "falseopt" are two settings of a Boolean option. * If "flag" is true, forcibly set the option to the "true" setting; otherwise, * if the option isn't present, set it to the false setting. */ static void replace_opts(char *options, int flag, char *trueopt, char *falseopt) { char *f; char *tmpoptsp; int found; char tmptopts[MNTMAXSTR]; (void) strcpy(tmptopts, options); tmpoptsp = tmptopts; (void) strcpy(options, ""); found = 0; for (f = getnextopt(&tmpoptsp); *f; f = getnextopt(&tmpoptsp)) { if (options[0] != '\0') (void) strcat(options, ","); if (strcmp(f, trueopt) == 0) { (void) strcat(options, f); found++; } else if (strcmp(f, falseopt) == 0) { if (flag) (void) strcat(options, trueopt); else (void) strcat(options, f); found++; } else (void) strcat(options, f); } if (!found) { if (options[0] != '\0') (void) strcat(options, ","); (void) strcat(options, flag ? trueopt : falseopt); } } /* * "trueopt" and "falseopt" are two settings of a Boolean option and "dflt" is * a default value for the option. Rewrite the contents of options to include * only the last mentioned occurrence of trueopt and falseopt. If neither is * mentioned, append one or the other to options, according to the value of * dflt. Return the resulting value of the option in boolean form. * * Note that the routine is implemented to have the resulting occurrence of * trueopt or falseopt appear at the end of the resulting option string. * * N.B. This routine should take the place of replace_opts, but there are * probably some compatibility issues to resolve before doing so. It * should certainly be used to handle new options that don't have * compatibility issues. */ static int replace_opts_dflt( char *options, int dflt, const char *trueopt, const char *falseopt) { char *f; char *tmpoptsp; int last; char tmptopts[MNTMAXSTR]; /* * Transfer the contents of options to tmptopts, in anticipation of * copying a subset of the contents back to options. */ (void) strcpy(tmptopts, options); tmpoptsp = tmptopts; (void) strcpy(options, ""); /* * Loop over each option value, copying non-matching values back into * options and updating the last seen occurrence of trueopt or * falseopt. */ last = dflt; for (f = getnextopt(&tmpoptsp); *f; f = getnextopt(&tmpoptsp)) { /* Check for both forms of the option of interest. */ if (strcmp(f, trueopt) == 0) { last = 1; } else if (strcmp(f, falseopt) == 0) { last = 0; } else { /* Not what we're looking for; transcribe. */ if (options[0] != '\0') (void) strcat(options, ","); (void) strcat(options, f); } } /* * Transcribe the correct form of the option of interest, using the * default value if it wasn't overwritten above. */ if (options[0] != '\0') (void) strcat(options, ","); (void) strcat(options, last ? trueopt : falseopt); return (last); } static void rpterr(char *bs, char *mp) { switch (errno) { case EPERM: (void) fprintf(stderr, gettext("%s: Insufficient privileges\n"), myname); break; case ENXIO: (void) fprintf(stderr, gettext("%s: %s no such device\n"), myname, bs); break; case ENOTDIR: (void) fprintf(stderr, gettext( "%s: %s not a directory\n\tor a component of %s is not a directory\n"), myname, mp, bs); break; case ENOENT: (void) fprintf(stderr, gettext( "%s: %s or %s, no such file or directory\n"), myname, bs, mp); break; case EINVAL: (void) fprintf(stderr, gettext("%s: %s is not this fstype\n"), myname, bs); break; case EBUSY: (void) fprintf(stderr, gettext("%s: %s is already mounted or %s is busy\n"), myname, bs, mp); break; case ENOTBLK: (void) fprintf(stderr, gettext( "%s: %s not a block device\n"), myname, bs); break; case EROFS: (void) fprintf(stderr, gettext("%s: %s write-protected\n"), myname, bs); break; case ENOSPC: (void) fprintf(stderr, gettext( "%s: The state of %s is not okay\n" "\tand it was attempted to be mounted read/write\n"), myname, bs); (void) printf(gettext( "mount: Please run fsck and try again\n")); break; case EFBIG: (void) fprintf(stderr, gettext( "%s: Large files may be present on %s,\n" "\tand it was attempted to be mounted nolargefiles\n"), myname, bs); break; default: perror(myname); (void) fprintf(stderr, gettext("%s: Cannot mount %s\n"), myname, bs); } } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1989,1996,1997 by Sun Microsystems, Inc. # All rights reserved. # FSTYPE= ufs LIBPROG= ncheck ATTMK= $(LIBPROG) include ../../Makefile.fstype include ../Makefile.roll OBJS= $(LIBPROG).o $(ROLLOBJS) SRCS= $(LIBPROG).c $(ROLLSRCS) # No msg catalog here. POFILE= $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) CPPFLAGS += -D_LARGEFILE64_SOURCE # Hammerhead: Suppress pointer/int cast warnings in legacy UFS code CERRWARN += -Wno-pointer-to-int-cast clean: $(RM) $(LIBPROG).o /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * ncheck -- obtain file names from reading filesystem */ #define MAXNINDIR (MAXBSIZE / sizeof (daddr_t)) #include #include #include #include #include #include #include #include #include #include #include #include #include "roll_log.h" union { struct fs sblk; char xxx[SBSIZE]; /* because fs is variable length */ } real_fs; #define sblock real_fs.sblk struct dinode *itab; unsigned itab_size; struct dinode *gip; /* inode list */ struct ilist { ino_t ino; ushort_t mode; uid_t uid; gid_t gid; } *ilist; int ilist_size = 0; /* size of ilist[] */ int ilist_index = 0; /* current index for storing into ilist; */ #define ILIST_SZ_INCR 1000 /* initial size, amount to incr sz of ilist */ #define MAX_ILIST_INDEX() (ilist_size - 1) struct htab { ino_t h_ino; ino_t h_pino; int h_name_index; /* index into string table */ } *htab; unsigned htab_size; /* how much malloc'd for htab */ /* * string table: used to hold filenames. */ char *strngtab; int strngloc; int strngtab_size; #define STRNGTAB_INCR (1024*16) /* amount to grow strngtab */ #define MAX_STRNGTAB_INDEX() (strngtab_size - 1) #define AVG_PATH_LEN 30 /* average (?) length of name */ long hsize; struct dirstuff { int loc; struct dinode *ip; char dbuf[MAXBSIZE]; }; int aflg = 0; int sflg = 0; int iflg = 0; /* number of inodes being searched for */ int mflg = 0; int fi; ino_t ino; int nhent; int nerror; long atol(); daddr_t bmap(daddr_t); void bread(diskaddr_t bno, char *buf, int cnt); void check(char *file); int dotname(struct direct *dp); offset_t llseek(); struct htab *lookup(ino_t i, int ef); void pass1(struct dinode *ip); void pass2(struct dinode *ip); void pass3(struct dinode *ip); void pname(ino_t i, int lev); char *strcpy(); void usage(); struct direct *dreaddir(); void extend_ilist(); int extend_strngtab(unsigned int size); uchar_t *extend_tbl(uchar_t *tbl, unsigned int *current_size, unsigned int new_size); extern int optind; extern char *optarg; char *subopts [] = { #define M_FLAG 0 "m", NULL }; int main(int argc, char *argv[]) { long n; int opt; char *suboptions, *value; int suboption; char *p; int first = 0; extend_ilist(); while ((opt = getopt(argc, argv, "ao:i:s")) != EOF) { switch (opt) { case 'a': aflg++; break; case 'o': /* * ufs specific options. */ suboptions = optarg; while (*suboptions != '\0') { suboption = getsubopt(&suboptions, subopts, &value); switch (suboption) { case M_FLAG: mflg++; break; default: usage(); } } break; case 'i': while ((p = (char *)strtok((first++ == 0 ? optarg : 0), ", ")) != NULL) { if ((n = atoi(p)) == 0) break; ilist[iflg].ino = n; iflg++; ilist_index = iflg; if (iflg > MAX_ILIST_INDEX()) extend_ilist(); } break; case 's': sflg++; break; #if 0 case 'V': { int opt_count; char *opt_text; (void) fprintf(stdout, "ncheck -F ufs "); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) fprintf(stdout, " %s ", opt_text); } (void) fprintf(stdout, "\n"); } break; #endif case '?': usage(); } } argc -= optind; argv = &argv[optind]; while (argc--) { check(*argv); argv++; } return (nerror); } void check(char *file) { int i, j, c; fi = open64(file, 0); if (fi < 0) { (void) fprintf(stderr, "ncheck: cannot open %s\n", file); nerror++; return; } nhent = 0; (void) printf("%s:\n", file); sync(); bread((diskaddr_t)SBLOCK, (char *)&sblock, SBSIZE); if ((sblock.fs_magic != FS_MAGIC) && (sblock.fs_magic != MTB_UFS_MAGIC)) { (void) printf("%s: not a ufs file system\n", file); nerror++; return; } if ((sblock.fs_magic == FS_MAGIC) && ((sblock.fs_version != UFS_EFISTYLE4NONEFI_VERSION_2) && (sblock.fs_version != UFS_VERSION_MIN))) { (void) printf("%s: unrecognized ufs version number %d\n", file, sblock.fs_version); nerror++; return; } if ((sblock.fs_magic == MTB_UFS_MAGIC) && ((sblock.fs_version > MTB_UFS_VERSION_1) || (sblock.fs_version < MTB_UFS_VERSION_MIN))) { (void) printf("%s: unrecognized ufs version number %d\n", file, sblock.fs_version); nerror++; return; } /* If fs is logged, roll the log. */ if (sblock.fs_logbno) { switch (rl_roll_log(file)) { case RL_SUCCESS: /* * Reread the superblock. Rolling the log may have * changed it. */ bread((diskaddr_t)SBLOCK, (char *)&sblock, SBSIZE); break; case RL_SYSERR: (void) printf("Warning: cannot roll log for %s. %s\n", file, strerror(errno)); break; default: (void) printf("Warning: cannot roll log for %s.\n", file); break; } } itab = (struct dinode *)extend_tbl((uchar_t *)itab, &itab_size, (unsigned)(sblock.fs_ipg * sizeof (struct dinode))); if (itab == 0) { (void) fprintf(stderr, "ncheck: not enough memory for itab table\n"); nerror++; return; } hsize = sblock.fs_ipg * sblock.fs_ncg - sblock.fs_cstotal.cs_nifree + 1; htab = (struct htab *)extend_tbl((uchar_t *)htab, &htab_size, (unsigned)(hsize * sizeof (struct htab))); if (htab == 0) { (void) fprintf(stderr, "ncheck: not enough memory for htab table\n"); nerror++; return; } if (!extend_strngtab(AVG_PATH_LEN * hsize)) { (void) printf("not enough memory to allocate tables\n"); nerror++; return; } strngloc = 0; ino = 0; for (c = 0; c < sblock.fs_ncg; c++) { bread(fsbtodb(&sblock, cgimin(&sblock, c)), (char *)itab, (int)(sblock.fs_ipg * sizeof (struct dinode))); for (j = 0; j < sblock.fs_ipg; j++) { if (itab[j].di_smode != 0) { itab[j].di_mode = itab[j].di_smode; if (itab[j].di_suid != UID_LONG) itab[j].di_uid = itab[j].di_suid; if (itab[j].di_sgid != GID_LONG) itab[j].di_gid = itab[j].di_sgid; pass1(&itab[j]); } ino++; } } ilist[ilist_index++].ino = 0; if (ilist_index > MAX_ILIST_INDEX()) extend_ilist(); ino = 0; for (c = 0; c < sblock.fs_ncg; c++) { bread(fsbtodb(&sblock, cgimin(&sblock, c)), (char *)itab, (int)(sblock.fs_ipg * sizeof (struct dinode))); for (j = 0; j < sblock.fs_ipg; j++) { if (itab[j].di_smode != 0) { itab[j].di_mode = itab[j].di_smode; pass2(&itab[j]); } ino++; } } ino = 0; for (c = 0; c < sblock.fs_ncg; c++) { bread(fsbtodb(&sblock, cgimin(&sblock, c)), (char *)itab, (int)(sblock.fs_ipg * sizeof (struct dinode))); for (j = 0; j < sblock.fs_ipg; j++) { if (itab[j].di_smode != 0) { itab[j].di_mode = itab[j].di_smode; pass3(&itab[j]); } ino++; } } (void) close(fi); /* * Clear those elements after inodes specified by "-i" out of * ilist. */ for (i = iflg; i < ilist_index; i++) { ilist[i].ino = 0; } ilist_index = iflg; } void pass1(struct dinode *ip) { int i; if (mflg) { for (i = 0; i < iflg; i++) if (ino == ilist[i].ino) { ilist[i].mode = ip->di_mode; ilist[i].uid = ip->di_uid; ilist[i].gid = ip->di_gid; } } if ((ip->di_mode & IFMT) != IFDIR) { if (sflg == 0) return; if ((ip->di_mode & IFMT) == IFBLK || (ip->di_mode & IFMT) == IFCHR || ip->di_mode&(ISUID|ISGID)) { ilist[ilist_index].ino = ino; ilist[ilist_index].mode = ip->di_mode; ilist[ilist_index].uid = ip->di_uid; ilist[ilist_index].gid = ip->di_gid; if (++ilist_index > MAX_ILIST_INDEX()) extend_ilist(); return; } } (void) lookup(ino, 1); } void pass2(struct dinode *ip) { struct direct *dp; struct dirstuff dirp; struct htab *hp; if ((ip->di_mode&IFMT) != IFDIR) return; dirp.loc = 0; dirp.ip = ip; gip = ip; for (dp = dreaddir(&dirp); dp != NULL; dp = dreaddir(&dirp)) { int nmlen; if (dp->d_ino == 0) continue; hp = lookup(dp->d_ino, 0); if (hp == 0) continue; if (dotname(dp)) continue; hp->h_pino = ino; nmlen = strlen(dp->d_name); if (strngloc + nmlen + 1 > MAX_STRNGTAB_INDEX()) { if (!extend_strngtab(STRNGTAB_INCR)) { perror("ncheck: can't grow string table\n"); exit(32); } } hp->h_name_index = strngloc; (void) strcpy(&strngtab[strngloc], dp->d_name); strngloc += nmlen + 1; } } void pass3(struct dinode *ip) { struct direct *dp; struct dirstuff dirp; int k; if ((ip->di_mode&IFMT) != IFDIR) return; dirp.loc = 0; dirp.ip = ip; gip = ip; for (dp = dreaddir(&dirp); dp != NULL; dp = dreaddir(&dirp)) { if (aflg == 0 && dotname(dp)) continue; if (sflg == 0 && iflg == 0) goto pr; for (k = 0; k < ilist_index && ilist[k].ino != 0; k++) { if (ilist[k].ino == dp->d_ino) { break; } } if (ilist[k].ino == 0) continue; if (mflg) (void) printf("mode %-6o uid %-5ld gid %-5ld ino ", ilist[k].mode, ilist[k].uid, ilist[k].gid); pr: (void) printf("%-5u\t", dp->d_ino); pname(ino, 0); (void) printf("/%s", dp->d_name); if (lookup(dp->d_ino, 0)) (void) printf("/."); (void) printf("\n"); } } /* * get next entry in a directory. */ struct direct * dreaddir(struct dirstuff *dirp) { struct direct *dp; daddr_t lbn, d; for (;;) { if (dirp->loc >= (int)dirp->ip->di_size) return (NULL); if (blkoff(&sblock, dirp->loc) == 0) { lbn = lblkno(&sblock, dirp->loc); d = bmap(lbn); if (d == 0) return (NULL); bread(fsbtodb(&sblock, d), dirp->dbuf, (int)dblksize(&sblock, dirp->ip, (int)lbn)); } dp = (struct direct *) (dirp->dbuf + blkoff(&sblock, dirp->loc)); dirp->loc += dp->d_reclen; if (dp->d_ino == 0) { continue; } return (dp); } } int dotname(struct direct *dp) { if (dp->d_name[0] == '.') { if (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0)) return (1); } return (0); } void pname(ino_t i, int lev) { struct htab *hp; if (i == UFSROOTINO) return; if ((hp = lookup(i, 0)) == 0) { (void) printf("???"); return; } if (lev > 10) { (void) printf("..."); return; } pname(hp->h_pino, ++lev); (void) printf("/%s", &(strngtab[hp->h_name_index])); } struct htab * lookup(ino_t i, int ef) { struct htab *hp; for (hp = &htab[(int)i%hsize]; hp->h_ino; ) { if (hp->h_ino == i) return (hp); if (++hp >= &htab[hsize]) hp = htab; } if (ef == 0) return (0); if (++nhent >= hsize) { (void) fprintf(stderr, "ncheck: hsize of %ld is too small\n", hsize); exit(32); } hp->h_ino = i; return (hp); } void bread(diskaddr_t bno, char *buf, int cnt) { int i; int got; if (llseek(fi, (offset_t)bno * DEV_BSIZE, 0) == -1) { (void) fprintf(stderr, "ncheck: lseek error %lld\n", (offset_t)bno * DEV_BSIZE); for (i = 0; i < cnt; i++) { buf[i] = 0; } return; } got = read((int)fi, buf, cnt); if (got != cnt) { (void) fprintf(stderr, "ncheck: read error at block %lld (wanted %d got %d)\n", bno, cnt, got); for (i = 0; i < cnt; i++) buf[i] = 0; } } daddr_t bmap(daddr_t i) { daddr_t ibuf[MAXNINDIR]; if (i < NDADDR) return (gip->di_db[i]); i -= NDADDR; if (i > NINDIR(&sblock)) { (void) fprintf(stderr, "ncheck: %lu - huge directory\n", ino); return ((daddr_t)0); } bread(fsbtodb(&sblock, gip->di_ib[0]), (char *)ibuf, sizeof (ibuf)); return (ibuf[i]); } void usage() { (void) fprintf(stderr, /*CSTYLED*/ "ufs usage: ncheck [-F ufs] [generic options] [-a -i #list -s] [-o m] special\n"); exit(32); } /* * Extend or create the inode list; * this is used to contains the list of inodes we've been * asked to check using the "-i" flag and to hold the * inode numbers of files which we detect as being * blk|char|setuid|setgid ("-s" flag support). * Preserves contents. */ void extend_ilist() { ilist_size += ILIST_SZ_INCR; ilist = (struct ilist *)realloc(ilist, (ilist_size * sizeof (struct ilist))); if (ilist == NULL) { perror("ncheck: not enough memory to grow ilist\n"); exit(32); } } /* * Extend or create the string table. * Preserves contents. * Return non-zero for success. */ int extend_strngtab(unsigned int size) { strngtab_size += size; strngtab = (char *)realloc(strngtab, strngtab_size); return ((int)strngtab); } /* * Extend or create a table, throwing away previous * contents. * Return null on failure. */ uchar_t * extend_tbl(uchar_t *tbl, unsigned int *current_size, unsigned int new_size) { /* * if we've already allocated tbl, * but it is too small, free it. * we don't realloc because we are throwing * away its contents. */ if (tbl && (*current_size < new_size)) { free(tbl); tbl = NULL; } if (tbl == NULL) { tbl = (uchar_t *)malloc(new_size); if (tbl == 0) return ((uchar_t *)0); *current_size = new_size; } (void) memset(tbl, 0, new_size); return (tbl); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= newfs OTHERINSTALL= $(ROOTUSRSBIN)/$(LIBPROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(LIBPROG) include ../../Makefile.fstype CPPFLAGS += -I../../ -D_LARGEFILE64_SOURCE LDLIBS += -ladm -lefi CERRWARN += -Wno-parentheses CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off OBJS= $(LIBPROG).o $(FSLIB) SRCS= $(LIBPROG).c $(FSLIBSRC) $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: -$(RM) $(OBJS) $(ROOTUSRSBIN)/$(LIBPROG): -$(RM) $@; $(SYMLINK) $(LINKVALUE) $@ # for messaging catalog # POFILE= newfs.po catalog: $(POFILE) $(POFILE): $(SRCS) $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) $(POFILE).i messages.po /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * newfs: friendly front end to mkfs * * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static unsigned int number(char *, char *, int, int); static int64_t number64(char *, char *, int, int64_t); static diskaddr_t getdiskbydev(char *); static int yes(void); static int notrand(char *); static void usage(); static diskaddr_t get_device_size(int, char *); static diskaddr_t brute_force_get_device_size(int); static int validate_size(char *disk, diskaddr_t size); static void exenv(void); static struct fs *read_sb(char *); /*PRINTFLIKE1*/ static void fatal(char *fmt, ...) __NORETURN; #define EPATH "PATH=/usr/sbin:/sbin:" #define CPATH "/sbin" /* an EPATH element */ #define MB (1024 * 1024) #define GBSEC ((1024 * 1024 * 1024) / DEV_BSIZE) /* sectors in a GB */ #define MINFREESEC ((64 * 1024 * 1024) / DEV_BSIZE) /* sectors in 64 MB */ #define MINCPG (16) /* traditional */ #define MAXDEFDENSITY (8 * 1024) /* arbitrary */ #define MINDENSITY (2 * 1024) /* traditional */ #define MIN_MTB_DENSITY (1024 * 1024) #define POWEROF2(num) (((num) & ((num) - 1)) == 0) #define SECTORS_PER_TERABYTE (1LL << 31) /* * The following constant specifies an upper limit for file system size * that is actually a lot bigger than we expect to support with UFS. (Since * it's specified in sectors, the file system size would be 2**44 * 512, * which is 2**53, which is 8192 Terabytes.) However, it's useful * for checking the basic sanity of a size value that is input on the * command line. */ #define FS_SIZE_UPPER_LIMIT 0x100000000000LL /* For use with number() */ #define NR_NONE 0 #define NR_PERCENT 0x01 /* * The following two constants set the default block and fragment sizes. * Both constants must be a power of 2 and meet the following constraints: * MINBSIZE <= DESBLKSIZE <= MAXBSIZE * DEV_BSIZE <= DESFRAGSIZE <= DESBLKSIZE * DESBLKSIZE / DESFRAGSIZE <= 8 */ #define DESBLKSIZE 8192 #define DESFRAGSIZE 1024 #ifdef DEBUG #define dbgprintf(x) printf x #else #define dbgprintf(x) #endif static int Nflag; /* run mkfs without writing file system */ static int Tflag; /* set up file system for growth to over 1 TB */ static int verbose; /* show mkfs line before exec */ static int fsize = 0; /* fragment size */ static int fsize_flag = 0; /* fragment size was specified on cmd line */ static int bsize; /* block size */ static int ntracks; /* # tracks/cylinder */ static int ntracks_set = 0; /* true if the user specified ntracks */ static int optim = FS_OPTTIME; /* optimization, t(ime) or s(pace) */ static int nsectors; /* # sectors/track */ static int cpg; /* cylinders/cylinder group */ static int cpg_set = 0; /* true if the user specified cpg */ static int minfree = -1; /* free space threshold */ static int rpm; /* revolutions/minute of drive */ static int rpm_set = 0; /* true if the user specified rpm */ static int nrpos = 8; /* # of distinguished rotational positions */ /* 8 is the historical default */ static int nrpos_set = 0; /* true if the user specified nrpos */ static int density = 0; /* number of bytes per inode */ static int apc; /* alternates per cylinder */ static int apc_set = 0; /* true if the user specified apc */ static int rot = -1; /* rotational delay (msecs) */ static int rot_set = 0; /* true if the user specified rot */ static int maxcontig = -1; /* maximum number of contig blocks */ static int text_sb = 0; /* no disk changes; just final sb text dump */ static int binary_sb = 0; /* no disk changes; just final sb binary dump */ static int label_type; /* see types below */ /* * The variable use_efi_dflts is an indicator of whether to use EFI logic * or the geometry logic in laying out the filesystem. This is decided * based on the size/type of the disk and is used only for non-EFI labeled * disks and removable media. */ static int use_efi_dflts = 0; static int isremovable = 0; static int ishotpluggable = 0; static char device[MAXPATHLEN]; static char cmd[BUFSIZ]; extern char *getfullrawname(); /* from libadm */ int main(int argc, char *argv[]) { char *special, *name; struct stat64 st; int status; int option; struct fs *sbp; /* Pointer to superblock (if present) */ diskaddr_t actual_fssize; diskaddr_t max_possible_fssize; diskaddr_t req_fssize = 0; diskaddr_t fssize = 0; char *req_fssize_str = NULL; /* requested size argument */ (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); opterr = 0; /* We print our own errors, disable getopt's message */ while ((option = getopt(argc, argv, "vNBSs:C:d:t:o:a:b:f:c:m:n:r:i:T")) != EOF) { switch (option) { case 'S': text_sb++; break; case 'B': binary_sb++; break; case 'v': verbose++; break; case 'N': Nflag++; break; case 's': /* * The maximum file system size is a lot smaller * than FS_SIZE_UPPER_LIMIT, but until we find out * the device size and block size, we don't know * what it is. So save the requested size in a * string so that we can print it out later if we * determine it's too big. */ req_fssize = number64("fssize", optarg, NR_NONE, FS_SIZE_UPPER_LIMIT); if (req_fssize < 1024) fatal(gettext( "%s: fssize must be at least 1024"), optarg); req_fssize_str = strdup(optarg); if (req_fssize_str == NULL) fatal(gettext( "Insufficient memory for string copy.")); break; case 'C': maxcontig = number("maxcontig", optarg, NR_NONE, -1); if (maxcontig < 0) fatal(gettext("%s: bad maxcontig"), optarg); break; case 'd': rot = number("rotdelay", optarg, NR_NONE, 0); rot_set = 1; if (rot < 0 || rot > 1000) fatal(gettext( "%s: bad rotational delay"), optarg); break; case 't': ntracks = number("ntrack", optarg, NR_NONE, 16); ntracks_set = 1; if ((ntracks < 0) || (ntracks > INT_MAX)) fatal(gettext("%s: bad total tracks"), optarg); break; case 'o': if (strcmp(optarg, "space") == 0) optim = FS_OPTSPACE; else if (strcmp(optarg, "time") == 0) optim = FS_OPTTIME; else fatal(gettext( "%s: bad optimization preference (options are `space' or `time')"), optarg); break; case 'a': apc = number("apc", optarg, NR_NONE, 0); apc_set = 1; if (apc < 0 || apc > 32768) /* see mkfs.c */ fatal(gettext( "%s: bad alternates per cyl"), optarg); break; case 'b': bsize = number("bsize", optarg, NR_NONE, DESBLKSIZE); if (bsize < MINBSIZE || bsize > MAXBSIZE) fatal(gettext( "%s: bad block size"), optarg); break; case 'f': fsize = number("fragsize", optarg, NR_NONE, DESFRAGSIZE); fsize_flag++; /* xxx ought to test against bsize for upper limit */ if (fsize < DEV_BSIZE) fatal(gettext("%s: bad frag size"), optarg); break; case 'c': cpg = number("cpg", optarg, NR_NONE, 16); cpg_set = 1; if (cpg < 1) fatal(gettext("%s: bad cylinders/group"), optarg); break; case 'm': minfree = number("minfree", optarg, NR_PERCENT, 10); if (minfree < 0 || minfree > 99) fatal(gettext("%s: bad free space %%"), optarg); break; case 'n': nrpos = number("nrpos", optarg, NR_NONE, 8); nrpos_set = 1; if (nrpos <= 0) fatal(gettext( "%s: bad number of rotational positions"), optarg); break; case 'r': rpm = number("rpm", optarg, NR_NONE, 3600); rpm_set = 1; if (rpm < 0) fatal(gettext("%s: bad revs/minute"), optarg); break; case 'i': /* xxx ought to test against fsize */ density = number("nbpi", optarg, NR_NONE, 2048); if (density < DEV_BSIZE) fatal(gettext("%s: bad bytes per inode"), optarg); break; case 'T': Tflag++; break; default: usage(); fatal(gettext("-%c: unknown flag"), optopt); } } /* At this point, there should only be one argument left: */ /* The raw-special-device itself. If not, print usage message. */ if ((argc - optind) != 1) { usage(); exit(1); } name = argv[optind]; special = getfullrawname(name); if (special == NULL) { (void) fprintf(stderr, gettext("newfs: malloc failed\n")); exit(1); } if (*special == '\0') { if (strchr(name, '/') != NULL) { if (stat64(name, &st) < 0) { (void) fprintf(stderr, gettext("newfs: %s: %s\n"), name, strerror(errno)); exit(2); } fatal(gettext("%s: not a raw disk device"), name); } (void) snprintf(device, sizeof (device), "/dev/rdsk/%s", name); if ((special = getfullrawname(device)) == NULL) { (void) fprintf(stderr, gettext("newfs: malloc failed\n")); exit(1); } if (*special == '\0') { (void) snprintf(device, sizeof (device), "/dev/%s", name); if ((special = getfullrawname(device)) == NULL) { (void) fprintf(stderr, gettext("newfs: malloc failed\n")); exit(1); } if (*special == '\0') fatal(gettext( "%s: not a raw disk device"), name); } } /* * getdiskbydev() determines the characteristics of the special * device on which the file system will be built. In the case * of devices with SMI labels (that is, non-EFI labels), the * following characteristics are set (if they were not already * set on the command line, since the command line settings * take precedence): * * nsectors - sectors per track * ntracks - tracks per cylinder * rpm - disk revolutions per minute * * apc is NOT set * * getdiskbydev() also sets the following quantities for all * devices, if not already set: * * bsize - file system block size * maxcontig * label_type (efi, vtoc, or other) * * getdiskbydev() returns the actual size of the device, in * sectors. */ actual_fssize = getdiskbydev(special); if (req_fssize == 0) { fssize = actual_fssize; } else { /* * If the user specified a size larger than what we've * determined as the actual size of the device, see if the * size specified by the user can be read. If so, use it, * since some devices and volume managers may not support * the vtoc and EFI interfaces we use to determine device * size. */ if (req_fssize > actual_fssize && validate_size(special, req_fssize)) { (void) fprintf(stderr, gettext( "Warning: the requested size of this file system\n" "(%lld sectors) is greater than the size of the\n" "device reported by the driver (%lld sectors).\n" "However, a read of the device at the requested size\n" "does succeed, so the requested size will be used.\n"), req_fssize, actual_fssize); fssize = req_fssize; } else { fssize = MIN(req_fssize, actual_fssize); } } if (label_type == LABEL_TYPE_VTOC) { if (nsectors < 0) fatal(gettext("%s: no default #sectors/track"), special); if (!use_efi_dflts) { if (ntracks < 0) fatal(gettext("%s: no default #tracks"), special); } if (rpm < 0) fatal(gettext( "%s: no default revolutions/minute value"), special); if (rpm < 60) { (void) fprintf(stderr, gettext("Warning: setting rpm to 60\n")); rpm = 60; } } if (label_type == LABEL_TYPE_EFI || label_type == LABEL_TYPE_OTHER) { if (ntracks_set) (void) fprintf(stderr, gettext( "Warning: ntracks is obsolete for this device and will be ignored.\n")); if (cpg_set) (void) fprintf(stderr, gettext( "Warning: cylinders/group is obsolete for this device and will be ignored.\n")); if (rpm_set) (void) fprintf(stderr, gettext( "Warning: rpm is obsolete for this device and will be ignored.\n")); if (rot_set) (void) fprintf(stderr, gettext( "Warning: rotational delay is obsolete for this device and" " will be ignored.\n")); if (nrpos_set) (void) fprintf(stderr, gettext( "Warning: number of rotational positions is obsolete for this device and\n" "will be ignored.\n")); if (apc_set) (void) fprintf(stderr, gettext( "Warning: number of alternate sectors per cylinder is obsolete for this\n" "device and will be ignored.\n")); /* * We need these for the call to mkfs, even though they are * meaningless. */ rpm = 60; nrpos = 1; apc = 0; rot = -1; /* * These values are set to produce a file system with * a cylinder group size of 48MB. For disks with * non-EFI labels, most geometries result in cylinder * groups of around 40 - 50 MB, so we arbitrarily choose * 48MB for disks with EFI labels. mkfs will reduce * cylinders per group even further if necessary. */ cpg = 16; nsectors = 128; ntracks = 48; /* * mkfs produces peculiar results for file systems * that are smaller than one cylinder so don't allow * them to be created (this check is only made for * disks with EFI labels. Eventually, it should probably * be enforced for all disks.) */ if (fssize < nsectors * ntracks) { fatal(gettext( "file system size must be at least %d sectors"), nsectors * ntracks); } } if (fssize > INT_MAX) Tflag = 1; /* * If the user requested that the file system be set up for * eventual growth to over a terabyte, or if it's already greater * than a terabyte, set the inode density (nbpi) to MIN_MTB_DENSITY * (unless the user has specified a larger nbpi), set the frag size * equal to the block size, and set the cylinders-per-group value * passed to mkfs to -1, which tells mkfs to make cylinder groups * as large as possible. */ if (Tflag) { if (density < MIN_MTB_DENSITY) density = MIN_MTB_DENSITY; fsize = bsize; cpg = -1; /* says make cyl groups as big as possible */ } else { if (fsize == 0) fsize = DESFRAGSIZE; } if (!POWEROF2(fsize)) { (void) fprintf(stderr, gettext( "newfs: fragment size must a power of 2, not %d\n"), fsize); fsize = bsize/8; (void) fprintf(stderr, gettext( "newfs: fragsize reset to %ld\n"), fsize); } /* * The file system is limited in size by the fragment size. * The number of fragments in the file system must fit into * a signed 32-bit quantity, so the number of sectors in the * file system is INT_MAX * the number of sectors in a frag. */ max_possible_fssize = ((uint64_t)fsize)/DEV_BSIZE * INT_MAX; if (fssize > max_possible_fssize) fssize = max_possible_fssize; /* * Now fssize is the final size of the file system (in sectors). * If it's less than what the user requested, print a message. */ if (fssize < req_fssize) { (void) fprintf(stderr, gettext( "newfs: requested size of %s disk blocks is too large.\n"), req_fssize_str); (void) fprintf(stderr, gettext( "newfs: Resetting size to %lld\n"), fssize); } /* * fssize now equals the size (in sectors) of the file system * that will be created. */ /* XXX - following defaults are both here and in mkfs */ if (density <= 0) { if (fssize < GBSEC) density = MINDENSITY; else density = (int)((((longlong_t)fssize + (GBSEC - 1)) / GBSEC) * MINDENSITY); if (density <= 0) density = MINDENSITY; if (density > MAXDEFDENSITY) density = MAXDEFDENSITY; } if (cpg == 0) { /* * maxcpg calculation adapted from mkfs * In the case of disks with EFI labels, cpg has * already been set, so we won't enter this code. */ long maxcpg, maxipg; maxipg = roundup(bsize * NBBY / 3, bsize / sizeof (struct inode)); maxcpg = (bsize - sizeof (struct cg) - howmany(maxipg, NBBY)) / (sizeof (long) + nrpos * sizeof (short) + nsectors / (MAXFRAG * NBBY)); cpg = (fssize / GBSEC) * 32; if (cpg > maxcpg) cpg = maxcpg; if (cpg <= 0) cpg = MINCPG; } if (minfree < 0) { minfree = (int)(((float)MINFREESEC / fssize) * 100); if (minfree > 10) minfree = 10; if (minfree <= 0) minfree = 1; } #ifdef i386 /* Bug 1170182 */ if (ntracks > 32 && (ntracks % 16) != 0) { ntracks -= (ntracks % 16); } #endif /* * Confirmation */ if (isatty(fileno(stdin)) && !Nflag) { /* * If we can read a valid superblock, report the mount * point on which this filesystem was last mounted. */ if (((sbp = read_sb(special)) != 0) && (*sbp->fs_fsmnt != '\0')) { (void) printf(gettext( "newfs: %s last mounted as %s\n"), special, sbp->fs_fsmnt); } (void) printf(gettext( "newfs: construct a new file system %s: (y/n)? "), special); (void) fflush(stdout); if (!yes()) exit(0); } dbgprintf(("DeBuG newfs : nsect=%d ntrak=%d cpg=%d\n", nsectors, ntracks, cpg)); /* * If alternates-per-cylinder is ever implemented: * need to get apc from dp->d_apc if no -a switch??? */ (void) snprintf(cmd, sizeof (cmd), "mkfs -F ufs " "%s%s%s%s %lld %d %d %d %d %d %d %d %d %s %d %d %d %d %s", Nflag ? "-o N " : "", binary_sb ? "-o calcbinsb " : "", text_sb ? "-o calcsb " : "", special, fssize, nsectors, ntracks, bsize, fsize, cpg, minfree, rpm/60, density, optim == FS_OPTSPACE ? "s" : "t", apc, rot, nrpos, maxcontig, Tflag ? "y" : "n"); if (verbose) { (void) printf("%s\n", cmd); (void) fflush(stdout); } exenv(); if (status = system(cmd)) exit(status >> 8); if (Nflag) exit(0); (void) snprintf(cmd, sizeof (cmd), "/usr/sbin/fsirand %s", special); if (notrand(special) && (status = system(cmd)) != 0) (void) fprintf(stderr, gettext("%s: failed, status = %d\n"), cmd, status); return (0); } static void exenv(void) { char *epath; /* executable file path */ char *cpath; /* current path */ if ((cpath = getenv("PATH")) == NULL) { (void) fprintf(stderr, gettext("newfs: no PATH in env\n")); /* * Background: the Bourne shell interpolates "." into * the path where said path starts with a colon, ends * with a colon, or has two adjacent colons. Thus, * the path ":/sbin::/usr/sbin:" is equivalent to * ".:/sbin:.:/usr/sbin:.". Now, we have no cpath, * and epath ends in a colon (to make for easy * catenation in the normal case). By the above, if * we use "", then "." becomes part of path. That's * bad, so use CPATH (which is just a duplicate of some * element in EPATH). No point in opening ourselves * up to a Trojan horse attack when we don't have to.... */ cpath = CPATH; } if ((epath = malloc(strlen(EPATH) + strlen(cpath) + 1)) == NULL) { (void) fprintf(stderr, gettext("newfs: malloc failed\n")); exit(1); } (void) strcpy(epath, EPATH); (void) strcat(epath, cpath); if (putenv(epath) < 0) { (void) fprintf(stderr, gettext("newfs: putenv failed\n")); exit(1); } } static int yes(void) { int i, b; i = b = getchar(); while (b != '\n' && b != '\0' && b != EOF) b = getchar(); return (i == 'y'); } /* * xxx Caller must run fmt through gettext(3) for us, if we ever * xxx go the i18n route.... */ static void fatal(char *fmt, ...) { va_list pvar; (void) fprintf(stderr, "newfs: "); va_start(pvar, fmt); (void) vfprintf(stderr, fmt, pvar); va_end(pvar); (void) putc('\n', stderr); exit(10); } static diskaddr_t getdiskbydev(char *disk) { struct dk_geom g; struct dk_cinfo ci; struct dk_minfo info; diskaddr_t actual_size; int fd; if ((fd = open64(disk, 0)) < 0) { perror(disk); exit(1); } /* * get_device_size() determines the actual size of the * device, and also the disk's attributes, such as geometry. */ actual_size = get_device_size(fd, disk); if (label_type == LABEL_TYPE_VTOC) { /* * Geometry information does not make sense for removable or * hotpluggable media anyway, so indicate mkfs to use EFI * default parameters. */ if (ioctl(fd, DKIOCREMOVABLE, &isremovable)) { dbgprintf(("DeBuG newfs : Unable to determine if %s is" " Removable Media. Proceeding with system" " determined parameters.\n", disk)); isremovable = 0; } /* If removable check if a floppy disk */ if (isremovable) { if (ioctl(fd, DKIOCGMEDIAINFO, &info)) { dbgprintf(("DeBuG newfs : Unable to get media" " info from %s.\n", disk)); } else { if (info.dki_media_type == DK_FLOPPY) { isremovable = 0; } } } if (ioctl(fd, DKIOCHOTPLUGGABLE, &ishotpluggable)) { dbgprintf(("DeBuG newfs : Unable to determine if %s is" " Hotpluggable Media. Proceeding with system" " determined parameters.\n", disk)); ishotpluggable = 0; } if ((isremovable || ishotpluggable) && !Tflag) use_efi_dflts = 1; if (ioctl(fd, DKIOCGGEOM, &g)) fatal(gettext( "%s: Unable to read Disk geometry"), disk); if ((((diskaddr_t)g.dkg_ncyl * g.dkg_nhead * g.dkg_nsect) > CHSLIMIT) && !Tflag) { use_efi_dflts = 1; } dbgprintf(("DeBuG newfs : geom=%llu, CHSLIMIT=%d " "isremovable = %d ishotpluggable = %d use_efi_dflts = %d\n", (diskaddr_t)g.dkg_ncyl * g.dkg_nhead * g.dkg_nsect, CHSLIMIT, isremovable, ishotpluggable, use_efi_dflts)); /* * The ntracks that is passed to mkfs is decided here based * on 'use_efi_dflts' and whether ntracks was specified as a * command line parameter to newfs. * If ntracks of -1 is passed to mkfs, mkfs uses DEF_TRACKS_EFI * and DEF_SECTORS_EFI for ntracks and nsectors respectively. */ if (nsectors == 0) nsectors = g.dkg_nsect; if (ntracks == 0) ntracks = use_efi_dflts ? -1 : g.dkg_nhead; if (rpm == 0) rpm = ((int)g.dkg_rpm <= 0) ? 3600: g.dkg_rpm; } if (bsize == 0) bsize = DESBLKSIZE; /* * Adjust maxcontig by the device's maxtransfer. If maxtransfer * information is not available, default to the min of a MB and * maxphys. */ if (maxcontig == -1 && ioctl(fd, DKIOCINFO, &ci) == 0) { maxcontig = ci.dki_maxtransfer * DEV_BSIZE; if (maxcontig < 0) { int error, gotit, maxphys; gotit = fsgetmaxphys(&maxphys, &error); /* * If we cannot get the maxphys value, default * to ufs_maxmaxphys (MB). */ if (gotit) { maxcontig = MIN(maxphys, MB); } else { (void) fprintf(stderr, gettext( "Warning: Could not get system value for maxphys. The value for maxcontig\n" "will default to 1MB.\n")); maxcontig = MB; } } maxcontig /= bsize; } (void) close(fd); return (actual_size); } /* * Figure out how big the partition we're dealing with is. */ static diskaddr_t get_device_size(int fd, char *name) { struct extvtoc vtoc; dk_gpt_t *efi_vtoc; diskaddr_t slicesize; int index = read_extvtoc(fd, &vtoc); if (index >= 0) { label_type = LABEL_TYPE_VTOC; } else { if (index == VT_ENOTSUP || index == VT_ERROR) { /* it might be an EFI label */ index = efi_alloc_and_read(fd, &efi_vtoc); if (index >= 0) label_type = LABEL_TYPE_EFI; } } if (index < 0) { /* * Since both attempts to read the label failed, we're * going to fall back to a brute force approach to * determining the device's size: see how far out we can * perform reads on the device. */ slicesize = brute_force_get_device_size(fd); if (slicesize == 0) { switch (index) { case VT_ERROR: (void) fprintf(stderr, gettext( "newfs: %s: %s\n"), name, strerror(errno)); exit(10); /*NOTREACHED*/ case VT_EIO: fatal(gettext( "%s: I/O error accessing VTOC"), name); /*NOTREACHED*/ case VT_EINVAL: fatal(gettext( "%s: Invalid field in VTOC"), name); /*NOTREACHED*/ default: fatal(gettext( "%s: unknown error accessing VTOC"), name); /*NOTREACHED*/ } } else { label_type = LABEL_TYPE_OTHER; } } if (label_type == LABEL_TYPE_EFI) { slicesize = efi_vtoc->efi_parts[index].p_size; efi_free(efi_vtoc); } else if (label_type == LABEL_TYPE_VTOC) { slicesize = vtoc.v_part[index].p_size; } return (slicesize); } /* * brute_force_get_device_size * * Determine the size of the device by seeing how far we can * read. Doing an llseek( , , SEEK_END) would probably work * in most cases, but we've seen at least one third-party driver * which doesn't correctly support the SEEK_END option when the * the device is greater than a terabyte. */ static diskaddr_t brute_force_get_device_size(int fd) { diskaddr_t min_fail = 0; diskaddr_t max_succeed = 0; diskaddr_t cur_db_off; char buf[DEV_BSIZE]; /* * First, see if we can read the device at all, just to * eliminate errors that have nothing to do with the * device's size. */ if (((llseek(fd, (offset_t)0, SEEK_SET)) == -1) || ((read(fd, buf, DEV_BSIZE)) == -1)) return (0); /* can't determine size */ /* * Now, go sequentially through the multiples of 4TB * to find the first read that fails (this isn't strictly * the most efficient way to find the actual size if the * size really could be anything between 0 and 2**64 bytes. * We expect the sizes to be less than 16 TB for some time, * so why do a bunch of reads that are larger than that? * However, this algorithm *will* work for sizes of greater * than 16 TB. We're just not optimizing for those sizes.) */ for (cur_db_off = SECTORS_PER_TERABYTE * 4; min_fail == 0 && cur_db_off < FS_SIZE_UPPER_LIMIT; cur_db_off += 4 * SECTORS_PER_TERABYTE) { if (((llseek(fd, (offset_t)(cur_db_off * DEV_BSIZE), SEEK_SET)) == -1) || ((read(fd, buf, DEV_BSIZE)) != DEV_BSIZE)) min_fail = cur_db_off; else max_succeed = cur_db_off; } if (min_fail == 0) return (0); /* * We now know that the size of the device is less than * min_fail and greater than or equal to max_succeed. Now * keep splitting the difference until the actual size in * sectors in known. We also know that the difference * between max_succeed and min_fail at this time is * 4 * SECTORS_PER_TERABYTE, which is a power of two, which * simplifies the math below. */ while (min_fail - max_succeed > 1) { cur_db_off = max_succeed + (min_fail - max_succeed)/2; if (((llseek(fd, (offset_t)(cur_db_off * DEV_BSIZE), SEEK_SET)) == -1) || ((read(fd, buf, DEV_BSIZE)) != DEV_BSIZE)) min_fail = cur_db_off; else max_succeed = cur_db_off; } /* the size is the last successfully read sector offset plus one */ return (max_succeed + 1); } /* * validate_size * * Return 1 if the device appears to be at least "size" sectors long. * Return 0 if it's shorter or we can't read it. */ static int validate_size(char *disk, diskaddr_t size) { char buf[DEV_BSIZE]; int fd, rc; if ((fd = open64(disk, O_RDONLY)) < 0) { perror(disk); exit(1); } if ((llseek(fd, (offset_t)((size - 1) * DEV_BSIZE), SEEK_SET) == -1) || (read(fd, buf, DEV_BSIZE)) != DEV_BSIZE) rc = 0; else rc = 1; (void) close(fd); return (rc); } /* * read_sb(char * rawdev) - Attempt to read the superblock from a raw device * * Returns: * 0 : * Could not read a valid superblock for a variety of reasons. * Since 'newfs' handles any fatal conditions, we're not going * to make any guesses as to why this is failing or what should * be done about it. * * struct fs *: * A pointer to (what we think is) a valid superblock. The * space for the superblock is static (inside the function) * since we will only be reading the values from it. */ struct fs * read_sb(char *fsdev) { static struct fs sblock; struct stat64 statb; int dskfd; char *bufp = NULL; int bufsz = 0; if (stat64(fsdev, &statb) < 0) return (0); if ((dskfd = open64(fsdev, O_RDONLY)) < 0) return (0); /* * We need a buffer whose size is a multiple of DEV_BSIZE in order * to read from a raw device (which we were probably passed). */ bufsz = ((sizeof (sblock) / DEV_BSIZE) + 1) * DEV_BSIZE; if ((bufp = malloc(bufsz)) == NULL) { (void) close(dskfd); return (0); } if (llseek(dskfd, (offset_t)SBOFF, SEEK_SET) < 0 || read(dskfd, bufp, bufsz) < 0) { (void) close(dskfd); free(bufp); return (0); } (void) close(dskfd); /* Done with the file */ (void) memcpy(&sblock, bufp, sizeof (sblock)); free(bufp); /* Don't need this anymore */ if (((sblock.fs_magic != FS_MAGIC) && (sblock.fs_magic != MTB_UFS_MAGIC)) || sblock.fs_ncg < 1 || sblock.fs_cpg < 1) return (0); if (sblock.fs_ncg * sblock.fs_cpg < sblock.fs_ncyl || (sblock.fs_ncg - 1) * sblock.fs_cpg >= sblock.fs_ncyl) return (0); if (sblock.fs_sbsize < 0 || sblock.fs_sbsize > SBSIZE) return (0); return (&sblock); } /* * Read the UFS file system on the raw device SPECIAL. If it does not * appear to be a UFS file system, return non-zero, indicating that * fsirand should be called (and it will spit out an error message). * If it is a UFS file system, take a look at the inodes in the first * cylinder group. If they appear to be randomized (non-zero), return * zero, which will cause fsirand to not be called. If the inode generation * counts are all zero, then we must call fsirand, so return non-zero. */ #define RANDOMIZED 0 #define NOT_RANDOMIZED 1 static int notrand(char *special) { long fsbuf[SBSIZE / sizeof (long)]; struct dinode dibuf[MAXBSIZE/sizeof (struct dinode)]; struct fs *fs; struct dinode *dip; offset_t seekaddr; int bno, inum; int fd; fs = (struct fs *)fsbuf; if ((fd = open64(special, 0)) == -1) return (NOT_RANDOMIZED); if (llseek(fd, (offset_t)SBLOCK * DEV_BSIZE, 0) == -1 || read(fd, (char *)fs, SBSIZE) != SBSIZE || ((fs->fs_magic != FS_MAGIC) && (fs->fs_magic != MTB_UFS_MAGIC))) { (void) close(fd); return (NOT_RANDOMIZED); } /* looks like a UFS file system; read the first cylinder group */ bsize = INOPB(fs) * sizeof (struct dinode); inum = 0; while (inum < fs->fs_ipg) { bno = itod(fs, inum); seekaddr = (offset_t)fsbtodb(fs, bno) * DEV_BSIZE; if (llseek(fd, seekaddr, 0) == -1 || read(fd, (char *)dibuf, bsize) != bsize) { (void) close(fd); return (NOT_RANDOMIZED); } for (dip = dibuf; dip < &dibuf[INOPB(fs)]; dip++) { if (dip->di_gen != 0) { (void) close(fd); return (RANDOMIZED); } inum++; } } (void) close(fd); return (NOT_RANDOMIZED); } static void usage(void) { (void) fprintf(stderr, gettext( "usage: newfs [ -v ] [ mkfs-options ] raw-special-device\n")); (void) fprintf(stderr, gettext("where mkfs-options are:\n")); (void) fprintf(stderr, gettext( "\t-N do not create file system, just print out parameters\n")); (void) fprintf(stderr, gettext( "\t-T configure file system for eventual growth to over a terabyte\n")); (void) fprintf(stderr, gettext("\t-s file system size (sectors)\n")); (void) fprintf(stderr, gettext("\t-b block size\n")); (void) fprintf(stderr, gettext("\t-f frag size\n")); (void) fprintf(stderr, gettext("\t-t tracks/cylinder\n")); (void) fprintf(stderr, gettext("\t-c cylinders/group\n")); (void) fprintf(stderr, gettext("\t-m minimum free space %%\n")); (void) fprintf(stderr, gettext( "\t-o optimization preference (`space' or `time')\n")); (void) fprintf(stderr, gettext("\t-r revolutions/minute\n")); (void) fprintf(stderr, gettext("\t-i number of bytes per inode\n")); (void) fprintf(stderr, gettext( "\t-a number of alternates per cylinder\n")); (void) fprintf(stderr, gettext("\t-C maxcontig\n")); (void) fprintf(stderr, gettext("\t-d rotational delay\n")); (void) fprintf(stderr, gettext( "\t-n number of rotational positions\n")); (void) fprintf(stderr, gettext( "\t-S print a textual version of the calculated superblock to stdout\n")); (void) fprintf(stderr, gettext( "\t-B dump a binary version of the calculated superblock to stdout\n")); } /* * Error-detecting version of atoi(3). Adapted from mkfs' number(). */ static unsigned int number(char *param, char *value, int flags, int def_value) { char *cs; int n; int cut = INT_MAX / 10; /* limit to avoid overflow */ int minus = 0; cs = value; if (*cs == '-') { minus = 1; cs += 1; } if ((*cs < '0') || (*cs > '9')) { goto bail_out; } n = 0; while ((*cs >= '0') && (*cs <= '9') && (n <= cut)) { n = n*10 + *cs++ - '0'; } if (minus) n = -n; for (;;) { switch (*cs++) { case '\0': return (n); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': (void) fprintf(stderr, gettext( "newfs: value for %s overflowed, using %d\n"), param, def_value); return (def_value); case '%': if (flags & NR_PERCENT) break; /* FALLTHROUGH */ default: bail_out: fatal(gettext("bad numeric arg for %s: \"%s\""), param, value); } } /* NOTREACHED */ } /* * Error-detecting version of atoi(3). Adapted from mkfs' number(). */ static int64_t number64(char *param, char *value, int flags, int64_t def_value) { char *cs; int64_t n; int64_t cut = FS_SIZE_UPPER_LIMIT/ 10; /* limit to avoid overflow */ int minus = 0; cs = value; if (*cs == '-') { minus = 1; cs += 1; } if ((*cs < '0') || (*cs > '9')) { goto bail_out; } n = 0; while ((*cs >= '0') && (*cs <= '9') && (n <= cut)) { n = n*10 + *cs++ - '0'; } if (minus) n = -n; for (;;) { switch (*cs++) { case '\0': return (n); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': (void) fprintf(stderr, gettext( "newfs: value for %s overflowed, using %d\n"), param, def_value); return (def_value); case '%': if (flags & NR_PERCENT) break; /* FALLTHROUGH */ default: bail_out: fatal(gettext("bad numeric arg for %s: \"%s\""), param, value); } } /* NOTREACHED */ } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= quot ATTMK= $(LIBPROG) OTHERINSTALL= $(ROOTUSRSBIN)/$(LIBPROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(LIBPROG) include ../../Makefile.fstype CPPFLAGS += -D_LARGEFILE64_SOURCE CFLAGS += $(CCVERBOSE) LDLIBS += -ladm # not linted SMATCH=off $(ROOTUSRSBIN)/$(LIBPROG): -$(RM) $@; $(SYMLINK) $(LINKVALUE) $@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * quot */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ISIZ (MAXBSIZE/sizeof (struct dinode)) static union { struct fs u_sblock; char dummy[SBSIZE]; } sb_un; #define sblock sb_un.u_sblock static struct dinode *itab; struct du { struct du *next; long blocks; long blocks30; long blocks60; long blocks90; long nfiles; uid_t uid; char *u_name; }; static struct du **du; #define UHASH 8209 static int ndu; #define HASH(u) ((uint_t)(u) % UHASH) static struct du *duhashtbl[UHASH]; #define TSIZE 2048 static int sizes[TSIZE]; static offset_t overflow; static int nflg; static int fflg; static int cflg; static int vflg; static int hflg; static int aflg; static long now; static unsigned ino; static void usage(void); static void quotall(void); static void qacct(struct dinode *); static void bread(int, diskaddr_t, char *, int); static void report(void); static int getdev(char **); static int check(char *, char *); static struct du *adduid(uid_t); static struct du *lookup(uid_t); static void sortprep(void); static void cleanup(void); static void usage() { (void) fprintf(stderr, "ufs usage: quot [-nfcvha] [filesystem ...]\n"); } int main(int argc, char *argv[]) { int opt; int i; if (argc == 1) { (void) fprintf(stderr, "ufs Usage: quot [-nfcvha] [filesystem ...]\n"); return (32); } now = time(0); while ((opt = getopt(argc, argv, "nfcvhaV")) != EOF) { switch (opt) { case 'n': nflg++; break; case 'f': fflg++; break; case 'c': cflg++; break; case 'v': vflg++; break; case 'h': hflg++; break; case 'a': aflg++; break; case 'V': /* Print command line */ { char *opt_text; int opt_count; (void) fprintf(stdout, "quot -F UFS "); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) fprintf(stdout, " %s ", opt_text); } (void) fprintf(stdout, "\n"); } break; case '?': usage(); return (32); } } if (aflg) { quotall(); } for (i = optind; i < argc; i++) { if ((getdev(&argv[i]) == 0) && (check(argv[i], (char *)NULL) == 0)) { report(); cleanup(); } } return (0); } static void quotall() { FILE *fstab; struct mnttab mntp; char *cp; extern char *getfullrawname(); fstab = fopen(MNTTAB, "r"); if (fstab == NULL) { (void) fprintf(stderr, "quot: no %s file\n", MNTTAB); exit(32); } while (getmntent(fstab, &mntp) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0) continue; if ((cp = getfullrawname(mntp.mnt_special)) == NULL) continue; if (*cp == '\0') continue; if (check(cp, mntp.mnt_mountp) == 0) { report(); cleanup(); } free(cp); } (void) fclose(fstab); } static int check(char *file, char *fsdir) { FILE *fstab; int i, j; int c, fd; /* * Initialize tables between checks; * because of the qsort done in report() * the hash tables must be rebuilt each time. */ for (i = 0; i < TSIZE; i++) sizes[i] = 0; overflow = 0LL; ndu = 0; fd = open64(file, O_RDONLY); if (fd < 0) { (void) fprintf(stderr, "quot: "); perror(file); exit(32); } (void) printf("%s", file); if (fsdir == NULL) { struct mnttab mntp; fstab = fopen(MNTTAB, "r"); if (fstab == NULL) { (void) fprintf(stderr, "quot: no %s file\n", MNTTAB); exit(32); } while (getmntent(fstab, &mntp) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0) continue; if (strcmp(mntp.mnt_special, file) == 0) { fsdir = mntp.mnt_mountp; break; } } } if (fsdir != NULL && *fsdir != '\0') (void) printf(" (%s)", fsdir); (void) printf(":\n"); sync(); bread(fd, (diskaddr_t)SBLOCK, (char *)&sblock, SBSIZE); if (nflg) { if (isdigit(c = getchar())) (void) ungetc(c, stdin); else while (c != '\n' && c != EOF) c = getchar(); } itab = (struct dinode *)calloc(sblock.fs_ipg, sizeof (struct dinode)); if (itab == NULL) { (void) fprintf(stderr, "not enough memory to allocate tables\n"); return (1); } ino = 0; for (c = 0; c < sblock.fs_ncg; c++) { bread(fd, (diskaddr_t)fsbtodb(&sblock, cgimin(&sblock, c)), (char *)itab, (int)(sblock.fs_ipg * sizeof (struct dinode))); for (j = 0; j < sblock.fs_ipg; j++, ino++) { if (ino < UFSROOTINO) continue; qacct(&itab[j]); } } (void) close(fd); return (0); } static void qacct(struct dinode *ip) { struct du *dp; long blks, frags, size; int n; static int fino; ip->di_mode = ip->di_smode; if (ip->di_suid != UID_LONG) { ip->di_uid = ip->di_suid; } if ((ip->di_mode & IFMT) == 0) return; /* * By default, take block count in inode. Otherwise (-h), * take the size field and estimate the blocks allocated. * The latter does not account for holes in files. */ if (!hflg) size = ip->di_blocks / 2; else { blks = lblkno(&sblock, ip->di_size); frags = blks * sblock.fs_frag + numfrags(&sblock, dblksize(&sblock, ip, blks)); /* * Must cast to offset_t because for a large file, * frags multiplied by sblock.fs_fsize will not fit in a long. * However, when divided by 1024, the end result will fit in * the 32 bit size variable (40 bit UFS). */ size = (long)((offset_t)frags * (offset_t)sblock.fs_fsize / 1024); } if (cflg) { if ((ip->di_mode&IFMT) != IFDIR && (ip->di_mode&IFMT) != IFREG) return; if (size >= TSIZE) { overflow += (offset_t)size; size = TSIZE-1; } sizes[size]++; return; } dp = lookup(ip->di_uid); if (dp == NULL) return; dp->blocks += size; #define DAY (60 * 60 * 24) /* seconds per day */ if (now - ip->di_atime > 30 * DAY) dp->blocks30 += size; if (now - ip->di_atime > 60 * DAY) dp->blocks60 += size; if (now - ip->di_atime > 90 * DAY) dp->blocks90 += size; dp->nfiles++; while (nflg) { if (fino == 0) if (scanf("%d", &fino) <= 0) return; if (fino > ino) return; if (fino < ino) { while ((n = getchar()) != '\n' && n != EOF) ; fino = 0; continue; } if (dp->u_name) (void) printf("%.7s ", dp->u_name); else (void) printf("%ld ", (long)ip->di_uid); while ((n = getchar()) == ' ' || n == '\t') ; (void) putchar(n); while (n != EOF && n != '\n') { n = getchar(); (void) putchar(n); } fino = 0; break; } } static void bread(int fd, diskaddr_t bno, char *buf, int cnt) { int ret; if (llseek(fd, (offset_t)(bno * DEV_BSIZE), SEEK_SET) < 0) { perror("llseek"); exit(32); } if ((ret = read(fd, buf, cnt)) != cnt) { (void) fprintf(stderr, "quot: read returns %d (cnt = %d)\n", ret, cnt); (void) fprintf(stderr, "quot: read error at block %lld\n", bno); perror("read"); exit(32); } } static int qcmp(const void *arg1, const void *arg2) { struct du **p1 = (struct du **)arg1; struct du **p2 = (struct du **)arg2; char *s1, *s2; if ((*p1)->blocks > (*p2)->blocks) return (-1); if ((*p1)->blocks < (*p2)->blocks) return (1); s1 = (*p1)->u_name; if (s1 == NULL) return (0); s2 = (*p2)->u_name; if (s2 == NULL) return (0); return (strcmp(s1, s2)); } static void report() { int i; struct du **dp; int cnt; if (nflg) return; if (cflg) { long t = 0; for (i = 0; i < TSIZE - 1; i++) if (sizes[i]) { t += i*sizes[i]; (void) printf("%d %d %ld\n", i, sizes[i], t); } if (sizes[TSIZE -1 ]) (void) printf("%d %d %lld\n", TSIZE - 1, sizes[TSIZE - 1], overflow + (offset_t)t); return; } sortprep(); qsort(du, ndu, sizeof (du[0]), qcmp); for (cnt = 0, dp = &du[0]; dp && cnt != ndu; dp++, cnt++) { if ((*dp)->blocks == 0) return; (void) printf("%5ld\t", (*dp)->blocks); if (fflg) (void) printf("%5ld\t", (*dp)->nfiles); if ((*dp)->u_name) (void) printf("%-8s", (*dp)->u_name); else (void) printf("#%-8ld", (long)(*dp)->uid); if (vflg) (void) printf("\t%5ld\t%5ld\t%5ld", (*dp)->blocks30, (*dp)->blocks60, (*dp)->blocks90); (void) printf("\n"); } } static int getdev(char **devpp) { struct stat64 statb; FILE *fstab; struct mnttab mntp; char *cp; /* Pointer to raw device name */ extern char *getfullrawname(); if (stat64(*devpp, &statb) < 0) { perror(*devpp); exit(32); } if ((statb.st_mode & S_IFMT) == S_IFCHR) return (0); if ((statb.st_mode & S_IFMT) == S_IFBLK) { /* If we can't get the raw name, keep the block name */ if ((cp = getfullrawname(*devpp)) != NULL) *devpp = strdup(cp); return (0); } fstab = fopen(MNTTAB, "r"); if (fstab == NULL) { (void) fprintf(stderr, "quot: no %s file\n", MNTTAB); exit(32); } while (getmntent(fstab, &mntp) == 0) { if (strcmp(mntp.mnt_mountp, *devpp) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0) { (void) fprintf(stderr, "quot: %s not ufs filesystem\n", *devpp); exit(32); } /* If we can't get the raw name, use the block name */ if ((cp = getfullrawname(mntp.mnt_special)) == NULL) cp = mntp.mnt_special; *devpp = strdup(cp); (void) fclose(fstab); return (0); } } (void) fclose(fstab); (void) fprintf(stderr, "quot: %s doesn't appear to be a filesystem.\n", *devpp); usage(); exit(32); /* NOTREACHED */ } static struct du * lookup(uid_t uid) { struct passwd *pwp; struct du *up; for (up = duhashtbl[HASH(uid)]; up != NULL; up = up->next) { if (up->uid == uid) return (up); } pwp = getpwuid(uid); up = adduid(uid); if (up && pwp) { up->u_name = strdup(pwp->pw_name); } return (up); } static struct du * adduid(uid_t uid) { struct du *up, **uhp; up = (struct du *)calloc(1, sizeof (struct du)); if (up == NULL) { (void) fprintf(stderr, "out of memory for du structures\n"); exit(32); } uhp = &duhashtbl[HASH(uid)]; up->next = *uhp; *uhp = up; up->uid = uid; up->u_name = NULL; ndu++; return (up); } static void sortprep() { struct du **dp, *ep; struct du **hp; int i, cnt = 0; dp = NULL; dp = (struct du **)calloc(ndu, sizeof (struct du **)); if (dp == NULL) { (void) fprintf(stderr, "out of memory for du structures\n"); exit(32); } for (hp = duhashtbl, i = 0; i != UHASH; i++) { if (hp[i] == NULL) continue; for (ep = hp[i]; ep; ep = ep->next) { dp[cnt++] = ep; } } du = dp; } static void cleanup() { int i; struct du *ep, *next; /* * Release memory from hash table and du */ if (du) { free(du); du = NULL; } for (i = 0; i != UHASH; i++) { if (duhashtbl[i] == NULL) continue; ep = duhashtbl[i]; while (ep) { next = ep->next; if (ep->u_name) { free(ep->u_name); } free(ep); ep = next; } duhashtbl[i] = NULL; } } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= quota ATTMK= $(LIBPROG) OTHERINSTALL= $(ROOTUSRSBIN)/$(LIBPROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(LIBPROG) include ../../Makefile.fstype OBJS= quota.o rquota_xdr.o replica.o SRCS= $(OBJS:%.o=%.c) XFILE= $(ROOT)/usr/include/rpcsvc/rquota.x CPPFLAGS += -I $(ROOT)/usr/include/sys -I$(SRC)/head/rpcsvc -D_LARGEFILE64_SOURCE # Hammerhead: -ldl for dlopen/dlsym of quota modules LDLIBS += -lnsl -ldl CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-unused-variable CERRWARN += -Wno-parentheses # not linted SMATCH=off FILEMODE= 04555 $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(OBJS) @# Hammerhead: do not delete pre-generated rquota_xdr.c $(ROOTUSRSBIN)/$(LIBPROG): -$(RM) $@; $(SYMLINK) $(LINKVALUE) $@ # Hammerhead: pre-generated (rpcgen + GNU cpp truncation bug) # rquota_xdr.c: $(XFILE) # $(RPCGEN) -c $(XFILE) -o $@ replica.o: ../../nfs/lib/replica.c $(COMPILE.c) ../../nfs/lib/replica.c /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Disk quota reporting program. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../nfs/lib/replica.h" #include #include int vflag; int nolocalquota; extern int optind; extern char *optarg; #define QFNAME "quotas" #if DEV_BSIZE < 1024 #define kb(x) ((x) / (1024 / DEV_BSIZE)) #else #define kb(x) ((x) * (DEV_BSIZE / 1024)) #endif #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif static void zexit(int); static int getzfsquota(char *, char *, struct dqblk *); static int getnfsquota(char *, char *, uid_t, struct dqblk *); static void showuid(uid_t); static void showquotas(uid_t, char *); static void warn(struct mnttab *, struct dqblk *); static void heading(uid_t, char *); static void prquota(struct mnttab *, struct dqblk *); static void fmttime(char *, long); static libzfs_handle_t *(*_libzfs_init)(void); static void (*_libzfs_fini)(libzfs_handle_t *); static zfs_handle_t *(*_zfs_open)(libzfs_handle_t *, const char *, int); static void (*_zfs_close)(zfs_handle_t *); static int (*_zfs_prop_get_userquota_int)(zfs_handle_t *, const char *, uint64_t *); static libzfs_handle_t *g_zfs = NULL; /* * Dynamically check for libzfs, in case the user hasn't installed the SUNWzfs * packages. 'quota' utility supports zfs as an option. */ static void load_libzfs(void) { void *hdl; if (g_zfs != NULL) return; if ((hdl = dlopen("libzfs.so", RTLD_LAZY)) != NULL) { _libzfs_init = (libzfs_handle_t *(*)(void))dlsym(hdl, "libzfs_init"); _libzfs_fini = (void (*)())dlsym(hdl, "libzfs_fini"); _zfs_open = (zfs_handle_t *(*)())dlsym(hdl, "zfs_open"); _zfs_close = (void (*)())dlsym(hdl, "zfs_close"); _zfs_prop_get_userquota_int = (int (*)()) dlsym(hdl, "zfs_prop_get_userquota_int"); if (_libzfs_init && _libzfs_fini && _zfs_open && _zfs_close && _zfs_prop_get_userquota_int) g_zfs = _libzfs_init(); } } int main(int argc, char *argv[]) { int opt; int i; int status = 0; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); /* * PRIV_FILE_DAC_READ is needed to read the QFNAME file * Clear all other privleges from the limit set, and add * the required privilege to the bracketed set. */ if (__init_suid_priv(PU_CLEARLIMITSET, PRIV_FILE_DAC_READ, NULL) == -1) { (void) fprintf(stderr, gettext("Insufficient privileges, " "quota must be set-uid root or have " "file_dac_read privileges\n")); exit(1); } load_libzfs(); while ((opt = getopt(argc, argv, "vV")) != EOF) { switch (opt) { case 'v': vflag++; break; case 'V': /* Print command line */ { char *opt_text; int opt_count; (void) fprintf(stdout, "quota -F UFS "); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) fprintf(stdout, " %s ", opt_text); } (void) fprintf(stdout, "\n"); } break; case '?': fprintf(stderr, "usage: quota [-v] [username]\n"); zexit(32); } } if (quotactl(Q_ALLSYNC, NULL, (uid_t)0, NULL) < 0 && errno == EINVAL) { if (vflag) fprintf(stderr, "There are no quotas on this system\n"); nolocalquota++; } if (argc == optind) { showuid(getuid()); zexit(0); } for (i = optind; i < argc; i++) { if (alldigits(argv[i])) { showuid((uid_t)atoi(argv[i])); } else status |= showname(argv[i]); } __priv_relinquish(); return (status); } static void showuid(uid_t uid) { struct passwd *pwd = getpwuid(uid); if (uid == 0) { if (vflag) printf("no disk quota for uid 0\n"); return; } if (pwd == NULL) showquotas(uid, "(no account)"); else showquotas(uid, pwd->pw_name); } int showname(char *name) { struct passwd *pwd = getpwnam(name); if (pwd == NULL) { fprintf(stderr, "quota: %s: unknown user\n", name); return (32); } if (pwd->pw_uid == 0) { if (vflag) printf("no disk quota for %s (uid 0)\n", name); return (0); } showquotas(pwd->pw_uid, name); return (0); } static void showquotas(uid_t uid, char *name) { struct mnttab mnt; FILE *mtab; struct dqblk dqblk; uid_t myuid; struct failed_srv { char *serv_name; struct failed_srv *next; }; struct failed_srv *failed_srv_list = NULL; int rc; char my_zonename[ZONENAME_MAX]; zoneid_t my_zoneid = getzoneid(); myuid = getuid(); if (uid != myuid && myuid != 0) { printf("quota: %s (uid %d): permission denied\n", name, uid); zexit(32); } memset(my_zonename, '\0', ZONENAME_MAX); getzonenamebyid(my_zoneid, my_zonename, ZONENAME_MAX); if (vflag) heading(uid, name); mtab = fopen(MNTTAB, "r"); while (getmntent(mtab, &mnt) == 0) { if (strcmp(mnt.mnt_fstype, MNTTYPE_ZFS) == 0) { bzero(&dqblk, sizeof (dqblk)); if (getzfsquota(name, mnt.mnt_special, &dqblk)) continue; } else if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) == 0) { if (nolocalquota || (quotactl(Q_GETQUOTA, mnt.mnt_mountp, uid, &dqblk) != 0 && !(vflag && getdiskquota(&mnt, uid, &dqblk)))) continue; } else if (strcmp(mnt.mnt_fstype, MNTTYPE_NFS) == 0) { struct replica *rl; int count; char *mntopt = NULL; /* * Skip checking quotas for file systems mounted * in other zones. Zone names will be passed in * following format from hasmntopt(): * "zone=," */ if ((mntopt = hasmntopt(&mnt, MNTOPT_ZONE)) && (my_zonename[0] != '\0')) { mntopt += strcspn(mntopt, "=") + 1; if (strncmp(mntopt, my_zonename, strcspn(mntopt, ",")) != 0) continue; } if (hasopt(MNTOPT_NOQUOTA, mnt.mnt_mntopts)) continue; /* * Skip quota processing if mounted with public * option. We are not likely to be able to pierce * a fire wall to contact the quota server. */ if (hasopt(MNTOPT_PUBLIC, mnt.mnt_mntopts)) continue; rl = parse_replica(mnt.mnt_special, &count); if (rl == NULL) { if (count < 0) fprintf(stderr, "cannot find hostname " "and/or pathname for %s\n", mnt.mnt_mountp); else fprintf(stderr, "no memory to parse " "mnttab entry for %s\n", mnt.mnt_mountp); continue; } /* * We skip quota reporting on mounts with replicas * for the following reasons: * * (1) Very little point in reporting quotas on * a set of read-only replicas ... how will the * user correct the problem? * * (2) Which replica would we report the quota * for? If we pick the current replica, what * happens when a fail over event occurs? The * next time quota is run, the quota will look * all different, or there won't even be one. * This has the potential to break scripts. * * If we prnt quouta for all replicas, how do * we present the output without breaking scripts? */ if (count > 1) { free_replica(rl, count); continue; } /* * Skip file systems mounted using public fh. * We are not likely to be able to pierce * a fire wall to contact the quota server. */ if (strcmp(rl[0].host, "nfs") == 0 && strncmp(rl[0].path, "//", 2) == 0) { free_replica(rl, count); continue; } /* * Skip getting quotas from failing servers */ if (failed_srv_list != NULL) { struct failed_srv *tmp_list; int found_failed = 0; size_t len = strlen(rl[0].host); tmp_list = failed_srv_list; do { if (strncasecmp(rl[0].host, tmp_list->serv_name, len) == 0) { found_failed = 1; break; } } while ((tmp_list = tmp_list->next) != NULL); if (found_failed) { free_replica(rl, count); continue; } } rc = getnfsquota(rl[0].host, rl[0].path, uid, &dqblk); if (rc != RPC_SUCCESS) { size_t len; struct failed_srv *tmp_srv; /* * Failed to get quota from this server. Add * this server to failed_srv_list and skip * getting quotas for other mounted filesystems * from this server. */ if (rc == RPC_TIMEDOUT || rc == RPC_CANTSEND) { len = strlen(rl[0].host); tmp_srv = (struct failed_srv *)malloc( sizeof (struct failed_srv)); tmp_srv->serv_name = (char *)malloc( len * sizeof (char) + 1); strncpy(tmp_srv->serv_name, rl[0].host, len); tmp_srv->serv_name[len] = '\0'; tmp_srv->next = failed_srv_list; failed_srv_list = tmp_srv; } free_replica(rl, count); continue; } free_replica(rl, count); } else { continue; } if (dqblk.dqb_bsoftlimit == 0 && dqblk.dqb_bhardlimit == 0 && dqblk.dqb_fsoftlimit == 0 && dqblk.dqb_fhardlimit == 0) continue; if (vflag) prquota(&mnt, &dqblk); else warn(&mnt, &dqblk); } /* * Free list of failed servers */ while (failed_srv_list != NULL) { struct failed_srv *tmp_srv = failed_srv_list; failed_srv_list = failed_srv_list->next; free(tmp_srv->serv_name); free(tmp_srv); } fclose(mtab); } static void warn(struct mnttab *mntp, struct dqblk *dqp) { struct timeval tv; time(&(tv.tv_sec)); tv.tv_usec = 0; if (dqp->dqb_bhardlimit && dqp->dqb_curblocks >= dqp->dqb_bhardlimit) { printf("Block limit reached on %s\n", mntp->mnt_mountp); } else if (dqp->dqb_bsoftlimit && dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) { if (dqp->dqb_btimelimit == 0) { printf("Over disk quota on %s, remove %luK\n", mntp->mnt_mountp, kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1)); } else if (dqp->dqb_btimelimit > tv.tv_sec) { char btimeleft[80]; fmttime(btimeleft, dqp->dqb_btimelimit - tv.tv_sec); printf("Over disk quota on %s, remove %luK within %s\n", mntp->mnt_mountp, kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1), btimeleft); } else { printf( "Over disk quota on %s, time limit has expired, remove %luK\n", mntp->mnt_mountp, kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1)); } } if (dqp->dqb_fhardlimit && dqp->dqb_curfiles >= dqp->dqb_fhardlimit) { printf("File count limit reached on %s\n", mntp->mnt_mountp); } else if (dqp->dqb_fsoftlimit && dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) { if (dqp->dqb_ftimelimit == 0) { printf("Over file quota on %s, remove %lu file%s\n", mntp->mnt_mountp, dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1, ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ? "s" : "")); } else if (dqp->dqb_ftimelimit > tv.tv_sec) { char ftimeleft[80]; fmttime(ftimeleft, dqp->dqb_ftimelimit - tv.tv_sec); printf( "Over file quota on %s, remove %lu file%s within %s\n", mntp->mnt_mountp, dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1, ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ? "s" : ""), ftimeleft); } else { printf( "Over file quota on %s, time limit has expired, remove %lu file%s\n", mntp->mnt_mountp, dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1, ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ? "s" : "")); } } } static void heading(uid_t uid, char *name) { printf("Disk quotas for %s (uid %ld):\n", name, (long)uid); printf("%-12s %7s%7s%7s%12s%7s%7s%7s%12s\n", "Filesystem", "usage", "quota", "limit", "timeleft", "files", "quota", "limit", "timeleft"); } static void prquota(struct mnttab *mntp, struct dqblk *dqp) { struct timeval tv; char ftimeleft[80], btimeleft[80]; char *cp; time(&(tv.tv_sec)); tv.tv_usec = 0; if (dqp->dqb_bsoftlimit && dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) { if (dqp->dqb_btimelimit == 0) { strlcpy(btimeleft, "NOT STARTED", sizeof (btimeleft)); } else if (dqp->dqb_btimelimit > tv.tv_sec) { fmttime(btimeleft, dqp->dqb_btimelimit - tv.tv_sec); } else { strlcpy(btimeleft, "EXPIRED", sizeof (btimeleft)); } } else { btimeleft[0] = '\0'; } if (dqp->dqb_fsoftlimit && dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) { if (dqp->dqb_ftimelimit == 0) { strlcpy(ftimeleft, "NOT STARTED", sizeof (ftimeleft)); } else if (dqp->dqb_ftimelimit > tv.tv_sec) { fmttime(ftimeleft, dqp->dqb_ftimelimit - tv.tv_sec); } else { strlcpy(ftimeleft, "EXPIRED", sizeof (ftimeleft)); } } else { ftimeleft[0] = '\0'; } if (strlen(mntp->mnt_mountp) > 12) { printf("%s\n", mntp->mnt_mountp); cp = ""; } else { cp = mntp->mnt_mountp; } if (dqp->dqb_curfiles == 0 && dqp->dqb_fsoftlimit == 0 && dqp->dqb_fhardlimit == 0) { printf("%-12.12s %7d %6d %6d %11s %6s %6s %6s %11s\n", cp, kb(dqp->dqb_curblocks), kb(dqp->dqb_bsoftlimit), kb(dqp->dqb_bhardlimit), "-", "-", "-", "-", "-"); } else { printf("%-12.12s %7d %6d %6d %11s %6d %6d %6d %11s\n", cp, kb(dqp->dqb_curblocks), kb(dqp->dqb_bsoftlimit), kb(dqp->dqb_bhardlimit), btimeleft, dqp->dqb_curfiles, dqp->dqb_fsoftlimit, dqp->dqb_fhardlimit, ftimeleft); } } static void fmttime(char *buf, long time) { int i; static struct { int c_secs; /* conversion units in secs */ char *c_str; /* unit string */ } cunits [] = { {60*60*24*28, "months"}, {60*60*24*7, "weeks"}, {60*60*24, "days"}, {60*60, "hours"}, {60, "mins"}, {1, "secs"} }; if (time <= 0) { strlcpy(buf, "EXPIRED", sizeof (*buf)); return; } for (i = 0; i < sizeof (cunits)/sizeof (cunits[0]); i++) { if (time >= cunits[i].c_secs) break; } snprintf(buf, sizeof (*buf), "%.1f %s", (double)time/cunits[i].c_secs, cunits[i].c_str); } int alldigits(char *s) { int c; c = *s++; do { if (!isdigit(c)) return (0); } while (c = *s++); return (1); } int getdiskquota(struct mnttab *mntp, uid_t uid, struct dqblk *dqp) { int fd; dev_t fsdev; struct stat64 statb; char qfilename[MAXPATHLEN]; if (stat64(mntp->mnt_special, &statb) < 0 || (statb.st_mode & S_IFMT) != S_IFBLK) return (0); fsdev = statb.st_rdev; (void) snprintf(qfilename, sizeof (qfilename), "%s/%s", mntp->mnt_mountp, QFNAME); if (stat64(qfilename, &statb) < 0 || statb.st_dev != fsdev) return (0); (void) __priv_bracket(PRIV_ON); fd = open64(qfilename, O_RDONLY); (void) __priv_bracket(PRIV_OFF); if (fd < 0) return (0); (void) llseek(fd, (offset_t)dqoff(uid), L_SET); switch (read(fd, dqp, sizeof (struct dqblk))) { case 0: /* EOF */ /* * Convert implicit 0 quota (EOF) * into an explicit one (zero'ed dqblk). */ memset((caddr_t)dqp, 0, sizeof (struct dqblk)); break; case sizeof (struct dqblk): /* OK */ break; default: /* ERROR */ close(fd); return (0); } close(fd); return (1); } int quotactl(int cmd, char *mountp, uid_t uid, caddr_t addr) { int fd; int status; struct quotctl quota; char qfile[MAXPATHLEN]; FILE *fstab; struct mnttab mnt; if ((mountp == NULL) && (cmd == Q_ALLSYNC)) { /* * Find the mount point of any mounted file system. This is * because the ioctl that implements the quotactl call has * to go to a real file, and not to the block device. */ if ((fstab = fopen(MNTTAB, "r")) == NULL) { fprintf(stderr, "%s: ", MNTTAB); perror("open"); zexit(32); } fd = -1; while ((status = getmntent(fstab, &mnt)) == 0) { if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) != 0 || hasopt(MNTOPT_RO, mnt.mnt_mntopts)) continue; if ((strlcpy(qfile, mnt.mnt_mountp, sizeof (qfile)) >= sizeof (qfile)) || (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof (qfile))) { continue; } (void) __priv_bracket(PRIV_ON); fd = open64(qfile, O_RDONLY); (void) __priv_bracket(PRIV_OFF); if (fd != -1) break; } fclose(fstab); if (fd == -1) { errno = ENOENT; return (-1); } } else { if (mountp == NULL || mountp[0] == '\0') { errno = ENOENT; return (-1); } if ((strlcpy(qfile, mountp, sizeof (qfile)) >= sizeof (qfile)) || (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof (qfile))) { errno = ENOENT; return (-1); } (void) __priv_bracket(PRIV_ON); fd = open64(qfile, O_RDONLY); (void) __priv_bracket(PRIV_OFF); if (fd < 0) return (-1); } /* else */ quota.op = cmd; quota.uid = uid; quota.addr = addr; status = ioctl(fd, Q_QUOTACTL, "a); if (fd != 0) close(fd); return (status); } /* * Return 1 if opt appears in optlist */ int hasopt(char *opt, char *optlist) { char *value; char *opts[2]; opts[0] = opt; opts[1] = NULL; if (optlist == NULL) return (0); while (*optlist != '\0') { if (getsubopt(&optlist, opts, &value) == 0) return (1); } return (0); } /* * If there are no quotas available, then getnfsquota() returns * RPC_SYSTEMERROR to caller. */ static int getnfsquota(char *hostp, char *path, uid_t uid, struct dqblk *dqp) { struct getquota_args gq_args; struct getquota_rslt gq_rslt; struct rquota *rquota; extern char *strchr(); int rpc_err; gq_args.gqa_pathp = path; gq_args.gqa_uid = uid; rpc_err = callaurpc(hostp, RQUOTAPROG, RQUOTAVERS, (vflag? RQUOTAPROC_GETQUOTA: RQUOTAPROC_GETACTIVEQUOTA), xdr_getquota_args, &gq_args, xdr_getquota_rslt, &gq_rslt); if (rpc_err != RPC_SUCCESS) { return (rpc_err); } switch (gq_rslt.status) { case Q_OK: { struct timeval tv; u_longlong_t limit; rquota = &gq_rslt.getquota_rslt_u.gqr_rquota; if (!vflag && rquota->rq_active == FALSE) { return (RPC_SYSTEMERROR); } gettimeofday(&tv, NULL); limit = (u_longlong_t)(rquota->rq_bhardlimit) * rquota->rq_bsize / DEV_BSIZE; dqp->dqb_bhardlimit = limit; limit = (u_longlong_t)(rquota->rq_bsoftlimit) * rquota->rq_bsize / DEV_BSIZE; dqp->dqb_bsoftlimit = limit; limit = (u_longlong_t)(rquota->rq_curblocks) * rquota->rq_bsize / DEV_BSIZE; dqp->dqb_curblocks = limit; dqp->dqb_fhardlimit = rquota->rq_fhardlimit; dqp->dqb_fsoftlimit = rquota->rq_fsoftlimit; dqp->dqb_curfiles = rquota->rq_curfiles; dqp->dqb_btimelimit = tv.tv_sec + rquota->rq_btimeleft; dqp->dqb_ftimelimit = tv.tv_sec + rquota->rq_ftimeleft; return (RPC_SUCCESS); } case Q_NOQUOTA: return (RPC_SYSTEMERROR); case Q_EPERM: fprintf(stderr, "quota permission error, host: %s\n", hostp); return (RPC_AUTHERROR); default: fprintf(stderr, "bad rpc result, host: %s\n", hostp); return (RPC_CANTDECODEARGS); } /* NOTREACHED */ } int callaurpc(char *host, int prognum, int versnum, int procnum, xdrproc_t inproc, char *in, xdrproc_t outproc, char *out) { static enum clnt_stat clnt_stat; struct timeval tottimeout = {20, 0}; static CLIENT *cl = NULL; static int oldprognum, oldversnum; static char oldhost[MAXHOSTNAMELEN+1]; /* * Cache the client handle in case there are lots * of entries in the /etc/mnttab for the same * server. If the server returns an error, don't * make further calls. */ if (cl == NULL || oldprognum != prognum || oldversnum != versnum || strcmp(oldhost, host) != 0) { if (cl) { clnt_destroy(cl); cl = NULL; } cl = clnt_create_timed(host, prognum, versnum, "udp", &tottimeout); if (cl == NULL) return ((int)RPC_TIMEDOUT); if ((cl->cl_auth = authunix_create_default()) == NULL) { clnt_destroy(cl); return (RPC_CANTSEND); } oldprognum = prognum; oldversnum = versnum; (void) strlcpy(oldhost, host, sizeof (oldhost)); clnt_stat = RPC_SUCCESS; } if (clnt_stat != RPC_SUCCESS) return ((int)clnt_stat); /* don't bother retrying */ clnt_stat = clnt_call(cl, procnum, inproc, in, outproc, out, tottimeout); return ((int)clnt_stat); } static int getzfsquota(char *user, char *dataset, struct dqblk *zq) { zfs_handle_t *zhp = NULL; char propname[ZFS_MAXPROPLEN]; uint64_t userquota, userused; if (g_zfs == NULL) return (1); if ((zhp = _zfs_open(g_zfs, dataset, ZFS_TYPE_DATASET)) == NULL) return (1); (void) snprintf(propname, sizeof (propname), "userquota@%s", user); if (_zfs_prop_get_userquota_int(zhp, propname, &userquota) != 0) { _zfs_close(zhp); return (1); } (void) snprintf(propname, sizeof (propname), "userused@%s", user); if (_zfs_prop_get_userquota_int(zhp, propname, &userused) != 0) { _zfs_close(zhp); return (1); } zq->dqb_bhardlimit = userquota / DEV_BSIZE; zq->dqb_bsoftlimit = userquota / DEV_BSIZE; zq->dqb_curblocks = userused / DEV_BSIZE; _zfs_close(zhp); return (0); } static void zexit(int n) { if (g_zfs != NULL) _libzfs_fini(g_zfs); exit(n); } /* * Please do not edit this file. * It was generated using rpcgen. */ #include "rquota.h" #ifndef _KERNEL #include #endif /* !_KERNEL */ bool_t xdr_getquota_args(xdrs, objp) XDR *xdrs; getquota_args *objp; { rpc_inline_t *buf __unused; if (!xdr_string(xdrs, &objp->gqa_pathp, RQ_PATHLEN)) return (FALSE); if (!xdr_int32_t(xdrs, &objp->gqa_uid)) return (FALSE); return (TRUE); } bool_t xdr_rquota(xdrs, objp) XDR *xdrs; rquota *objp; { rpc_inline_t *buf __unused; if (!xdr_int32_t(xdrs, &objp->rq_bsize)) return (FALSE); if (!xdr_bool(xdrs, &objp->rq_active)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_bhardlimit)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_bsoftlimit)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_curblocks)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_fhardlimit)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_fsoftlimit)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_curfiles)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_btimeleft)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->rq_ftimeleft)) return (FALSE); return (TRUE); } bool_t xdr_gqr_status(xdrs, objp) XDR *xdrs; gqr_status *objp; { rpc_inline_t *buf __unused; if (!xdr_enum(xdrs, (enum_t *)objp)) return (FALSE); return (TRUE); } bool_t xdr_getquota_rslt(xdrs, objp) XDR *xdrs; getquota_rslt *objp; { rpc_inline_t *buf __unused; if (!xdr_gqr_status(xdrs, &objp->status)) return (FALSE); switch (objp->status) { case Q_OK: if (!xdr_rquota(xdrs, &objp->getquota_rslt_u.gqr_rquota)) return (FALSE); break; case Q_NOQUOTA: break; case Q_EPERM: break; default: return (FALSE); } return (TRUE); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2003 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= quotacheck ATTMK= $(LIBPROG) OTHERINSTALL= $(ROOTUSRSBIN)/$(LIBPROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(LIBPROG) include ../../Makefile.fstype CPPFLAGS += -D_LARGEFILE64_SOURCE # Hammerhead: -ldl for dlopen/dlsym in preenlib LDLIBS += -ladm -ldl OBJS= quotacheck.o FSOBJS= preenlib.o FSSRCS= ../../preenlib.c SRCS= $(OBJS:.o=.c) CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-type-limits # Hammerhead: preenlib.c uses int-to-pointer and pointer-to-int casts CERRWARN += -Wno-pointer-to-int-cast CERRWARN += -Wno-int-to-pointer-cast # not linted SMATCH=off $(FSOBJS): $(FSSRCS) $(COMPILE.c) -o $@ $(FSSRCS) $(LIBPROG): $(OBJS) $(FSOBJS) $(LINK.c) -o $@ $(OBJS) $(FSOBJS) $(LDLIBS) $(POST_PROCESS) clean: -$(RM) $(OBJS) $(FSOBJS) $(ROOTUSRSBIN)/$(LIBPROG): -$(RM) $@; $(SYMLINK) $(LINKVALUE) $@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Fix up / report on disc quotas & usage */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include union { struct fs sblk; char dummy[MAXBSIZE]; } un; #define sblock un.sblk #define ITABSZ 256 struct dinode itab[ITABSZ]; struct dinode *dp; struct fileusage { struct fileusage *fu_next; ulong_t fu_curfiles; uint64_t fu_curblocks; uid_t fu_uid; }; #define FUHASH 997 struct fileusage *fuhead[FUHASH]; struct fileusage *lookup(uid_t); struct fileusage *adduid(uid_t); int fi; ino_t ino; struct dinode *ginode(); char *mntopt(), *hasvfsopt(), *hasmntopt(); extern int optind; extern char *optarg; extern int fsync(int); static void acct(); static void bread(); static void usage(); static int chkquota(); static int quotactl(); static int preen(); static int waiter(); static int oneof(); int vflag; /* verbose */ int aflag; /* all file systems */ int pflag; /* fsck like parallel check */ int fflag; /* force flag */ #define QFNAME "quotas" #define CHUNK 50 char **listbuf; struct dqblk zerodqbuf; struct fileusage zerofileusage; int main(int argc, char **argv) { struct mnttab mntp; struct vfstab vfsbuf; char **listp; int listcnt; int listmax = 0; char quotafile[MAXPATHLEN]; FILE *mtab, *vfstab; int errs = 0; int opt; if ((listbuf = (char **)malloc(sizeof (char *) * CHUNK)) == NULL) { fprintf(stderr, "Can't alloc lisbuf array."); exit(31+1); } listmax = CHUNK; while ((opt = getopt(argc, argv, "vapVf")) != EOF) { switch (opt) { case 'v': vflag++; break; case 'a': aflag++; break; case 'p': pflag++; break; case 'V': /* Print command line */ { char *opt_text; int opt_count; (void) fprintf(stdout, "quotacheck -F UFS "); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) fprintf(stdout, " %s ", opt_text); } (void) fprintf(stdout, "\n"); } break; case 'f': fflag++; break; case '?': usage(); } } if (argc <= optind && !aflag) { usage(); } if (quotactl(Q_ALLSYNC, NULL, (uid_t)0, NULL) < 0 && errno == EINVAL && vflag) printf("Warning: Quotas are not compiled into this kernel\n"); sync(); if (aflag) { /* * Go through vfstab and make a list of appropriate * filesystems. */ listp = listbuf; listcnt = 0; if ((vfstab = fopen(VFSTAB, "r")) == NULL) { fprintf(stderr, "Can't open "); perror(VFSTAB); exit(31+8); } while (getvfsent(vfstab, &vfsbuf) == 0) { if (strcmp(vfsbuf.vfs_fstype, MNTTYPE_UFS) != 0 || (vfsbuf.vfs_mntopts == 0) || hasvfsopt(&vfsbuf, MNTOPT_RO) || (!hasvfsopt(&vfsbuf, MNTOPT_RQ) && !hasvfsopt(&vfsbuf, MNTOPT_QUOTA))) continue; *listp = malloc(strlen(vfsbuf.vfs_special) + 1); strcpy(*listp, vfsbuf.vfs_special); listp++; listcnt++; /* grow listbuf if needed */ if (listcnt >= listmax) { listmax += CHUNK; listbuf = (char **)realloc(listbuf, sizeof (char *) * listmax); if (listbuf == NULL) { fprintf(stderr, "Can't grow listbuf.\n"); exit(31+1); } listp = &listbuf[listcnt]; } } fclose(vfstab); *listp = (char *)0; listp = listbuf; } else { listp = &argv[optind]; listcnt = argc - optind; } if (pflag) { errs = preen(listcnt, listp); } else { if ((mtab = fopen(MNTTAB, "r")) == NULL) { fprintf(stderr, "Can't open "); perror(MNTTAB); exit(31+8); } while (getmntent(mtab, &mntp) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) == 0 && !hasmntopt(&mntp, MNTOPT_RO) && (oneof(mntp.mnt_special, listp, listcnt) || oneof(mntp.mnt_mountp, listp, listcnt))) { (void) snprintf(quotafile, sizeof (quotafile), "%s/%s", mntp.mnt_mountp, QFNAME); errs += chkquota(mntp.mnt_special, mntp.mnt_mountp, quotafile); } } fclose(mtab); } while (listcnt--) { if (*listp) { fprintf(stderr, "Cannot check %s\n", *listp); errs++; } listp++; } if (errs > 0) errs += 31; return (errs); } struct active { char *rdev; pid_t pid; struct active *nxt; }; int preen(int listcnt, char **listp) { int i, rc, errs; char **lp, *rdev, *bdev; extern char *getfullrawname(), *getfullblkname(); struct mnttab mntp, mpref; struct active *alist, *ap; FILE *mtab; char quotafile[MAXPATHLEN]; char name[MAXPATHLEN]; int nactive, serially; if ((mtab = fopen(MNTTAB, "r")) == NULL) { fprintf(stderr, "Can't open "); perror(MNTTAB); exit(31+8); } memset(&mpref, 0, sizeof (struct mnttab)); errs = 0; for (lp = listp, i = 0; i < listcnt; lp++, i++) { serially = 0; rdev = getfullrawname(*lp); if (rdev == NULL || *rdev == '\0') { fprintf(stderr, "can't get rawname for `%s'\n", *lp); serially = 1; } else if (preen_addev(rdev) != 0) { fprintf(stderr, "preen_addev error\n"); serially = 1; } if (rdev != NULL) free(rdev); if (serially) { rewind(mtab); mpref.mnt_special = *lp; if (getmntany(mtab, &mntp, &mpref) == 0 && strcmp(mntp.mnt_fstype, MNTTYPE_UFS) == 0 && !hasmntopt(&mntp, MNTOPT_RO)) { errs += (31+chkquota(mntp.mnt_special, mntp.mnt_mountp, quotafile)); *lp = (char *)0; } } } nactive = 0; alist = NULL; while ((rc = preen_getdev(name)) > 0) { switch (rc) { case 1: bdev = getfullblkname(name); if (bdev == NULL || *bdev == '\0') { fprintf(stderr, "can't get blkname for `%s'\n", name); if (bdev) free(bdev); continue; } rewind(mtab); mpref.mnt_special = bdev; if (getmntany(mtab, &mntp, &mpref) != 0) { fprintf(stderr, "`%s' not mounted?\n", name); preen_releasedev(name); free(bdev); continue; } else if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 || hasmntopt(&mntp, MNTOPT_RO) || (!oneof(mntp.mnt_special, listp, listcnt) && !oneof(mntp.mnt_mountp, listp, listcnt))) { preen_releasedev(name); free(bdev); continue; } free(bdev); ap = (struct active *)malloc(sizeof (struct active)); if (ap == NULL) { fprintf(stderr, "out of memory\n"); exit(31+8); } ap->rdev = (char *)strdup(name); if (ap->rdev == NULL) { fprintf(stderr, "out of memory\n"); exit(31+8); } ap->nxt = alist; alist = ap; switch (ap->pid = fork()) { case -1: perror("fork"); exit(31+8); break; case 0: (void) snprintf(quotafile, sizeof (quotafile), "%s/%s", mntp.mnt_mountp, QFNAME); exit(31+chkquota(mntp.mnt_special, mntp.mnt_mountp, quotafile)); break; default: nactive++; break; } break; case 2: errs += waiter(&alist); nactive--; break; } } fclose(mtab); while (nactive > 0) { errs += waiter(&alist); nactive--; } return (errs); } int waiter(struct active **alp) { pid_t curpid; int status; struct active *ap, *lap; curpid = wait(&status); if (curpid == -1) { if (errno == ECHILD) return (0); perror("wait"); exit(31+8); } for (lap = NULL, ap = *alp; ap != NULL; lap = ap, ap = ap->nxt) { if (ap->pid == curpid) break; } if (ap == NULL) { fprintf(stderr, "wait returns unknown pid\n"); exit(31+8); } else if (lap) { lap->nxt = ap->nxt; } else { *alp = ap->nxt; } preen_releasedev(ap->rdev); free(ap->rdev); free(ap); return (WHIBYTE(status)); } int chkquota(char *fsdev, char *fsfile, char *qffile) { struct fileusage *fup; dev_t quotadev; FILE *qf; uid_t uid; struct passwd *pw; int cg, i; char *rawdisk; struct stat64 statb; struct dqblk dqbuf; extern char *getfullrawname(); if ((rawdisk = getfullrawname(fsdev)) == NULL) { fprintf(stderr, "malloc failed\n"); return (1); } if (*rawdisk == '\0') { fprintf(stderr, "Could not find character device for %s\n", fsdev); return (1); } if (vflag) printf("*** Checking quotas for %s (%s)\n", rawdisk, fsfile); fi = open64(rawdisk, 0); if (fi < 0) { perror(rawdisk); return (1); } qf = fopen64(qffile, "r+"); if (qf == NULL) { perror(qffile); close(fi); return (1); } if (fstat64(fileno(qf), &statb) < 0) { perror(qffile); fclose(qf); close(fi); return (1); } quotadev = statb.st_dev; if (stat64(fsdev, &statb) < 0) { perror(fsdev); fclose(qf); close(fi); return (1); } if (quotadev != statb.st_rdev) { fprintf(stderr, "%s dev (0x%x) mismatch %s dev (0x%x)\n", qffile, quotadev, fsdev, statb.st_rdev); fclose(qf); close(fi); return (1); } bread((diskaddr_t)SBLOCK, (char *)&sblock, SBSIZE); /* * Flush filesystem since we are going to read * disk raw and we want to make sure everything is * synced to disk before we read it. */ if (ioctl(fileno(qf), _FIOFFS, NULL) == -1) { perror(qffile); (void) fprintf(stderr, "%s: cannot flush file system.\n", qffile); (void) fclose(qf); return (1); } /* * no need to quotacheck a rw, mounted, and logging file system */ if ((fflag == 0) && pflag && (FSOKAY == (sblock.fs_state + sblock.fs_time)) && (sblock.fs_clean == FSLOG)) { fclose(qf); close(fi); return (0); } ino = 0; for (cg = 0; cg < sblock.fs_ncg; cg++) { dp = NULL; for (i = 0; i < sblock.fs_ipg; i++) acct(ginode()); } for (uid = 0; uid <= MAXUID && uid >= 0; uid++) { (void) fread(&dqbuf, sizeof (struct dqblk), 1, qf); if (feof(qf)) break; fup = lookup(uid); if (fup == 0) fup = &zerofileusage; if (dqbuf.dqb_bhardlimit == 0 && dqbuf.dqb_bsoftlimit == 0 && dqbuf.dqb_fhardlimit == 0 && dqbuf.dqb_fsoftlimit == 0) { fup->fu_curfiles = 0; fup->fu_curblocks = 0; } if (dqbuf.dqb_curfiles == fup->fu_curfiles && dqbuf.dqb_curblocks == fup->fu_curblocks) { fup->fu_curfiles = 0; fup->fu_curblocks = 0; continue; } /* * The maximum number of blocks that can be stored in the * dqb_curblocks field in the quota record is 2^32 - 1, * since it must fit into an unsigned 32-bit quantity. * If this user has more blocks than that, print a message * to that effect and reduce the count of allocated blocks * to the maximum value, which is UINT_MAX. */ if (fup->fu_curblocks > UINT_MAX) { if (pflag || aflag) printf("%s: ", rawdisk); printf("512-byte blocks allocated to user "); if ((pw = getpwuid(uid)) && pw->pw_name[0]) printf("%-10s ", pw->pw_name); else printf("#%-9d ", uid); printf(" = %lld\n", fup->fu_curblocks); printf( "This exceeds the maximum number of blocks recordable in a quota record.\n"); printf( "The value will be set to the maximum, which is %lu.\n", UINT_MAX); fup->fu_curblocks = UINT_MAX; } if (vflag) { if (pflag || aflag) printf("%s: ", rawdisk); if ((pw = getpwuid(uid)) && pw->pw_name[0]) printf("%-10s fixed:", pw->pw_name); else printf("#%-9d fixed:", uid); if (dqbuf.dqb_curfiles != fup->fu_curfiles) printf(" files %lu -> %lu", dqbuf.dqb_curfiles, fup->fu_curfiles); if (dqbuf.dqb_curblocks != fup->fu_curblocks) printf(" blocks %lu -> %llu", dqbuf.dqb_curblocks, fup->fu_curblocks); printf("\n"); } dqbuf.dqb_curfiles = fup->fu_curfiles; dqbuf.dqb_curblocks = fup->fu_curblocks; /* * If quotas are not enabled for the current filesystem * then just update the quotas file directly. */ if ((quotactl(Q_SETQUOTA, fsfile, uid, &dqbuf) < 0) && (errno == ESRCH)) { /* back up, overwrite the entry we just read */ (void) fseeko64(qf, (offset_t)dqoff(uid), 0); (void) fwrite(&dqbuf, sizeof (struct dqblk), 1, qf); (void) fflush(qf); } fup->fu_curfiles = 0; fup->fu_curblocks = 0; } (void) fflush(qf); (void) fsync(fileno(qf)); fclose(qf); close(fi); return (0); } void acct(struct dinode *ip) { struct fileusage *fup; if (ip == NULL) return; ip->di_mode = ip->di_smode; if (ip->di_suid != UID_LONG) { ip->di_uid = ip->di_suid; } if (ip->di_mode == 0) return; fup = adduid(ip->di_uid); fup->fu_curfiles++; if ((ip->di_mode & IFMT) == IFCHR || (ip->di_mode & IFMT) == IFBLK) return; fup->fu_curblocks += ip->di_blocks; } int oneof(char *target, char **olistp, int on) { char **listp = olistp; int n = on; while (n--) { if (*listp && strcmp(target, *listp) == 0) { *listp = (char *)0; return (1); } listp++; } return (0); } struct dinode * ginode() { ulong_t iblk; if (dp == NULL || ++dp >= &itab[ITABSZ]) { iblk = itod(&sblock, ino); bread(fsbtodb(&sblock, iblk), (char *)itab, sizeof (itab)); dp = &itab[(int)ino % (int)INOPB(&sblock)]; } if (ino++ < UFSROOTINO) return (NULL); return (dp); } void bread(diskaddr_t bno, char *buf, int cnt) { extern offset_t llseek(); offset_t pos; pos = (offset_t)bno * DEV_BSIZE; if (llseek(fi, pos, 0) != pos) { perror("lseek"); exit(31+1); } if (read(fi, buf, cnt) != cnt) { perror("read"); exit(31+1); } } struct fileusage * lookup(uid_t uid) { struct fileusage *fup; for (fup = fuhead[uid % FUHASH]; fup != 0; fup = fup->fu_next) if (fup->fu_uid == uid) return (fup); return ((struct fileusage *)0); } struct fileusage * adduid(uid_t uid) { struct fileusage *fup, **fhp; fup = lookup(uid); if (fup != 0) return (fup); fup = (struct fileusage *)calloc(1, sizeof (struct fileusage)); if (fup == 0) { fprintf(stderr, "out of memory for fileusage structures\n"); exit(31+1); } fhp = &fuhead[uid % FUHASH]; fup->fu_next = *fhp; *fhp = fup; fup->fu_uid = uid; return (fup); } void usage() { fprintf(stderr, "ufs usage:\n"); fprintf(stderr, "\tquotacheck [-v] [-f] [-p] -a\n"); fprintf(stderr, "\tquotacheck [-v] [-f] [-p] filesys ...\n"); exit(31+1); } int quotactl(int cmd, char *mountp, uid_t uid, caddr_t addr) { int fd; int status; struct quotctl quota; char qfile[MAXPATHLEN]; FILE *fstab; struct mnttab mntp; if ((mountp == NULL) && (cmd == Q_ALLSYNC)) { /* * Find the mount point of any ufs file system. This is * because the ioctl that implements the quotactl call has * to go to a real file, and not to the block device. */ if ((fstab = fopen(MNTTAB, "r")) == NULL) { fprintf(stderr, "%s: ", MNTTAB); perror("open"); exit(31+1); } fd = -1; while ((status = getmntent(fstab, &mntp)) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 || hasmntopt(&mntp, MNTOPT_RO)) continue; if ((strlcpy(qfile, mntp.mnt_mountp, sizeof (qfile)) >= sizeof (qfile)) || (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof (qfile))) { continue; } if ((fd = open64(qfile, O_RDWR)) == -1) break; } fclose(fstab); if (fd == -1) { errno = ENOENT; return (-1); } } else { if (mountp == NULL || mountp[0] == '\0') { errno = ENOENT; return (-1); } if ((strlcpy(qfile, mountp, sizeof (qfile)) >= sizeof (qfile)) || (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof (qfile))) { errno = ENOENT; return (-1); } if ((fd = open64(qfile, O_RDWR)) < 0) { fprintf(stderr, "quotactl: "); perror("open"); exit(31+1); } } /* else */ quota.op = cmd; quota.uid = uid; quota.addr = addr; status = ioctl(fd, Q_QUOTACTL, "a); if (fd != 0) close(fd); return (status); } char * hasvfsopt(struct vfstab *vfs, char *opt) { char *f, *opts; static char *tmpopts; if (tmpopts == 0) { tmpopts = (char *)calloc(256, sizeof (char)); if (tmpopts == 0) return (0); } strcpy(tmpopts, vfs->vfs_mntopts); opts = tmpopts; f = mntopt(&opts); for (; *f; f = mntopt(&opts)) { if (strncmp(opt, f, strlen(opt)) == 0) return (f - tmpopts + vfs->vfs_mntopts); } return (NULL); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # Copyright 2015 Nexenta Systems, Inc. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= quotaon QUOTAOFF= quotaoff ATTMK= $(LIBPROG) OTHERINSTALL= $(ROOTLIBFSTYPE)/$(QUOTAOFF) \ $(ROOTUSRSBIN)/$(LIBPROG) $(ROOTUSRSBIN)/$(QUOTAOFF) LINKVALUEON= ../lib/fs/$(FSTYPE)/$(LIBPROG) LINKVALUEOFF= ../lib/fs/$(FSTYPE)/$(QUOTAOFF) include ../../Makefile.fstype $(ROOTINIT_SCRIPT) : FILEMODE = 0744 CPPFLAGS += -D_LARGEFILE64_SOURCE CERRWARN += -Wno-unused-variable CERRWARN += -Wno-unused-function # not linted SMATCH=off $(ROOTLIBFSTYPE)/$(QUOTAOFF): $(ROOTLIBFSTYPE)/$(LIBPROG) -$(RM) $@; $(LN) $(ROOTLIBFSTYPE)/$(LIBPROG) $@ $(ROOTUSRSBIN)/$(LIBPROG): -$(RM) $@; $(SYMLINK) $(LINKVALUEON) $@ $(ROOTUSRSBIN)/$(QUOTAOFF): -$(RM) $@; $(SYMLINK) $(LINKVALUEOFF) $@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Turn quota on/off for a filesystem. */ #include #include #include #include #define bcopy(f, t, n) memcpy(t, f, n) #define bzero(s, n) memset(s, 0, n) #define bcmp(s, d, n) memcmp(s, d, n) #define index(s, r) strchr(s, r) #define rindex(s, r) strrchr(s, r) #include #include #include #include #include #include #include #include #include #include #include int vflag; /* verbose */ int aflag; /* all file systems */ #define QFNAME "quotas" #define CHUNK 50 char **listbuf; char *mntopt(), *hasvfsopt(), *hasmntopt(); char *whoami; static void fixmntent(); static void mnterror(); static void usage(char *); static int oneof(); static int quotaonoff(); static int quotactl(int, char *, uid_t, caddr_t); extern int optind; extern char *optarg; int main(int argc, char **argv) { struct mnttab mntp; struct vfstab vfsbuf; char **listp; int listcnt; FILE *mtab, *vfstab, *tmp; int offmode = 0; int listmax = 0; int errs = 0; char *tmpname = "/etc/mnttab.temp"; int status; int opt; mode_t oldumask; struct stat statbuf; whoami = (char *)rindex(*argv, '/') + 1; if (whoami == (char *)1) whoami = *argv; if (strcmp(whoami, "quotaoff") == 0) offmode++; else if (strcmp(whoami, "quotaon") != 0) { fprintf(stderr, "Name must be quotaon or quotaoff not %s\n", whoami); exit(31+1); } if ((listbuf = (char **)malloc(sizeof (char *) * CHUNK)) == NULL) { fprintf(stderr, "Can't alloc lisbuf array."); exit(31+1); } listmax = CHUNK; while ((opt = getopt(argc, argv, "avV")) != EOF) { switch (opt) { case 'v': vflag++; break; case 'a': aflag++; break; case 'V': /* Print command line */ { char *opt_text; int opt_cnt; (void) fprintf(stdout, "%s -F UFS ", whoami); for (opt_cnt = 1; opt_cnt < argc; opt_cnt++) { opt_text = argv[opt_cnt]; if (opt_text) (void) fprintf(stdout, " %s ", opt_text); } (void) fprintf(stdout, "\n"); } break; case '?': usage(whoami); } } if (argc <= optind && !aflag) { usage(whoami); } /* * If aflag go through vfstab and make a list of appropriate * filesystems. */ if (aflag) { listp = listbuf; listcnt = 0; vfstab = fopen(VFSTAB, "r"); if (vfstab == NULL) { fprintf(stderr, "Can't open %s\n", VFSTAB); perror(VFSTAB); exit(31+1); } while ((status = getvfsent(vfstab, &vfsbuf)) == 0) { if (strcmp(vfsbuf.vfs_fstype, MNTTYPE_UFS) != 0 || (vfsbuf.vfs_mntopts == 0) || hasvfsopt(&vfsbuf, MNTOPT_RO) || (!hasvfsopt(&vfsbuf, MNTOPT_RQ) && !hasvfsopt(&vfsbuf, MNTOPT_QUOTA))) continue; *listp = malloc(strlen(vfsbuf.vfs_special) + 1); strcpy(*listp, vfsbuf.vfs_special); listp++; listcnt++; /* grow listbuf if needed */ if (listcnt >= listmax) { listmax += CHUNK; listbuf = (char **)realloc(listbuf, sizeof (char *) * listmax); if (listbuf == NULL) { fprintf(stderr, "Can't grow listbuf.\n"); exit(31+1); } listp = &listbuf[listcnt]; } } fclose(vfstab); *listp = (char *)0; listp = listbuf; } else { listp = &argv[optind]; listcnt = argc - optind; } /* * Open real mnttab */ mtab = fopen(MNTTAB, "r"); if (mtab == NULL) { fprintf(stderr, "Can't open %s\n", MNTTAB); perror(whoami); exit(31+1); } /* check every entry for validity before we change mnttab */ while ((status = getmntent(mtab, &mntp)) == 0) ; if (status > 0) mnterror(status); rewind(mtab); signal(SIGHUP, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGINT, SIG_IGN); /* * Loop through mnttab, if a file system gets turned on or off * do the quota call. */ while ((status = getmntent(mtab, &mntp)) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) == 0 && !hasmntopt(&mntp, MNTOPT_RO) && (oneof(mntp.mnt_special, listp, listcnt) || oneof(mntp.mnt_mountp, listp, listcnt))) { errs += quotaonoff(&mntp, offmode); } } fclose(mtab); while (listcnt--) { if (*listp) { fprintf(stderr, "Cannot do %s\n", *listp); errs++; } listp++; } if (errs > 0) errs += 31; return (errs); } int quotaonoff(struct mnttab *mntp, int offmode) { if (offmode) { if (quotactl(Q_QUOTAOFF, mntp->mnt_mountp, (uid_t)0, NULL) < 0) goto bad; if (vflag) printf("%s: quotas turned off\n", mntp->mnt_mountp); } else { if (quotactl(Q_QUOTAON, mntp->mnt_mountp, (uid_t)0, NULL) < 0) goto bad; if (vflag) printf("%s: quotas turned on\n", mntp->mnt_mountp); } return (0); bad: fprintf(stderr, "quotactl: "); perror(mntp->mnt_special); return (1); } int oneof(char *target, char **olistp, int on) { int n = on; char **listp = olistp; while (n--) { if (*listp && strcmp(target, *listp) == 0) { *listp = (char *)0; return (1); } listp++; } return (0); } void usage(char *whoami) { fprintf(stderr, "ufs usage:\n"); fprintf(stderr, "\t%s [-v] -a\n", whoami); fprintf(stderr, "\t%s [-v] filesys ...\n", whoami); exit(31+1); } int quotactl(int cmd, char *mountpt, uid_t uid, caddr_t addr) { int fd; int status; struct quotctl quota; char qfile[MAXPATHLEN]; if (mountpt == NULL || mountpt[0] == '\0') { errno = ENOENT; return (-1); } if ((strlcpy(qfile, mountpt, sizeof (qfile)) >= sizeof (qfile)) || (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof (qfile))) { errno = ENOENT; return (-1); } if ((fd = open64(qfile, O_RDWR)) < 0) { fprintf(stderr, "quotactl: %s ", qfile); perror("open"); exit(31+1); } quota.op = cmd; quota.uid = uid; quota.addr = addr; status = ioctl(fd, Q_QUOTACTL, "a); close(fd); return (status); } char * hasvfsopt(struct vfstab *vfs, char *opt) { char *f, *opts; static char *tmpopts; if (tmpopts == 0) { tmpopts = (char *)calloc(256, sizeof (char)); if (tmpopts == 0) return (0); } strcpy(tmpopts, vfs->vfs_mntopts); opts = tmpopts; f = mntopt(&opts); for (; *f; f = mntopt(&opts)) { if (strncmp(opt, f, strlen(opt)) == 0) return (f - tmpopts + vfs->vfs_mntopts); } return (NULL); } void mnterror(int flag) { switch (flag) { case MNT_TOOLONG: fprintf(stderr, "%s: line in mnttab exceeds %d characters\n", whoami, MNT_LINE_MAX-2); break; case MNT_TOOFEW: fprintf(stderr, "%s: line in mnttab has too few entries\n", whoami); break; case MNT_TOOMANY: fprintf(stderr, "%s: line in mnttab has too many entries\n", whoami); break; } exit(1); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1989,1996 by Sun Microsystems, Inc. # All rights reserved. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= repquota ATTMK= $(LIBPROG) OTHERINSTALL= $(ROOTUSRSBIN)/$(LIBPROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(LIBPROG) include ../../Makefile.fstype CPPFLAGS += -D_LARGEFILE64_SOURCE CERRWARN += -Wno-type-limits CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off $(ROOTUSRSBIN)/$(LIBPROG): -$(RM) $@; $(SYMLINK) $(LINKVALUE) $@ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Quota report */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOGINNAMESIZE 8 struct username { struct username *u_next; uid_t u_uid; char u_name[LOGINNAMESIZE + 1]; }; #define UHASH 997 static struct username *uhead[UHASH]; static struct username *lookup(uid_t); static struct username *adduid(uid_t); static int repquota(char *, char *, char *); static void prquota(uid_t, struct dqblk *); static void header(void); static void usage(void); static void fmttime(char *, long); static char *hasvfsopt(struct vfstab *, char *); static int quotactl(int, char *, uid_t, caddr_t); static int oneof(char *, char **, int); extern char *mntopt(); extern char *hasmntopt(); static int vflag; /* verbose */ static int aflag; /* all file systems */ static char **listbuf; #define QFNAME "quotas" #define CHUNK 50 #if DEV_BSIZE < 1024 #define dbtok(x) ((x) / (1024 / DEV_BSIZE)) #else #define dbtok(x) ((x) * (DEV_BSIZE / 1024)) #endif int main(int argc, char **argv) { struct mnttab mntp; struct vfstab vfsbuf; char **listp; int listcnt; int listmax = 0; char quotafile[MAXPATHLEN]; FILE *mtab, *vfstab; int errs = 0; int opt; if ((listbuf = malloc(sizeof (char *) * CHUNK)) == NULL) { (void) fprintf(stderr, "Can't alloc lisbuf array."); exit(31+1); } listmax = CHUNK; while ((opt = getopt(argc, argv, "avV")) != EOF) switch (opt) { case 'v': vflag++; break; case 'a': aflag++; break; case 'V': { /* Print command line */ char *optt; int optc; (void) printf("repquota -F ufs "); for (optc = 1; optc < argc; optc++) { optt = argv[optc]; if (optt) (void) printf(" %s ", optt); } (void) putchar('\n'); } break; case '?': default: usage(); } if (argc <= optind && !aflag) usage(); /* * Sync quota information to disk (as userdata). On logging * file systems, this operation does nothing because quota * information is treated as metadata. Logging file systems * are dealt with below in repquota(). */ if (quotactl(Q_ALLSYNC, NULL, 0, NULL) < 0 && errno == EINVAL && vflag) (void) printf("Warning: " "Quotas are not available in this kernel\n"); /* * If aflag go through vfstab and make a list of appropriate * filesystems. */ if (aflag) { listp = listbuf; listcnt = 0; if ((vfstab = fopen(VFSTAB, "r")) == NULL) { (void) fprintf(stderr, "Can't open "); perror(VFSTAB); exit(31+8); } while (getvfsent(vfstab, &vfsbuf) == 0) { if (strcmp(vfsbuf.vfs_fstype, MNTTYPE_UFS) != 0 || (vfsbuf.vfs_mntopts == 0) || hasvfsopt(&vfsbuf, MNTOPT_RO) || (!hasvfsopt(&vfsbuf, MNTOPT_RQ) && !hasvfsopt(&vfsbuf, MNTOPT_QUOTA))) continue; *listp = malloc(strlen(vfsbuf.vfs_special) + 1); (void) strcpy(*listp, vfsbuf.vfs_special); listp++; listcnt++; /* grow listbuf if needed */ if (listcnt >= listmax) { listmax += CHUNK; listbuf = realloc(listbuf, sizeof (char *) * listmax); if (listbuf == NULL) { (void) fprintf(stderr, "Can't grow listbuf.\n"); exit(31+1); } listp = &listbuf[listcnt]; } } (void) fclose(vfstab); *listp = (char *)0; listp = listbuf; } else { listp = &argv[optind]; listcnt = argc - optind; } if ((mtab = fopen(MNTTAB, "r")) == NULL) { (void) fprintf(stderr, "Can't open "); perror(MNTTAB); exit(31+8); } while (getmntent(mtab, &mntp) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) == 0 && !hasmntopt(&mntp, MNTOPT_RO) && (oneof(mntp.mnt_special, listp, listcnt) || oneof(mntp.mnt_mountp, listp, listcnt))) { (void) snprintf(quotafile, sizeof (quotafile), "%s/%s", mntp.mnt_mountp, QFNAME); errs += repquota(mntp.mnt_special, mntp.mnt_mountp, quotafile); } } (void) fclose(mtab); while (listcnt--) { if (*listp) (void) fprintf(stderr, "Cannot report on %s\n", *listp); listp++; } if (errs > 0) exit(31+1); return (0); } static int repquota(char *fsdev, char *fsfile, char *qffile) { FILE *qf; uid_t uid; struct dqblk dqbuf; struct stat64 statb; if (vflag || aflag) (void) printf("%s (%s):\n", fsdev, fsfile); qf = fopen64(qffile, "r"); if (qf == NULL) { perror(qffile); return (1); } if (fstat64(fileno(qf), &statb) < 0) { perror(qffile); (void) fclose(qf); return (1); } /* * Flush the file system. On logging file systems, this makes * sure that the quota information (as metadata) gets rolled * forward. */ if (ioctl(fileno(qf), _FIOFFS, NULL) == -1) { perror(qffile); (void) fprintf(stderr, "%s: cannot flush file system.\n", qffile); (void) fclose(qf); return (1); } header(); for (uid = 0; uid <= MAXUID && uid >= 0; uid++) { (void) fread(&dqbuf, sizeof (struct dqblk), 1, qf); if (feof(qf)) break; if (!vflag && dqbuf.dqb_curfiles == 0 && dqbuf.dqb_curblocks == 0) continue; prquota(uid, &dqbuf); } (void) fclose(qf); return (0); } static void header(void) { (void) printf(" Block limits" " File limits\n"); (void) printf("User used soft hard timeleft" " used soft hard timeleft\n"); } static void prquota(uid_t uid, struct dqblk *dqp) { struct timeval tv; struct username *up; char ftimeleft[80], btimeleft[80]; if (dqp->dqb_bsoftlimit == 0 && dqp->dqb_bhardlimit == 0 && dqp->dqb_fsoftlimit == 0 && dqp->dqb_fhardlimit == 0) return; (void) time(&(tv.tv_sec)); tv.tv_usec = 0; up = lookup(uid); if (up) (void) printf("%-10s", up->u_name); else (void) printf("#%-9ld", uid); if (dqp->dqb_bsoftlimit && dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) { if (dqp->dqb_btimelimit == 0) (void) strcpy(btimeleft, "NOT STARTED"); else if (dqp->dqb_btimelimit > tv.tv_sec) fmttime(btimeleft, (long)(dqp->dqb_btimelimit - tv.tv_sec)); else (void) strcpy(btimeleft, "EXPIRED"); } else btimeleft[0] = '\0'; if (dqp->dqb_fsoftlimit && dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) { if (dqp->dqb_ftimelimit == 0) (void) strcpy(ftimeleft, "NOT STARTED"); else if (dqp->dqb_ftimelimit > tv.tv_sec) fmttime(ftimeleft, (long)(dqp->dqb_ftimelimit - tv.tv_sec)); else (void) strcpy(ftimeleft, "EXPIRED"); } else ftimeleft[0] = '\0'; (void) printf("%c%c %6lu %6lu %6lu %11s %7lu %6lu %6lu %11s\n", (dqp->dqb_bsoftlimit && dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) ? '+' : '-', (dqp->dqb_fsoftlimit && dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) ? '+' : '-', dbtok(dqp->dqb_curblocks), dbtok(dqp->dqb_bsoftlimit), dbtok(dqp->dqb_bhardlimit), btimeleft, dqp->dqb_curfiles, dqp->dqb_fsoftlimit, dqp->dqb_fhardlimit, ftimeleft); } static void fmttime(char *buf, long time) { int i; static struct { int c_secs; /* conversion units in secs */ char *c_str; /* unit string */ } cunits [] = { {60*60*24*28, "months"}, {60*60*24*7, "weeks"}, {60*60*24, "days"}, {60*60, "hours"}, {60, "mins"}, {1, "secs"} }; if (time <= 0) { (void) strcpy(buf, "EXPIRED"); return; } for (i = 0; i < sizeof (cunits) / sizeof (cunits[0]); i++) { if (time >= cunits[i].c_secs) break; } (void) sprintf(buf, "%.1f %s", (double)time / cunits[i].c_secs, cunits[i].c_str); } static int oneof(char *target, char **olistp, int on) { char **listp = olistp; int n = on; while (n--) { if (*listp && strcmp(target, *listp) == 0) { *listp = (char *)0; return (1); } listp++; } return (0); } static struct username * lookup(uid_t uid) { struct passwd *pwp; struct username *up; for (up = uhead[uid % UHASH]; up != 0; up = up->u_next) if (up->u_uid == uid) return (up); if ((pwp = getpwuid((uid_t)uid)) == NULL) return ((struct username *)0); up = adduid(pwp->pw_uid); (void) strncpy(up->u_name, pwp->pw_name, sizeof (up->u_name)); return (up); } /* * adduid() should *ONLY* be called from lookup in order * to avoid duplicate entries. */ static struct username * adduid(uid_t uid) { struct username *up, **uhp; up = calloc(1, sizeof (struct username)); if (up == 0) { (void) fprintf(stderr, "out of memory for username structures\n"); exit(31+1); } uhp = &uhead[uid % UHASH]; up->u_next = *uhp; *uhp = up; up->u_uid = uid; return (up); } static void usage(void) { (void) fprintf(stderr, "ufs usage:\n"); (void) fprintf(stderr, "\trepquota [-v] -a \n"); (void) fprintf(stderr, "\trepquota [-v] filesys ...\n"); exit(31+1); } static int quotactl(int cmd, char *special, uid_t uid, caddr_t addr) { int fd; int status; struct quotctl quota; char qfile[MAXPATHLEN]; FILE *fstab; struct mnttab mntp; if ((special == NULL) && (cmd == Q_ALLSYNC)) { /* * Find the mount point of the special device. This is * because the ioctl that implements the quotactl call has * to go to a real file, and not to the block device. */ if ((fstab = fopen(MNTTAB, "r")) == NULL) { (void) fprintf(stderr, "%s: ", MNTTAB); perror("open"); exit(31+1); } fd = -1; while ((status = getmntent(fstab, &mntp)) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 || hasmntopt(&mntp, MNTOPT_RO)) continue; if ((strlcpy(qfile, mntp.mnt_mountp, sizeof (qfile)) >= sizeof (qfile)) || (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof (qfile))) { continue; } /* If we find *ANY* valid "quotas" file, use it */ if ((fd = open64(qfile, O_RDONLY)) >= 0) break; } (void) fclose(fstab); if (fd == -1) { errno = ENOENT; (void) printf("quotactl: no quotas file " "on any mounted file system\n"); return (-1); } } quota.op = cmd; quota.uid = uid; quota.addr = addr; status = ioctl(fd, Q_QUOTACTL, "a); (void) close(fd); return (status); } static char * hasvfsopt(struct vfstab *vfs, char *opt) { char *f, *opts; static char *tmpopts; if (tmpopts == 0) { tmpopts = calloc(256, sizeof (char)); if (tmpopts == 0) return (0); } (void) strcpy(tmpopts, vfs->vfs_mntopts); opts = tmpopts; f = mntopt(&opts); for (; *f; f = mntopt(&opts)) { if (strncmp(opt, f, strlen(opt)) == 0) return (f - tmpopts + vfs->vfs_mntopts); } return (NULL); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1996,1997 by Sun Microsystems, Inc. # All rights reserved. # include ../../Makefile.fstype INCLUDES= roll_log.h SRCS= roll_log.c OBJS= $(SRCS:%.c=$(MACH)/%.o) POFILE= roll_log.po CERRWARN += -Wno-switch .KEEP_STATE: all install: $(MACH) .WAIT $(OBJS) $(OBJS): $(INCLUDES) $(MACH): $(MKDIR) -p $@ $(MACH)/%.o: %.c $(COMPILE.c) -o $@ $< clean: $(RM) $(OBJS) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This file contains functions that allow applications to roll the log. * It is intended for use by applications that open a raw device with the * understanding that it contains a Unix File System. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "roll_log.h" /* * The following is the template string passed to mktemp(3C). This * string is used as the name of a temporary mount point which is * used to roll the log. */ #define RLG_TEMPLATE ".rlg.XXXXXX" #define SYSERR (-1) #define RLM_RW 0 #define RLM_RO 1 /* * Structure definitions: */ typedef struct log_info { char *li_blkname; /* Path of block device. */ char *li_mntpoint; /* Path of mounted device. */ char *li_tmpmp_parent; /* Temporary parent directory of mount point */ char *li_tmpmp; /* Temporary mount point. */ } log_info_t; /* * Static function declarations: */ static rl_result_t is_mounted(log_info_t *lip, char *dev); static void cleanup(log_info_t *lip); static rl_result_t make_mp(log_info_t *lip); static rl_result_t rlflush(log_info_t *lip); static rl_result_t rlmount(log_info_t *lip, int mntopt); static rl_result_t rlumount(log_info_t *lip); /* * NAME * rl_roll_log * * SYNOPSIS * rl_roll_log(block_dev) * * DESCRIPTION * Roll the log for the block device "block_dev". */ rl_result_t rl_roll_log(char *bdev) { log_info_t li; rl_result_t rv = RL_SUCCESS; (void) memset((void *)&li, 0, (size_t)sizeof (li)); if (is_mounted(&li, bdev) == RL_TRUE) { rv = rlflush(&li); } else { /* * Device appears not to be mounted. * We need to mount the device read only. * This automatically causes the log to be rolled, then we can * unmount the device again. To do the mount, we need to * create a temporary directory, and then remove it when we * are done. */ rv = make_mp(&li); switch (rv) { case RL_CORRUPT: /* corrupt mnttab - the file sys really was mounted */ rv = rlflush(&li); break; case RL_SUCCESS: rv = rlmount(&li, RLM_RO); if (rv == RL_SUCCESS) { rv = rlflush(&li); if (umount(li.li_blkname) == SYSERR) { (void) fprintf(stderr, "WARNING: rl_roll_log(): Can't unmount %s\n", li.li_blkname); } } break; } } cleanup(&li); return (rv); } /* * Static function definitions: */ /* * NAME * cleanup * * SYNOPSIS * cleanup(log_infop) * * DESCRIPTION * Remove the temporary mount directroy and free the dynamically * allocated memory that is pointed to by log_infop. */ static void cleanup(log_info_t *lip) { if (lip->li_blkname != (char *)NULL) { free(lip->li_blkname); lip->li_blkname = (char *)NULL; } if (lip->li_mntpoint != (char *)NULL) { free(lip->li_mntpoint); lip->li_mntpoint = (char *)NULL; } if (lip->li_tmpmp != (char *)NULL) { (void) rmdir(lip->li_tmpmp); free(lip->li_tmpmp); lip->li_tmpmp = (char *)NULL; } if (lip->li_tmpmp_parent != (char *)NULL) { (void) rmdir(lip->li_tmpmp_parent); free(lip->li_tmpmp_parent); lip->li_tmpmp_parent = (char *)NULL; } } /* * NAME * is_mounted * * SYNOPSIS * is_mounted(log_infop, dev) * * DESCRIPTION * Determine if device dev is mounted, and return RL_TRUE if it is. * As a side effect, li_blkname is set to point the the full path * names of the block device. Memory for this path is dynamically * allocated and must be freed by the caller. */ extern char *getfullblkname(char *); static rl_result_t is_mounted(log_info_t *lip, char *dev) { struct mnttab mntbuf; FILE *mnttable; rl_result_t rv = RL_FALSE; /* Make sure that we have the full path name. */ lip->li_blkname = getfullblkname(dev); if (lip->li_blkname == NULL) lip->li_blkname = strdup(dev); /* Search mnttab to see if it device is mounted. */ if ((mnttable = fopen(MNTTAB, "r")) == NULL) return (rv); while (getmntent(mnttable, &mntbuf) == 0) { if (strcmp(mntbuf.mnt_fstype, MNTTYPE_UFS) == 0) { /* Entry is UFS */ if ((strcmp(mntbuf.mnt_mountp, dev) == 0) || (strcmp(mntbuf.mnt_special, lip->li_blkname) == 0) || (strcmp(mntbuf.mnt_special, dev) == 0)) { lip->li_mntpoint = strdup(mntbuf.mnt_mountp); rv = RL_TRUE; break; } } } (void) fclose(mnttable); return (rv); } /* * NAME * make_mp * * SYNOPSIS * make_mp(loginfop) * * DESCRIPTION * Create a temporary directory to be used as a mount point. li_tmpmp * will be set to the path of the mount point. li_tmpmp_parent is the * parent directory of the mount point. The parent directory is * created with restrictive permissions. Memory pointed to by * li_tmpmp and li_tmpmp_parent should be freed by the caller. */ static rl_result_t make_mp(log_info_t *lip) { size_t i; rl_result_t rv = RL_FAIL; /* * Note tmp_dir_list[] should all be directories in the * original root file system. */ static const char *tmp_dir_list[] = { "/tmp/", "/var/tmp/", "/", }; char dirname[] = RLG_TEMPLATE; char tmp_dir[MAXPATHLEN + 1]; char mountpt_dir[MAXPATHLEN + 1]; static size_t list_len = sizeof (tmp_dir_list) / sizeof (const char *); int merr = 0; /* * Sequence of events: * - Create a random name using mktemp(3C) (e.g., ".rlg.123456") * - Cycle through tmp_dir_list to find a path where we can create * a temporary parent directory (e.g., /tmp/.rlg.123456) with * restrictive permissions. This prevents any non-root processes, * such as a 'find', from wandering in where it doesn't belong. * - Create the mount-point (/tmp/.rlg.123456/.rlg.123456). */ (void) mktemp(dirname); for (i = 0; i < list_len; i++) { /* Make the directory containing the mount-point */ (void) snprintf(tmp_dir, sizeof (tmp_dir), "%s%s", tmp_dir_list[i], dirname); if (mkdir(tmp_dir, 0) == SYSERR) { merr = errno; continue; } /* Now, make the mount-point */ (void) snprintf(mountpt_dir, sizeof (mountpt_dir), "%s/%s", tmp_dir, dirname); if (mkdir(mountpt_dir, 0) == SYSERR) { merr = errno; continue; } lip->li_tmpmp = strdup(mountpt_dir); lip->li_tmpmp_parent = strdup(tmp_dir); /* Make sure that the strdup()s both succeeded */ if ((lip->li_tmpmp != NULL) && (lip->li_tmpmp_parent != NULL)) { rv = RL_SUCCESS; } break; } /* Get some help if we cannot make the directory. */ if (rv != RL_SUCCESS) { /* * If we get a read only filesystem failure (EROFS) * to make a directory in "/", then we must be fsck'ing * at boot with a incorrect mnttab. * * Just return RL_CORRUPT to indicate it really * was mounted. */ if (merr == EROFS) { lip->li_mntpoint = strdup("/"); return (RL_CORRUPT); } (void) fprintf(stderr, gettext( "Unable to create temporary " "directory in any of the directories listed " "below:\n")); for (i = 0; i < list_len; i++) { (void) fprintf(stderr, "\t%s\n", tmp_dir_list[i]); } (void) fprintf(stderr, gettext( "Please correct this problem " "and rerun the program.\n")); } return (rv); } /* * NAME * rlflush * * SYNOPSIS * rlflush(log_infop) * * DESCRIPTION * Open the mount point of the file system (li_mntpoint) to get a * file descriptor. Issue the _FIOFFS ioctl to flush the file system * and then close the device. */ static rl_result_t rlflush(log_info_t *lip) { int fd; /* File descriptor. */ rl_result_t rv = RL_SUCCESS; if ((fd = open((lip->li_mntpoint ? lip->li_mntpoint : lip->li_tmpmp), O_RDONLY)) == SYSERR) { return (RL_SYSERR); } if (ioctl(fd, _FIOFFS, NULL) == SYSERR) { rv = RL_SYSERR; } (void) close(fd); return (rv); } /* * NAME * rlmount * * SYNOPSIS * rlmount(log_infop, mntopt) * * DESCRIPTION * Mount the device specified by li_blkname on li_tmpmp. mntopt specifies * whether it's mounted RLM_RO or RLM_RW. */ static rl_result_t rlmount(log_info_t *lip, int mntopt) { struct ufs_args args; rl_result_t rv = RL_SUCCESS; char opt[MAX_MNTOPT_STR]; char *optstr; int optflg; args.flags = 0; /* Initialize ufs_args */ /* * Use a minimal restrictive set of mount options. Make sure * to use "largefiles" option otherwise mount() can fail w/EFBIG. * (Although "nosub" isn't a currently supported option on UFS, * it would be a good one to have if it ever is implemented * since submounts would prevent a umount.) */ args.flags |= UFSMNT_LARGEFILES; switch (mntopt) { case RLM_RO: optstr = MNTOPT_RO; optflg = MS_RDONLY; break; case RLM_RW: optstr = MNTOPT_RW; optflg = 0; break; default: return (RL_FAIL); } (void) snprintf(opt, sizeof (opt), "%s,%s,%s", optstr, MNTOPT_NOSUID, MNTOPT_LARGEFILES); if (mount(lip->li_blkname, lip->li_tmpmp, optflg | MS_DATA | MS_OPTIONSTR, MNTTYPE_UFS, &args, sizeof (args), opt, MAX_MNTOPT_STR) == SYSERR) { rv = RL_SYSERR; } return (rv); } /* * NAME * rlumount * * SYNOPSIS * rlumount(log_infop) * * DESCRIPTION * Unmounts the device specified by li_blkname, printing an * error message on failure. */ static rl_result_t rlumount(log_info_t *lip) { rl_result_t rv = RL_SUCCESS; if (umount(lip->li_blkname) == SYSERR) { (void) fprintf(stderr, gettext( "WARNING: rlumount(): Can't unmount %s\n"), lip->li_blkname); rv = RL_SYSERR; } return (rv); } /* * NAME * rl_log_control * * SYNOPSIS * rl_log_control(block_dev, request) * * DESCRIPTION * Enable/disable logging for the block device "block_dev". * The request parameter should be set to _FIOLOGENABLE or * _FIOLOGDISABLE. */ rl_result_t rl_log_control(char *bdev, int request) { log_info_t li; rl_result_t rv = RL_SUCCESS; rl_result_t alreadymounted; int fd; fiolog_t fl; int logenabled = 0; if ((request != _FIOLOGENABLE) && (request != _FIOLOGDISABLE)) return (RL_FAIL); (void) memset((void *)&li, '\0', (size_t)sizeof (li)); if ((alreadymounted = is_mounted(&li, bdev)) != RL_TRUE) { /* * Device is not mounted. Need to mount it rw to allow * the log to be enabled/disabled. To do the mount, we need * to create a temporary directory, and then remove it when * we are done. */ if (make_mp(&li) != RL_SUCCESS) { cleanup(&li); return (RL_FAIL); } if (rlmount(&li, RLM_RW) != RL_SUCCESS) { cleanup(&li); return (RL_FAIL); } } if (alreadymounted == RL_TRUE) fd = open(li.li_mntpoint, O_RDONLY); else fd = open(li.li_tmpmp, O_RDONLY); if (fd == SYSERR) { perror("open"); rv = RL_SYSERR; goto out; } fl.nbytes_requested = 0; fl.nbytes_actual = 0; fl.error = FIOLOG_ENONE; if (ioctl(fd, request, &fl) == SYSERR) { perror("ioctl"); (void) close(fd); rv = RL_SYSERR; goto out; } if (ioctl(fd, _FIOISLOG, &logenabled) == SYSERR) { perror("ioctl"); (void) close(fd); rv = RL_SYSERR; goto out; } if (((request == _FIOLOGENABLE) && (!logenabled)) || ((request == _FIOLOGDISABLE) && logenabled)) rv = RL_FAIL; (void) close(fd); out: if (alreadymounted != RL_TRUE) (void) rlumount(&li); cleanup(&li); return (rv); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1996,1997,2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef _ROLL_LOG_H #define _ROLL_LOG_H #include #ifdef __cplusplus extern "C" { #endif /* * This file contains definitions for the module that rolls the Unix File * System log. */ /* * rl_result_t defines the type of the value that is returned by all roll * log functions. */ typedef enum rl_result { /* * Choose values so that all passing returns are >= 0, and all * failing returns are < 0. */ RL_CORRUPT = -4, /* Corrupted on disk structure. */ RL_FAIL = -3, /* Generic failure. */ RL_SYSERR = -2, /* Failing system call. */ RL_FALSE = -1, RL_SUCCESS = 0, RL_TRUE = 1 } rl_result_t; /* Functions defined in roll_log.c */ extern rl_result_t rl_roll_log(char *dev); extern rl_result_t rl_log_control(char *dev, int request); #ifdef __cplusplus } #endif #endif /* _ROLL_LOG_H */ # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 1989,1996, by Sun Microsystems, Inc. # All rights reserved. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= tunefs ATTMK= $(LIBPROG) OTHERINSTALL= $(ROOTUSRSBIN)/$(LIBPROG) LINKVALUE= ../lib/fs/$(FSTYPE)/$(LIBPROG) include ../../Makefile.fstype LDLIBS += -ladm CPPFLAGS += -D_LARGEFILE64_SOURCE CERRWARN += -Wno-parentheses CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off $(ROOTUSRSBIN)/$(LIBPROG): FRC -$(RM) $@; $(SYMLINK) $(LINKVALUE) $@ FRC: /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * tunefs: change layout parameters to an existing file system. */ #include #include #include #include #include #include #include #include #define bcopy(f, t, n) memcpy(t, f, n) #define bzero(s, n) memset(s, 0, n) #define bcmp(s, d, n) memcmp(s, d, n) #define index(s, r) strchr(s, r) #define rindex(s, r) strrchr(s, r) #include #include #include #include #include #include #include #include #include #include #include #include extern offset_t llseek(); union { struct fs sb; char pad[SBSIZE]; } sbun; #define sblock sbun.sb int fi; struct ustat ustatarea; extern int optind; extern char *optarg; static void usage(); static void getsb(struct fs *, char *); static void bwrite(diskaddr_t, char *, int); static void fatal(); static int bread(diskaddr_t, char *, int); static int isnumber(char *); extern char *getfullrawname(), *getfullblkname(); static void searchvfstab(char **specialp) { FILE *vfstab; struct vfstab vfsbuf; char *blockspecial; blockspecial = getfullblkname(*specialp); if (blockspecial == NULL) blockspecial = *specialp; if ((vfstab = fopen(VFSTAB, "r")) == NULL) { fprintf(stderr, "%s: ", VFSTAB); perror("open"); } while (getvfsent(vfstab, &vfsbuf) == 0) if (strcmp(vfsbuf.vfs_fstype, MNTTYPE_UFS) == 0) if ((strcmp(vfsbuf.vfs_mountp, *specialp) == 0) || (strcmp(vfsbuf.vfs_special, *specialp) == 0) || (strcmp(vfsbuf.vfs_special, blockspecial) == 0) || (strcmp(vfsbuf.vfs_fsckdev, *specialp) == 0)) { *specialp = strdup(vfsbuf.vfs_special); return; } fclose(vfstab); } static void searchmnttab(char **specialp, char **mountpointp) { FILE *mnttab; struct mnttab mntbuf; char *blockspecial; blockspecial = getfullblkname(*specialp); if (blockspecial == NULL) blockspecial = *specialp; if ((mnttab = fopen(MNTTAB, "r")) == NULL) return; while (getmntent(mnttab, &mntbuf) == 0) if (strcmp(mntbuf.mnt_fstype, MNTTYPE_UFS) == 0) if ((strcmp(mntbuf.mnt_mountp, *specialp) == 0) || (strcmp(mntbuf.mnt_special, blockspecial) == 0) || (strcmp(mntbuf.mnt_special, *specialp) == 0)) { *specialp = strdup(mntbuf.mnt_special); *mountpointp = strdup(mntbuf.mnt_mountp); return; } fclose(mnttab); } int main(int argc, char *argv[]) { char *special, *name, *mountpoint = NULL; struct stat64 st; int i, mountfd; int Aflag = 0; char *chg[2]; int opt; struct fiotune fiotune; if (argc < 3) usage(); special = argv[argc - 1]; /* * For performance, don't search mnttab unless necessary */ if (stat64(special, &st) >= 0) { /* * If mounted directory, search mnttab for special */ if ((st.st_mode & S_IFMT) == S_IFDIR) { if (st.st_ino == UFSROOTINO) searchmnttab(&special, &mountpoint); /* * If mounted device, search mnttab for mountpoint */ } else if ((st.st_mode & S_IFMT) == S_IFBLK || (st.st_mode & S_IFMT) == S_IFCHR) { if (ustat(st.st_rdev, &ustatarea) >= 0) searchmnttab(&special, &mountpoint); } } /* * Doesn't appear to be mounted; take ``unmounted'' path */ if (mountpoint == NULL) searchvfstab(&special); if ((special = getfullrawname(special)) == NULL) { fprintf(stderr, "tunefs: malloc failed\n"); exit(32); } if (*special == '\0') { fprintf(stderr, "tunefs: Could not find raw device for %s\n", argv[argc -1]); exit(32); } if (stat64(special, &st) < 0) { fprintf(stderr, "tunefs: "); perror(special); exit(31+1); } /* * If a mountpoint has been found then we will ioctl() the file * system instead of writing to the file system's device */ /* ustat() ok because max number of UFS inodes can fit in ino_t */ if (ustat(st.st_rdev, &ustatarea) >= 0) { if (mountpoint == NULL) { printf("%s is mounted, can't tunefs\n", special); exit(32); } } else mountpoint = NULL; if ((st.st_mode & S_IFMT) != S_IFBLK && (st.st_mode & S_IFMT) != S_IFCHR) fatal("%s: not a block or character device", special); getsb(&sblock, special); while ((opt = getopt(argc, argv, "o:m:e:d:a:AV")) != EOF) { switch (opt) { case 'A': Aflag++; continue; case 'a': name = "maximum contiguous block count"; if (!isnumber(optarg)) fatal("%s: %s must be >= 1", *argv, name); i = atoi(optarg); if (i < 1) fatal("%s: %s must be >= 1", *argv, name); fprintf(stdout, "%s changes from %d to %d\n", name, sblock.fs_maxcontig, i); sblock.fs_maxcontig = i; continue; case 'd': sblock.fs_rotdelay = 0; continue; case 'e': name = "maximum blocks per file in a cylinder group"; if (!isnumber(optarg)) fatal("%s: %s must be >= 1", *argv, name); i = atoi(optarg); if (i < 1) fatal("%s: %s must be >= 1", *argv, name); fprintf(stdout, "%s changes from %d to %d\n", name, sblock.fs_maxbpg, i); sblock.fs_maxbpg = i; continue; case 'm': name = "minimum percentage of free space"; if (!isnumber(optarg)) fatal("%s: bad %s", *argv, name); i = atoi(optarg); if (i < 0 || i > 99) fatal("%s: bad %s", *argv, name); fprintf(stdout, "%s changes from %d%% to %d%%\n", name, sblock.fs_minfree, i); sblock.fs_minfree = i; continue; case 'o': name = "optimization preference"; chg[FS_OPTSPACE] = "space"; chg[FS_OPTTIME] = "time"; if (strcmp(optarg, chg[FS_OPTSPACE]) == 0) i = FS_OPTSPACE; else if (strcmp(optarg, chg[FS_OPTTIME]) == 0) i = FS_OPTTIME; else fatal("%s: bad %s (options are `space' or `time')", optarg, name); if (sblock.fs_optim == i) { fprintf(stdout, "%s remains unchanged as %s\n", name, chg[i]); continue; } fprintf(stdout, "%s changes from %s to %s\n", name, chg[sblock.fs_optim], chg[i]); sblock.fs_optim = i; continue; case 'V': { char *opt_text; int opt_count; (void) fprintf(stdout, "tunefs -F ufs "); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) fprintf(stdout, " %s ", opt_text); } (void) fprintf(stdout, "\n"); } break; default: usage(); } } if ((argc - optind) != 1) usage(); if (mountpoint) { mountfd = open(mountpoint, O_RDONLY); if (mountfd == -1) { perror(mountpoint); fprintf(stderr, "tunefs: can't tune %s\n", mountpoint); exit(32); } fiotune.maxcontig = sblock.fs_maxcontig; fiotune.rotdelay = sblock.fs_rotdelay; fiotune.maxbpg = sblock.fs_maxbpg; fiotune.minfree = sblock.fs_minfree; fiotune.optim = sblock.fs_optim; if (ioctl(mountfd, _FIOTUNE, &fiotune) == -1) { perror(mountpoint); fprintf(stderr, "tunefs: can't tune %s\n", mountpoint); exit(32); } close(mountfd); } else { bwrite((diskaddr_t)SBLOCK, (char *)&sblock, SBSIZE); if (Aflag) for (i = 0; i < sblock.fs_ncg; i++) bwrite(fsbtodb(&sblock, cgsblock(&sblock, i)), (char *)&sblock, SBSIZE); } close(fi); return (0); } void usage() { fprintf(stderr, "ufs usage: tunefs tuneup-options special-device\n"); fprintf(stderr, "where tuneup-options are:\n"); fprintf(stderr, "\t-a maximum contiguous blocks\n"); fprintf(stderr, "\t-d rotational delay between contiguous blocks\n"); fprintf(stderr, "\t-e maximum blocks per file in a cylinder group\n"); fprintf(stderr, "\t-m minimum percentage of free space\n"); fprintf(stderr, "\t-o optimization preference (`space' or `time')\n"); exit(31+2); } void getsb(struct fs *fs, char *file) { fi = open64(file, O_RDWR); if (fi < 0) { fprintf(stderr, "Cannot open "); perror(file); exit(31+3); } if (bread((diskaddr_t)SBLOCK, (char *)fs, SBSIZE)) { fprintf(stderr, "Bad super block "); perror(file); exit(31+4); } if ((fs->fs_magic != FS_MAGIC) && (fs->fs_magic != MTB_UFS_MAGIC)) { fprintf(stderr, "%s: bad magic number\n", file); exit(31+5); } if (fs->fs_magic == FS_MAGIC && (fs->fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && fs->fs_version != UFS_VERSION_MIN)) { fprintf(stderr, "%s: unrecognized ufs version: %d\n", file, fs->fs_version); exit(31+5); } if (fs->fs_magic == MTB_UFS_MAGIC && (fs->fs_version > MTB_UFS_VERSION_1 || fs->fs_version < MTB_UFS_VERSION_MIN)) { fprintf(stderr, "%s: unrecognized ufs version: %d\n", file, fs->fs_version); exit(31+5); } } void bwrite(diskaddr_t blk, char *buf, int size) { if (llseek(fi, (offset_t)blk * DEV_BSIZE, 0) < 0) { perror("FS SEEK"); exit(31+6); } if (write(fi, buf, size) != size) { perror("FS WRITE"); exit(31+7); } } int bread(diskaddr_t bno, char *buf, int cnt) { int i; if (llseek(fi, (offset_t)bno * DEV_BSIZE, 0) < 0) { fprintf(stderr, "bread: "); perror("llseek"); return (1); } if ((i = read(fi, buf, cnt)) != cnt) { perror("read"); for (i = 0; i < sblock.fs_bsize; i++) buf[i] = 0; return (1); } return (0); } /* VARARGS1 */ void fatal(char *fmt, char *arg1, char *arg2) { fprintf(stderr, "tunefs: "); fprintf(stderr, fmt, arg1, arg2); putc('\n', stderr); exit(31+10); } int isnumber(char *s) { int c; while (c = *s++) if (c < '0' || c > '9') return (0); return (1); } # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 1989,2003 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. FSTYPE= ufs LIBPROG= volcopy ATTMK= $(LIBPROG) include ../../Makefile.fstype CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-unused-variable CERRWARN += $(CNOWARN_UNINIT) # Hammerhead: Suppress pointer/int cast warnings in legacy UFS code CERRWARN += -Wno-pointer-to-int-cast # not linted SMATCH=off LDLIBS += -lmapmalloc # for messaging catalog # POFILE= volcopy.po XGETFLAGS += -a -x volcopy.xcl catalog: $(POFILE) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "volcopy.h" #include /* * main I/O information structure, contains information for the * source and destination files. */ struct file_info { char *f_dev_p, /* name of device */ *f_vol_p; /* volume name */ int f_bsize, /* size to buffer I/O to */ f_des, /* file descriptor */ f_dev; /* device type (generic I/O library) */ } In, Out; int Sem_id[BUFCNT], /* semaphore ids for controlling shared memory */ Shm_id[BUFCNT], /* shared memory identifier */ *Cnts[BUFCNT]; /* an array of byte counts for shared memory */ char Empty[BLKSIZ], /* empty memory used to clear sections of memory */ *Buf[BUFCNT]; /* buffer pointers (possibly to shared memory) */ struct sembuf Sem_buf, /* semaphore operation buffer */ Rstsem_buf; /* semaphore reset operation buffer */ typedef union { char dummy[SBSIZE]; struct fs sblk; } sb_un; sb_un isup, osup, tsup; #define Isup isup.sblk #define Osup osup.sblk struct fs *Sptr = (struct fs *)&tsup.sblk; /* super-block pointer */ char *Ifname, *Ifpack, *Ofname, *Ofpack; struct volcopy_label V_labl; char From_vol[VVOLLEN + 1], To_vol[VVOLLEN + 1], *Fsys_p; static char *nolabel = ""; /* used when there is no room for label */ int Blk_cnt = 1, /* Do I/O in (Blk_cnt * BLKSIZ) byte blocks */ Blocks = 0, /* Number of blocks transferred */ Bpi = 0, Bufcnt, Bufflg = 0, Bufsz = BLKSIZ, Disk_cnt = 1, /* Disk I/O (Disk_cnt * Blk_cnt * BLKSIZ) byte blocks */ Drive_typ = 0, /* Flag for special tape drive types */ Eomflg = 0, Ipc = 0, Itape, M3b15 = 0, /* Machine types, set to 1 for the machine */ M3b2 = 0, /* the command is executing on */ Otape, Pid = -1, R_blks = 0, /* Number of blocks per tape reel */ R_cur = 1, /* Current tape reel being processed */ R_len = 0, /* Length in feet of tape reels */ R_num = 0, /* Number of tape reels to be processed */ Shell_esc = 1, /* Allow shell after delete -nosh (3b15) can disable */ Yesflg = 0; void (*singal())(); long Fs, Fstype; time_t Tvec; FILE *Devtty; static void getinfs(), getoutfs(); static char *getfslabel(struct fs *); static char *getvolabel(struct fs *); static void prompt(int, const char *, ...); static void perr(int, const char *, ...); static void mklabel(void); static void get_mach_type(void); static void mem_setup(void); static void rprt(void); static void chgreel(struct file_info *, int); static void parent_copy(void); static void copy(void); static void flush_bufs(int); static void cleanup(void); #ifdef LOG static int fslog(void); #endif /* LOG */ /* * g_init(), g_read(), g_write() originally came from libgenIO, * a totally obsolete device interface library, now deleted. * volcopy should be deleted too, since it doesn't work. */ #define G_TM_TAPE 1 /* Tapemaster controller */ #define G_XY_DISK 3 /* xy disks */ #define G_SD_DISK 7 /* scsi sd disk */ #define G_XT_TAPE 8 /* xt tapes */ #define G_SF_FLOPPY 9 /* sf floppy */ #define G_XD_DISK 10 /* xd disks */ #define G_ST_TAPE 11 /* scsi tape */ #define G_NS 12 /* noswap pseudo-dev */ #define G_RAM 13 /* ram pseudo-dev */ #define G_FT 14 /* tftp */ #define G_HD 15 /* 386 network disk */ #define G_FD 16 /* 386 AT disk */ #define G_FILE 28 /* file, not a device */ #define G_NO_DEV 29 /* device does not require special treatment */ #define G_DEV_MAX 30 /* last valid device type */ /* * g_init: Determine the device being accessed, set the buffer size, * and perform any device specific initialization. Since at this point * Sun has no system call to read the configuration, the major numbers * are assumed to be static and types are figured out as such. However, * as a rough estimate, the buffer size for all types is set to 512 * as a default. */ static int g_init(int *devtype, int *fdes) { major_t maj; int bufsize; struct stat64 st_buf; struct statvfs64 stfs_buf; *devtype = G_NO_DEV; if (fstat64(*fdes, &st_buf) == -1) return (-1); if (!S_ISCHR(st_buf.st_mode) && !S_ISBLK(st_buf.st_mode)) { if (S_ISFIFO(st_buf.st_mode)) bufsize = 512; else { /* find block size for this file system */ *devtype = G_FILE; if (fstatvfs64(*fdes, &stfs_buf) < 0) { bufsize = -1; errno = ENODEV; } else { bufsize = stfs_buf.f_bsize; } } return (bufsize); } return (512); } /* * g_read: Read nbytes of data from fdes (of type devtype) and place * data in location pointed to by buf. In case of end of medium, * translate (where necessary) device specific EOM indications into * the generic EOM indication of rv = -1, errno = ENOSPC. */ /* ARGSUSED */ static ssize_t g_read(int devtype, int fdes, void *buf, size_t nbytes) { ssize_t rv; rv = read(fdes, buf, nbytes); /* st devices return 0 when no space left */ if ((rv == 0 && errno == 0) || (rv == -1 && errno == EIO)) { errno = ENOSPC; rv = -1; } return (rv); } /* * g_write: Write nbytes of data to fdes (of type devtype) from * the location pointed to by buf. In case of end of medium, * translate (where necessary) device specific EOM indications into * the generic EOM indication of rv = -1, errno = ENOSPC. */ /* ARGSUSED */ static ssize_t g_write(int devtype, int fdes, void *buf, size_t nbytes) { ssize_t rv; rv = write(fdes, buf, nbytes); /* st devices return 0 when no more space left */ if ((rv == 0 && errno == 0) || (rv == -1 && errno == EIO)) { errno = ENOSPC; rv = -1; } return (rv); } /* * filesystem copy with propagation of volume ID and filesystem name: * * volcopy [-options] filesystem /dev/from From_vol /dev/to To_vol * * options are: * -feet - length of tape * -bpi - recording density * -reel - reel number (if not starting from beginning) * -buf - use double buffered i/o (if dens >= 1600 bpi) * -block - Set the transfer block size to NUM physical blocks (512 * bytes on 3B2 and 3B15). Note that an arbitrary block size might * or might not work on a given system. Also, the block size * read from the header of an input tape silently overrides this. * -nosh - Don't offer the user a shell after hitting break or delete. * -r - Read NUM transfer blocks from the disk at once and write it * to the output device one block at a time. Intended only to * boost the 3B15 EDFC disk to tape performance. Disabled on 3B2. * -a - ask "y or n" instead of "DEL if wrong" * -s - inverse of -a, from/to devices are printed followed by `?'. * User has 10 seconds to DEL if mistaken! * -y - assume "yes" response to all questions * * Examples: * * volcopy root /dev/rdsk/0s2 pk5 /dev/rdsk/1s2 pk12 * * volcopy u3 /dev/rdsk/1s5 pk1 /dev/rmt/0m tp123 * * volcopy u5 /dev/rmt/0m - /dev/rdsk/1s5 - */ int main(int argc, char *argv[]) { char c; int lfdes, altflg = 0, result, verify; long dist; char *align(); void sigalrm(), sigint(); struct stat stbuf; int cnt; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); (void) get_mach_type(); (void) signal(SIGINT, sigint); (void) signal(SIGALRM, sigalrm); In.f_bsize = Out.f_bsize = BLKSIZ; while (argc > 1 && argv[1][0] == '-') { if (EQ(argv[1], "-a", 2)) { altflg |= MINUSA; } else if (EQ(argv[1], "-e", 2)) { Eomflg = 1; } else if (EQ(argv[1], "-s", 2)) { altflg |= MINUSS; } else if (EQ(argv[1], "-y", 2)) { Yesflg++; } else if (EQ(argv[1], "-buf", 4)) { Bufflg++; } else if (EQ(argv[1], "-bpi", 4)) { if ((c = argv[1][4]) >= '0' && c <= '9') Bpi = getbpi(&argv[1][4]); else { ++argv; --argc; Bpi = getbpi(&argv[1][0]); } } else if (EQ(argv[1], "-feet", 5)) { if ((c = argv[1][5]) >= '0' && c <= '9') R_len = atoi(&argv[1][5]); else { ++argv; --argc; R_len = atoi(&argv[1][0]); } } else if (EQ(argv[1], "-reel", 5)) { if ((c = argv[1][5]) >= '0' && c <= '9') R_cur = atoi(&argv[1][5]); else { ++argv; --argc; R_cur = atoi(&argv[1][0]); } } else if (EQ(argv[1], "-r", 2)) { /* 3b15 only */ if ((c = argv[1][2]) >= '0' && c <= '9') Disk_cnt = atoi(&argv[1][2]); else { ++argv; --argc; Disk_cnt = atoi(&argv[1][0]); } if (Disk_cnt == 0) perr(1, "volcopy: Need a non-zero value for the -r option\n"); } else if (EQ(argv[1], "-block", 6)) { /* 3b15 only */ if ((c = argv[1][6]) >= '0' && c <= '9') Blk_cnt = atoi(&argv[1][6]); else { ++argv; --argc; Blk_cnt = atoi(&argv[1][0]); } if (Blk_cnt == 0) perr(1, "volcopy: Need a non-zero value for the -block option\n"); } else if (EQ(argv[1], "-nosh", 5)) { /* 3b15 only */ Shell_esc = 0; } else perr(1, "<%s> invalid option\n", argv[1]); ++argv; --argc; } /* argv[1][0] == '-' */ Devtty = fopen("/dev/tty", "r"); if ((Devtty == NULL) && !isatty(0)) Devtty = stdin; time(&Tvec); if (Eomflg && R_len) perr(9, "volcopy: -e and -feet are mutually exclusive\n"); if ((altflg & MINUSA) && (altflg & MINUSS)) perr(9, "volcopy: -a and -s are mutually exclusive\n"); if (argc != 6) /* if mandatory fields not present */ perr(9, "ufs usage: volcopy [-F ufs] [generic options] \ fsname /devfrom volfrom /devto volto\n"); if (!(altflg & MINUSA)) /* -a was not specified, use default (-s) */ altflg |= MINUSS; In.f_dev_p = argv[DEV_IN]; Out.f_dev_p = argv[DEV_OUT]; strncpy(To_vol, argv[VOL_OUT], VVOLLEN); To_vol[VVOLLEN] = '\0'; Out.f_vol_p = &To_vol[0]; strncpy(From_vol, argv[VOL_IN], VVOLLEN); From_vol[VVOLLEN] = '\0'; In.f_vol_p = &From_vol[0]; Fsys_p = argv[FIL_SYS]; if ((In.f_des = open(In.f_dev_p, O_RDONLY)) < 1) perr(10, "%s: cannot open\n", In.f_dev_p); if ((Out.f_des = open(Out.f_dev_p, O_RDONLY)) < 1) perr(10, "%s: cannot open\n", Out.f_dev_p); if (fstat(In.f_des, &stbuf) < 0 || (stbuf.st_mode & S_IFMT) != S_IFCHR) perr(10, "From device not character-special\n"); if (fstat(Out.f_des, &stbuf) < 0 || (stbuf.st_mode & S_IFMT) != S_IFCHR) perr(10, "To device not character-special\n"); if ((Itape = tapeck(&In, INPUT)) == 1) R_blks = V_labl.v_reelblks; Otape = tapeck(&Out, OUTPUT); if (Otape && Itape) perr(10, "Use dd(8) command to copy tapes\n"); (void) mem_setup(); if (Bufflg && !Ipc) perr(1, "The -buf option requires ipc\n"); if (!Itape && !Otape) R_cur = 1; if (R_cur == 1 || !Itape) { /* read in superblock */ verify = 0; (void) getinfs(In.f_dev, In.f_des, Sptr); if ((Sptr->fs_magic != FS_MAGIC) && (Sptr->fs_magic != MTB_UFS_MAGIC)) perr(10, "File System type unknown--get help\n"); if (Sptr->fs_magic == FS_MAGIC && (Sptr->fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && Sptr->fs_version != UFS_VERSION_MIN)) perr(10, "Unrecognized version of UFS--get help\n"); if (Sptr->fs_magic == MTB_UFS_MAGIC && (Sptr->fs_version > MTB_UFS_VERSION_1 || Sptr->fs_version < MTB_UFS_VERSION_MIN)) perr(10, "Unrecognized version of UFS--get help\n"); (void) memcpy(&Isup, Sptr, Sptr->fs_sbsize); Ifname = getfslabel(&Isup); Ifpack = getvolabel(&Isup); Fs = Sptr->fs_size * Sptr->fs_nspf; } /* R_cur == 1 || !Itape */ /* read in superblock */ verify = !Otape || (altflg & MINUSS); (void) getoutfs(Out.f_dev, Out.f_des, Sptr, verify); if ((Sptr->fs_magic == FS_MAGIC) || (Sptr->fs_magic == MTB_UFS_MAGIC)) { (void) memcpy(&Osup, Sptr, Sptr->fs_sbsize); Ofname = getfslabel(&Osup); Ofpack = getvolabel(&Osup); } else { int i; /* out vol does not contain a ufs file system */ /* stuff let over from Isup */ (void) memcpy(&Osup, &Isup, Isup.fs_sbsize); Ofname = getfslabel(&Osup); Ofpack = getvolabel(&Osup); /* wipe out the fs name and pack name for warning purposes */ for (i = 0; i < 6; i++) Ofname[i] = ' '; for (i = 0; i < 6; i++) Ofpack[i] = ' '; } if (Itape) { if (R_cur != 1) { (void) printf(gettext( "\nvolcopy: IF REEL 1 HAS NOT BEEN RESTORED,")); (void) printf(gettext( " STOP NOW AND START OVER ***\07\n")); if (!ask(" Continue? ")) { cleanup(); exit(31+9); } strncpy(Ifname, Fsys_p, 6); strncpy(Ifpack, In.f_vol_p, 6); } if (V_labl.v_reel != R_cur || V_labl.v_reels != R_num) prompt(1, "Tape disagrees: Reel %d of %d : looking for %d of %d\n", V_labl.v_reel, V_labl.v_reels, R_cur, R_num); } else if (Otape) { strncpy(V_labl.v_volume, Out.f_vol_p, 6); strncpy(Ofpack, Out.f_vol_p, 6); strncpy(Ofname, Fsys_p, 6); if (!Eomflg) { R_num = Fs / R_blks + ((Fs % R_blks) ? 1 : 0); (void) printf(gettext( "You will need %d reels.\n"), R_num); (void) printf(gettext( "(\tThe same size and density is expected for all reels)\n")); } } if (NOT_EQ(Fsys_p, Ifname, 6)) { verify = !Otape || (altflg & MINUSS); prompt(verify, "arg. (%.6s) doesn't agree with from fs. (%.6s)\n", Fsys_p, Ifname); } if (NOT_EQ(In.f_vol_p, "-", 6) && NOT_EQ(In.f_vol_p, Ifpack, 6)) { verify = !Otape || (altflg & MINUSS); prompt(verify, "arg. (%.6s) doesn't agree with from vol.(%.6s)\n", In.f_vol_p, Ifpack); } if (*In.f_vol_p == '-') In.f_vol_p = Ifpack; if (*Out.f_vol_p == '-') Out.f_vol_p = Ofpack; if (R_cur == 1 && (Osup.fs_time + _2_DAYS) > Isup.fs_time) { time_t t; verify = altflg & MINUSS; t = (time_t)Osup.fs_time; prompt(verify, "%s less than 48 hours older than %s\n" "To filesystem dated: %s", Out.f_dev_p, In.f_dev_p, ctime(&t)); } if (NOT_EQ(Out.f_vol_p, Ofpack, 6)) { prompt(1, "arg.(%.6s) doesn't agree with to vol.(%.6s)\n", Out.f_vol_p, Ofpack); strncpy(Ofpack, Out.f_vol_p, 6); } if (Isup.fs_size > Osup.fs_size && !Otape) prompt(1, "from fs larger than to fs\n"); if (!Otape && NOT_EQ(Ifname, Ofname, 6)) { verify = altflg & MINUSS; prompt(verify, "warning! from fs(%.6s) differs from to fs(%.6s)\n", Ifname, Ofname); } (void) printf(gettext("From: %s, to: %s? "), In.f_dev_p, Out.f_dev_p); if (!(altflg & MINUSA)) { (void) printf(gettext("(DEL if wrong)\n")); sleep(10); } else if (!ask("(y or n) ")) perr(10, "\nvolcopy: STOP\n"); close(In.f_des); close(Out.f_des); sync(); In.f_des = open(In.f_dev_p, O_RDONLY); Out.f_des = open(Out.f_dev_p, O_WRONLY); errno = 0; if (g_init(&In.f_dev, &In.f_des) < 0 || g_init(&Out.f_dev, &Out.f_des) < 0) perr(1, "volcopy: Error %d during initialization\n", errno); if (Itape) { errno = 0; if (g_read(In.f_dev, In.f_des, &V_labl, sizeof (V_labl)) < sizeof (V_labl)) perr(10, "Error while reading label\n"); } else if (Otape) { V_labl.v_reels = R_num; V_labl.v_reel = R_cur; V_labl.v_time = Tvec; V_labl.v_reelblks = R_blks; V_labl.v_blksize = BLKSIZ * Blk_cnt; V_labl.v_nblocks = Blk_cnt; V_labl.v_offset = 0L; V_labl.v_type = T_TYPE; errno = 0; if (g_write(Out.f_dev, Out.f_des, &V_labl, sizeof (V_labl)) < sizeof (V_labl)) perr(10, "Error while writing label\n"); } if (R_cur > 1) { if (!Eomflg) { Fs = (R_cur - 1) * actual_blocks(); lfdes = Otape ? In.f_des : Out.f_des; dist = (long)(Fs * BLKSIZ); } else { /* Eomflg */ if (Otape) perr(1, "Cannot use -reel with -e when copying to tape\n"); lfdes = Out.f_des; dist = (long)(V_labl.v_offset * BLKSIZ); Fs = V_labl.v_offset; } if (lseek(lfdes, dist, 0) < 0) perr(1, "Cannot lseek()\n"); Sptr = Otape ? &Isup : &Osup; if ((Sptr -> fs_magic != FS_MAGIC) && (Sptr -> fs_magic != MTB_UFS_MAGIC)) perr(10, "File System type unknown--get help!\n"); if (Sptr->fs_magic == FS_MAGIC && (Sptr->fs_version != UFS_EFISTYLE4NONEFI_VERSION_2 && Sptr->fs_version != UFS_VERSION_MIN)) perr(10, "Unrecognized version of UFS--get help\n"); if (Sptr->fs_magic == MTB_UFS_MAGIC && (Sptr->fs_version > MTB_UFS_VERSION_1 || Sptr->fs_version < MTB_UFS_VERSION_MIN)) perr(10, "Unrecognized version of UFS--get help\n"); Fs = (Sptr->fs_size * Sptr->fs_nspf) - Fs; } if (Itape || Otape) rprt(); if (Ipc) { parent_copy(); (void) cleanup(); } else copy(); (void) printf(gettext(" END: %ld blocks.\n"), Blocks); #ifdef LOG fslog(); #endif if (Blocks) return (0); return (31+1); /* failed.. 0 blocks */ } /* * sigalrm: catch alarm signals. */ void sigalrm() { void (*signal())(); (void) signal(SIGALRM, sigalrm); } /* * sigsys: catch illegal system calls to determine if IPC is available. */ void sigsys() { Ipc = 0; } /* * sigint: catch interrupts and prompt user for shell or to quit. */ void sigint() { void (*signal())(); extern char **environ; int tmpflg, i = 0, ps1 = -1, ps2 = -1; tmpflg = Yesflg; /* override yesflag for duration of interrupt */ Yesflg = 0; if (Shell_esc && ask("Want Shell? ")) { if (!fork()) { /* both PS1 and PS2 must be exported */ while (environ[i]) { if (EQ(environ[i], "PS1", 3)) ps1 = i; if (EQ(environ[i], "PS2", 3)) ps2 = i; i++; } if (ps1 >= 0 && ps2 >= 0) environ[ps1] = environ[ps2]; (void) signal(SIGINT, SIG_DFL); execl("/usr/bin/sh", "/usr/bin/sh", 0); } else { /* parent */ (void) signal(SIGINT, SIG_IGN); wait((int *)0); } } else if (ask("Want to quit? ")) { if (Pid > 0) kill(Pid, 9); (void) cleanup(); /* ipc */ exit(31+2); } (void) signal(SIGINT, sigint); Yesflg = tmpflg; /* reset Yesflg */ } /* * actual_blocks: Calculate the actual number of blocks written to * the tape (will differ from V_labl.v_reelblks if v_reelblks is not * an even multiple of the blocking factor Blk_cnt). */ int actual_blocks() { if (R_blks % Blk_cnt) return (((R_blks / Blk_cnt) + 1) * Blk_cnt); else return (R_blks); } /* * get_mach_type: Determine what machine this is executing on. */ static void get_mach_type(void) { struct utsname utsinfo; errno = 0; if (uname(&utsinfo) < 0) perr(1, "Unable to determine machine type\n"); if (strcmp(utsinfo.machine, "3B2") == 0) M3b2 = 1; else if (strcmp(utsinfo.machine, "3B15") == 0) M3b15 = 1; } /* * mem_setup: Determine memory needs and check for IPC. If IPC is available, * used shared memory and semaphores to increase performance. If no IPC, * get normal memory and only use one process. */ static void mem_setup(void) { void (*signal())(); int cnt, num, size; char *align(); union semun { int val; struct semid_ds *buf; ushort_t *array; } sem_arg; if (Blk_cnt == 1) { switch (Drive_typ) { case A_DRIVE: Blk_cnt = 32; break; case C_DRIVE: Blk_cnt = 10; break; case K_DRIVE: Blk_cnt = 4; break; case T_DRIVE: if (Bpi == 6250) Blk_cnt = 50; else Blk_cnt = 10; break; default: if (M3b15) { if (Itape || Otape) Blk_cnt = 16; } else { if (Otape || Itape) { if (Bpi == 6250) Blk_cnt = 50; else Blk_cnt = 10; } } break; } /* Drive_typ */ } /* Blk_cnt == 1 */ if (Blk_cnt > 1) /* user overrode g_init */ In.f_bsize = Out.f_bsize = Blk_cnt * BLKSIZ; In.f_bsize = (!Itape) ? Disk_cnt * In.f_bsize : In.f_bsize; Out.f_bsize = (!Otape) ? Disk_cnt * Out.f_bsize : Out.f_bsize; Bufsz = find_lcm(In.f_bsize, Out.f_bsize); num = _128K / (Bufsz + sizeof (int)); Bufsz *= num; size = Bufsz + sizeof (int); /* test to see if ipc is available, the shmat should fail with EINVAL */ (void) signal(SIGSYS, sigsys); errno = 0; if (Ipc) { if ((int)shmat(0, (char *)NULL, 0) < 0 && errno != EINVAL) Ipc = 0; /* something went wrong */ } if (Ipc) { /* ipc is available */ Bufcnt = 2; sem_arg.val = 0; for (cnt = 0; cnt < BUFCNT; cnt++) { errno = 0; if ((Sem_id[cnt] = semget(IPC_PRIVATE, 1, 0)) < 0) perr(1, "Error allocating semaphores: %d", errno); if (semctl(Sem_id[cnt], 0, SETVAL, sem_arg) < 0) perr(1, "Error setting semaphores: %d", errno); if ((Shm_id[cnt] = shmget(IPC_PRIVATE, size, 0)) < 0) perr(1, "Error allocating shared memory: %d", errno); if ((Buf[cnt] = shmat(Shm_id[cnt], 0, 0)) == (void *)-1) perr(1, "Error attaching shared memory: %d", errno); if (shmctl(Shm_id[cnt], SHM_LOCK, 0) < 0) perr(0, "Error locking in shared memory: %d", errno); Cnts[cnt] = (int *)(Buf[cnt] + Bufsz); } } else { /* ipc is not available */ Bufcnt = 1; if ((Buf[0] = align(size)) == (char *)NULL) perr(1, "Out of memory\n"); Cnts[0] = (int *)(Buf[0] + Bufsz); *Cnts[0] = 0; } } /* * prompt: Prompt the user for verification. */ static void prompt(int verify, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (fmt != NULL) (void) vfprintf(stdout, gettext(fmt), ap); va_end(ap); if (verify) { (void) fprintf(stdout, gettext("Type 'y' to override: ")); if (!ask("")) { cleanup(); exit(31+9); } } } /* * ask: Ask the user a question and get the answer. */ int ask(char *s) { char ans[12]; (void) printf(gettext(s)); if (Yesflg) { (void) printf(gettext("YES\n")); return (1); } ans[0] = '\0'; fgets(ans, 10, Devtty); for (;;) { switch (ans[0]) { case 'a': case 'A': if (Pid > 0) /* parent with a child */ kill(Pid, 9); cleanup(); exit(31+1); case 'y': case 'Y': return (1); case 'n': case 'N': return (0); default: (void) printf(gettext("\n(y or n)?")); fgets(ans, 10, Devtty); } } } /* * align: Align a malloc'd memory section on a page boundry. */ char * align(int size) { int pad; if ((pad = ((int)malloc(0) & (PAGESIZE-1))) > 0) { pad = PAGESIZE - pad; if (malloc(pad) == (char *)NULL) return ((char *)NULL); } return (malloc((unsigned)size)); } /* * child_copy: Using IPC, this child process reads from shared memory * and writes to the destination file system. */ int child_copy() { int rv, cur_buf, left, have, tpcnt; char *c_p; (void) signal(SIGINT, SIG_IGN); Sem_buf.sem_op = -1; (void) close(In.f_des); if (Otape && !Eomflg) tpcnt = actual_blocks() * BLKSIZ; cur_buf = 0; for (;;) { if (semop(Sem_id[cur_buf], &Sem_buf, 1) < 0) perr(1, "semaphore operation error %d\n", errno); left = *Cnts[cur_buf]; if (!left) break; c_p = Buf[cur_buf]; rv = 0; while (left) { have = (left < Out.f_bsize) ? left : Out.f_bsize; if (!Eomflg && Otape) { if (!tpcnt) { (void) chgreel(&Out, OUTPUT); tpcnt = actual_blocks() * BLKSIZ; } have = (tpcnt < have) ? tpcnt : have; } errno = 0; if ((rv = g_write(Out.f_dev, Out.f_des, c_p, have)) < 0) { if (Eomflg && errno == ENOSPC) { (void) chgreel(&Out, OUTPUT); continue; } else perr(1, "I/O error %d on write\n", errno); } left -= rv; c_p += rv; V_labl.v_offset += rv; if (!Eomflg && Otape) tpcnt -= rv; } if (semop(Sem_id[cur_buf], &Sem_buf, 1) < 0) perr(2, "semaphore operation error %d\n", errno); cur_buf = (cur_buf + 1) % BUFCNT; } exit(0); } /* * parent_copy: Using shared memory, the parent process reads fromt the * source file system and writes to shared memory. */ static void parent_copy(void) { int rv, left, have, tpcnt, cur_buf; char *c_p; int eom = 0, xfer_cnt = Fs * BLKSIZ; Sem_buf.sem_num = 0; Sem_buf.sem_flg = 0; if ((Pid = fork()) == 0) child_copy(); /* child does not return */ (void) close(Out.f_des); Rstsem_buf.sem_num = 0; Rstsem_buf.sem_flg = 0; Rstsem_buf.sem_op = 2; Sem_buf.sem_op = 0; cur_buf = 0; if (Itape && !Eomflg) tpcnt = actual_blocks() * BLKSIZ; while (xfer_cnt) { if (semop(Sem_id[cur_buf], &Sem_buf, 1) < 0) perr(1, "Semaphore operation error %d\n", errno); c_p = Buf[cur_buf]; left = Bufsz; rv = 0; while (left >= In.f_bsize && xfer_cnt) { have = (xfer_cnt < In.f_bsize) ? xfer_cnt : In.f_bsize; if (!Eomflg && Itape) { if (!tpcnt) { *Cnts[cur_buf] = Bufsz - left; (void) flush_bufs(cur_buf); (void) chgreel(&In, INPUT); tpcnt = actual_blocks() * BLKSIZ; cur_buf = (cur_buf == 0) ? 1 : 0; eom = 1; break; } have = (tpcnt < have) ? tpcnt : have; } errno = 0; if ((rv = g_read(In.f_dev, In.f_des, c_p, have)) < 0) { if (Eomflg && errno == ENOSPC) { *Cnts[cur_buf] = Bufsz - left; (void) flush_bufs(cur_buf); (void) chgreel(&In, INPUT); cur_buf = (cur_buf == 0) ? 1 : 0; eom = 1; break; } else perr(1, "I/O error %d on read\n", errno); } left -= rv; c_p += rv; xfer_cnt -= rv; if (!Eomflg && Itape) tpcnt -= rv; } if (eom > 0) { eom = 0; if (Eomflg) xfer_cnt -= rv; else if (Itape) tpcnt -= rv; continue; } *Cnts[cur_buf] = Bufsz - left; Blocks += *Cnts[cur_buf]; if (semop(Sem_id[cur_buf], &Rstsem_buf, 1) < 0) perr(2, "Semaphore operation error %d\n", errno); cur_buf = (cur_buf == 0) ? 1 : 0; } if (semop(Sem_id[cur_buf], &Sem_buf, 1) < 0) perr(3, "Semaphore operation error %d\n", errno); *Cnts[cur_buf] = 0; if (semop(Sem_id[cur_buf], &Rstsem_buf, 1) < 0) perr(4, "Semaphore operation error %d\n", errno); wait((int *)NULL); Blocks /= BLKSIZ; } /* * copy: Copy without shared memory. The process reads from the source * filesystem and writes to the destination filesystem. */ static void copy(void) { int rv, left, have, tpcnt = 1, xfer_cnt = Fs * BLKSIZ; char *c_p; if ((Itape || Otape) && !Eomflg) tpcnt = actual_blocks() * BLKSIZ; while (xfer_cnt) { c_p = (char *)(Buf[0] + *Cnts[0]); left = Bufsz - *Cnts[0]; rv = 0; while (left >= In.f_bsize && xfer_cnt) { have = (xfer_cnt < In.f_bsize) ? xfer_cnt : In.f_bsize; if (!Eomflg && Itape) { if (!tpcnt) { *Cnts[0] = Bufsz - left; (void) chgreel(&In, INPUT); tpcnt = actual_blocks() * BLKSIZ; break; } have = (tpcnt < have) ? tpcnt : have; } errno = 0; if ((rv = g_read(In.f_dev, In.f_des, c_p, have)) < 0) { if (Eomflg && errno == ENOSPC) { (void) chgreel(&In, INPUT); break; } else perr(1, "I/O error %d on read\n", errno); } left -= rv; c_p += rv; xfer_cnt -= rv; if (!Eomflg && Itape) tpcnt -= rv; } /* left >= In.f_bsize && xfer_cnt */ *Cnts[0] = Bufsz - left; Blocks += *Cnts[0]; c_p = Buf[0]; left = *Cnts[0]; rv = 0; while (left >= Out.f_bsize || (left > 0 && !xfer_cnt)) { have = (left < Out.f_bsize) ? left : Out.f_bsize; if (!Eomflg && Otape) { if (!tpcnt) { (void) chgreel(&Out, OUTPUT); tpcnt = actual_blocks() * BLKSIZ; } have = (tpcnt < have) ? tpcnt : have; } errno = 0; if ((rv = g_write(Out.f_dev, Out.f_des, c_p, have)) < 0) { if (Eomflg && errno == ENOSPC) { (void) chgreel(&Out, OUTPUT); continue; } else perr(1, "I/O error %d on write\n", errno); } left -= rv; c_p += rv; V_labl.v_offset += rv; if (!Eomflg && Otape) tpcnt -= rv; } /* left >= Out.f_bsize */ if (left) { (void) memcpy(Buf[0], c_p, left); Blocks -= left; } *Cnts[0] = left; } /* xfer_cnt */ Blocks /= BLKSIZ; } /* * flush_bufs: Permit child to read the remaining data from the * buffer before prompting user for end-of-media. */ static void flush_bufs(int buffer) { Blocks += *Cnts[buffer]; if (semop(Sem_id[buffer], &Rstsem_buf, 1) < 0) perr(5, "Semaphore operation error %d\n", errno); if (semop(Sem_id[buffer], &Sem_buf, 1) < 0) perr(6, "Semaphore operation error %d\n", errno); } /* * cleanup: Clean up shared memory and semaphore resources. */ static void cleanup(void) { int cnt; if (Ipc) { for (cnt = 0; cnt < BUFCNT; cnt++) { (void) semctl(Sem_id[cnt], IPC_RMID, 0); (void) shmctl(Shm_id[cnt], IPC_RMID, 0); } } } /* * find_lcm: Find the lowest common multiple of two numbers. This is used * to determine the buffer size that should be malloc(3)'d such that the * input and output data blocks can both fit evenly into the buffer. */ int find_lcm(int sz1, int sz2) { int inc, lcm, small; if (sz1 < sz2) { lcm = inc = sz2; small = sz1; } else { /* sz1 >= sz2 */ lcm = inc = sz1; small = sz2; } while (lcm % small != 0) lcm += inc; return (lcm); } /* * Determine bpi information from drive names. */ int getbpi(char *inp) { /* * Kludge to recognize Accellerated Tape Controller usage from * letter 'a' or 'A' following density given by user. * * Kludge to recognize 3B15 Compatibility Mode from * letter 'c' or 'C' following density given by user. */ if (M3b15) { if (inp[4] == 'a' || inp[4] == 'A') { Drive_typ = A_DRIVE; inp[4] = '\0'; } if (inp[4] == 'c' || inp[4] == 'C') { Drive_typ = C_DRIVE; inp[4] = '\0'; } } return (atoi(inp)); } /* * blks_per_ft: Determine the number of blocks per foot of tape. * Inter-block gap (dgap) is 0.3 in. */ int blks_per_ft(double disc) { double dcnt = Blk_cnt, dBpi = Bpi, dsiz = BLKSIZ, dgap = 0.3; return ((int)(dcnt / (((dcnt * dsiz / dBpi) + dgap) / 12.0) * disc)); } /* * tapeck: Arbitrary block size. Determine the number of physical blocks per * foot of tape, including the inter-block gap, and the possibility of a short * tape. Assume the usable portion of a tape is 85% of its length for small * block sizes and 88% for large block sizes. */ int tapeck(struct file_info *f_p, int dir) { int again = 1, verify, old_style, new_style; char resp[16]; errno = 0; if ((f_p->f_bsize = g_init(&f_p->f_dev, &f_p->f_des)) < 0) perr(1, "volcopy: Error %d during initialization\n", errno); if ((f_p->f_dev != G_TM_TAPE) && (f_p->f_dev != G_XT_TAPE) && (f_p->f_dev != G_ST_TAPE)) return (0); V_labl.v_magic[0] = '\0'; /* scribble on old data */ alarm(5); if (g_read(f_p->f_dev, f_p->f_des, &V_labl, sizeof (V_labl)) <= 0) { if (dir == INPUT) perror("input tape"); else perror("output tape"); } alarm(0); if (V_labl.v_reel == '\0' && dir == INPUT) perr(9, "Input tape is empty\n"); else { old_style = strncmp(V_labl.v_magic, "Volcopy", 7) == 0; new_style = strncmp(V_labl.v_magic, "VOLCOPY", 7) == 0; if (!old_style && !new_style) { verify = (dir == INPUT) ? 0 : 1; prompt(verify, "Not a labeled tape\n"); if (dir == INPUT) perr(10, "Input tape not made by volcopy\n"); mklabel(); strncpy(V_labl.v_volume, f_p->f_vol_p, 6); Osup.fs_time = 0; } else if (new_style) { Eomflg = (dir == INPUT) ? 1 : Eomflg; if (!Eomflg) strncpy(V_labl.v_magic, "Volcopy", 7); } } if (*f_p->f_vol_p == '-') strncpy(f_p->f_vol_p, V_labl.v_volume, 6); else if (NOT_EQ(V_labl.v_volume, f_p->f_vol_p, 6)) { prompt(1, "Header volume(%.6s) does not match (%s)\n", V_labl.v_volume, f_p->f_vol_p); strncpy(V_labl.v_volume, f_p->f_vol_p, 6); } if (dir == INPUT) { Bpi = V_labl.v_dens; if (!Eomflg) { R_len = V_labl.v_length; R_num = V_labl.v_reels; } if (M3b15) { if (V_labl.v_type == T_TYPE) { if (V_labl.v_nblocks == 0) { Blk_cnt = 10; Drive_typ = C_DRIVE; } else Blk_cnt = V_labl.v_nblocks; if (V_labl.v_nblocks == 32) Drive_typ = A_DRIVE; else Drive_typ = 0; } else { Drive_typ = 0; Blk_cnt = 10; Drive_typ = C_DRIVE; } } } while (!Eomflg && (R_len <= 0 || R_len > 3600)) { (void) printf(gettext( "Enter size of reel in feet for <%s>: "), f_p->f_vol_p); fgets(resp, 10, Devtty); R_len = atoi(resp); if (R_len > 0 && R_len <= 3600) break; perr(0, "Size of reel must be > 0, <= 3600\n"); } while (!Eomflg && again) { again = 0; if (!Bpi) { (void) printf(gettext( "Tape density? (i.e., 800 | 1600 | 6250)? ")); fgets(resp, 10, Devtty); Bpi = getbpi(resp); } switch (Bpi) { case 800: R_blks = Ft800x10 * R_len; break; case 1600: if (M3b15) { switch (Blk_cnt) { case 1: /* Writing a new tape */ if (Drive_typ == A_DRIVE) R_blks = Ft1600x32 * R_len; else if (Drive_typ == C_DRIVE) R_blks = Ft1600x10 * R_len; else R_blks = Ft1600x16 * R_len; break; case 10: R_blks = Ft1600x10 * R_len; break; case 16: R_blks = Ft1600x16 * R_len; break; case 32: R_blks = Ft1600x32 * R_len; break; default: if (Blk_cnt < 32) R_blks = blks_per_ft(0.85); else R_blks = blks_per_ft(0.88); R_blks *= R_len; } /* Blk_cnt */ } else R_blks = Ft1600x10 * R_len; break; case 6250: if (M3b15) { switch (Blk_cnt) { case 1: /* Writing a new tape */ if (Drive_typ == A_DRIVE) R_blks = Ft6250x32 * R_len; else if (Drive_typ == C_DRIVE) R_blks = Ft6250x10 * R_len; else R_blks = Ft6250x16 * R_len; break; case 10: R_blks = Ft6250x10 * R_len; break; case 16: R_blks = Ft6250x16 * R_len; break; case 32: R_blks = Ft6250x32 * R_len; break; default: if (Blk_cnt < 32) R_blks = blks_per_ft(0.85); else R_blks = blks_per_ft(0.88); R_blks *= R_len; } } else R_blks = Ft6250x50 * R_len; break; default: perr(0, "Bpi must be 800, 1600, or 6250\n"); Bpi = 0; again = 1; } /* Bpi */ } /* again */ (void) printf(gettext("\nReel %.6s"), V_labl.v_volume); if (!Eomflg) { V_labl.v_length = R_len; V_labl.v_dens = Bpi; (void) printf(gettext(", %d feet, %d BPI\n"), R_len, Bpi); } else (void) printf(gettext(", ? feet\n")); return (1); } /* * hdrck: Look for and validate a volcopy style tape label. */ int hdrck(int dev, int fd, char *tvol) { int verify; struct volcopy_label tlabl; alarm(15); /* don't scan whole tape for label */ errno = 0; if (g_read(dev, fd, &tlabl, sizeof (tlabl)) != sizeof (tlabl)) { alarm(0); verify = Otape; prompt(verify, "Cannot read header\n"); if (Itape) close(fd); else strncpy(V_labl.v_volume, tvol, 6); return (verify); } alarm(0); V_labl.v_reel = tlabl.v_reel; if (NOT_EQ(tlabl.v_volume, tvol, 6)) { perr(0, "Volume is <%.6s>, not <%s>.\n", tlabl.v_volume, tvol); if (ask("Want to override? ")) { if (Otape) strncpy(V_labl.v_volume, tvol, 6); else strncpy(tvol, tlabl.v_volume, 6); return (1); } return (0); } return (1); } /* * mklabel: Zero out and initialize a volcopy label. */ static void mklabel(void) { (void) memcpy(&V_labl, Empty, sizeof (V_labl)); if (!Eomflg) (void) strcpy(V_labl.v_magic, "Volcopy"); else (void) strcpy(V_labl.v_magic, "VOLCOPY"); } /* * rprt: Report activity to user. */ static void rprt(void) { if (Itape) (void) printf(gettext("\nReading ")); else /* Otape */ (void) printf(gettext("\nWriting ")); if (!Eomflg) (void) printf(gettext( "REEL %d of %d VOL = %.6s\n"), R_cur, R_num, In.f_vol_p); else (void) printf(gettext( "REEL %d of ? VOL = %.6s\n"), R_cur, In.f_vol_p); } #ifdef LOG /* * fslog: Log current activity. */ static int fslog(void) { FILE *fp = NULL; fp = fopen("/var/adm/filesave.log", "a"); if (fp == NULL) { perr(1, "volcopy: cannot open /var/adm/filesave.log\n"); } fprintf(fp, "%s%c%.6s%c%.6s -> %s%c%.6s%c%.6s on %.24s\n", In.f_dev_p, ';', Ifname, ';', Ifpack, Out.f_dev_p, ';', Ofname, ';', Ofpack, ctime(&Tvec)); fclose(fp); return (0); } #endif /* LOG */ /* * getname: Get device name. */ void getname(char *nam_p) { int lastchar; char nam_buf[21]; nam_buf[0] = '\0'; (void) printf(gettext("Changing drives? (type RETURN for no,\n")); (void) printf(gettext("\t`/dev/rmt/??\' or `/dev/rtp/??\' for yes: ")); fgets(nam_buf, 20, Devtty); nam_buf[20] = '\0'; lastchar = strlen(nam_buf) - 1; if (nam_buf[lastchar] == '\n') nam_buf[lastchar] = '\0'; /* remove it */ if (nam_buf[0] != '\0') (void) strcpy(nam_p, nam_buf); } /* * chgreel: Change reel on end-of-media. */ static void chgreel(struct file_info *f_p, int dir) { int again = 1, lastchar, temp; char vol_tmp[11]; R_cur++; while (again) { again = 0; errno = 0; (void) close(f_p->f_des); (void) getname(f_p->f_dev_p); (void) printf(gettext( "Mount tape %d\nType volume-ID when ready: "), R_cur); vol_tmp[0] = '\0'; fgets(vol_tmp, 10, Devtty); vol_tmp[10] = '\0'; lastchar = strlen(vol_tmp) - 1; if (vol_tmp[lastchar] == '\n') vol_tmp[lastchar] = '\0'; /* remove it */ if (vol_tmp[0] != '\0') { /* if null string, use old vol-id */ strncpy(f_p->f_vol_p, vol_tmp, 6); strncpy(V_labl.v_volume, vol_tmp, 6); } errno = 0; f_p->f_des = open(f_p->f_dev_p, 0); if (f_p->f_des <= 0 || f_p->f_des > 10) { if (dir == INPUT) perror("input ERR"); else perror("output ERR"); } errno = 0; if (g_init(&(f_p->f_dev), &(f_p->f_des)) < 0) perr(0, "Initialization error %d\n", errno); if ((f_p->f_dev != G_TM_TAPE) && (f_p->f_dev != G_XT_TAPE) && (f_p->f_dev != G_ST_TAPE)) { (void) printf(gettext( "\n'%s' is not a valid device"), f_p->f_dev_p); (void) printf(gettext( "\n\tenter device name `/dev/rmt/??\' or `/dev/rtp/??\' :")); again = 1; continue; } if (!hdrck(f_p->f_dev, f_p->f_des, f_p->f_vol_p)) { again = 1; continue; } switch (dir) { case INPUT: if (V_labl.v_reel != R_cur) { perr(0, "Need reel %d, label says reel %d\n", R_cur, V_labl.v_reel); again = 1; continue; } break; case OUTPUT: V_labl.v_reel = R_cur; temp = V_labl.v_offset; V_labl.v_offset /= BUFSIZ; close(f_p->f_des); sleep(2); errno = 0; f_p->f_des = open(f_p->f_dev_p, 1); if (f_p->f_des <= 0 || f_p->f_des > 10) perror("output ERR"); errno = 0; if (g_init(&(f_p->f_dev), &(f_p->f_des)) < 0) perr(1, "Initialization error %d\n", errno); errno = 0; if (g_write(f_p->f_dev, f_p->f_des, &V_labl, sizeof (V_labl)) < 0) { perr(0, "Cannot re-write header -Try again!\n"); again = 1; V_labl.v_offset = temp; continue; } V_labl.v_offset = temp; break; default: perr(1, "Impossible case\n"); } /* dir */ } /* again */ rprt(); } /* * perr: Print error messages. */ static void perr(int severity, const char *fmt, ...) { va_list ap; va_start(ap, fmt); (void) fflush(stdout); (void) fflush(stderr); if (severity == 10) { (void) vfprintf(stdout, gettext(fmt), ap); (void) fprintf(stdout, gettext( "\t%d reel(s) completed\n"), --R_cur); (void) fflush(stdout); (void) fflush(stderr); cleanup(); exit(31+9); } (void) vfprintf(stderr, gettext(fmt), ap); (void) fflush(stderr); va_end(ap); if (severity > 0) { (void) cleanup(); exit(31+severity); } } static void getinfs(int dev, int fd, char *buf) { int cnt; int i; if (lseek(fd, SBLOCK * DEV_BSIZE, 0) != SBLOCK * DEV_BSIZE) { perr(10, "Unable to lseek on input\n"); } cnt = SBSIZE/DEV_BSIZE; for (i = 0; i < cnt; i++) { if (g_read(dev, fd, (char *)buf + i*DEV_BSIZE, DEV_BSIZE) != DEV_BSIZE) { perr(10, "Unable to read on input\n"); } } } static void getoutfs(int dev, int fd, char *buf, int verify) { int cnt; int i; errno = 0; if (lseek(fd, SBLOCK * DEV_BSIZE, 0) != SBLOCK * DEV_BSIZE) { prompt(verify, "Unable to lseek on output\n", errno); } cnt = SBSIZE/DEV_BSIZE; for (i = 0; i < cnt; i++) { if (g_read(dev, fd, (char *)buf + i*DEV_BSIZE, DEV_BSIZE) != DEV_BSIZE) { prompt(verify, "Unable to read on output\n", errno); } } } static char * getfslabel(struct fs *sb) { int i; int blk; /* * is there room for label? */ if (sb->fs_cpc <= 0) return (nolabel); /* * calculate the available blocks for each rotational position */ blk = sb->fs_spc * sb->fs_cpc / sb->fs_nspf; for (i = 0; i < blk; i += sb->fs_frag) /* void */; i -= sb->fs_frag; blk = i / sb->fs_frag; return ((char *)&(fs_rotbl(sb)[blk])); } static char * getvolabel(struct fs *sb) { char *p; int i; p = getfslabel(sb); if (p == nolabel || p == NULL) return (nolabel); for (i = 0; *p && i < 6; p++, i++) ; p++; return (p); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.1 */ #define LOG #define MINUSA 1 #define MINUSS 2 #define FIL_SYS 1 #define DEV_IN 2 #define VOL_IN 3 #define DEV_OUT 4 #define VOL_OUT 5 #define INPUT 0 #define OUTPUT 1 #define T_TYPE 0xfd187e20 /* like FsMAGIC */ #define EQ(X,Y,Z) !strncmp(X,Y,Z) #define NOT_EQ(X,Y,Z) strncmp(X,Y,Z) #define BLKSIZ 512 /* use physical blocks */ #define _2_DAYS 172800L #define MAX_BLKS 20 #define Ft800x10 15L #define Ft1600x4 22L #define Ft1600x10 28L #define Ft1600x16 30L #define Ft1600x32 32L #define Ft6250x10 90L #define Ft6250x16 95L #define Ft6250x32 115L #define Ft6250x50 120L #define BUFCNT 2 #define _128K 131072 /* * Special tape drive types. */ #define A_DRIVE 1 /* 3B15 Accellerated Tape Ctlr (32 blks/rec) */ #define C_DRIVE 2 /* 3B15 Compatibility Mode (10 blks/rec) */ #define K_DRIVE 3 /* 3B20 Kennedy tape drive (4 blks/rec max) */ #define T_DRIVE 4 /* 3B20 Tape File Controller (50 blks/rec) */ /* Synchronization flags */ #define P_NONE 0 /* Null message */ #define P_READ 1 /* Read allowed */ #define P_WRITE 2 /* Write allowed */ #define P_DONE 3 /* Done reading */ #define P_ABORT 4 /* Abort due to unrecoverable error */ #define P_TEST 5 # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # msgid "-a" msgid "-block" msgid "-bpi" msgid "-buf" msgid "-e" msgid "-feet" msgid "-nosh" msgid "-r" msgid "-reel" msgid "-s" msgid "-y" msgid "/dev/tty" msgid "/usr/bin/sh" msgid "3B15" msgid "3B2" msgid "PS1" msgid "PS2" msgid "VOLCOPY" msgid "Volcopy" msgid "\nReel %.6s" msgid "cp /tmp/FSJUNK /var/adm/log/filesave.log" msgid "echo \"%s;%.6s;%.6s -> %s;%.6s;%.6s on %.24s\" >>/var/adm/log/filesave.log" msgid "r" msgid "rm /tmp/FSJUNK" msgid "tail -200 /var/adm/log/filesave.log >/tmp/FSJUNK"