#include <sys/types.h>
#include <sys/t_lock.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/signal.h>
#include <sys/cred.h>
#include <sys/proc.h>
#include <sys/disp.h>
#include <sys/user.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/stat.h>
#include <sys/mode.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/dnlc.h>
#include <sys/fs/ufs_inode.h>
#include <sys/fs/ufs_fs.h>
#include <sys/mount.h>
#include <sys/fs/ufs_fsdir.h>
#include <sys/fs/ufs_trans.h>
#include <sys/fs/ufs_panic.h>
#include <sys/fs/ufs_quota.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <vm/seg.h>
#include <sys/sysmacros.h>
#include <sys/cmn_err.h>
#include <sys/cpuvar.h>
#include <sys/unistd.h>
#include <sys/policy.h>
#if !ISP2(DIRBLKSIZ)
#error "DIRBLKSIZ not a power of 2"
#endif
static struct dirtemplate mastertemplate = {
0, 12, 1, ".",
0, DIRBLKSIZ - 12, 2, ".."
};
#define LDIRSIZ(len) \
((sizeof (struct direct) - (MAXNAMLEN + 1)) + ((len + 1 + 3) &~ 3))
#define MAX_DIR_NAME_LEN(len) \
(((len) - (sizeof (struct direct) - (MAXNAMLEN + 1))) - 1)
#define H_TO_INO(h) (uint32_t)((h) & UINT_MAX)
#define H_TO_OFF(h) (off_t)((h) >> 32)
#define INO_OFF_TO_H(ino, off) (uint64_t)(((uint64_t)(off) << 32) | (ino))
#define AV_DIRECT_SHIFT 4
int ufs_min_dir_cache = 1024 << AV_DIRECT_SHIFT;
static hrtime_t ufs_dc_disable_at;
static hrtime_t ufs_dc_disable_duration = (hrtime_t)NANOSEC * 5;
#ifdef DEBUG
int dirchk = 1;
#else
int dirchk = 0;
#endif
int ufs_negative_cache = 1;
uint64_t ufs_dirremove_retry_cnt;
static void dirbad();
static int ufs_dirrename();
static int ufs_diraddentry();
static int ufs_dirempty();
static int ufs_dirscan();
static int ufs_dirclrdotdot();
static int ufs_dirfixdotdot();
static int ufs_dirpurgedotdot();
static int dirprepareentry();
static int ufs_dirmakedirect();
static int dirbadname();
static int dirmangled();
int
ufs_diraccess(struct inode *ip, int mode, struct cred *cr)
{
if (((ip->i_mode & IFMT) != IFDIR) &&
((ip->i_mode & IFMT) != IFATTRDIR))
return (ENOTDIR);
return (ufs_iaccess(ip, mode, cr, 1));
}
int
ufs_dirlook(
struct inode *dp,
char *namep,
struct inode **ipp,
struct cred *cr,
int skipdnlc,
int skipcaching)
{
uint64_t handle;
struct fbuf *fbp;
struct direct *ep;
struct vnode *vp;
struct vnode *dvp;
struct ulockfs *ulp;
dcanchor_t *dcap;
off_t endsearch;
off_t offset;
off_t start_off;
off_t last_offset;
int entryoffsetinblock;
int numdirpasses;
int namlen;
int err;
int doingchk;
int i;
int caching;
int indeadlock;
ino_t ep_ino;
ino_t chkino;
ushort_t ep_reclen;
ASSERT(*namep != '\0');
if (dp->i_ufsvfs)
ulp = &dp->i_ufsvfs->vfs_ulockfs;
dvp = ITOV(dp);
if (!skipdnlc && (vp = dnlc_lookup(dvp, namep))) {
if (vp == DNLC_NO_VNODE) {
VN_RELE(vp);
return (ENOENT);
}
*ipp = VTOI(vp);
return (0);
}
dcap = &dp->i_danchor;
ufs_tryirwlock((&dp->i_rwlock), RW_READER, retry_dircache);
if (indeadlock)
return (EAGAIN);
switch (dnlc_dir_lookup(dcap, namep, &handle)) {
case DFOUND:
ep_ino = (ino_t)H_TO_INO(handle);
if (dp->i_number == ep_ino) {
VN_HOLD(dvp);
*ipp = dp;
rw_exit(&dp->i_rwlock);
return (0);
}
if (namep[0] == '.' && namep[1] == '.' && namep[2] == 0) {
uint64_t handle2;
rw_exit(&dp->i_rwlock);
rw_enter(&dp->i_ufsvfs->vfs_dqrwlock, RW_READER);
err = ufs_iget_alloced(dp->i_vfs, ep_ino, ipp, cr);
rw_exit(&dp->i_ufsvfs->vfs_dqrwlock);
ufs_tryirwlock(&dp->i_rwlock, RW_READER, retry_parent);
if (indeadlock) {
if (!err)
VN_RELE(ITOV(*ipp));
return (EAGAIN);
}
if (!err && (dnlc_dir_lookup(dcap, namep, &handle2)
== DFOUND) && (handle == handle2)) {
dnlc_update(dvp, namep, ITOV(*ipp));
rw_exit(&dp->i_rwlock);
return (0);
}
if (!err) {
VN_RELE(ITOV(*ipp));
}
goto restart;
}
rw_enter(&dp->i_ufsvfs->vfs_dqrwlock, RW_READER);
err = ufs_iget_alloced(dp->i_vfs, ep_ino, ipp, cr);
rw_exit(&dp->i_ufsvfs->vfs_dqrwlock);
if (err) {
rw_exit(&dp->i_rwlock);
return (err);
}
dnlc_update(dvp, namep, ITOV(*ipp));
rw_exit(&dp->i_rwlock);
return (0);
case DNOENT:
if (ufs_negative_cache && (dp->i_nlink > 0)) {
dnlc_enter(dvp, namep, DNLC_NO_VNODE);
}
rw_exit(&dp->i_rwlock);
return (ENOENT);
default:
break;
}
restart:
fbp = NULL;
doingchk = 0;
chkino = 0;
caching = 0;
if (!skipcaching && (dp->i_size >= ufs_min_dir_cache)) {
if (dp->i_cachedir == CD_DISABLED_NOMEM &&
gethrtime() - ufs_dc_disable_at > ufs_dc_disable_duration) {
ufs_dc_disable_at = 0;
dp->i_cachedir = CD_ENABLED;
}
if (dp->i_cachedir == CD_ENABLED) {
switch (dnlc_dir_start(dcap, dp->i_size >>
AV_DIRECT_SHIFT)) {
case DNOMEM:
dp->i_cachedir = CD_DISABLED_NOMEM;
ufs_dc_disable_at = gethrtime();
break;
case DTOOBIG:
dp->i_cachedir = CD_DISABLED_TOOBIG;
break;
case DOK:
caching = 1;
break;
default:
break;
}
}
}
*ipp = NULL;
recheck:
if (caching) {
offset = 0;
entryoffsetinblock = 0;
numdirpasses = 1;
} else {
offset = dp->i_diroff;
if (offset > dp->i_size) {
offset = 0;
}
if (offset == 0) {
entryoffsetinblock = 0;
numdirpasses = 1;
} else {
start_off = offset;
entryoffsetinblock = blkoff(dp->i_fs, offset);
if (entryoffsetinblock != 0) {
err = blkatoff(dp, offset, (char **)0, &fbp);
if (err)
goto bad;
}
numdirpasses = 2;
}
}
endsearch = P2ROUNDUP_TYPED(dp->i_size, DIRBLKSIZ, u_offset_t);
namlen = strlen(namep);
last_offset = 0;
searchloop:
while (offset < endsearch) {
if (blkoff(dp->i_fs, offset) == 0) {
if (fbp != NULL) {
fbrelse(fbp, S_OTHER);
}
err = blkatoff(dp, offset, (char **)0, &fbp);
if (err)
goto bad;
entryoffsetinblock = 0;
}
ep = (struct direct *)(fbp->fb_addr + entryoffsetinblock);
if ((entryoffsetinblock & 0x3) || ep->d_reclen == 0 ||
(dirchk || (ep->d_reclen & 0x3)) &&
dirmangled(dp, ep, entryoffsetinblock, offset)) {
i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
offset += i;
entryoffsetinblock += i;
if (caching) {
dnlc_dir_purge(dcap);
caching = 0;
}
continue;
}
ep_reclen = ep->d_reclen;
if (caching) {
ushort_t extra;
off_t off2;
if (ep->d_ino == 0) {
extra = ep_reclen;
if (offset & (DIRBLKSIZ - 1)) {
dnlc_dir_purge(dcap);
dp->i_cachedir = CD_DISABLED;
caching = 0;
}
} else {
if (offset & (DIRBLKSIZ - 1)) {
off2 = last_offset;
} else {
off2 = offset + 1;
}
caching = (dnlc_dir_add_entry(dcap, ep->d_name,
INO_OFF_TO_H(ep->d_ino, off2)) == DOK);
extra = ep_reclen - DIRSIZ(ep);
}
if (caching && (extra >= LDIRSIZ(1))) {
caching = (dnlc_dir_add_space(dcap, extra,
(uint64_t)offset) == DOK);
}
}
if (ep->d_ino && ep->d_namlen == namlen &&
*namep == *ep->d_name &&
bcmp(namep, ep->d_name, (int)ep->d_namlen) == 0) {
ep_ino = (ino_t)ep->d_ino;
ASSERT(fbp != NULL);
fbrelse(fbp, S_OTHER);
fbp = NULL;
dp->i_diroff = offset;
if (namlen == 2 && namep[0] == '.' && namep[1] == '.') {
struct timeval32 omtime;
if (caching) {
dnlc_dir_purge(dcap);
caching = 0;
}
if (doingchk) {
if (ep_ino == chkino)
goto checkok;
else {
VN_RELE(ITOV(*ipp));
goto restart;
}
}
omtime = dp->i_mtime;
rw_exit(&dp->i_rwlock);
rw_enter(&dp->i_ufsvfs->vfs_dqrwlock,
RW_READER);
err = ufs_iget_alloced(dp->i_vfs, ep_ino, ipp,
cr);
rw_exit(&dp->i_ufsvfs->vfs_dqrwlock);
ufs_tryirwlock(&dp->i_rwlock, RW_READER,
retry_disk);
if (indeadlock) {
if (!err)
VN_RELE(ITOV(*ipp));
return (EAGAIN);
}
if (err)
goto bad;
if (timercmp(&omtime, &dp->i_mtime, !=)) {
doingchk = 1;
chkino = ep_ino;
entryoffsetinblock = 0;
if (caching) {
dnlc_dir_purge(dcap);
caching = 0;
}
goto recheck;
}
} else if (dp->i_number == ep_ino) {
VN_HOLD(dvp);
*ipp = dp;
if (caching) {
dnlc_dir_purge(dcap);
caching = 0;
}
} else {
rw_enter(&dp->i_ufsvfs->vfs_dqrwlock,
RW_READER);
err = ufs_iget_alloced(dp->i_vfs, ep_ino, ipp,
cr);
rw_exit(&dp->i_ufsvfs->vfs_dqrwlock);
if (err)
goto bad;
}
checkok:
ASSERT(*ipp);
dnlc_update(dvp, namep, ITOV(*ipp));
if (!caching) {
rw_exit(&dp->i_rwlock);
return (0);
}
err = blkatoff(dp, offset, (char **)0, &fbp);
if (err)
goto bad;
}
last_offset = offset;
offset += ep_reclen;
entryoffsetinblock += ep_reclen;
}
if (numdirpasses == 2) {
numdirpasses--;
offset = 0;
endsearch = start_off;
goto searchloop;
}
if (*ipp == NULL) {
err = ENOENT;
if (ufs_negative_cache && (dp->i_nlink > 0)) {
dnlc_enter(dvp, namep, DNLC_NO_VNODE);
}
}
if (caching) {
dnlc_dir_complete(dcap);
caching = 0;
}
bad:
if (err && *ipp) {
VN_RELE(ITOV(*ipp));
*ipp = NULL;
}
if (fbp)
fbrelse(fbp, S_OTHER);
rw_exit(&dp->i_rwlock);
if (caching)
dnlc_dir_purge(dcap);
return (err);
}
int
ufs_direnter_cm(
struct inode *tdp,
char *namep,
enum de_op op,
struct vattr *vap,
struct inode **ipp,
struct cred *cr,
int flags)
{
struct inode *tip;
char *s;
struct ufs_slot slot;
int namlen;
int err;
struct inode *nip;
int do_rele_nip = 0;
int noentry = flags & ~IQUIET;
int quiet = flags & IQUIET;
int indeadlock;
struct ulockfs *ulp;
ASSERT(RW_WRITE_HELD(&tdp->i_rwlock));
if (((tdp->i_mode & IFMT) == IFATTRDIR) && ((op == DE_MKDIR) ||
((vap->va_type == VCHR) || (vap->va_type == VBLK) ||
(vap->va_type == VDOOR) || (vap->va_type == VSOCK) ||
(vap->va_type == VFIFO))))
return (EINVAL);
for (s = namep, namlen = 0; *s; s++, namlen++)
if (*s == '/')
return (EACCES);
ASSERT(namlen);
if (err = ufs_diraccess(tdp, IEXEC, cr))
return (err);
if (namep[0] == '.' &&
(namlen == 1 || (namlen == 2 && namep[1] == '.'))) {
if (tdp->i_ufsvfs)
ulp = &tdp->i_ufsvfs->vfs_ulockfs;
rw_exit(&tdp->i_rwlock);
if (err = ufs_dirlook(tdp, namep, ipp, cr, 0, 0)) {
if (err == EAGAIN)
return (err);
ufs_tryirwlock(&tdp->i_rwlock, RW_WRITER, retry_err);
if (indeadlock)
return (EAGAIN);
return (err);
}
ufs_tryirwlock(&tdp->i_rwlock, RW_WRITER, retry);
if (indeadlock) {
VN_RELE(ITOV(*ipp));
return (EAGAIN);
}
return (EEXIST);
}
if (tdp->i_nlink <= 0) {
return (ENOENT);
}
tip = NULL;
slot.fbp = NULL;
slot.status = NONE;
rw_enter(&tdp->i_ufsvfs->vfs_dqrwlock, RW_READER);
rw_enter(&tdp->i_contents, RW_WRITER);
err = ufs_dircheckforname(tdp, namep, namlen, &slot, &tip, cr, noentry);
if (err)
goto out;
if (tip) {
ASSERT(!noentry);
*ipp = tip;
err = EEXIST;
} else {
if (err = ufs_iaccess(tdp, IWRITE, cr, 0))
goto out;
tdp->i_flag |= quiet;
if (err = ufs_dirmakeinode(tdp, &nip, vap, op, cr)) {
if (nip != NULL)
do_rele_nip = 1;
goto out;
}
if (err = ufs_diraddentry(tdp, namep, op,
namlen, &slot, nip, NULL, cr)) {
rw_enter(&nip->i_contents, RW_WRITER);
if (((nip->i_mode & IFMT) == IFDIR) ||
((nip->i_mode & IFMT) == IFATTRDIR)) {
tdp->i_nlink--;
ufs_setreclaim(tdp);
tdp->i_flag |= ICHG;
tdp->i_seq++;
TRANS_INODE(tdp->i_ufsvfs, tdp);
ITIMES_NOLOCK(tdp);
}
nip->i_nlink = 0;
ufs_setreclaim(nip);
TRANS_INODE(nip->i_ufsvfs, nip);
nip->i_flag |= ICHG;
nip->i_seq++;
ITIMES_NOLOCK(nip);
rw_exit(&nip->i_contents);
do_rele_nip = 1;
} else {
*ipp = nip;
}
}
out:
if (slot.fbp)
fbrelse(slot.fbp, S_OTHER);
tdp->i_flag &= ~quiet;
rw_exit(&tdp->i_contents);
rw_exit(&tdp->i_ufsvfs->vfs_dqrwlock);
if (do_rele_nip) {
VN_RELE(ITOV(nip));
}
return (err);
}
int
ufs_direnter_lr(
struct inode *tdp,
char *namep,
enum de_op op,
struct inode *sdp,
struct inode *sip,
struct cred *cr)
{
struct inode *tip;
char *s;
struct ufs_slot slot;
int namlen;
int err;
for (s = namep, namlen = 0; *s; s++, namlen++)
if (*s == '/')
return (EACCES);
ASSERT(namlen);
ASSERT(RW_WRITE_HELD(&tdp->i_rwlock));
if (namep[0] == '.' &&
(namlen == 1 || (namlen == 2 && namep[1] == '.'))) {
if (op == DE_RENAME) {
return (EINVAL);
}
return (EEXIST);
}
if (err = TRANS_SYNCIP(sip, 0, I_DSYNC, TOP_FSYNC)) {
return (err);
}
rw_enter(&sip->i_contents, RW_WRITER);
if (sip->i_nlink <= 0) {
rw_exit(&sip->i_contents);
return (ENOENT);
}
if (sip->i_nlink == MAXLINK) {
rw_exit(&sip->i_contents);
return (EMLINK);
}
if (err = ufs_sync_indir(sip)) {
rw_exit(&sip->i_contents);
return (err);
}
if (op != DE_SYMLINK)
sip->i_nlink++;
TRANS_INODE(sip->i_ufsvfs, sip);
sip->i_flag |= ICHG;
sip->i_seq++;
ufs_iupdat(sip, I_SYNC);
rw_exit(&sip->i_contents);
if (tdp->i_nlink <= 0) {
err = ENOENT;
goto out2;
}
if (err = ufs_diraccess(tdp, IEXEC, cr))
goto out2;
tip = NULL;
slot.status = NONE;
slot.fbp = NULL;
rw_enter(&tdp->i_ufsvfs->vfs_dqrwlock, RW_READER);
rw_enter(&tdp->i_contents, RW_WRITER);
err = ufs_dircheckforname(tdp, namep, namlen, &slot, &tip, cr, 0);
if (err)
goto out;
if (tip) {
switch (op) {
case DE_RENAME:
err = ufs_dirrename(sdp, sip, tdp, namep,
tip, &slot, cr);
break;
case DE_LINK:
case DE_SYMLINK:
err = EEXIST;
break;
default:
break;
}
} else {
if (err = ufs_iaccess(tdp, IWRITE, cr, 0))
goto out;
err = ufs_diraddentry(tdp, namep, op, namlen, &slot, sip, sdp,
cr);
}
out:
if (slot.fbp)
fbrelse(slot.fbp, S_OTHER);
rw_exit(&tdp->i_contents);
rw_exit(&tdp->i_ufsvfs->vfs_dqrwlock);
if (tip)
VN_RELE(ITOV(tip));
out2:
if (err) {
if (op != DE_SYMLINK) {
rw_enter(&sip->i_contents, RW_WRITER);
sip->i_nlink--;
ufs_setreclaim(sip);
TRANS_INODE(sip->i_ufsvfs, sip);
sip->i_flag |= ICHG;
sip->i_seq++;
ITIMES_NOLOCK(sip);
rw_exit(&sip->i_contents);
}
}
return (err);
}
int
ufs_dircheckforname(
struct inode *tdp,
char *namep,
int namlen,
struct ufs_slot *slotp,
struct inode **ipp,
struct cred *cr,
int noentry)
{
uint64_t handle;
struct fbuf *fbp;
struct direct *ep;
struct direct *nep;
dcanchor_t *dcap;
vnode_t *dvp;
off_t dirsize;
off_t offset;
off_t last_offset;
off_t enduseful;
int entryoffsetinblk;
int i;
int needed;
int err;
int first;
int caching;
int stat;
ino_t ep_ino;
slotstat_t initstat = slotp->status;
ASSERT(RW_WRITE_HELD(&tdp->i_rwlock));
ASSERT(RW_WRITE_HELD(&tdp->i_contents));
ASSERT(*ipp == NULL);
fbp = NULL;
dvp = ITOV(tdp);
dcap = &tdp->i_danchor;
if (noentry) {
stat = DNOENT;
} else {
stat = dnlc_dir_lookup(dcap, namep, &handle);
}
switch (stat) {
case DFOUND:
ep_ino = (ino_t)H_TO_INO(handle);
if (tdp->i_number == ep_ino) {
*ipp = tdp;
VN_HOLD(dvp);
} else {
err = ufs_iget_alloced(tdp->i_vfs, ep_ino, ipp, cr);
if (err)
return (err);
}
offset = H_TO_OFF(handle);
first = 0;
if (offset & 1) {
first = 1;
offset -= 1;
ASSERT((offset & (DIRBLKSIZ - 1)) == 0);
}
err = blkatoff(tdp, offset, (char **)&ep, &fbp);
if (err) {
VN_RELE(ITOV(*ipp));
*ipp = NULL;
return (err);
}
if ((ep->d_reclen == 0) || (ep->d_reclen & 0x3)) {
VN_RELE(ITOV(*ipp));
*ipp = NULL;
dnlc_dir_purge(dcap);
break;
}
if (first) {
ASSERT((offset & (DIRBLKSIZ - 1)) == 0);
slotp->offset = offset;
slotp->size = 0;
slotp->ep = ep;
} else {
nep = (struct direct *)((char *)ep + ep->d_reclen);
if ((nep->d_reclen == 0) || (nep->d_reclen & 0x3) ||
(nep->d_ino != ep_ino)) {
VN_RELE(ITOV(*ipp));
*ipp = NULL;
dnlc_dir_purge(dcap);
break;
}
slotp->offset = offset + ep->d_reclen;
slotp->size = ep->d_reclen;
slotp->ep = nep;
}
slotp->status = EXIST;
slotp->fbp = fbp;
slotp->endoff = 0;
slotp->cached = 1;
dnlc_update(dvp, namep, ITOV(*ipp));
return (0);
case DNOENT:
ASSERT(slotp->fbp == NULL);
if (slotp->status == FOUND) {
return (0);
}
switch (dnlc_dir_rem_space_by_len(dcap, LDIRSIZ(namlen),
&handle)) {
case DFOUND:
offset = (off_t)handle;
err = blkatoff(tdp, offset, (char **)&ep, &fbp);
if (err) {
dnlc_dir_purge(dcap);
ASSERT(*ipp == NULL);
return (err);
}
if ((ep->d_reclen == 0) || (ep->d_reclen & 0x3)) {
dnlc_dir_purge(dcap);
break;
}
slotp->status = FOUND;
slotp->ep = ep;
slotp->offset = offset;
slotp->fbp = fbp;
slotp->size = ep->d_reclen;
slotp->endoff = 0;
slotp->cached = 1;
return (0);
case DNOENT:
slotp->status = NONE;
slotp->offset = P2ROUNDUP_TYPED(tdp->i_size,
DIRBLKSIZ, u_offset_t);
slotp->size = DIRBLKSIZ;
slotp->endoff = 0;
slotp->cached = 1;
return (0);
default:
break;
}
break;
}
slotp->cached = 0;
caching = 0;
if (!noentry && tdp->i_size >= ufs_min_dir_cache) {
if (tdp->i_cachedir == CD_DISABLED_NOMEM &&
gethrtime() - ufs_dc_disable_at > ufs_dc_disable_duration) {
ufs_dc_disable_at = 0;
tdp->i_cachedir = CD_ENABLED;
}
if (tdp->i_cachedir == CD_ENABLED) {
switch (dnlc_dir_start(dcap,
tdp->i_size >> AV_DIRECT_SHIFT)) {
case DNOMEM:
tdp->i_cachedir = CD_DISABLED_NOMEM;
ufs_dc_disable_at = gethrtime();
break;
case DTOOBIG:
tdp->i_cachedir = CD_DISABLED_TOOBIG;
break;
case DOK:
caching = 1;
break;
default:
break;
}
}
}
dirsize = P2ROUNDUP_TYPED(tdp->i_size, DIRBLKSIZ, u_offset_t);
enduseful = 0;
offset = last_offset = 0;
entryoffsetinblk = 0;
needed = (int)LDIRSIZ(namlen);
while (offset < dirsize) {
if (blkoff(tdp->i_fs, offset) == 0) {
if (fbp != NULL)
fbrelse(fbp, S_OTHER);
err = blkatoff(tdp, offset, (char **)0, &fbp);
if (err) {
ASSERT(*ipp == NULL);
if (caching) {
dnlc_dir_purge(dcap);
}
return (err);
}
entryoffsetinblk = 0;
}
if (slotp->status == NONE &&
(entryoffsetinblk & (DIRBLKSIZ - 1)) == 0) {
slotp->offset = -1;
}
ep = (struct direct *)(fbp->fb_addr + entryoffsetinblk);
if (ep->d_reclen == 0 ||
(dirchk || (ep->d_reclen & 0x3)) &&
dirmangled(tdp, ep, entryoffsetinblk, offset)) {
i = DIRBLKSIZ - (entryoffsetinblk & (DIRBLKSIZ - 1));
offset += i;
entryoffsetinblk += i;
if (caching) {
dnlc_dir_purge(dcap);
caching = 0;
}
continue;
}
if (caching) {
ushort_t extra;
off_t off2;
if (ep->d_ino == 0) {
extra = ep->d_reclen;
if (offset & (DIRBLKSIZ - 1)) {
dnlc_dir_purge(dcap);
caching = 0;
}
} else {
if (offset & (DIRBLKSIZ - 1)) {
off2 = last_offset;
} else {
off2 = offset + 1;
}
caching = (dnlc_dir_add_entry(dcap, ep->d_name,
INO_OFF_TO_H(ep->d_ino, off2)) == DOK);
extra = ep->d_reclen - DIRSIZ(ep);
}
if (caching && (extra >= LDIRSIZ(1))) {
caching = (dnlc_dir_add_space(dcap, extra,
(uint64_t)offset) == DOK);
}
}
if ((slotp->status != FOUND) && (slotp->status != EXIST)) {
int size = ep->d_reclen;
if (ep->d_ino != 0)
size -= DIRSIZ(ep);
if (size > 0) {
if (size >= needed) {
slotp->offset = offset;
slotp->size = ep->d_reclen;
if (noentry) {
slotp->ep = ep;
slotp->fbp = fbp;
slotp->status = FOUND;
slotp->endoff = 0;
return (0);
}
slotp->status = FOUND;
} else if (slotp->status == NONE) {
if (slotp->offset == -1)
slotp->offset = offset;
}
}
}
if (ep->d_ino && ep->d_namlen == namlen &&
*namep == *ep->d_name &&
bcmp(namep, ep->d_name, namlen) == 0) {
tdp->i_diroff = offset;
if (tdp->i_number == ep->d_ino) {
*ipp = tdp;
VN_HOLD(dvp);
} else {
err = ufs_iget_alloced(tdp->i_vfs,
(ino_t)ep->d_ino, ipp, cr);
if (err) {
fbrelse(fbp, S_OTHER);
if (caching)
dnlc_dir_purge(dcap);
return (err);
}
}
slotp->status = EXIST;
slotp->offset = offset;
slotp->size = (int)(offset - last_offset);
slotp->fbp = fbp;
slotp->ep = ep;
slotp->endoff = 0;
if (caching)
dnlc_dir_purge(dcap);
return (0);
}
last_offset = offset;
offset += ep->d_reclen;
entryoffsetinblk += ep->d_reclen;
if (ep->d_ino)
enduseful = offset;
}
if (fbp) {
fbrelse(fbp, S_OTHER);
}
if (caching) {
dnlc_dir_complete(dcap);
slotp->cached = 1;
if (slotp->status == FOUND) {
if (initstat == FOUND) {
return (0);
}
(void) dnlc_dir_rem_space_by_handle(dcap,
slotp->offset);
slotp->endoff = 0;
return (0);
}
}
if (slotp->status == NONE) {
slotp->offset = dirsize;
slotp->size = DIRBLKSIZ;
slotp->endoff = 0;
} else {
if (enduseful < slotp->offset + slotp->size)
enduseful = slotp->offset + slotp->size;
slotp->endoff = P2ROUNDUP_TYPED(enduseful, DIRBLKSIZ, off_t);
}
*ipp = NULL;
return (0);
}
uint64_t ufs_dirrename_retry_cnt;
static int
ufs_dirrename(
struct inode *sdp,
struct inode *sip,
struct inode *tdp,
char *namep,
struct inode *tip,
struct ufs_slot *slotp,
struct cred *cr)
{
vnode_t *tdvp;
off_t offset;
int err;
int doingdirectory;
ASSERT(sdp->i_ufsvfs != NULL);
ASSERT(RW_WRITE_HELD(&tdp->i_rwlock));
ASSERT(RW_WRITE_HELD(&tdp->i_contents));
if (sip->i_number == tip->i_number) {
return (ESAME);
}
retry:
rw_enter(&tip->i_contents, RW_WRITER);
if (!rw_tryenter(&sip->i_contents, RW_READER)) {
rw_exit(&tip->i_contents);
rw_enter(&sip->i_contents, RW_READER);
if (!rw_tryenter(&tip->i_contents, RW_WRITER)) {
ufs_dirrename_retry_cnt++;
rw_exit(&sip->i_contents);
goto retry;
}
}
if ((ITOV(tip)->v_vfsp != ITOV(tdp)->v_vfsp) ||
(ITOV(tip)->v_vfsp != ITOV(sip)->v_vfsp)) {
err = EXDEV;
goto out;
}
if ((err = ufs_iaccess(tdp, IWRITE, cr, 0)) != 0 ||
(err = ufs_sticky_remove_access(tdp, tip, cr)) != 0)
goto out;
doingdirectory = (((sip->i_mode & IFMT) == IFDIR) ||
((sip->i_mode & IFMT) == IFATTRDIR));
if (((tip->i_mode & IFMT) == IFDIR) ||
((tip->i_mode & IFMT) == IFATTRDIR)) {
if (!doingdirectory) {
err = EISDIR;
goto out;
}
if (vn_vfsrlock(ITOV(tip))) {
err = EBUSY;
goto out;
}
if (vn_mountedvfs(ITOV(tip)) != NULL) {
vn_vfsunlock(ITOV(tip));
err = EBUSY;
goto out;
}
if (!ufs_dirempty(tip, tdp->i_number, cr) || tip->i_nlink > 2) {
vn_vfsunlock(ITOV(tip));
err = EEXIST;
goto out;
}
} else if (doingdirectory) {
err = ENOTDIR;
goto out;
}
tdvp = ITOV(tdp);
slotp->ep->d_ino = (int32_t)sip->i_number;
dnlc_update(tdvp, namep, ITOV(sip));
if (slotp->size) {
offset = slotp->offset - slotp->size;
} else {
offset = slotp->offset + 1;
}
if (slotp->cached) {
(void) dnlc_dir_update(&tdp->i_danchor, namep,
INO_OFF_TO_H(slotp->ep->d_ino, offset));
}
err = TRANS_DIR(tdp, slotp->offset);
if (err)
fbrelse(slotp->fbp, S_OTHER);
else
err = ufs_fbwrite(slotp->fbp, tdp);
slotp->fbp = NULL;
if (err) {
if (doingdirectory)
vn_vfsunlock(ITOV(tip));
goto out;
}
TRANS_INODE(tdp->i_ufsvfs, tdp);
tdp->i_flag |= IUPD|ICHG;
tdp->i_seq++;
ITIMES_NOLOCK(tdp);
tip->i_nlink--;
TRANS_INODE(tip->i_ufsvfs, tip);
tip->i_flag |= ICHG;
tip->i_seq++;
ITIMES_NOLOCK(tip);
if (doingdirectory) {
vn_vfsunlock(ITOV(tip));
if (--tip->i_nlink != 0) {
err = ufs_fault(ITOV(tip),
"ufs_dirrename: target directory link count != 0 (%s)",
tip->i_fs->fs_fsmnt);
rw_exit(&tip->i_contents);
return (err);
}
TRANS_INODE(tip->i_ufsvfs, tip);
ufs_setreclaim(tip);
tdp->i_nlink--;
ufs_setreclaim(tdp);
TRANS_INODE(tdp->i_ufsvfs, tdp);
tdp->i_flag |= ICHG;
tdp->i_seq++;
ITIMES_NOLOCK(tdp);
if (sdp != tdp) {
rw_exit(&tip->i_contents);
rw_exit(&sip->i_contents);
err = ufs_dirfixdotdot(sip, sdp, tdp);
return (err);
}
} else
ufs_setreclaim(tip);
out:
rw_exit(&tip->i_contents);
rw_exit(&sip->i_contents);
return (err);
}
static int
ufs_dirfixdotdot(
struct inode *dp,
struct inode *opdp,
struct inode *npdp)
{
struct fbuf *fbp;
struct dirtemplate *dirp;
vnode_t *dvp;
int err;
ASSERT(RW_WRITE_HELD(&npdp->i_rwlock));
ASSERT(RW_WRITE_HELD(&npdp->i_contents));
rw_enter(&dp->i_rwlock, RW_WRITER);
rw_enter(&dp->i_contents, RW_WRITER);
err = blkatoff(dp, (off_t)0, (char **)&dirp, &fbp);
if (err)
goto bad;
if (dp->i_nlink <= 0 ||
dp->i_size < sizeof (struct dirtemplate)) {
err = ENOENT;
goto bad;
}
if (dirp->dotdot_namlen != 2 ||
dirp->dotdot_name[0] != '.' ||
dirp->dotdot_name[1] != '.') {
dirbad(dp, "mangled .. entry", (off_t)0);
err = ENOTDIR;
goto bad;
}
if (npdp->i_nlink == MAXLINK) {
err = EMLINK;
goto bad;
}
npdp->i_nlink++;
TRANS_INODE(npdp->i_ufsvfs, npdp);
npdp->i_flag |= ICHG;
npdp->i_seq++;
ufs_iupdat(npdp, I_SYNC);
dvp = ITOV(dp);
dirp->dotdot_ino = (uint32_t)npdp->i_number;
dnlc_update(dvp, "..", ITOV(npdp));
(void) dnlc_dir_update(&dp->i_danchor, "..",
INO_OFF_TO_H(dirp->dotdot_ino, 0));
err = TRANS_DIR(dp, 0);
if (err)
fbrelse(fbp, S_OTHER);
else
err = ufs_fbwrite(fbp, dp);
fbp = NULL;
if (err)
goto bad;
rw_exit(&dp->i_contents);
rw_exit(&dp->i_rwlock);
ASSERT(opdp);
rw_enter(&opdp->i_contents, RW_WRITER);
ASSERT(opdp->i_nlink > 0);
opdp->i_nlink--;
ufs_setreclaim(opdp);
TRANS_INODE(opdp->i_ufsvfs, opdp);
opdp->i_flag |= ICHG;
opdp->i_seq++;
ufs_iupdat(opdp, I_SYNC);
rw_exit(&opdp->i_contents);
return (0);
bad:
if (fbp)
fbrelse(fbp, S_OTHER);
rw_exit(&dp->i_contents);
rw_exit(&dp->i_rwlock);
return (err);
}
static int
ufs_diraddentry(
struct inode *tdp,
char *namep,
enum de_op op,
int namlen,
struct ufs_slot *slotp,
struct inode *sip,
struct inode *sdp,
struct cred *cr)
{
struct direct *ep, *nep;
vnode_t *tdvp;
dcanchor_t *dcap = &tdp->i_danchor;
off_t offset;
int err;
ushort_t extra;
ASSERT(RW_WRITE_HELD(&tdp->i_rwlock));
ASSERT(RW_WRITE_HELD(&tdp->i_contents));
err = dirprepareentry(tdp, slotp, cr);
if (err) {
if (slotp->fbp) {
fbrelse(slotp->fbp, S_OTHER);
slotp->fbp = NULL;
}
return (err);
}
if (ITOV(tdp)->v_vfsp != ITOV(sip)->v_vfsp) {
err = EXDEV;
goto bad;
}
if ((op == DE_RENAME) && (((sip->i_mode & IFMT) == IFDIR) ||
((sip->i_mode & IFMT) == IFATTRDIR)) && (sdp != tdp)) {
err = ufs_dirfixdotdot(sip, sdp, tdp);
if (err)
goto bad;
}
ep = slotp->ep;
ep->d_namlen = (ushort_t)namlen;
(void) strncpy(ep->d_name, namep, (size_t)((namlen + 4) & ~3));
ep->d_ino = (uint32_t)sip->i_number;
tdvp = ITOV(tdp);
dnlc_update(tdvp, namep, ITOV(sip));
if (slotp->size) {
offset = slotp->offset - slotp->size;
} else {
offset = slotp->offset + 1;
}
if (slotp->cached) {
extra = ep->d_reclen - DIRSIZ(ep);
if (extra >= LDIRSIZ(1)) {
(void) dnlc_dir_add_space(dcap, extra,
(uint64_t)slotp->offset);
}
(void) dnlc_dir_add_entry(dcap, namep,
INO_OFF_TO_H(ep->d_ino, offset));
nep = (struct direct *)((char *)ep + ep->d_reclen);
if ((uintptr_t)nep & (DIRBLKSIZ - 1)) {
if ((nep->d_reclen == 0) || (nep->d_reclen & 0x3) ||
dnlc_dir_update(dcap, nep->d_name,
INO_OFF_TO_H(nep->d_ino, slotp->offset))
== DNOENT) {
dnlc_dir_purge(dcap);
slotp->cached = 0;
}
}
}
err = TRANS_DIR(tdp, slotp->offset);
if (err)
fbrelse(slotp->fbp, S_OTHER);
else
err = ufs_fbwrite(slotp->fbp, tdp);
slotp->fbp = NULL;
if (err)
return (err);
TRANS_INODE(tdp->i_ufsvfs, tdp);
tdp->i_flag |= IUPD|ICHG;
tdp->i_seq++;
tdp->i_diroff = 0;
ITIMES_NOLOCK(tdp);
if (tdp->i_flag & IATTCHG) {
ufs_iupdat(tdp, I_SYNC);
}
if (slotp->endoff && (slotp->endoff < tdp->i_size)) {
if (!TRANS_ISTRANS(tdp->i_ufsvfs)) {
(void) ufs_itrunc(tdp, (u_offset_t)slotp->endoff, 0,
cr);
}
}
return (0);
bad:
if (slotp->cached) {
dnlc_dir_purge(dcap);
fbrelse(slotp->fbp, S_OTHER);
slotp->cached = 0;
slotp->fbp = NULL;
return (err);
}
slotp->ep->d_ino = 0;
slotp->ep->d_namlen = 0;
if (TRANS_DIR(tdp, slotp->offset))
fbrelse(slotp->fbp, S_OTHER);
else
(void) ufs_fbwrite(slotp->fbp, tdp);
slotp->fbp = NULL;
return (err);
}
static int
dirprepareentry(
struct inode *dp,
struct ufs_slot *slotp,
struct cred *cr)
{
struct direct *ep, *nep;
off_t entryend;
int err;
slotstat_t status = slotp->status;
ushort_t dsize;
ASSERT((status == NONE) || (status == FOUND));
ASSERT(RW_WRITE_HELD(&dp->i_rwlock));
ASSERT(RW_WRITE_HELD(&dp->i_contents));
entryend = slotp->offset + slotp->size;
if (status == NONE) {
ASSERT((slotp->offset & (DIRBLKSIZ - 1)) == 0);
if (DIRBLKSIZ > dp->i_fs->fs_fsize) {
err = ufs_fault(ITOV(dp),
"dirprepareentry: bad fs_fsize, DIRBLKSIZ: %d"
" > dp->i_fs->fs_fsize: %d (%s)",
DIRBLKSIZ, dp->i_fs->fs_fsize, dp->i_fs->fs_fsmnt);
return (err);
}
err = BMAPALLOC(dp, (u_offset_t)slotp->offset,
(int)(blkoff(dp->i_fs, slotp->offset) + DIRBLKSIZ), cr);
if (err) {
return (err);
}
dp->i_size = entryend;
TRANS_INODE(dp->i_ufsvfs, dp);
dp->i_flag |= IUPD|ICHG|IATTCHG;
dp->i_seq++;
ITIMES_NOLOCK(dp);
} else if (entryend > dp->i_size) {
dp->i_size = P2ROUNDUP_TYPED(entryend, DIRBLKSIZ, off_t);
TRANS_INODE(dp->i_ufsvfs, dp);
dp->i_flag |= IUPD|ICHG|IATTCHG;
dp->i_seq++;
ITIMES_NOLOCK(dp);
}
if (slotp->fbp == NULL) {
err = blkatoff(dp, slotp->offset, (char **)&slotp->ep,
&slotp->fbp);
if (err) {
return (err);
}
}
ep = slotp->ep;
switch (status) {
case NONE:
ep->d_reclen = DIRBLKSIZ;
slotp->size = 0;
break;
case FOUND:
if (ep->d_ino == 0) {
slotp->size = 0;
} else {
dsize = DIRSIZ(ep);
nep = (struct direct *)((char *)ep + dsize);
nep->d_reclen = ep->d_reclen - dsize;
ep->d_reclen = dsize;
slotp->ep = nep;
slotp->offset += dsize;
slotp->size = dsize;
}
break;
default:
break;
}
return (0);
}
int
ufs_dirmakeinode(
struct inode *tdp,
struct inode **ipp,
struct vattr *vap,
enum de_op op,
struct cred *cr)
{
struct inode *ip;
enum vtype type;
int imode;
ino_t ipref;
int err;
timestruc_t now;
ASSERT(vap != NULL);
ASSERT(op == DE_CREATE || op == DE_MKDIR || op == DE_ATTRDIR ||
op == DE_SYMLINK);
ASSERT((vap->va_mask & (AT_TYPE|AT_MODE)) == (AT_TYPE|AT_MODE));
ASSERT(RW_WRITE_HELD(&tdp->i_rwlock));
ASSERT(RW_WRITE_HELD(&tdp->i_contents));
type = vap->va_type;
if (type == VDIR) {
ipref = dirpref(tdp);
} else {
ipref = tdp->i_number;
}
if (op == DE_ATTRDIR)
imode = vap->va_mode;
else
imode = MAKEIMODE(type, vap->va_mode);
*ipp = NULL;
err = ufs_ialloc(tdp, ipref, imode, &ip, cr);
if (err)
return (err);
ASSERT(RW_READ_HELD(&ip->i_ufsvfs->vfs_dqrwlock));
rw_enter(&ip->i_contents, RW_WRITER);
if (ip->i_dquot != NULL) {
err = ufs_fault(ITOV(ip),
"ufs_dirmakeinode, ip->i_dquot != NULL: dquot (%s)",
tdp->i_fs->fs_fsmnt);
rw_exit(&ip->i_contents);
return (err);
}
*ipp = ip;
ip->i_mode = (o_mode_t)imode;
if (type == VBLK || type == VCHR) {
dev_t d = vap->va_rdev;
dev32_t dev32;
if (!cmpldev(&dev32, d)) {
err = EOVERFLOW;
goto fail;
}
ITOV(ip)->v_rdev = ip->i_rdev = d;
if (dev32 & ~((O_MAXMAJ << L_BITSMINOR32) | O_MAXMIN)) {
ip->i_ordev = dev32;
} else {
ip->i_ordev = cmpdev(d);
}
}
ITOV(ip)->v_type = type;
ufs_reset_vnode(ip->i_vnode);
if (type == VDIR) {
ip->i_nlink = 2;
} else {
ip->i_nlink = 1;
}
if (op == DE_ATTRDIR) {
ip->i_uid = vap->va_uid;
ip->i_gid = vap->va_gid;
} else
ip->i_uid = crgetuid(cr);
if (op != DE_ATTRDIR && (vap->va_mask & AT_GID) &&
((vap->va_gid == tdp->i_gid) || groupmember(vap->va_gid, cr) ||
secpolicy_vnode_create_gid(cr) == 0)) {
ip->i_gid = vap->va_gid;
} else
ip->i_gid = (tdp->i_mode & ISGID) ? tdp->i_gid : crgetgid(cr);
ip->i_suid =
(ulong_t)ip->i_uid > (ulong_t)USHRT_MAX ? UID_LONG : ip->i_uid;
ip->i_sgid =
(ulong_t)ip->i_gid > (ulong_t)USHRT_MAX ? GID_LONG : ip->i_gid;
if ((tdp->i_mode & ISGID) && (type == VDIR))
ip->i_mode |= ISGID;
else {
if ((ip->i_mode & ISGID) &&
secpolicy_vnode_setids_setgids(cr, ip->i_gid) != 0)
ip->i_mode &= ~ISGID;
}
if (((vap->va_mask & AT_ATIME) && TIMESPEC_OVERFLOW(&vap->va_atime)) ||
((vap->va_mask & AT_MTIME) && TIMESPEC_OVERFLOW(&vap->va_mtime))) {
err = EOVERFLOW;
goto fail;
}
if (op != DE_ATTRDIR)
ip->i_dquot = getinoquota(ip);
else
ip->i_dquot = NULL;
if (op == DE_MKDIR || op == DE_ATTRDIR) {
err = ufs_dirmakedirect(ip, tdp, (op == DE_MKDIR) ? 0 : 1, cr);
if (err)
goto fail;
}
ASSERT((tdp->i_shadow && tdp->i_ufs_acl) ||
(!tdp->i_shadow && !tdp->i_ufs_acl));
if (tdp->i_shadow && tdp->i_ufs_acl &&
(((tdp->i_mode & IFMT) == IFDIR) ||
((tdp->i_mode & IFMT) == IFATTRDIR))) {
err = ufs_si_inherit(ip, tdp, ip->i_mode, cr);
if (err) {
if (op == DE_MKDIR) {
tdp->i_nlink--;
TRANS_INODE(tdp->i_ufsvfs, tdp);
tdp->i_flag |= ICHG;
tdp->i_seq++;
ufs_iupdat(tdp, I_SYNC);
}
goto fail;
}
}
if (vap->va_mask & (AT_MTIME|AT_ATIME)) {
if (vap->va_mask & AT_ATIME) {
ip->i_atime.tv_sec = vap->va_atime.tv_sec;
ip->i_atime.tv_usec = vap->va_atime.tv_nsec / 1000;
ip->i_flag &= ~IACC;
} else
ip->i_flag |= IACC;
if (vap->va_mask & AT_MTIME) {
ip->i_mtime.tv_sec = vap->va_mtime.tv_sec;
ip->i_mtime.tv_usec = vap->va_mtime.tv_nsec / 1000;
gethrestime(&now);
if (now.tv_sec > TIME32_MAX) {
ip->i_ctime.tv_sec = TIME32_MAX;
ip->i_ctime.tv_usec = 0;
} else {
ip->i_ctime.tv_sec = now.tv_sec;
ip->i_ctime.tv_usec = now.tv_nsec / 1000;
}
ip->i_flag &= ~(IUPD|ICHG);
ip->i_flag |= IMODTIME;
} else
ip->i_flag |= IUPD|ICHG;
ip->i_flag |= IMOD;
} else
ip->i_flag |= IACC|IUPD|ICHG;
ip->i_seq++;
if ((tdp->i_mode & IFMT) == IFATTRDIR) {
ip->i_cflags |= IXATTR;
}
TRANS_INODE(ip->i_ufsvfs, ip);
ufs_iupdat(ip, I_SYNC);
rw_exit(&ip->i_contents);
return (0);
fail:
ip->i_nlink = 0;
ufs_setreclaim(ip);
TRANS_INODE(ip->i_ufsvfs, ip);
ip->i_flag |= ICHG;
ip->i_seq++;
ITIMES_NOLOCK(ip);
rw_exit(&ip->i_contents);
return (err);
}
static int
ufs_dirmakedirect(
struct inode *ip,
struct inode *dp,
int attrdir,
struct cred *cr)
{
struct dirtemplate *dirp;
struct fbuf *fbp;
int err;
ASSERT(RW_WRITE_HELD(&ip->i_contents));
ASSERT(RW_WRITE_HELD(&dp->i_rwlock));
ASSERT(RW_WRITE_HELD(&dp->i_contents));
err = BMAPALLOC(ip, (u_offset_t)0, DIRBLKSIZ, cr);
if (err)
return (err);
if (DIRBLKSIZ > dp->i_fs->fs_fsize) {
err = ufs_fault(ITOV(dp),
"ufs_dirmakedirect: bad fs_fsize, DIRBLKSIZ: %d > dp->i_fs->fs_fsize: %d (%s)",
DIRBLKSIZ, dp->i_fs->fs_fsize,
dp->i_fs->fs_fsmnt);
return (err);
}
ip->i_size = DIRBLKSIZ;
TRANS_INODE(ip->i_ufsvfs, ip);
ip->i_flag |= IUPD|ICHG|IATTCHG;
ip->i_seq++;
ITIMES_NOLOCK(ip);
if (dp->i_nlink == MAXLINK)
return (EMLINK);
if (attrdir == 0)
dp->i_nlink++;
TRANS_INODE(dp->i_ufsvfs, dp);
dp->i_flag |= ICHG;
dp->i_seq++;
ufs_iupdat(dp, I_SYNC);
err = fbread(ITOV(ip), (offset_t)0, (uint_t)ip->i_fs->fs_fsize,
S_READ, &fbp);
if (err) {
goto fail;
}
dirp = (struct dirtemplate *)fbp->fb_addr;
*dirp = mastertemplate;
dirp->dot_ino = (uint32_t)ip->i_number;
dirp->dotdot_ino = (uint32_t)dp->i_number;
err = TRANS_DIR(ip, 0);
if (err) {
fbrelse(fbp, S_OTHER);
goto fail;
}
err = ufs_fbwrite(fbp, ip);
if (err) {
goto fail;
}
return (0);
fail:
if (attrdir == 0)
dp->i_nlink--;
TRANS_INODE(dp->i_ufsvfs, dp);
dp->i_flag |= ICHG;
dp->i_seq++;
ufs_iupdat(dp, I_SYNC);
return (err);
}
int
ufs_dirremove(
struct inode *dp,
char *namep,
struct inode *oip,
struct vnode *cdir,
enum dr_op op,
struct cred *cr)
{
struct direct *ep, *pep, *nep;
struct inode *ip;
vnode_t *dvp, *vp;
struct ufs_slot slot;
int namlen;
int err;
int mode;
ushort_t extra;
namlen = (int)strlen(namep);
if (namlen == 0) {
struct fs *fs = dp->i_fs;
cmn_err(CE_WARN, "%s: ufs_dirremove: attempted to remove"
" nameless file in directory (directory inode %llu)",
fs->fs_fsmnt, (u_longlong_t)dp->i_number);
ASSERT(namlen != 0);
return (ENOENT);
}
if (namep[0] == '.') {
if (namlen == 1)
return (EINVAL);
else if (namlen == 2 && namep[1] == '.') {
return (EEXIST);
}
}
ASSERT(RW_WRITE_HELD(&dp->i_rwlock));
retry:
if (err = ufs_diraccess(dp, IEXEC|IWRITE, cr))
return (err);
ip = NULL;
slot.fbp = NULL;
slot.status = FOUND;
rw_enter(&dp->i_ufsvfs->vfs_dqrwlock, RW_READER);
rw_enter(&dp->i_contents, RW_WRITER);
err = ufs_dircheckforname(dp, namep, namlen, &slot, &ip, cr, 0);
if (err)
goto out_novfs;
if (ip == NULL) {
err = ENOENT;
goto out_novfs;
}
vp = ITOV(ip);
if (oip && oip != ip) {
err = ENOENT;
goto out_novfs;
}
mode = ip->i_mode & IFMT;
if (mode == IFDIR || mode == IFATTRDIR) {
if (vn_vfsrlock(vp)) {
err = EBUSY;
goto out_novfs;
}
if (vn_mountedvfs(vp) != NULL && op != DR_RENAME) {
err = EBUSY;
goto out;
}
if (!rw_tryenter(&ip->i_rwlock, RW_WRITER)) {
ufs_dirremove_retry_cnt++;
vn_vfsunlock(vp);
if (slot.fbp)
fbrelse(slot.fbp, S_OTHER);
rw_exit(&dp->i_contents);
rw_exit(&dp->i_ufsvfs->vfs_dqrwlock);
rw_exit(&dp->i_rwlock);
VN_RELE(vp);
delay(2);
rw_enter(&dp->i_rwlock, RW_WRITER);
goto retry;
}
}
rw_enter(&ip->i_contents, RW_READER);
if ((err = ufs_sticky_remove_access(dp, ip, cr)) != 0) {
rw_exit(&ip->i_contents);
if (mode == IFDIR || mode == IFATTRDIR)
rw_exit(&ip->i_rwlock);
goto out;
}
if (op == DR_RMDIR) {
if (dp == ip || vp == cdir)
err = EINVAL;
else if (((ip->i_mode & IFMT) != IFDIR) &&
((ip->i_mode & IFMT) != IFATTRDIR))
err = ENOTDIR;
else if ((ip->i_nlink > 2) ||
!ufs_dirempty(ip, dp->i_number, cr)) {
err = EEXIST;
}
if (err) {
rw_exit(&ip->i_contents);
if (mode == IFDIR || mode == IFATTRDIR)
rw_exit(&ip->i_rwlock);
goto out;
}
} else if (op == DR_REMOVE) {
if (vp->v_type == VDIR &&
secpolicy_fs_linkdir(cr, vp->v_vfsp)) {
err = EPERM;
rw_exit(&ip->i_contents);
rw_exit(&ip->i_rwlock);
goto out;
}
}
rw_exit(&ip->i_contents);
dvp = ITOV(dp);
dnlc_remove(dvp, namep);
ep = slot.ep;
ep->d_ino = 0;
if (slot.cached) {
dcanchor_t *dcap = &dp->i_danchor;
(void) dnlc_dir_rem_entry(dcap, namep, NULL);
if (((int)ep->d_reclen - (int)DIRSIZ(ep)) >= LDIRSIZ(1)) {
(void) dnlc_dir_rem_space_by_handle(dcap, slot.offset);
}
if (slot.offset & (DIRBLKSIZ - 1)) {
ASSERT(slot.size);
pep = (struct direct *)((char *)ep - slot.size);
if ((pep->d_ino == 0) &&
((uintptr_t)pep & (DIRBLKSIZ - 1))) {
dnlc_dir_purge(dcap);
slot.cached = 0;
goto nocache;
}
if (pep->d_ino) {
extra = pep->d_reclen - DIRSIZ(pep);
} else {
extra = pep->d_reclen;
}
if (extra >= LDIRSIZ(1)) {
(void) dnlc_dir_rem_space_by_handle(dcap,
(uint64_t)(slot.offset - slot.size));
}
pep->d_reclen += ep->d_reclen;
(void) dnlc_dir_add_space(dcap, extra + ep->d_reclen,
(uint64_t)(slot.offset - slot.size));
nep = (struct direct *)((char *)ep + ep->d_reclen);
if ((uintptr_t)nep & (DIRBLKSIZ - 1)) {
if ((nep->d_reclen == 0) ||
(nep->d_reclen & 0x3) ||
(dnlc_dir_update(dcap, nep->d_name,
INO_OFF_TO_H(nep->d_ino,
slot.offset - slot.size)) == DNOENT)) {
dnlc_dir_purge(dcap);
slot.cached = 0;
}
}
} else {
(void) dnlc_dir_add_space(dcap, ep->d_reclen,
(uint64_t)slot.offset);
}
} else {
if (slot.offset & (DIRBLKSIZ - 1)) {
pep = (struct direct *)((char *)ep - slot.size);
pep->d_reclen += ep->d_reclen;
}
}
nocache:
err = TRANS_DIR(dp, slot.offset);
if (err)
fbrelse(slot.fbp, S_OTHER);
else
err = ufs_fbwrite(slot.fbp, dp);
slot.fbp = NULL;
if (err) {
if (mode == IFDIR || mode == IFATTRDIR)
rw_exit(&ip->i_rwlock);
goto out;
}
rw_enter(&ip->i_contents, RW_WRITER);
dp->i_flag |= IUPD|ICHG;
dp->i_seq++;
ip->i_flag |= ICHG;
ip->i_seq++;
TRANS_INODE(dp->i_ufsvfs, dp);
TRANS_INODE(ip->i_ufsvfs, ip);
if (ip->i_nlink > 0) {
if (op == DR_RMDIR && (ip->i_mode & IFMT) == IFDIR) {
ip->i_nlink -= 2;
dp->i_nlink--;
ufs_setreclaim(dp);
dnlc_remove(vp, ".");
dnlc_remove(vp, "..");
(void) ufs_dirpurgedotdot(ip, dp->i_number, cr);
} else {
ip->i_nlink--;
}
ufs_setreclaim(ip);
}
ITIMES_NOLOCK(dp);
ITIMES_NOLOCK(ip);
if (!TRANS_ISTRANS(dp->i_ufsvfs))
ufs_iupdat(dp, I_SYNC);
if (!TRANS_ISTRANS(ip->i_ufsvfs))
ufs_iupdat(ip, I_SYNC);
rw_exit(&ip->i_contents);
if (mode == IFDIR || mode == IFATTRDIR)
rw_exit(&ip->i_rwlock);
out:
if (mode == IFDIR || mode == IFATTRDIR) {
vn_vfsunlock(vp);
}
out_novfs:
ASSERT(RW_WRITE_HELD(&dp->i_contents));
if (slot.fbp)
fbrelse(slot.fbp, S_OTHER);
rw_exit(&dp->i_contents);
rw_exit(&dp->i_ufsvfs->vfs_dqrwlock);
if (ip)
VN_RELE(vp);
return (err);
}
int
blkatoff(
struct inode *ip,
off_t offset,
char **res,
struct fbuf **fbpp)
{
struct fs *fs;
struct fbuf *fbp;
daddr_t lbn;
uint_t bsize;
int err;
CPU_STATS_ADD_K(sys, ufsdirblk, 1);
fs = ip->i_fs;
lbn = (daddr_t)lblkno(fs, offset);
bsize = (uint_t)blksize(fs, ip, lbn);
err = fbread(ITOV(ip), (offset_t)(offset & fs->fs_bmask),
bsize, S_READ, &fbp);
if (err) {
*fbpp = (struct fbuf *)NULL;
return (err);
}
if (res)
*res = fbp->fb_addr + blkoff(fs, offset);
*fbpp = fbp;
return (0);
}
static int
dirmangled(
struct inode *dp,
struct direct *ep,
int entryoffsetinblock,
off_t offset)
{
int i;
i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
if ((ep->d_reclen & 0x3) != 0 || (int)ep->d_reclen > i ||
(uint_t)ep->d_reclen < DIRSIZ(ep) || ep->d_namlen > MAXNAMLEN ||
ep->d_ino && dirbadname(ep->d_name, (int)ep->d_namlen)) {
dirbad(dp, "mangled entry", offset);
return (1);
}
return (0);
}
static void
dirbad(struct inode *ip, char *how, off_t offset)
{
cmn_err(CE_NOTE, "%s: bad dir ino %d at offset %ld: %s",
ip->i_fs->fs_fsmnt, (int)ip->i_number, offset, how);
}
static int
dirbadname(char *sp, int l)
{
while (l--) {
if (*sp++ == '\0') {
return (1);
}
}
return (*sp);
}
static int
ufs_dirempty(
struct inode *ip,
ino_t parentino,
struct cred *cr)
{
return (ufs_dirscan(ip, parentino, cr, 0));
}
static int
ufs_dirpurgedotdot(
struct inode *ip,
ino_t parentino,
struct cred *cr)
{
return (ufs_dirscan(ip, parentino, cr, 1));
}
static int
ufs_dirscan(
struct inode *ip,
ino_t parentino,
struct cred *cr,
int clr_dotdot)
{
offset_t off;
struct tmp_dir dbuf, *dp;
int err, count;
int empty = 1;
dp = &dbuf;
ASSERT(RW_LOCK_HELD(&ip->i_contents));
ASSERT(ip->i_size <= (offset_t)MAXOFF_T);
for (off = 0; off < ip->i_size; off += dp->d_reclen) {
err = ufs_rdwri(UIO_READ, FREAD, ip, (caddr_t)dp,
sizeof (struct tmp_dir), off, UIO_SYSSPACE, &count, cr);
if (err || count != 0 || dp->d_reclen == 0) {
empty = 0;
break;
}
if (dp->d_ino == 0)
continue;
if (dp->d_namlen > 2 || dp->d_name[0] != '.') {
empty = 0;
break;
}
if (dp->d_namlen == 1)
continue;
if (dp->d_name[1] == '.' &&
(ino_t)dp->d_ino == parentino) {
if (clr_dotdot) {
empty = ufs_dirclrdotdot(ip, parentino);
break;
} else {
continue;
}
}
empty = 0;
break;
}
return (empty);
}
clock_t retry_backoff_delay = 1;
uint64_t dircheck_retry_cnt;
int
ufs_dircheckpath(
ino_t source_ino,
struct inode *target,
struct inode *sdp,
struct cred *cr)
{
struct fbuf *fbp;
struct dirtemplate *dirp;
struct inode *ip;
struct ufsvfs *ufsvfsp;
struct inode *tip;
ino_t dotdotino;
int err;
ASSERT(target->i_ufsvfs != NULL);
ASSERT(RW_LOCK_HELD(&target->i_rwlock));
ASSERT(RW_LOCK_HELD(&sdp->i_rwlock));
ip = target;
if (ip->i_number == source_ino) {
err = EINVAL;
goto out;
}
if (ip->i_number == UFSROOTINO) {
err = 0;
goto out;
}
fbp = NULL;
for (;;) {
struct vfs *vfs;
err = blkatoff(ip, (off_t)0, (char **)&dirp, &fbp);
if (err)
break;
if (((ip->i_mode & IFMT) != IFDIR) || ip->i_nlink == 0 ||
ip->i_size < sizeof (struct dirtemplate)) {
dirbad(ip, "bad size, unlinked or not dir", (off_t)0);
err = ENOTDIR;
break;
}
if (dirp->dotdot_namlen != 2 ||
dirp->dotdot_name[0] != '.' ||
dirp->dotdot_name[1] != '.') {
dirbad(ip, "mangled .. entry", (off_t)0);
err = ENOTDIR;
break;
}
dotdotino = (ino_t)dirp->dotdot_ino;
if (dotdotino == source_ino) {
err = EINVAL;
break;
}
if (dotdotino == UFSROOTINO)
break;
if (fbp) {
fbrelse(fbp, S_OTHER);
fbp = NULL;
}
vfs = ip->i_vfs;
ufsvfsp = ip->i_ufsvfs;
if (ip != target) {
rw_exit(&ip->i_rwlock);
VN_RELE(ITOV(ip));
}
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
if (err = ufs_iget_alloced(vfs, dotdotino, &tip, cr)) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
ip = NULL;
break;
}
rw_exit(&ufsvfsp->vfs_dqrwlock);
if (tip == sdp) {
VN_RELE(ITOV(tip));
ip = NULL;
break;
}
ip = tip;
retry_lock:
if (!rw_tryenter(&ip->i_rwlock, RW_READER)) {
if (RW_ISWRITER(&ip->i_rwlock) &&
!(RW_WRITE_HELD(&ip->i_rwlock))) {
err = EAGAIN;
if (fbp) {
fbrelse(fbp, S_OTHER);
}
VN_RELE(ITOV(ip));
return (err);
} else {
delay(retry_backoff_delay);
dircheck_retry_cnt++;
goto retry_lock;
}
}
}
if (fbp) {
fbrelse(fbp, S_OTHER);
}
out:
if (ip) {
if (ip != target) {
rw_exit(&ip->i_rwlock);
VN_RELE(ITOV(ip));
}
}
return (err);
}
int
ufs_xattrdirempty(struct inode *ip, ino_t parentino, struct cred *cr)
{
offset_t off;
struct tmp_dir dbuf, *dp;
int err, count;
int empty = 1;
dp = &dbuf;
ASSERT(RW_LOCK_HELD(&ip->i_contents));
ASSERT(ip->i_size <= (offset_t)MAXOFF_T);
for (off = 0; off < ip->i_size; off += dp->d_reclen) {
err = ufs_rdwri(UIO_READ, FREAD, ip, (caddr_t)dp,
sizeof (struct tmp_dir), off, UIO_SYSSPACE, &count, cr);
if (err || count != 0 || dp->d_reclen == 0) {
empty = 0;
break;
}
if (dp->d_ino == 0)
continue;
if (dp->d_namlen == 1 && dp->d_name[0] == '.' &&
(ino_t)dp->d_ino == parentino)
continue;
if (dp->d_namlen == 2 && dp->d_name[0] == '.' &&
dp->d_name[1] == '.') {
continue;
}
empty = 0;
break;
}
return (empty);
}
int
ufs_xattrmkdir(
struct inode *tdp,
struct inode **ipp,
int flags,
struct cred *cr)
{
struct inode *ip;
struct vattr va;
int err;
int retry = 1;
struct ufsvfs *ufsvfsp;
struct ulockfs *ulp;
int issync;
int trans_size;
int dorwlock;
if ((err = ufs_iaccess(tdp, IWRITE, cr, 1)) != 0) {
return (err);
}
if (vn_is_readonly(ITOV(tdp)))
return (EROFS);
again:
dorwlock = 0;
va.va_type = VDIR;
va.va_uid = tdp->i_uid;
va.va_gid = tdp->i_gid;
if ((tdp->i_mode & IFMT) == IFDIR) {
va.va_mode = (o_mode_t)IFATTRDIR;
va.va_mode |= tdp->i_mode & 0777;
} else {
va.va_mode = (o_mode_t)IFATTRDIR|0700;
if (tdp->i_mode & 0040)
va.va_mode |= 0750;
if (tdp->i_mode & 0004)
va.va_mode |= 0705;
}
va.va_mask = AT_TYPE|AT_MODE;
ufsvfsp = tdp->i_ufsvfs;
err = ufs_lockfs_begin(ufsvfsp, &ulp, ULOCKFS_MKDIR_MASK);
if (err)
return (err);
if (ITOV(tdp)->v_type != VDIR) {
rw_enter(&tdp->i_rwlock, RW_WRITER);
dorwlock = 1;
}
if (ulp) {
trans_size = (int)TOP_MKDIR_SIZE(tdp);
TRANS_BEGIN_CSYNC(ufsvfsp, issync, TOP_MKDIR, trans_size);
}
if (dorwlock == 0) {
rw_enter(&tdp->i_rwlock, RW_WRITER);
dorwlock = 2;
}
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
rw_enter(&tdp->i_contents, RW_WRITER);
if (retry)
tdp->i_flag |= IQUIET;
err = ufs_dirmakeinode(tdp, &ip, &va, DE_ATTRDIR, cr);
tdp->i_flag &= ~IQUIET;
if (err)
goto fail;
if (flags) {
tdp->i_oeftflag = ip->i_number;
}
ip->i_cflags |= IXATTR;
ITOV(ip)->v_flag |= V_XATTRDIR;
TRANS_INODE(ufsvfsp, tdp);
tdp->i_flag |= ICHG | IUPD;
tdp->i_seq++;
ufs_iupdat(tdp, I_SYNC);
rw_exit(&tdp->i_contents);
rw_exit(&ufsvfsp->vfs_dqrwlock);
rw_enter(&ip->i_rwlock, RW_WRITER);
rw_enter(&ip->i_contents, RW_WRITER);
TRANS_INODE(ufsvfsp, ip);
ip->i_flag |= ICHG| IUPD;
ip->i_seq++;
ufs_iupdat(ip, I_SYNC);
rw_exit(&ip->i_contents);
rw_exit(&ip->i_rwlock);
if (dorwlock == 2)
rw_exit(&tdp->i_rwlock);
if (ulp) {
int terr = 0;
TRANS_END_CSYNC(ufsvfsp, err, issync, TOP_MKDIR, trans_size);
ufs_lockfs_end(ulp);
if (err == 0)
err = terr;
}
if (dorwlock == 1)
rw_exit(&tdp->i_rwlock);
*ipp = ip;
return (err);
fail:
rw_exit(&tdp->i_contents);
rw_exit(&ufsvfsp->vfs_dqrwlock);
if (dorwlock == 2)
rw_exit(&tdp->i_rwlock);
if (ulp) {
TRANS_END_CSYNC(ufsvfsp, err, issync, TOP_MKDIR, trans_size);
ufs_lockfs_end(ulp);
}
if (dorwlock == 1)
rw_exit(&tdp->i_rwlock);
if (ip != NULL)
VN_RELE(ITOV(ip));
if ((err == ENOSPC) && retry && TRANS_ISTRANS(ufsvfsp)) {
ufs_delete_drain_wait(ufsvfsp, 1);
retry = 0;
goto again;
}
return (err);
}
static int
ufs_dirclrdotdot(struct inode *ip, ino_t parentino)
{
struct fbuf *fbp;
struct direct *dotp, *dotdotp;
int err = 0;
ASSERT(RW_WRITE_HELD(&ip->i_rwlock));
ASSERT(RW_LOCK_HELD(&ip->i_contents));
err = blkatoff(ip, 0, NULL, &fbp);
if (err) {
return (err);
}
dotp = (struct direct *)fbp->fb_addr;
if ((dotp->d_namlen < (MAXNAMLEN + 1)) &&
((DIRBLKSIZ - DIRSIZ(dotp)) >= (sizeof (struct dirtemplate) / 2))) {
dotdotp = (struct direct *)((char *)dotp + dotp->d_reclen);
if ((dotdotp->d_namlen < (MAXNAMLEN + 1)) &&
((DIRBLKSIZ - DIRSIZ(dotp)) >= dotdotp->d_reclen)) {
dotp->d_reclen += dotdotp->d_reclen;
if (parentino == dotdotp->d_ino) {
dotdotp->d_ino = 0;
dotdotp->d_namlen = 0;
dotdotp->d_reclen = 0;
}
err = TRANS_DIR(ip, 0);
if (err) {
fbrelse(fbp, S_OTHER);
} else {
err = ufs_fbwrite(fbp, ip);
}
}
} else {
err = -1;
}
return (err);
}