/*
* Copyright (c) 1990, 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <libadm.h>
#include <note.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/mntent.h>
#include <sys/filio.h>
#include <sys/fs/ufs_fs.h>
#include <sys/vnode.h>
#include <sys/fs/ufs_acl.h>
#include <sys/fs/ufs_inode.h>
#include <sys/fs/ufs_log.h>
#define _KERNEL
#include <sys/fs/ufs_fsdir.h>
#undef _KERNEL
#include <sys/mnttab.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <sys/vfstab.h>
#include <sys/lockfs.h>
#include <errno.h>
#include <sys/cmn_err.h>
#include <sys/dkio.h>
#include <sys/vtoc.h>
#include <sys/efi_partition.h>
#include <fslib.h>
#include <inttypes.h>
#include "fsck.h"
caddr_t mount_point = NULL;
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 : "<null>",
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 him.
*/
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);
}