src/sbin/ncheck_ffs/ncheck_ffs.c

698 lines
18 KiB
C

/* $OpenBSD: ncheck_ffs.c,v 1.56 2024/04/23 13:34:50 jsg Exp $ */
/*-
* Copyright (c) 1995, 1996 SigmaSoft, Th. Lockert <tholo@sigmasoft.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*-
* Copyright (c) 1980, 1988, 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/param.h> /* MAXBSIZE DEV_BSIZE */
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/disklabel.h>
#include <sys/dkio.h>
#include <ufs/ffs/fs.h>
#include <ufs/ufs/dir.h>
#include <ufs/ufs/dinode.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <limits.h>
#include <fstab.h>
#include <errno.h>
#include <err.h>
#include <util.h>
#define DIP(dp, field) \
((sblock->fs_magic == FS_UFS1_MAGIC) ? \
((struct ufs1_dinode *)(dp))->field : \
((struct ufs2_dinode *)(dp))->field)
char *disk; /* name of the disk file */
char rdisk[PATH_MAX];/* resolved name of the disk file */
int diskfd; /* disk file descriptor */
struct fs *sblock; /* the file system super block */
char sblock_buf[MAXBSIZE];
int sblock_try[] = SBLOCKSEARCH; /* possible superblock locations */
ufsino_t *ilist; /* list of inodes to check */
int ninodes; /* number of inodes in list */
int sflag; /* only suid and special files */
int aflag; /* print the . and .. entries too */
int mflag; /* verbose output */
int iflag; /* specific inode */
char *format; /* output format */
struct disklabel lab;
struct icache_s {
ufsino_t ino;
union {
struct ufs1_dinode dp1;
struct ufs2_dinode dp2;
} di;
} *icache;
int nicache;
int maxicache;
void addinode(ufsino_t inum);
void *getino(ufsino_t inum);
void findinodes(ufsino_t);
void bread(daddr_t, char *, int);
__dead void usage(void);
void scanonedir(ufsino_t, const char *);
void dirindir(ufsino_t, daddr_t, int, off_t *, const char *);
void searchdir(ufsino_t, daddr_t, long, off_t, const char *);
int matchino(const void *, const void *);
int matchcache(const void *, const void *);
void cacheino(ufsino_t, void *);
void *cached(ufsino_t);
int main(int, char *[]);
char *rawname(char *);
void format_entry(const char *, struct direct *);
/*
* Check to see if the indicated inodes are the same
*/
int
matchino(const void *key, const void *val)
{
ufsino_t k = *(ufsino_t *)key;
ufsino_t v = *(ufsino_t *)val;
if (k < v)
return -1;
else if (k > v)
return 1;
return 0;
}
/*
* Check if the indicated inode match the entry in the cache
*/
int
matchcache(const void *key, const void *val)
{
ufsino_t ino = *(ufsino_t *)key;
struct icache_s *ic = (struct icache_s *)val;
if (ino < ic->ino)
return -1;
else if (ino > ic->ino)
return 1;
return 0;
}
/*
* Add an inode to the cached entries
*/
void
cacheino(ufsino_t ino, void *dp)
{
if (nicache == maxicache) {
struct icache_s *newicache;
/* grow exponentially */
maxicache += 10 + maxicache/2;
newicache = reallocarray(icache, maxicache, sizeof(*icache));
if (newicache == NULL)
errx(1, "malloc");
icache = newicache;
}
icache[nicache].ino = ino;
if (sblock->fs_magic == FS_UFS1_MAGIC)
icache[nicache++].di.dp1 = *(struct ufs1_dinode *)dp;
else
icache[nicache++].di.dp2 = *(struct ufs2_dinode *)dp;
}
/*
* Get a cached inode
*/
void *
cached(ufsino_t ino)
{
struct icache_s *ic;
void *dp = NULL;
ic = bsearch(&ino, icache, nicache, sizeof(*icache), matchcache);
if (ic != NULL) {
if (sblock->fs_magic == FS_UFS1_MAGIC)
dp = &ic->di.dp1;
else
dp = &ic->di.dp2;
}
return (dp);
}
/*
* Walk the inode list for a filesystem to find all allocated inodes
* Remember inodes we want to give information about and cache all
* inodes pointing to directories
*/
void
findinodes(ufsino_t maxino)
{
ufsino_t ino;
void *dp;
mode_t mode;
for (ino = ROOTINO; ino < maxino; ino++) {
dp = getino(ino);
mode = DIP(dp, di_mode) & IFMT;
if (!mode)
continue;
if (mode == IFDIR)
cacheino(ino, dp);
if (iflag ||
(sflag && (mode == IFDIR ||
((DIP(dp, di_mode) & (ISGID | ISUID)) == 0 &&
(mode == IFREG || mode == IFLNK)))))
continue;
addinode(ino);
}
}
/*
* Get a specified inode from disk. Attempt to minimize reads to once
* per cylinder group
*/
void *
getino(ufsino_t inum)
{
static char *itab = NULL;
static daddr_t iblk = -1;
void *dp;
size_t dsize;
if (inum < ROOTINO || inum >= sblock->fs_ncg * sblock->fs_ipg)
return NULL;
if ((dp = cached(inum)) != NULL)
return dp;
if (sblock->fs_magic == FS_UFS1_MAGIC)
dsize = sizeof(struct ufs1_dinode);
else
dsize = sizeof(struct ufs2_dinode);
if ((inum / sblock->fs_ipg) != iblk || itab == NULL) {
iblk = inum / sblock->fs_ipg;
if (itab == NULL &&
(itab = reallocarray(NULL, sblock->fs_ipg, dsize)) == NULL)
errx(1, "no memory for inodes");
bread(fsbtodb(sblock, cgimin(sblock, iblk)), itab,
sblock->fs_ipg * dsize);
}
return itab + (inum % sblock->fs_ipg) * dsize;
}
/*
* Read a chunk of data from the disk.
* Try to recover from hard errors by reading in sector sized pieces.
* Error recovery is attempted at most BREADEMAX times before seeking
* consent from the operator to continue.
*/
int breaderrors = 0;
#define BREADEMAX 32
void
bread(daddr_t blkno, char *buf, int size)
{
off_t offset;
int cnt, i;
u_int32_t secsize = lab.d_secsize;
offset = blkno * DEV_BSIZE;
loop:
if ((cnt = pread(diskfd, buf, size, offset)) == size)
return;
if (blkno + (size / DEV_BSIZE) >
fsbtodb(sblock, sblock->fs_ffs1_size)) {
/*
* Trying to read the final fragment.
*
* NB - dump only works in TP_BSIZE blocks, hence
* rounds `DEV_BSIZE' fragments up to TP_BSIZE pieces.
* It should be smarter about not actually trying to
* read more than it can get, but for the time being
* we punt and scale back the read only when it gets
* us into trouble. (mkm 9/25/83)
*/
size -= secsize;
goto loop;
}
if (cnt == -1)
warnx("read error from %s: %s: [block %lld]: count=%d",
disk, strerror(errno), (long long)blkno, size);
else
warnx("short read error from %s: [block %lld]: count=%d, "
"got=%d", disk, (long long)blkno, size, cnt);
if (++breaderrors > BREADEMAX)
errx(1, "More than %d block read errors from %s", BREADEMAX,
disk);
/*
* Zero buffer, then try to read each sector of buffer separately.
*/
memset(buf, 0, size);
for (i = 0; i < size; i += secsize, buf += secsize) {
if ((cnt = pread(diskfd, buf, secsize, offset + i)) ==
secsize)
continue;
if (cnt == -1) {
warnx("read error from %s: %s: [sector %lld]: "
"count=%u", disk, strerror(errno),
(long long)(offset + i) / DEV_BSIZE, secsize);
continue;
}
warnx("short read error from %s: [sector %lld]: count=%u, "
"got=%d", disk, (long long)(offset + i) / DEV_BSIZE,
secsize, cnt);
}
}
/*
* Add an inode to the in-memory list of inodes to dump
*/
void
addinode(ufsino_t ino)
{
ufsino_t *newilist;
newilist = reallocarray(ilist, ninodes + 1, sizeof(*ilist));
if (newilist == NULL)
errx(4, "not enough memory to allocate tables");
ilist = newilist;
ilist[ninodes] = ino;
ninodes++;
}
/*
* Scan the directory pointer at by ino
*/
void
scanonedir(ufsino_t ino, const char *path)
{
void *dp;
off_t filesize;
int i;
if ((dp = cached(ino)) == NULL)
return;
filesize = (off_t)DIP(dp, di_size);
for (i = 0; filesize > 0 && i < NDADDR; i++) {
if (DIP(dp, di_db[i]) != 0) {
searchdir(ino, DIP(dp, di_db[i]),
sblksize(sblock, DIP(dp, di_size), i),
filesize, path);
}
filesize -= sblock->fs_bsize;
}
for (i = 0; filesize > 0 && i < NIADDR; i++) {
if (DIP(dp, di_ib[i]))
dirindir(ino, DIP(dp, di_ib[i]), i, &filesize, path);
}
}
/*
* Read indirect blocks, and pass the data blocks to be searched
* as directories.
*/
void
dirindir(ufsino_t ino, daddr_t blkno, int ind_level, off_t *filesizep,
const char *path)
{
int i;
void *idblk;
if ((idblk = malloc(sblock->fs_bsize)) == NULL)
errx(1, "dirindir: cannot allocate indirect memory.\n");
bread(fsbtodb(sblock, blkno), idblk, (int)sblock->fs_bsize);
if (ind_level <= 0) {
for (i = 0; *filesizep > 0 && i < NINDIR(sblock); i++) {
if (sblock->fs_magic == FS_UFS1_MAGIC)
blkno = ((int32_t *)idblk)[i];
else
blkno = ((int64_t *)idblk)[i];
if (blkno != 0)
searchdir(ino, blkno, sblock->fs_bsize,
*filesizep, path);
*filesizep -= sblock->fs_bsize;
}
} else {
ind_level--;
for (i = 0; *filesizep > 0 && i < NINDIR(sblock); i++) {
if (sblock->fs_magic == FS_UFS1_MAGIC)
blkno = ((int32_t *)idblk)[i];
else
blkno = ((int64_t *)idblk)[i];
if (blkno != 0)
dirindir(ino, blkno, ind_level, filesizep,
path);
}
}
free(idblk);
}
/*
* Scan a disk block containing directory information looking to see if
* any of the entries are on the dump list and to see if the directory
* contains any subdirectories.
*/
void
searchdir(ufsino_t ino, daddr_t blkno, long size, off_t filesize,
const char *path)
{
char *dblk;
struct direct *dp;
void *di;
mode_t mode;
char *npath;
ufsino_t subino;
long loc;
if ((dblk = malloc(sblock->fs_bsize)) == NULL)
errx(1, "searchdir: cannot allocate directory memory.");
bread(fsbtodb(sblock, blkno), dblk, (int)size);
if (filesize < size)
size = filesize;
for (loc = 0; loc < size; ) {
dp = (struct direct *)(dblk + loc);
if (dp->d_reclen == 0) {
warnx("corrupted directory, inode %llu",
(unsigned long long)ino);
break;
}
loc += dp->d_reclen;
if (dp->d_ino == 0)
continue;
if (dp->d_name[0] == '.') {
if (!aflag && (dp->d_name[1] == '\0' ||
(dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
continue;
}
di = getino(dp->d_ino);
mode = DIP(di, di_mode) & IFMT;
subino = dp->d_ino;
if (bsearch(&subino, ilist, ninodes, sizeof(*ilist), matchino)) {
if (format) {
format_entry(path, dp);
} else {
if (mflag)
printf("mode %-6o uid %-5u gid %-5u ino ",
DIP(di, di_mode), DIP(di, di_uid),
DIP(di, di_gid));
printf("%-7llu %s/%s%s\n",
(unsigned long long)dp->d_ino, path,
dp->d_name, mode == IFDIR ? "/." : "");
}
}
if (mode == IFDIR) {
if (dp->d_name[0] == '.') {
if (dp->d_name[1] == '\0' ||
(dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
continue;
}
if (asprintf(&npath, "%s/%s", path, dp->d_name) == -1)
errx(1, "malloc");
scanonedir(dp->d_ino, npath);
free(npath);
}
}
free(dblk);
}
char *
rawname(char *name)
{
static char newname[PATH_MAX];
char *p;
if ((p = strrchr(name, '/')) == NULL)
return name;
*p = '\0';
strlcpy(newname, name, sizeof newname - 2);
*p++ = '/';
strlcat(newname, "/r", sizeof newname);
strlcat(newname, p, sizeof newname);
return(newname);
}
__dead void
usage(void)
{
extern char *__progname;
fprintf(stderr,
"usage: %s [-ams] [-f format] [-i number ...] filesystem\n",
__progname);
exit(3);
}
int
main(int argc, char *argv[])
{
struct stat stblock;
struct fstab *fsp;
unsigned long long ullval;
ssize_t n;
char *ep;
int c, i;
while ((c = getopt(argc, argv, "af:i:ms")) != -1)
switch (c) {
case 'a':
aflag = 1;
break;
case 'i':
iflag = 1;
errno = 0;
ullval = strtoull(optarg, &ep, 10);
if (optarg[0] == '\0' || *ep != '\0')
errx(1, "%s is not a number",
optarg);
if ((errno == ERANGE && ullval == ULLONG_MAX) ||
(ufsino_t)ullval != ullval)
errx(1, "%s is out of range",
optarg);
addinode((ufsino_t)ullval);
while (optind < argc) {
errno = 0;
ullval = strtoull(argv[optind], &ep, 10);
if (argv[optind][0] == '\0' || *ep != '\0')
break;
if ((errno == ERANGE && ullval == ULLONG_MAX)
|| (ufsino_t)ullval != ullval)
errx(1, "%s is out of range",
argv[optind]);
addinode((ufsino_t)ullval);
optind++;
}
break;
case 'f':
format = optarg;
break;
case 'm':
mflag = 1;
break;
case 's':
sflag = 1;
break;
default:
usage();
exit(2);
}
if (optind != argc - 1 || (mflag && format))
usage();
disk = argv[optind];
if ((diskfd = opendev(disk, O_RDONLY, 0, NULL)) >= 0) {
if (fstat(diskfd, &stblock))
err(1, "cannot stat %s", disk);
if (S_ISCHR(stblock.st_mode))
goto gotdev;
close(diskfd);
}
if (realpath(disk, rdisk) == NULL)
err(1, "cannot find real path for %s", disk);
disk = rdisk;
if (stat(disk, &stblock) == -1)
err(1, "cannot stat %s", disk);
if (S_ISBLK(stblock.st_mode)) {
disk = rawname(disk);
} else if (!S_ISCHR(stblock.st_mode)) {
if ((fsp = getfsfile(disk)) == NULL)
err(1, "could not find file system %s", disk);
disk = rawname(fsp->fs_spec);
}
if ((diskfd = opendev(disk, O_RDONLY, 0, NULL)) == -1)
err(1, "cannot open %s", disk);
gotdev:
if (ioctl(diskfd, DIOCGDINFO, (char *)&lab) == -1)
err(1, "ioctl (DIOCGDINFO)");
if (ioctl(diskfd, DIOCGPDINFO, (char *)&lab) == -1)
err(1, "ioctl (DIOCGPDINFO)");
if (pledge("stdio", NULL) == -1)
err(1, "pledge");
sblock = (struct fs *)sblock_buf;
for (i = 0; sblock_try[i] != -1; i++) {
n = pread(diskfd, sblock, SBLOCKSIZE, (off_t)sblock_try[i]);
if (n == SBLOCKSIZE && (sblock->fs_magic == FS_UFS1_MAGIC ||
(sblock->fs_magic == FS_UFS2_MAGIC &&
sblock->fs_sblockloc == sblock_try[i])) &&
sblock->fs_bsize <= MAXBSIZE &&
sblock->fs_bsize >= sizeof(struct fs))
break;
}
if (sblock_try[i] == -1)
errx(1, "cannot find filesystem superblock");
findinodes(sblock->fs_ipg * sblock->fs_ncg);
if (!format)
printf("%s:\n", disk);
scanonedir(ROOTINO, "");
close(diskfd);
exit (0);
}
void
format_entry(const char *path, struct direct *dp)
{
static size_t size;
static char *buf;
char *src, *dst, *newbuf;
int len;
if (buf == NULL) {
if ((buf = malloc(LINE_MAX)) == NULL)
err(1, "malloc");
size = LINE_MAX;
}
for (src = format, dst = buf; *src; src++) {
/* Need room for at least one character in buf. */
if (size <= dst - buf) {
expand_buf:
if ((newbuf = reallocarray(buf, size, 2)) == NULL)
err(1, "realloc");
buf = newbuf;
size = size * 2;
}
if (src[0] =='\\') {
switch (src[1]) {
case 'I':
len = snprintf(dst, size - (dst - buf), "%llu",
(unsigned long long)dp->d_ino);
if (len < 0 || len >= size - (dst - buf))
goto expand_buf;
dst += len;
break;
case 'P':
len = snprintf(dst, size - (dst - buf), "%s/%s",
path, dp->d_name);
if (len < 0 || len >= size - (dst - buf))
goto expand_buf;
dst += len;
break;
case '\\':
*dst++ = '\\';
break;
case '0':
/* XXX - support other octal numbers? */
*dst++ = '\0';
break;
case 'a':
*dst++ = '\a';
break;
case 'b':
*dst++ = '\b';
break;
case 'e':
*dst++ = '\e';
break;
case 'f':
*dst++ = '\f';
break;
case 'n':
*dst++ = '\n';
break;
case 'r':
*dst++ = '\r';
break;
case 't':
*dst++ = '\t';
break;
case 'v':
*dst++ = '\v';
break;
default:
*dst++ = src[1];
break;
}
src++;
} else
*dst++ = *src;
}
fwrite(buf, dst - buf, 1, stdout);
}