#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ulfs_lookup.c,v 1.48 2024/09/08 09:36:52 rillig Exp $");
#ifdef _KERNEL_OPT
#include "opt_lfs.h"
#endif
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/namei.h>
#include <sys/buf.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/kernel.h>
#include <sys/kauth.h>
#include <sys/proc.h>
#include <sys/kmem.h>
#include <ufs/lfs/lfs.h>
#include <ufs/lfs/lfs_accessors.h>
#include <ufs/lfs/lfs_extern.h>
#include <ufs/lfs/ulfs_inode.h>
#ifdef LFS_DIRHASH
#include <ufs/lfs/ulfs_dirhash.h>
#endif
#include <ufs/lfs/ulfsmount.h>
#include <ufs/lfs/ulfs_extern.h>
#include <ufs/lfs/ulfs_bswap.h>
#include <miscfs/genfs/genfs.h>
#ifdef DIAGNOSTIC
int lfs_dirchk = 1;
#else
int lfs_dirchk = 0;
#endif
int
ulfs_lookup(void *v)
{
struct vop_lookup_v2_args
*ap = v;
struct vnode *vdp = ap->a_dvp;
struct inode *dp = VTOI(vdp);
struct buf *bp;
LFS_DIRHEADER *ep;
int entryoffsetinblock;
enum {
NONE,
COMPACT,
FOUND,
} slotstatus;
doff_t slotoffset;
int slotsize;
int slotfreespace;
int slotneeded;
int numdirpasses;
doff_t endsearch;
doff_t prevoff;
struct vnode *tdp;
doff_t enduseful;
u_long bmask;
int error;
struct vnode **vpp = ap->a_vpp;
struct componentname *cnp = ap->a_cnp;
kauth_cred_t cred = cnp->cn_cred;
int flags;
int nameiop = cnp->cn_nameiop;
struct lfs *fs = dp->i_lfs;
int dirblksiz = fs->um_dirblksiz;
ino_t foundino;
struct ulfs_lookup_results *results;
int iswhiteout;
flags = cnp->cn_flags;
bp = NULL;
slotoffset = -1;
*vpp = NULL;
endsearch = 0;
if ((error = VOP_ACCESS(vdp, VEXEC, cred)) != 0)
return (error);
if ((flags & ISLASTCN) && (vdp->v_mount->mnt_flag & MNT_RDONLY) &&
(nameiop == DELETE || nameiop == RENAME))
return (EROFS);
if (cache_lookup(vdp, cnp->cn_nameptr, cnp->cn_namelen,
cnp->cn_nameiop, cnp->cn_flags, &iswhiteout, vpp)) {
if (iswhiteout) {
cnp->cn_flags |= ISWHITEOUT;
}
return *vpp == NULLVP ? ENOENT : 0;
}
if (VOP_ISLOCKED(vdp) != LK_EXCLUSIVE)
return ENOLCK;
results = &dp->i_crap;
dp->i_crapcounter++;
if (iswhiteout) {
cnp->cn_flags |= ISWHITEOUT;
}
slotstatus = FOUND;
slotfreespace = slotsize = slotneeded = 0;
if ((nameiop == CREATE || nameiop == RENAME) && (flags & ISLASTCN)) {
slotstatus = NONE;
slotneeded = LFS_DIRECTSIZ(fs, cnp->cn_namelen);
}
bmask = vdp->v_mount->mnt_stat.f_iosize - 1;
#ifdef LFS_DIRHASH
if (ulfsdirhash_build(dp) == 0) {
enduseful = dp->i_size;
if (slotstatus != FOUND) {
slotoffset = ulfsdirhash_findfree(dp, slotneeded,
&slotsize);
if (slotoffset >= 0) {
slotstatus = COMPACT;
enduseful = ulfsdirhash_enduseful(dp);
if (enduseful < 0)
enduseful = dp->i_size;
}
}
numdirpasses = 1;
entryoffsetinblock = 0;
switch (ulfsdirhash_lookup(dp, cnp->cn_nameptr, cnp->cn_namelen,
&results->ulr_offset, &bp, nameiop == DELETE ? &prevoff : NULL)) {
case 0:
ep = (LFS_DIRHEADER *)((char *)bp->b_data +
(results->ulr_offset & bmask));
goto foundentry;
case ENOENT:
results->ulr_offset = roundup(dp->i_size, dirblksiz);
goto notfound;
default:
break;
}
}
#endif
if (nameiop != LOOKUP || results->ulr_diroff == 0 ||
results->ulr_diroff >= dp->i_size) {
entryoffsetinblock = 0;
results->ulr_offset = 0;
numdirpasses = 1;
} else {
results->ulr_offset = results->ulr_diroff;
if ((entryoffsetinblock = results->ulr_offset & bmask) &&
(error = ulfs_blkatoff(vdp, (off_t)results->ulr_offset,
NULL, &bp, false)))
goto out;
numdirpasses = 2;
namecache_count_2passes();
}
prevoff = results->ulr_offset;
endsearch = roundup(dp->i_size, dirblksiz);
enduseful = 0;
searchloop:
while (results->ulr_offset < endsearch) {
preempt_point();
if ((results->ulr_offset & bmask) == 0) {
if (bp != NULL)
brelse(bp, 0);
error = ulfs_blkatoff(vdp, (off_t)results->ulr_offset,
NULL, &bp, false);
if (error)
goto out;
entryoffsetinblock = 0;
}
if (slotstatus == NONE &&
(entryoffsetinblock & (dirblksiz - 1)) == 0) {
slotoffset = -1;
slotfreespace = 0;
}
KASSERT(bp != NULL);
ep = (LFS_DIRHEADER *)((char *)bp->b_data + entryoffsetinblock);
if (lfs_dir_getreclen(fs, ep) == 0 ||
(lfs_dirchk && ulfs_dirbadentry(vdp, ep, entryoffsetinblock))) {
int i;
ulfs_dirbad(dp, results->ulr_offset, "mangled entry");
i = dirblksiz - (entryoffsetinblock & (dirblksiz - 1));
results->ulr_offset += i;
entryoffsetinblock += i;
continue;
}
if (slotstatus != FOUND) {
int size = lfs_dir_getreclen(fs, ep);
if (lfs_dir_getino(fs, ep) != 0)
size -= LFS_DIRSIZ(fs, ep);
if (size > 0) {
if (size >= slotneeded) {
slotstatus = FOUND;
slotoffset = results->ulr_offset;
slotsize = lfs_dir_getreclen(fs, ep);
} else if (slotstatus == NONE) {
slotfreespace += size;
if (slotoffset == -1)
slotoffset = results->ulr_offset;
if (slotfreespace >= slotneeded) {
slotstatus = COMPACT;
slotsize = results->ulr_offset +
lfs_dir_getreclen(fs, ep) -
slotoffset;
}
}
}
}
if (lfs_dir_getino(fs, ep)) {
int namlen;
namlen = lfs_dir_getnamlen(fs, ep);
if (namlen == cnp->cn_namelen &&
!memcmp(cnp->cn_nameptr, lfs_dir_nameptr(fs, ep),
(unsigned)namlen)) {
#ifdef LFS_DIRHASH
foundentry:
#endif
if (!FSFMT(vdp) && lfs_dir_gettype(fs, ep) == LFS_DT_WHT) {
slotstatus = FOUND;
slotoffset = results->ulr_offset;
slotsize = lfs_dir_getreclen(fs, ep);
results->ulr_reclen = slotsize;
enduseful = endsearch;
cnp->cn_flags |= ISWHITEOUT;
numdirpasses--;
goto notfound;
}
foundino = lfs_dir_getino(fs, ep);
results->ulr_reclen = lfs_dir_getreclen(fs, ep);
goto found;
}
}
prevoff = results->ulr_offset;
results->ulr_offset += lfs_dir_getreclen(fs, ep);
entryoffsetinblock += lfs_dir_getreclen(fs, ep);
if (lfs_dir_getino(fs, ep))
enduseful = results->ulr_offset;
}
notfound:
if (numdirpasses == 2) {
numdirpasses--;
results->ulr_offset = 0;
endsearch = results->ulr_diroff;
goto searchloop;
}
if (bp != NULL)
brelse(bp, 0);
if ((nameiop == CREATE || nameiop == RENAME ||
(nameiop == DELETE &&
(cnp->cn_flags & DOWHITEOUT) &&
(cnp->cn_flags & ISWHITEOUT))) &&
(flags & ISLASTCN) && dp->i_nlink != 0) {
error = VOP_ACCESS(vdp, VWRITE, cred);
if (error)
goto out;
if (slotstatus == NONE) {
results->ulr_offset = roundup(dp->i_size, dirblksiz);
results->ulr_count = 0;
enduseful = results->ulr_offset;
} else if (nameiop == DELETE) {
results->ulr_offset = slotoffset;
if ((results->ulr_offset & (dirblksiz - 1)) == 0)
results->ulr_count = 0;
else
results->ulr_count =
results->ulr_offset - prevoff;
} else {
results->ulr_offset = slotoffset;
results->ulr_count = slotsize;
if (enduseful < slotoffset + slotsize)
enduseful = slotoffset + slotsize;
}
results->ulr_endoff = roundup(enduseful, dirblksiz);
#if 0
dp->i_state |= IN_CHANGE | IN_UPDATE;
#endif
error = EJUSTRETURN;
goto out;
}
if (nameiop != CREATE) {
cache_enter(vdp, *vpp, cnp->cn_nameptr, cnp->cn_namelen,
cnp->cn_flags);
}
error = ENOENT;
goto out;
found:
if (numdirpasses == 2)
namecache_count_pass2();
if (results->ulr_offset + LFS_DIRSIZ(fs, ep) > dp->i_size) {
ulfs_dirbad(dp, results->ulr_offset, "i_size too small");
dp->i_size =
results->ulr_offset + LFS_DIRSIZ(fs, ep);
DIP_ASSIGN(dp, size, dp->i_size);
dp->i_state |= IN_CHANGE | IN_UPDATE;
}
brelse(bp, 0);
if ((flags & ISLASTCN) && nameiop == LOOKUP)
results->ulr_diroff = results->ulr_offset &~ (dirblksiz - 1);
if (nameiop == DELETE && (flags & ISLASTCN)) {
if ((results->ulr_offset & (dirblksiz - 1)) == 0)
results->ulr_count = 0;
else
results->ulr_count = results->ulr_offset - prevoff;
if (dp->i_number == foundino) {
vref(vdp);
tdp = vdp;
} else {
error = vcache_get(vdp->v_mount,
&foundino, sizeof(foundino), &tdp);
if (error)
goto out;
}
error = VOP_ACCESS(vdp, VWRITE, cred);
if (error) {
vrele(tdp);
goto out;
}
if (dp->i_mode & ISVTX) {
error = kauth_authorize_vnode(cred, KAUTH_VNODE_DELETE,
tdp, vdp, genfs_can_sticky(vdp, cred, dp->i_uid,
VTOI(tdp)->i_uid));
if (error) {
vrele(tdp);
error = EPERM;
goto out;
}
}
*vpp = tdp;
error = 0;
goto out;
}
if (nameiop == RENAME && (flags & ISLASTCN)) {
error = VOP_ACCESS(vdp, VWRITE, cred);
if (error)
goto out;
if (dp->i_number == foundino) {
error = EISDIR;
goto out;
}
error = vcache_get(vdp->v_mount,
&foundino, sizeof(foundino), &tdp);
if (error)
goto out;
*vpp = tdp;
error = 0;
goto out;
}
if (dp->i_number == foundino) {
vref(vdp);
*vpp = vdp;
} else {
error = vcache_get(vdp->v_mount,
&foundino, sizeof(foundino), &tdp);
if (error)
goto out;
*vpp = tdp;
}
cache_enter(vdp, *vpp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_flags);
error = 0;
out:
return error;
}
void
ulfs_dirbad(struct inode *ip, doff_t offset, const char *how)
{
struct mount *mp;
mp = ITOV(ip)->v_mount;
printf("%s: bad dir ino %llu at offset %d: %s\n",
mp->mnt_stat.f_mntonname, (unsigned long long)ip->i_number,
offset, how);
if ((mp->mnt_flag & MNT_RDONLY) == 0)
panic("bad dir");
}
int
ulfs_dirbadentry(struct vnode *dp, LFS_DIRHEADER *ep, int entryoffsetinblock)
{
int i;
int namlen;
unsigned reclen;
struct ulfsmount *ump = VFSTOULFS(dp->v_mount);
struct lfs *fs = ump->um_lfs;
int dirblksiz = fs->um_dirblksiz;
const char *name;
namlen = lfs_dir_getnamlen(fs, ep);
reclen = lfs_dir_getreclen(fs, ep);
if ((reclen & 0x3) != 0 ||
reclen > dirblksiz - (entryoffsetinblock & (dirblksiz - 1)) ||
reclen < LFS_DIRSIZ(fs, ep) || namlen > LFS_MAXNAMLEN) {
printf("First bad, reclen=%#x, DIRSIZ=%lu, namlen=%d, "
"flags=%#x, entryoffsetinblock=%d, dirblksiz = %d\n",
lfs_dir_getreclen(fs, ep),
(u_long)LFS_DIRSIZ(fs, ep),
namlen, dp->v_mount->mnt_flag, entryoffsetinblock,
dirblksiz);
goto bad;
}
if (lfs_dir_getino(fs, ep) == 0)
return (0);
name = lfs_dir_nameptr(fs, ep);
for (i = 0; i < namlen; i++)
if (name[i] == '\0') {
printf("Second bad\n");
goto bad;
}
if (name[i])
goto bad;
return (0);
bad:
return (1);
}
static void
ulfs_direntry_assign(struct lfs *fs, LFS_DIRHEADER *dirp,
const char *name, size_t namlen,
ino_t inum, unsigned dtype)
{
lfs_dir_setino(fs, dirp, inum);
lfs_dir_setnamlen(fs, dirp, namlen);
lfs_dir_settype(fs, dirp, dtype);
memcpy(lfs_dir_nameptr(fs, dirp), name, namlen);
lfs_dir_nameptr(fs, dirp)[namlen] = '\0';
}
int
ulfs_direnter(struct vnode *dvp, const struct ulfs_lookup_results *ulr,
struct vnode *tvp,
struct componentname *cnp, ino_t inum, unsigned dtype,
struct buf *newdirbp)
{
kauth_cred_t cr;
int newentrysize;
struct inode *dp;
struct buf *bp;
u_int dsize;
LFS_DIRHEADER *ep, *nep;
int error, ret, lfs_blkoff, loc, spacefree;
char *dirbuf;
struct timespec ts;
struct ulfsmount *ump = VFSTOULFS(dvp->v_mount);
struct lfs *fs = ump->um_lfs;
int dirblksiz = fs->um_dirblksiz;
const char *name;
unsigned namlen, reclen;
#ifdef LFS_DIRHASH
int dohashadd;
#endif
error = 0;
name = cnp->cn_nameptr;
namlen = cnp->cn_namelen;
cr = cnp->cn_cred;
dp = VTOI(dvp);
newentrysize = LFS_DIRECTSIZ(fs, namlen);
if (ulr->ulr_count == 0) {
if (ulr->ulr_offset & (dirblksiz - 1))
panic("ulfs_direnter: newblk");
if ((error = lfs_balloc(dvp, (off_t)ulr->ulr_offset, dirblksiz,
cr, B_CLRBUF | B_SYNC, &bp)) != 0) {
return (error);
}
dp->i_size = ulr->ulr_offset + dirblksiz;
DIP_ASSIGN(dp, size, dp->i_size);
dp->i_state |= IN_CHANGE | IN_UPDATE;
uvm_vnp_setsize(dvp, dp->i_size);
lfs_blkoff = ulr->ulr_offset & (ump->um_mountp->mnt_stat.f_iosize - 1);
ep = (LFS_DIRHEADER *)((char *)bp->b_data + lfs_blkoff);
ulfs_direntry_assign(fs, ep, name, namlen, inum, dtype);
lfs_dir_setreclen(fs, ep, dirblksiz);
#ifdef LFS_DIRHASH
if (dp->i_dirhash != NULL) {
ulfsdirhash_newblk(dp, ulr->ulr_offset);
ulfsdirhash_add(dp, ep, ulr->ulr_offset);
ulfsdirhash_checkblock(dp, (char *)bp->b_data + lfs_blkoff,
ulr->ulr_offset);
}
#endif
error = VOP_BWRITE(bp->b_vp, bp);
vfs_timestamp(&ts);
ret = lfs_update(dvp, &ts, &ts, UPDATE_DIROP);
if (error == 0)
return (ret);
return (error);
}
if (ulr->ulr_offset + ulr->ulr_count > dp->i_size) {
#ifdef DIAGNOSTIC
printf("ulfs_direnter: reached 4.2-only block, "
"not supposed to happen\n");
#endif
dp->i_size = ulr->ulr_offset + ulr->ulr_count;
DIP_ASSIGN(dp, size, dp->i_size);
dp->i_state |= IN_CHANGE | IN_UPDATE;
}
error = ulfs_blkatoff(dvp, (off_t)ulr->ulr_offset, &dirbuf, &bp, true);
if (error) {
return (error);
}
ep = (LFS_DIRHEADER *)dirbuf;
dsize = (lfs_dir_getino(fs, ep) != 0) ? LFS_DIRSIZ(fs, ep) : 0;
spacefree = lfs_dir_getreclen(fs, ep) - dsize;
for (loc = lfs_dir_getreclen(fs, ep); loc < ulr->ulr_count; ) {
nep = (LFS_DIRHEADER *)(dirbuf + loc);
lfs_dir_setreclen(fs, ep, dsize);
ep = LFS_NEXTDIR(fs, ep);
reclen = lfs_dir_getreclen(fs, nep);
loc += reclen;
if (lfs_dir_getino(fs, nep) == 0) {
spacefree += reclen;
lfs_dir_setino(fs, ep, 0);
dsize = 0;
continue;
}
dsize = LFS_DIRSIZ(fs, nep);
spacefree += reclen - dsize;
#ifdef LFS_DIRHASH
if (dp->i_dirhash != NULL)
ulfsdirhash_move(dp, nep,
ulr->ulr_offset + ((char *)nep - dirbuf),
ulr->ulr_offset + ((char *)ep - dirbuf));
#endif
memcpy((void *)ep, (void *)nep, dsize);
}
if (lfs_dir_getino(fs, ep) == 0 ||
(lfs_dir_getino(fs, ep) == ULFS_WINO &&
memcmp(lfs_dir_nameptr(fs, ep), name, namlen) == 0)) {
if (spacefree + dsize < newentrysize)
panic("ulfs_direnter: compact1");
reclen = spacefree + dsize;
#ifdef LFS_DIRHASH
dohashadd = (lfs_dir_getino(fs, ep) == 0);
#endif
} else {
if (spacefree < newentrysize)
panic("ulfs_direnter: compact2");
reclen = spacefree;
lfs_dir_setreclen(fs, ep, dsize);
ep = LFS_NEXTDIR(fs, ep);
#ifdef LFS_DIRHASH
dohashadd = 1;
#endif
}
ulfs_direntry_assign(fs, ep, name, namlen, inum, dtype);
lfs_dir_setreclen(fs, ep, reclen);
#ifdef LFS_DIRHASH
if (dp->i_dirhash != NULL && dohashadd)
ulfsdirhash_add(dp, ep, ulr->ulr_offset + ((char *)ep - dirbuf));
if (dp->i_dirhash != NULL)
ulfsdirhash_checkblock(dp, dirbuf -
(ulr->ulr_offset & (dirblksiz - 1)),
ulr->ulr_offset & ~(dirblksiz - 1));
#endif
error = VOP_BWRITE(bp->b_vp, bp);
dp->i_state |= IN_CHANGE | IN_UPDATE;
if (error == 0 && ulr->ulr_endoff && ulr->ulr_endoff < dp->i_size) {
#ifdef LFS_DIRHASH
if (dp->i_dirhash != NULL)
ulfsdirhash_dirtrunc(dp, ulr->ulr_endoff);
#endif
(void) lfs_truncate(dvp, (off_t)ulr->ulr_endoff, IO_SYNC, cr);
}
return (error);
}
int
ulfs_dirremove(struct vnode *dvp, const struct ulfs_lookup_results *ulr,
struct inode *ip, int flags, int isrmdir)
{
struct inode *dp = VTOI(dvp);
struct lfs *fs = dp->i_lfs;
LFS_DIRHEADER *ep;
struct buf *bp;
int error;
if (flags & DOWHITEOUT) {
error = ulfs_blkatoff(dvp, (off_t)ulr->ulr_offset, (void *)&ep,
&bp, true);
if (error)
return (error);
lfs_dir_setino(fs, ep, ULFS_WINO);
lfs_dir_settype(fs, ep, LFS_DT_WHT);
goto out;
}
if ((error = ulfs_blkatoff(dvp,
(off_t)(ulr->ulr_offset - ulr->ulr_count), (void *)&ep, &bp, true)) != 0)
return (error);
#ifdef LFS_DIRHASH
if (dp->i_dirhash != NULL)
ulfsdirhash_remove(dp, (ulr->ulr_count == 0) ? ep :
LFS_NEXTDIR(fs, ep), ulr->ulr_offset);
#endif
if (ulr->ulr_count == 0) {
lfs_dir_setino(fs, ep, 0);
} else {
lfs_dir_setreclen(fs, ep,
lfs_dir_getreclen(fs, ep) + ulr->ulr_reclen);
}
#ifdef LFS_DIRHASH
if (dp->i_dirhash != NULL) {
int dirblksiz = ip->i_lfs->um_dirblksiz;
ulfsdirhash_checkblock(dp, (char *)ep -
((ulr->ulr_offset - ulr->ulr_count) & (dirblksiz - 1)),
ulr->ulr_offset & ~(dirblksiz - 1));
}
#endif
out:
if (ip) {
ip->i_nlink--;
DIP_ASSIGN(ip, nlink, ip->i_nlink);
ip->i_state |= IN_CHANGE;
}
error = VOP_BWRITE(bp->b_vp, bp);
dp->i_state |= IN_CHANGE | IN_UPDATE;
if (ip != 0 && (ip->i_flags & SF_SNAPSHOT) != 0 &&
ip->i_nlink == 0)
ulfs_snapgone(ip);
return (error);
}
int
ulfs_dirrewrite(struct inode *dp, off_t offset,
struct inode *oip, ino_t newinum, int newtype,
int isrmdir, int iflags)
{
struct lfs *fs = dp->i_lfs;
struct buf *bp;
LFS_DIRHEADER *ep;
struct vnode *vdp = ITOV(dp);
int error;
error = ulfs_blkatoff(vdp, offset, (void *)&ep, &bp, true);
if (error)
return (error);
lfs_dir_setino(fs, ep, newinum);
lfs_dir_settype(fs, ep, newtype);
oip->i_nlink--;
DIP_ASSIGN(oip, nlink, oip->i_nlink);
oip->i_state |= IN_CHANGE;
error = VOP_BWRITE(bp->b_vp, bp);
dp->i_state |= iflags;
if ((oip->i_flags & SF_SNAPSHOT) != 0 && oip->i_nlink == 0)
ulfs_snapgone(oip);
return (error);
}
int
ulfs_dirempty(struct inode *ip, ino_t parentino, kauth_cred_t cred)
{
struct lfs *fs = ip->i_lfs;
doff_t off;
union lfs_dirtemplate dbuf;
LFS_DIRHEADER *dp = (LFS_DIRHEADER *)&dbuf;
int error, namlen;
const char *name;
size_t count;
#define MINDIRSIZ (sizeof (struct lfs_dirtemplate64) / 2)
for (off = 0; off < ip->i_size; off += lfs_dir_getreclen(fs, dp)) {
error = ulfs_bufio(UIO_READ, ITOV(ip), (void *)dp, MINDIRSIZ,
off, IO_NODELOCKED, cred, &count, NULL);
if (error || count != 0)
return (0);
if (lfs_dir_getreclen(fs, dp) == 0)
return (0);
if (lfs_dir_getino(fs, dp) == 0 ||
lfs_dir_getino(fs, dp) == ULFS_WINO)
continue;
namlen = lfs_dir_getnamlen(fs, dp);
name = lfs_dir_nameptr(fs, dp);
if (namlen > 2)
return (0);
if (name[0] != '.')
return (0);
if (namlen == 1 && lfs_dir_getino(fs, dp) == ip->i_number)
continue;
if (name[1] == '.' && lfs_dir_getino(fs, dp) == parentino)
continue;
return (0);
}
return (1);
}
#define ULFS_DIRRABLKS 0
int ulfs_dirrablks = ULFS_DIRRABLKS;
int
ulfs_blkatoff(struct vnode *vp, off_t offset, char **res, struct buf **bpp,
bool modify)
{
struct inode *ip __diagused;
struct buf *bp;
daddr_t lbn;
const int dirrablks = ulfs_dirrablks;
daddr_t *blks;
int *blksizes;
int run, error;
struct mount *mp = vp->v_mount;
const int bshift = mp->mnt_fs_bshift;
const int bsize = 1 << bshift;
off_t eof;
blks = kmem_alloc((1 + dirrablks) * sizeof(daddr_t), KM_SLEEP);
blksizes = kmem_alloc((1 + dirrablks) * sizeof(int), KM_SLEEP);
ip = VTOI(vp);
KASSERT(vp->v_size == ip->i_size);
GOP_SIZE(vp, vp->v_size, &eof, 0);
lbn = offset >> bshift;
for (run = 0; run <= dirrablks;) {
const off_t curoff = lbn << bshift;
const int size = MIN(eof - curoff, bsize);
if (size == 0) {
break;
}
KASSERT(curoff < eof);
blks[run] = lbn;
blksizes[run] = size;
lbn++;
run++;
if (size != bsize) {
break;
}
}
KASSERT(run >= 1);
error = breadn(vp, blks[0], blksizes[0], &blks[1], &blksizes[1],
run - 1, (modify ? B_MODIFY : 0), &bp);
if (error != 0) {
*bpp = NULL;
goto out;
}
if (res) {
*res = (char *)bp->b_data + (offset & (bsize - 1));
}
*bpp = bp;
out:
kmem_free(blks, (1 + dirrablks) * sizeof(daddr_t));
kmem_free(blksizes, (1 + dirrablks) * sizeof(int));
return error;
}