#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ufs_lookup.c,v 1.161 2026/01/22 03:24:19 riastradh Exp $");
#ifdef _KERNEL_OPT
#include "opt_ffs.h"
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/buf.h>
#include <sys/file.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/proc.h>
#include <sys/sdt.h>
#include <sys/stat.h>
#include <sys/systm.h>
#include <sys/vnode.h>
#include <sys/wapbl.h>
#include <ufs/ufs/dir.h>
#ifdef UFS_DIRHASH
#include <ufs/ufs/dirhash.h>
#endif
#include <ufs/ufs/inode.h>
#include <ufs/ufs/ufs_bswap.h>
#include <ufs/ufs/ufs_extern.h>
#include <ufs/ufs/ufs_wapbl.h>
#include <ufs/ufs/ufsmount.h>
#include <miscfs/genfs/genfs.h>
#ifdef DIAGNOSTIC
int dirchk = 1;
#else
int dirchk = 0;
#endif
#if BYTE_ORDER == LITTLE_ENDIAN
# define ENDIANSWAP(needswap) ((needswap) == 0)
#else
# define ENDIANSWAP(needswap) ((needswap) != 0)
#endif
#define NAMLEN(fsfmt, needswap, dp) \
((fsfmt) && ENDIANSWAP(needswap) ? (dp)->d_type : (dp)->d_namlen)
static void
ufs_dirswap(struct direct *dirp)
{
uint8_t tmp = dirp->d_namlen;
dirp->d_namlen = dirp->d_type;
dirp->d_type = tmp;
}
struct slotinfo {
enum {
NONE,
COMPACT,
FOUND,
} status;
doff_t offset;
int size;
int freespace;
int needed;
};
static void
calc_count(struct ufs_lookup_results *results, int dirblksiz, doff_t prevoff)
{
if ((results->ulr_offset & (dirblksiz - 1)) == 0)
results->ulr_count = 0;
else
results->ulr_count = results->ulr_offset - prevoff;
}
static void
slot_init(struct slotinfo *slot)
{
slot->status = FOUND;
slot->offset = -1;
slot->freespace = slot->size = slot->needed = 0;
}
#ifdef UFS_DIRHASH
static doff_t
slot_findfree(struct slotinfo *slot, struct inode *dp)
{
if (slot->status == FOUND)
return dp->i_size;
slot->offset = ufsdirhash_findfree(dp, slot->needed, &slot->size);
if (slot->offset < 0)
return dp->i_size;
slot->status = COMPACT;
doff_t enduseful = ufsdirhash_enduseful(dp);
if (enduseful < 0)
return dp->i_size;
return enduseful;
}
#endif
static void
slot_white(struct slotinfo *slot, uint16_t reclen,
struct ufs_lookup_results *results)
{
slot->status = FOUND;
slot->offset = results->ulr_offset;
slot->size = reclen;
results->ulr_reclen = slot->size;
}
static void
slot_update(struct slotinfo *slot, int size, uint16_t reclen, doff_t offset)
{
if (size >= slot->needed) {
slot->status = FOUND;
slot->offset = offset;
slot->size = reclen;
} else if (slot->status == NONE) {
slot->freespace += size;
if (slot->offset == -1)
slot->offset = offset;
if (slot->freespace >= slot->needed) {
slot->status = COMPACT;
slot->size = offset + reclen - slot->offset;
}
}
}
static int
slot_estimate(const struct slotinfo *slot, int dirblksiz, int nameiop,
doff_t prevoff, doff_t enduseful, const struct inode *ip,
struct ufs_lookup_results *results)
{
if (slot->status == NONE) {
results->ulr_offset = roundup(ip->i_size, dirblksiz);
results->ulr_count = 0;
enduseful = results->ulr_offset;
} else if (nameiop == DELETE) {
results->ulr_offset = slot->offset;
calc_count(results, dirblksiz, prevoff);
} else {
results->ulr_offset = slot->offset;
results->ulr_count = slot->size;
if (enduseful < slot->offset + slot->size)
enduseful = slot->offset + slot->size;
}
results->ulr_endoff = roundup(enduseful, dirblksiz);
#if 0
ip->i_flag |= IN_CHANGE | IN_UPDATE;
#endif
return SET_ERROR(EJUSTRETURN);
}
static int
ufs_can_delete(struct vnode *tdp, struct vnode *vdp, struct inode *ip,
kauth_cred_t cred)
{
int error;
#ifdef UFS_ACL
error = VOP_ACCESS(vdp, VEXEC, cred);
if (error)
goto out;
#if 0
error = VOP_ACCESSX(tdp, VDELETE, cred);
if (error == 0)
return 0;
#endif
error = VOP_ACCESSX(vdp, VDELETE_CHILD, cred);
if (error == 0)
return 0;
error = VOP_ACCESSX(vdp, VEXPLICIT_DENY | VDELETE_CHILD, cred);
if (error)
goto out;
#endif
error = VOP_ACCESS(vdp, VWRITE, cred);
if (error)
goto out;
if (!(ip->i_mode & ISVTX))
return 0;
error = kauth_authorize_vnode(cred, KAUTH_VNODE_DELETE, tdp, vdp,
genfs_can_sticky(vdp, cred, ip->i_uid, VTOI(tdp)->i_uid));
if (error) {
error = SET_ERROR(EPERM);
goto out;
}
return 0;
out:
vrele(tdp);
return error;
}
static int
ufs_getino(struct vnode *vdp, struct inode *ip, ino_t foundino,
struct vnode **tdp, bool same)
{
if (ip->i_number == foundino) {
if (same)
return SET_ERROR(EISDIR);
vref(vdp);
*tdp = vdp;
return 0;
}
return vcache_get(vdp->v_mount, &foundino, sizeof(foundino), tdp);
}
int
ufs_lookup(void *v)
{
struct vop_lookup_v2_args
*ap = v;
struct vnode *vdp = ap->a_dvp;
struct inode *dp = VTOI(vdp);
struct buf *bp;
struct direct *ep;
int entryoffsetinblock;
struct slotinfo slot;
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 ufsmount *ump = dp->i_ump;
const int needswap = UFS_MPNEEDSWAP(ump);
int dirblksiz = ump->um_dirblksiz;
ino_t foundino;
struct ufs_lookup_results *results;
int iswhiteout;
const int fsfmt = FSFMT(vdp);
uint16_t reclen;
flags = cnp->cn_flags;
bp = NULL;
*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 SET_ERROR(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 ? SET_ERROR(ENOENT) : 0;
}
if (VOP_ISLOCKED(vdp) != LK_EXCLUSIVE) {
return SET_ERROR(ENOLCK);
}
results = &dp->i_crap;
dp->i_crapcounter++;
if (iswhiteout) {
cnp->cn_flags |= ISWHITEOUT;
}
slot_init(&slot);
if ((nameiop == CREATE || nameiop == RENAME) && (flags & ISLASTCN)) {
slot.status = NONE;
slot.needed = UFS_DIRECTSIZ(cnp->cn_namelen);
}
bmask = vdp->v_mount->mnt_stat.f_iosize - 1;
#ifdef UFS_DIRHASH
if (ufsdirhash_build(dp) == 0) {
enduseful = slot_findfree(&slot, dp);
numdirpasses = 1;
entryoffsetinblock = 0;
switch (ufsdirhash_lookup(dp, cnp->cn_nameptr, cnp->cn_namelen,
&results->ulr_offset, &bp,
nameiop == DELETE ? &prevoff : NULL)) {
case 0:
ep = (void *)((char *)bp->b_data +
(results->ulr_offset & bmask));
reclen = ufs_rw16(ep->d_reclen, needswap);
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;
entryoffsetinblock = results->ulr_offset & bmask;
if (entryoffsetinblock != 0 &&
(error = ufs_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 = ufs_blkatoff(vdp, (off_t)results->ulr_offset,
NULL, &bp, false);
if (error)
goto out;
entryoffsetinblock = 0;
}
if (slot.status == NONE &&
(entryoffsetinblock & (dirblksiz - 1)) == 0) {
slot.offset = -1;
slot.freespace = 0;
}
KASSERT(bp != NULL);
ep = (void *)((char *)bp->b_data + entryoffsetinblock);
const char *msg;
reclen = ufs_rw16(ep->d_reclen, needswap);
if ((reclen == 0 && (msg = "null entry")) || (dirchk &&
(msg = ufs_dirbadentry(vdp, ep, entryoffsetinblock)))) {
ufs_dirbad(dp, results->ulr_offset, msg);
reclen = dirblksiz -
(entryoffsetinblock & (dirblksiz - 1));
goto next;
}
if (slot.status != FOUND) {
int size = reclen;
if (ep->d_ino != 0)
size -= UFS_DIRSIZ(fsfmt, ep, needswap);
if (size > 0)
slot_update(&slot, size, reclen,
results->ulr_offset);
}
if (ep->d_ino == 0)
goto next;
const uint16_t namlen = NAMLEN(fsfmt, needswap, ep);
if (namlen != cnp->cn_namelen ||
memcmp(cnp->cn_nameptr, ep->d_name, (size_t)namlen))
goto next;
#ifdef UFS_DIRHASH
foundentry:
#endif
if (!fsfmt && ep->d_type == DT_WHT) {
slot_white(&slot, reclen, results);
enduseful = endsearch;
cnp->cn_flags |= ISWHITEOUT;
numdirpasses--;
goto notfound;
}
foundino = ufs_rw32(ep->d_ino, needswap);
results->ulr_reclen = reclen;
goto found;
next:
prevoff = results->ulr_offset;
results->ulr_offset += reclen;
entryoffsetinblock += reclen;
if (ep->d_ino)
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) {
if (flags & WILLBEDIR)
error = VOP_ACCESSX(vdp, VWRITE | VAPPEND, cred);
else
error = VOP_ACCESS(vdp, VWRITE, cred);
if (error)
goto out;
error = slot_estimate(&slot, dirblksiz, nameiop,
prevoff, enduseful, dp, results);
goto out;
}
if (nameiop != CREATE) {
cache_enter(vdp, *vpp, cnp->cn_nameptr, cnp->cn_namelen,
cnp->cn_flags);
}
error = SET_ERROR(ENOENT);
goto out;
found:
if (numdirpasses == 2)
namecache_count_pass2();
const uint64_t newisize =
results->ulr_offset + UFS_DIRSIZ(fsfmt, ep, needswap);
if (newisize > dp->i_size) {
ufs_dirbad(dp, results->ulr_offset, "i_size too small");
dp->i_size = newisize;
DIP_ASSIGN(dp, size, dp->i_size);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
UFS_WAPBL_UPDATE(vdp, NULL, NULL, UPDATE_DIROP);
}
brelse(bp, 0);
if ((flags & ISLASTCN) && nameiop == LOOKUP)
results->ulr_diroff = results->ulr_offset & ~(dirblksiz - 1);
if (nameiop == DELETE && (flags & ISLASTCN)) {
calc_count(results, dirblksiz, prevoff);
if ((error = ufs_getino(vdp, dp, foundino, &tdp, false)) != 0)
goto out;
if ((error = ufs_can_delete(tdp, vdp, dp, cred)) != 0)
goto out;
*vpp = tdp;
goto out;
}
if (nameiop == RENAME && (flags & ISLASTCN)) {
if (flags & WILLBEDIR)
error = VOP_ACCESSX(vdp, VWRITE | VAPPEND, cred);
else
error = VOP_ACCESS(vdp, VWRITE, cred);
if (error)
goto out;
if ((error = ufs_getino(vdp, dp, foundino, &tdp, true)) != 0)
goto out;
*vpp = tdp;
goto out;
}
if ((error = ufs_getino(vdp, dp, foundino, &tdp, false)) != 0)
goto out;
*vpp = tdp;
cache_enter(vdp, *vpp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_flags);
error = 0;
out:
return error;
}
void
ufs_dirbad(struct inode *ip, doff_t offset, const char *how)
{
struct mount *mp = ITOV(ip)->v_mount;
void (*p)(const char *, ...) __printflike(1, 2) =
(mp->mnt_flag & MNT_RDONLY) == 0 ? panic : printf;
(*p)("%s: bad dir ino %ju at offset %d: %s\n",
mp->mnt_stat.f_mntonname, (uintmax_t)ip->i_number,
offset, how);
}
const char *
ufs_dirbadentry(const struct vnode *dp, const struct direct *ep,
int entryoffsetinblock)
{
const struct ufsmount *ump = VFSTOUFS(dp->v_mount);
const int needswap = UFS_MPNEEDSWAP(ump);
const int dirblksiz = ump->um_dirblksiz;
const int maxsize = dirblksiz - (entryoffsetinblock & (dirblksiz - 1));
const int fsfmt = FSFMT(dp);
const uint8_t namlen = NAMLEN(fsfmt, needswap, ep);
const uint16_t reclen = ufs_rw16(ep->d_reclen, needswap);
const int dirsiz = (int)UFS_DIRSIZ(fsfmt, ep, needswap);
const char *name = ep->d_name;
const char *str;
#ifdef DIAGNOSTIC
static char buf[512];
#endif
if ((reclen & 0x3) != 0)
str = "not rounded";
else if (reclen > maxsize)
str = "too big";
else if (reclen < dirsiz)
str = "too small";
#if FFS_MAXNAMLEN < 255
else if (namlen > FFS_MAXNAMLEN)
str = "long name";
#endif
else
str = NULL;
if (str) {
#ifdef DIAGNOSTIC
snprintf(buf, sizeof(buf), "Bad dir (%s), reclen=%#x, "
"namlen=%d, dirsiz=%d <= reclen=%d <= maxsize=%d, "
"flags=%#x, entryoffsetinblock=%d, dirblksiz=%d",
str, reclen, namlen, dirsiz, reclen, maxsize,
dp->v_mount->mnt_flag, entryoffsetinblock, dirblksiz);
str = buf;
#endif
return str;
}
if (ep->d_ino == 0)
return NULL;
for (uint8_t i = 0; i < namlen; i++)
if (name[i] == '\0') {
str = "NUL in name";
#ifdef DIAGNOSTIC
snprintf(buf, sizeof(buf), "%s [%s] i=%d, namlen=%d",
str, name, i, namlen);
str = buf;
#endif
return str;
}
if (name[namlen]) {
str = "missing NUL in name";
#ifdef DIAGNOSTIC
snprintf(buf, sizeof(buf), "%s [%*.*s] namlen=%d", str,
namlen, namlen, name, namlen);
str = buf;
#endif
return str;
}
return NULL;
}
void
ufs_makedirentry(struct inode *ip, struct componentname *cnp,
struct direct *newdirp)
{
size_t namelen = cnp->cn_namelen;
newdirp->d_ino = ip->i_number;
newdirp->d_namlen = namelen;
memcpy(newdirp->d_name, cnp->cn_nameptr, namelen);
memset(&newdirp->d_name[namelen], 0, UFS_NAMEPAD(namelen));
if (FSFMT(ITOV(ip)))
newdirp->d_type = 0;
else
newdirp->d_type = IFTODT(ip->i_mode);
}
static int
ufs_dirgrow(struct vnode *dvp, const struct ufs_lookup_results *ulr,
struct vnode *tvp, struct direct *dirp,
struct componentname *cnp, struct buf *newdirbp)
{
const kauth_cred_t cr = cnp->cn_cred;
const struct ufsmount *ump = VFSTOUFS(dvp->v_mount);
const int needswap = UFS_MPNEEDSWAP(ump);
const int dirblksiz = ump->um_dirblksiz;
const int fsfmt = FSFMT(dvp);
const u_int newentrysize = UFS_DIRSIZ(0, dirp, 0);
struct inode *dp = VTOI(dvp);
int error, ret, blkoff;
struct timespec ts;
struct buf *bp;
if (ulr->ulr_offset & (dirblksiz - 1))
panic("%s: newblk", __func__);
if ((error = UFS_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_flag |= IN_CHANGE | IN_UPDATE;
uvm_vnp_setsize(dvp, dp->i_size);
dirp->d_reclen = ufs_rw16(dirblksiz, needswap);
dirp->d_ino = ufs_rw32(dirp->d_ino, needswap);
if (fsfmt && ENDIANSWAP(needswap))
ufs_dirswap(dirp);
blkoff = ulr->ulr_offset & (ump->um_mountp->mnt_stat.f_iosize - 1);
memcpy((char *)bp->b_data + blkoff, dirp, newentrysize);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL) {
ufsdirhash_newblk(dp, ulr->ulr_offset);
ufsdirhash_add(dp, dirp, ulr->ulr_offset);
ufsdirhash_checkblock(dp, (char *)bp->b_data + blkoff,
ulr->ulr_offset);
}
#endif
error = VOP_BWRITE(bp->b_vp, bp);
vfs_timestamp(&ts);
ret = UFS_UPDATE(dvp, &ts, &ts, UPDATE_DIROP);
if (error == 0)
return ret;
return error;
}
static int
#if __GNUC_PREREQ__(5, 3)
__attribute__((__optimize__("no-tree-vrp")))
#endif
ufs_dircompact(struct vnode *dvp, const struct ufs_lookup_results *ulr,
struct vnode *tvp, struct direct *dirp,
struct componentname *cnp, struct buf *newdirbp)
{
const struct ufsmount *ump = VFSTOUFS(dvp->v_mount);
const int needswap = UFS_MPNEEDSWAP(ump);
const int fsfmt = FSFMT(dvp);
const u_int newentrysize = UFS_DIRSIZ(0, dirp, 0);
struct inode *dp = VTOI(dvp);
struct buf *bp;
u_int dsize;
struct direct *ep, *nep;
int error, loc, spacefree;
char *dirbuf;
uint16_t reclen;
UFS_WAPBL_JLOCK_ASSERT(dvp->v_mount);
if (ulr->ulr_offset + ulr->ulr_count > dp->i_size) {
#ifdef DIAGNOSTIC
printf("%s: reached 4.2-only block, not supposed to happen\n",
__func__);
#endif
dp->i_size = ulr->ulr_offset + ulr->ulr_count;
DIP_ASSIGN(dp, size, dp->i_size);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
UFS_WAPBL_UPDATE(dvp, NULL, NULL, UPDATE_DIROP);
}
error = ufs_blkatoff(dvp, (off_t)ulr->ulr_offset, &dirbuf, &bp, true);
if (error)
return error;
ep = (void *)dirbuf;
dsize = (ep->d_ino != 0) ? UFS_DIRSIZ(fsfmt, ep, needswap) : 0;
reclen = ufs_rw16(ep->d_reclen, needswap);
spacefree = reclen - dsize;
for (loc = reclen; loc < ulr->ulr_count; ) {
nep = (void *)(dirbuf + loc);
ep->d_reclen = ufs_rw16(dsize, needswap);
ep = (void *)((char *)ep + dsize);
reclen = ufs_rw16(nep->d_reclen, needswap);
loc += reclen;
if (nep->d_ino == 0) {
spacefree += reclen;
ep->d_ino = 0;
dsize = 0;
continue;
}
dsize = UFS_DIRSIZ(fsfmt, nep, needswap);
spacefree += reclen - dsize;
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_move(dp, nep,
ulr->ulr_offset + ((char *)nep - dirbuf),
ulr->ulr_offset + ((char *)ep - dirbuf));
#endif
memcpy(ep, nep, dsize);
}
if (ep->d_ino == 0 ||
(ufs_rw32(ep->d_ino, needswap) == UFS_WINO &&
memcmp(ep->d_name, dirp->d_name, dirp->d_namlen) == 0)) {
if (spacefree + dsize < newentrysize)
panic("%s: too big", __func__);
dirp->d_reclen = spacefree + dsize;
} else {
if (spacefree < newentrysize)
panic("%s: nospace", __func__);
dirp->d_reclen = spacefree;
ep->d_reclen = ufs_rw16(dsize, needswap);
ep = (void *)((char *)ep + dsize);
}
dirp->d_reclen = ufs_rw16(dirp->d_reclen, needswap);
dirp->d_ino = ufs_rw32(dirp->d_ino, needswap);
if (fsfmt && ENDIANSWAP(needswap))
ufs_dirswap(dirp);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL && (ep->d_ino == 0 ||
dirp->d_reclen == spacefree))
ufsdirhash_add(dp, dirp, ulr->ulr_offset + ((char *)ep - dirbuf));
#endif
memcpy(ep, dirp, newentrysize);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL) {
const int dirblkmsk = ump->um_dirblksiz - 1;
ufsdirhash_checkblock(dp, dirbuf -
(ulr->ulr_offset & dirblkmsk),
ulr->ulr_offset & ~dirblkmsk);
}
#endif
error = VOP_BWRITE(bp->b_vp, bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
if (error == 0 && ulr->ulr_endoff && ulr->ulr_endoff < dp->i_size) {
const kauth_cred_t cr = cnp->cn_cred;
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_dirtrunc(dp, ulr->ulr_endoff);
#endif
(void) UFS_TRUNCATE(dvp, (off_t)ulr->ulr_endoff, IO_SYNC, cr);
}
UFS_WAPBL_UPDATE(dvp, NULL, NULL, UPDATE_DIROP);
return error;
}
int
ufs_direnter(struct vnode *dvp, const struct ufs_lookup_results *ulr,
struct vnode *tvp, struct direct *dirp,
struct componentname *cnp, struct buf *newdirbp)
{
if (ulr->ulr_count == 0)
return ufs_dirgrow(dvp, ulr, tvp, dirp, cnp, newdirbp);
else
return ufs_dircompact(dvp, ulr, tvp, dirp, cnp, newdirbp);
}
int
ufs_dirremove(struct vnode *dvp, const struct ufs_lookup_results *ulr,
struct inode *ip, int flags, int isrmdir)
{
struct inode *dp = VTOI(dvp);
struct direct *ep;
struct buf *bp;
int error;
const int needswap = UFS_MPNEEDSWAP(dp->i_ump);
uint16_t reclen;
UFS_WAPBL_JLOCK_ASSERT(dvp->v_mount);
if (flags & DOWHITEOUT) {
error = ufs_blkatoff(dvp, (off_t)ulr->ulr_offset, &ep,
&bp, true);
if (error)
return error;
ep->d_ino = ufs_rw32(UFS_WINO, needswap);
ep->d_type = DT_WHT;
goto out;
}
if ((error = ufs_blkatoff(dvp,
(off_t)(ulr->ulr_offset - ulr->ulr_count), &ep, &bp, true)) != 0)
return error;
reclen = ufs_rw16(ep->d_reclen, needswap);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_remove(dp, (ulr->ulr_count == 0) ? ep :
(void *)((char *)ep + reclen), ulr->ulr_offset);
#endif
if (ulr->ulr_count == 0) {
ep->d_ino = 0;
} else {
ep->d_reclen = ufs_rw16(reclen + ulr->ulr_reclen, needswap);
}
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL) {
int dirblksiz = ip->i_ump->um_dirblksiz;
ufsdirhash_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_flag |= IN_CHANGE;
UFS_WAPBL_UPDATE(ITOV(ip), NULL, NULL, 0);
}
error = VOP_BWRITE(bp->b_vp, bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
if (ip != 0 && (ip->i_flags & SF_SNAPSHOT) != 0 &&
ip->i_nlink == 0)
UFS_SNAPGONE(ITOV(ip));
UFS_WAPBL_UPDATE(dvp, NULL, NULL, 0);
return error;
}
int
ufs_dirrewrite(struct inode *dp, off_t offset,
struct inode *oip, ino_t newinum, int newtype,
int isrmdir, int iflags)
{
struct buf *bp;
struct direct *ep;
struct vnode *vdp = ITOV(dp);
int error;
error = ufs_blkatoff(vdp, offset, &ep, &bp, true);
if (error)
return error;
ep->d_ino = ufs_rw32(newinum, UFS_MPNEEDSWAP(dp->i_ump));
if (!FSFMT(vdp))
ep->d_type = newtype;
oip->i_nlink--;
DIP_ASSIGN(oip, nlink, oip->i_nlink);
oip->i_flag |= IN_CHANGE;
UFS_WAPBL_UPDATE(ITOV(oip), NULL, NULL, UPDATE_DIROP);
error = VOP_BWRITE(bp->b_vp, bp);
dp->i_flag |= iflags;
if ((oip->i_flags & SF_SNAPSHOT) != 0 && oip->i_nlink == 0)
UFS_SNAPGONE(ITOV(oip));
UFS_WAPBL_UPDATE(vdp, NULL, NULL, UPDATE_DIROP);
return error;
}
int
ufs_dirempty(struct inode *ip, ino_t parentino, kauth_cred_t cred)
{
doff_t off;
struct direct dbuf;
struct direct *dp = &dbuf;
int error;
size_t count;
const int needswap = UFS_IPNEEDSWAP(ip);
const int fsfmt = FSFMT(ITOV(ip));
#define MINDIRSIZ (sizeof (struct dirtemplate) / 2)
for (off = 0; off < ip->i_size;
off += ufs_rw16(dp->d_reclen, needswap)) {
error = ufs_bufio(UIO_READ, ITOV(ip), dp, MINDIRSIZ,
off, IO_NODELOCKED, cred, &count, NULL);
if (error || count != 0)
return 0;
if (dp->d_reclen == 0)
return 0;
ino_t ino = ufs_rw32(dp->d_ino, needswap);
if (ino == 0 || ino == UFS_WINO)
continue;
const uint8_t namlen = NAMLEN(fsfmt, needswap, dp);
if (namlen > 2)
return 0;
if (dp->d_name[0] != '.')
return 0;
if (namlen == 1 && ino == ip->i_number)
continue;
if (dp->d_name[1] == '.' && ino == parentino)
continue;
return 0;
}
return 1;
}
#define UFS_DIRRABLKS 0
int ufs_dirrablks = UFS_DIRRABLKS;
int
ufs_blkatoff(struct vnode *vp, off_t offset, void *v, struct buf **bpp,
bool modify)
{
char **res = v;
struct inode *ip __diagused;
struct buf *bp;
daddr_t lbn;
const int dirrablks = ufs_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;
}