#include "opt_suiddir.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/fcntl.h>
#include <sys/filio.h>
#include <sys/limits.h>
#include <sys/stat.h>
#include <sys/bio.h>
#include <sys/buf2.h>
#include <sys/endian.h>
#include <sys/caps.h>
#include <sys/mount.h>
#include <sys/unistd.h>
#include <sys/time.h>
#include <sys/vnode.h>
#include <sys/namei.h>
#include <sys/lockf.h>
#include <sys/event.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/vmmeter.h>
#include <sys/vfsops.h>
#include <sys/malloc.h>
#include <sys/uio.h>
#include <sys/jail.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/vm_extern.h>
#include <vm/vm_object.h>
#include <vm/vm_page2.h>
#include <vm/vm_pager.h>
#include <vm/vnode_pager.h>
#include <vfs/ufs/dir.h>
#include <vfs/fifofs/fifo.h>
#include <vfs/ext2fs/fs.h>
#include <vfs/ext2fs/inode.h>
#include <vfs/ext2fs/ext2fs.h>
#include <vfs/ext2fs/ext2_extern.h>
#include <vfs/ext2fs/ext2_dinode.h>
#include <vfs/ext2fs/ext2_dir.h>
#include <vfs/ext2fs/ext2_mount.h>
#include <vfs/ext2fs/ext2_extents.h>
SDT_PROVIDER_DECLARE(ext2fs);
SDT_PROBE_DEFINE2(ext2fs, , vnops, trace, "int", "char*");
static int ext2_makeinode(int mode, struct vnode *, struct vnode **, struct componentname *);
static int ext2_chmod(struct vnode *, int, struct ucred *, struct thread *);
static int ext2_chown(struct vnode *, uid_t, gid_t, struct ucred *,
struct thread *);
static struct dirtemplate mastertemplate = {
0, htole16(12), 1, EXT2_FT_DIR, ".",
0, htole16(DIRBLKSIZ - 12), 2, EXT2_FT_DIR, ".."
};
static struct dirtemplate omastertemplate = {
0, htole16(12), 1, EXT2_FT_UNKNOWN, ".",
0, htole16(DIRBLKSIZ - 12), 2, EXT2_FT_UNKNOWN, ".."
};
void
ext2_itimes(struct vnode *vp)
{
struct inode *ip;
struct timespec ts;
ip = VTOI(vp);
if ((ip->i_flag & (IN_ACCESS | IN_CHANGE | IN_UPDATE)) == 0)
return;
if ((vp->v_type == VBLK || vp->v_type == VCHR))
ip->i_flag |= IN_LAZYMOD;
else
ip->i_flag |= IN_MODIFIED;
if ((vp->v_mount->mnt_flag & MNT_RDONLY) == 0) {
vfs_timestamp(&ts);
if (ip->i_flag & IN_ACCESS) {
ip->i_atime = ts.tv_sec;
ip->i_atimensec = ts.tv_nsec;
}
if (ip->i_flag & IN_UPDATE) {
ip->i_mtime = ts.tv_sec;
ip->i_mtimensec = ts.tv_nsec;
ip->i_modrev++;
}
if (ip->i_flag & IN_CHANGE) {
ip->i_ctime = ts.tv_sec;
ip->i_ctimensec = ts.tv_nsec;
}
}
ip->i_flag &= ~(IN_ACCESS | IN_CHANGE | IN_UPDATE);
}
static int
ext2_create(struct vop_old_create_args *ap)
{
int error;
error =
ext2_makeinode(MAKEIMODE(ap->a_vap->va_type, ap->a_vap->va_mode),
ap->a_dvp, ap->a_vpp, ap->a_cnp);
if (error != 0)
return (error);
return (0);
}
static int
ext2_open(struct vop_open_args *ap)
{
if (ap->a_vp->v_type == VBLK || ap->a_vp->v_type == VCHR)
return (EOPNOTSUPP);
if ((VTOI(ap->a_vp)->i_flags & APPEND) &&
(ap->a_mode & (FWRITE | O_APPEND)) == FWRITE)
return (EPERM);
return (vop_stdopen(ap));
}
static int
ext2_close(struct vop_close_args *ap)
{
struct vnode *vp = ap->a_vp;
if (VREFCNT(vp) > 1)
ext2_itimes(vp);
return (vop_stdclose(ap));
}
static int
ext2_access(struct vop_access_args *ap)
{
struct vnode *vp = ap->a_vp;
struct inode *ip = VTOI(vp);
int error;
if (vp->v_type == VBLK || vp->v_type == VCHR)
return (EOPNOTSUPP);
error = vop_helper_access(ap, ip->i_uid, ip->i_gid, ip->i_mode,
ip->i_flags);
return (error);
}
static int
ext2_getattr(struct vop_getattr_args *ap)
{
struct vnode *vp = ap->a_vp;
struct inode *ip = VTOI(vp);
struct vattr *vap = ap->a_vap;
ext2_itimes(vp);
vap->va_fsid = devid_from_dev(ip->i_dev);
vap->va_fileid = ip->i_number;
vap->va_mode = ip->i_mode & ~IFMT;
vap->va_nlink = ip->i_nlink;
vap->va_uid = ip->i_uid;
vap->va_gid = ip->i_gid;
vap->va_size = ip->i_size;
vap->va_atime.tv_sec = ip->i_atime;
vap->va_atime.tv_nsec = E2DI_HAS_XTIME(ip) ? ip->i_atimensec : 0;
vap->va_mtime.tv_sec = ip->i_mtime;
vap->va_mtime.tv_nsec = E2DI_HAS_XTIME(ip) ? ip->i_mtimensec : 0;
vap->va_ctime.tv_sec = ip->i_ctime;
vap->va_ctime.tv_nsec = E2DI_HAS_XTIME(ip) ? ip->i_ctimensec : 0;
vap->va_flags = ip->i_flags;
vap->va_gen = ip->i_gen;
vap->va_blocksize = vp->v_mount->mnt_stat.f_iosize;
vap->va_bytes = dbtob((u_quad_t)ip->i_blocks);
vap->va_type = IFTOVT(ip->i_mode);
vap->va_filerev = ip->i_modrev;
return (0);
}
static int
ext2_setattr(struct vop_setattr_args *ap)
{
struct vattr *vap = ap->a_vap;
struct vnode *vp = ap->a_vp;
struct inode *ip = VTOI(vp);
struct ucred *cred = ap->a_cred;
struct thread *td = curthread;
int error;
if ((vap->va_type != VNON) || (vap->va_nlink != VNOVAL) ||
(vap->va_fsid != VNOVAL) || (vap->va_fileid != VNOVAL) ||
(vap->va_blocksize != VNOVAL) || (vap->va_rmajor != VNOVAL) ||
((int)vap->va_bytes != VNOVAL) || (vap->va_gen != VNOVAL)) {
return (EINVAL);
}
if (vap->va_flags != VNOVAL) {
if (vap->va_flags & ~(SF_APPEND | SF_IMMUTABLE | UF_NODUMP))
return (EOPNOTSUPP);
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EROFS);
if (cred->cr_uid != ip->i_uid &&
(error = caps_priv_check(cred, SYSCAP_NOVFS_SETATTR)))
{
return (error);
}
if (cred->cr_uid == 0 &&
(!jailed(cred) || PRISON_CAP_ISSET(cred->cr_prison->pr_caps,
PRISON_CAP_VFS_CHFLAGS))) {
if ((ip->i_flags
& (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND)) &&
securelevel > 0)
return (EPERM);
ip->i_flags = vap->va_flags;
} else {
if (ip->i_flags
& (SF_NOUNLINK | SF_IMMUTABLE | SF_APPEND) ||
(vap->va_flags & UF_SETTABLE) != vap->va_flags)
return (EPERM);
ip->i_flags &= SF_SETTABLE;
ip->i_flags |= (vap->va_flags & UF_SETTABLE);
}
ip->i_flag |= IN_CHANGE;
if (vap->va_flags & (IMMUTABLE | APPEND))
return (0);
}
if (ip->i_flags & (IMMUTABLE | APPEND))
return (EPERM);
if (vap->va_uid != (uid_t)VNOVAL || vap->va_gid != (gid_t)VNOVAL) {
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EROFS);
if ((error = ext2_chown(vp, vap->va_uid, vap->va_gid, cred,
td)) != 0)
return (error);
}
if (vap->va_size != VNOVAL) {
switch (vp->v_type) {
case VDIR:
return (EISDIR);
case VLNK:
case VREG:
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EROFS);
break;
default:
break;
}
if ((error = ext2_truncate(vp, vap->va_size, 0, cred)) != 0)
return (error);
}
if (vap->va_atime.tv_sec != VNOVAL || vap->va_mtime.tv_sec != VNOVAL) {
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EROFS);
if (cred->cr_uid != ip->i_uid &&
(error = caps_priv_check(cred, SYSCAP_NOVFS_SETATTR)) &&
((vap->va_vaflags & VA_UTIMES_NULL) == 0 ||
(error = VOP_EACCESS(vp, VWRITE, cred))))
{
return (error);
}
ip->i_flag |= IN_CHANGE | IN_MODIFIED;
if (vap->va_atime.tv_sec != VNOVAL) {
ip->i_flag &= ~IN_ACCESS;
ip->i_atime = vap->va_atime.tv_sec;
ip->i_atimensec = vap->va_atime.tv_nsec;
}
if (vap->va_mtime.tv_sec != VNOVAL) {
ip->i_flag &= ~IN_UPDATE;
ip->i_mtime = vap->va_mtime.tv_sec;
ip->i_mtimensec = vap->va_mtime.tv_nsec;
}
error = ext2_update(vp, 0);
if (error)
return (error);
}
error = 0;
if (vap->va_mode != (mode_t)VNOVAL) {
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EROFS);
error = ext2_chmod(vp, (int)vap->va_mode, cred, td);
}
return (error);
}
static int
ext2_chmod(struct vnode *vp, int mode, struct ucred *cred, struct thread *td)
{
struct inode *ip = VTOI(vp);
int error;
if (cred->cr_uid != ip->i_uid) {
error = caps_priv_check(cred, SYSCAP_NOVFS_CHMOD);
if (error)
return (error);
}
if (cred->cr_uid) {
if (vp->v_type != VDIR && (mode & S_ISTXT))
return (EFTYPE);
if (!groupmember(ip->i_gid, cred) && (mode & ISGID))
return (EPERM);
}
ip->i_mode &= ~ALLPERMS;
ip->i_mode |= (mode & ALLPERMS);
ip->i_flag |= IN_CHANGE;
return (0);
}
static int
ext2_chown(struct vnode *vp, uid_t uid, gid_t gid, struct ucred *cred,
struct thread *td)
{
struct inode *ip = VTOI(vp);
uid_t ouid;
gid_t ogid;
int error = 0;
if (uid == (uid_t)VNOVAL)
uid = ip->i_uid;
if (gid == (gid_t)VNOVAL)
gid = ip->i_gid;
if ((cred->cr_uid != ip->i_uid || uid != ip->i_uid ||
(gid != ip->i_gid && !(cred->cr_gid == gid ||
groupmember(gid, cred)))) &&
(error = caps_priv_check(cred, SYSCAP_NOVFS_CHOWN)))
{
return (error);
}
ogid = ip->i_gid;
ouid = ip->i_uid;
ip->i_gid = gid;
ip->i_uid = uid;
ip->i_flag |= IN_CHANGE;
if ((ip->i_mode & (ISUID | ISGID)) && (ouid != uid || ogid != gid)) {
if (caps_priv_check(cred, SYSCAP_NOVFS_RETAINSUGID) != 0)
ip->i_mode &= ~(ISUID | ISGID);
}
return (0);
}
struct ext2_fsync_bp_info {
struct vnode *vp;
int waitfor;
};
static int
ext2_fsync_bp(struct buf *bp, void *data)
{
struct ext2_fsync_bp_info *info = data;
if (BUF_LOCK(bp, LK_EXCLUSIVE | LK_NOWAIT))
return (0);
if ((bp->b_flags & B_DELWRI) == 0)
panic("ext2_fsync: not dirty");
bremfree(bp);
if (bp->b_vp == info->vp || (info->waitfor & MNT_NOWAIT))
bawrite(bp);
else
bwrite(bp);
return (1);
}
static int
ext2_fsync(struct vop_fsync_args *ap)
{
struct ext2_fsync_bp_info info;
struct vnode *vp = ap->a_vp;
int count;
lwkt_gettoken(&vp->v_token);
info.vp = vp;
loop:
info.waitfor = ap->a_waitfor;
count = RB_SCAN(buf_rb_tree, &vp->v_rbdirty_tree, NULL,
ext2_fsync_bp, &info);
if (count)
goto loop;
if (ap->a_waitfor == MNT_WAIT) {
bio_track_wait(&vp->v_track_write, 0, 0);
#ifdef DIAGNOSTIC
if (!RB_EMPTY(&vp->v_rbdirty_tree)) {
vprint("ext2_fsync: dirty", vp);
goto loop;
}
#endif
}
lwkt_reltoken(&vp->v_token);
return (ext2_update(ap->a_vp, ap->a_waitfor == MNT_WAIT));
}
static int
ext2_mknod(struct vop_old_mknod_args *ap)
{
struct vattr *vap = ap->a_vap;
struct vnode **vpp = ap->a_vpp;
struct inode *ip;
ino_t ino;
int error;
if (vap->va_rmajor != VNOVAL &&
makeudev(vap->va_rmajor, vap->va_rminor) == NOUDEV) {
return (EINVAL);
}
error = ext2_makeinode(MAKEIMODE(vap->va_type, vap->va_mode),
ap->a_dvp, vpp, ap->a_cnp);
if (error)
return (error);
ip = VTOI(*vpp);
ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE;
if (vap->va_rmajor != VNOVAL) {
ip->i_rdev = makeudev(vap->va_rmajor, vap->va_rminor);
}
(*vpp)->v_type = VNON;
ino = ip->i_number;
vgone_vxlocked(*vpp);
vput(*vpp);
error = VFS_VGET(ap->a_dvp->v_mount, NULL, ino, vpp);
if (error) {
*vpp = NULL;
return (error);
}
return (0);
}
static int
ext2_remove(struct vop_old_remove_args *ap)
{
struct inode *ip;
struct vnode *vp = ap->a_vp;
struct vnode *dvp = ap->a_dvp;
int error;
ip = VTOI(vp);
if ((ip->i_flags & (NOUNLINK | IMMUTABLE | APPEND)) ||
(VTOI(dvp)->i_flags & APPEND)) {
error = EPERM;
goto out;
}
error = ext2_dirremove(dvp, ap->a_cnp);
if (error == 0) {
ip->i_nlink--;
ip->i_flag |= IN_CHANGE;
}
out:
return (error);
}
static int
ext2_link(struct vop_old_link_args *ap)
{
struct vnode *vp = ap->a_vp;
struct vnode *tdvp = ap->a_tdvp;
struct componentname *cnp = ap->a_cnp;
struct inode *ip;
int error;
if (tdvp->v_mount != vp->v_mount) {
error = EXDEV;
goto out2;
}
if (tdvp != vp) {
error = vn_lock(vp, LK_EXCLUSIVE | LK_FAILRECLAIM);
if (error)
goto out2;
}
ip = VTOI(vp);
if ((nlink_t)ip->i_nlink >= EXT4_LINK_MAX) {
error = EMLINK;
goto out;
}
if (ip->i_flags & (IMMUTABLE | APPEND)) {
error = EPERM;
goto out;
}
ip->i_nlink++;
ip->i_flag |= IN_CHANGE;
error = ext2_update(vp, !DOINGASYNC(vp));
if (!error)
error = ext2_direnter(ip, tdvp, cnp);
if (error) {
ip->i_nlink--;
ip->i_flag |= IN_CHANGE;
}
out:
if (tdvp != vp)
vn_unlock(vp);
out2:
return (error);
}
static int
ext2_inc_nlink(struct inode *ip)
{
ip->i_nlink++;
if (S_ISDIR(ip->i_mode) &&
EXT2_HAS_RO_COMPAT_FEATURE(ip->i_e2fs, EXT2F_ROCOMPAT_DIR_NLINK) &&
ip->i_nlink > 1) {
if (ip->i_nlink >= EXT4_LINK_MAX || ip->i_nlink == 2)
ip->i_nlink = 1;
} else if (ip->i_nlink > EXT4_LINK_MAX) {
ip->i_nlink--;
return (EMLINK);
}
return (0);
}
static void
ext2_dec_nlink(struct inode *ip)
{
if (!S_ISDIR(ip->i_mode) || ip->i_nlink > 2)
ip->i_nlink--;
}
static int
ext2_rename(struct vop_old_rename_args *ap)
{
struct vnode *tvp = ap->a_tvp;
struct vnode *tdvp = ap->a_tdvp;
struct vnode *fvp = ap->a_fvp;
struct vnode *fdvp = ap->a_fdvp;
struct componentname *tcnp = ap->a_tcnp;
struct componentname *fcnp = ap->a_fcnp;
struct inode *ip, *xp, *dp;
struct dirtemplate *dirbuf;
int doingdirectory = 0, oldparent = 0, newparent = 0;
int error = 0;
u_char namlen;
if ((fvp->v_mount != tdvp->v_mount) ||
(tvp && (fvp->v_mount != tvp->v_mount))) {
error = EXDEV;
abortit:
if (tdvp == tvp)
vrele(tdvp);
else
vput(tdvp);
if (tvp)
vput(tvp);
vrele(fdvp);
vrele(fvp);
return (error);
}
if (tvp && ((VTOI(tvp)->i_flags & (NOUNLINK | IMMUTABLE | APPEND)) ||
(VTOI(tdvp)->i_flags & APPEND))) {
error = EPERM;
goto abortit;
}
if (fvp == tvp) {
SDT_PROBE2(ext2fs, , vnops, trace, 1,
"rename: fvp == tvp (can't happen)");
error = 0;
goto abortit;
}
if ((error = vn_lock(fvp, LK_EXCLUSIVE | LK_FAILRECLAIM)) != 0)
goto abortit;
dp = VTOI(fdvp);
ip = VTOI(fvp);
if (ip->i_nlink >= EXT4_LINK_MAX &&
!EXT2_HAS_RO_COMPAT_FEATURE(ip->i_e2fs, EXT2F_ROCOMPAT_DIR_NLINK)) {
vn_unlock(fvp);
error = EMLINK;
goto abortit;
}
if ((ip->i_flags & (NOUNLINK | IMMUTABLE | APPEND))
|| (dp->i_flags & APPEND)) {
vn_unlock(fvp);
error = EPERM;
goto abortit;
}
if ((ip->i_mode & IFMT) == IFDIR) {
if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') ||
dp == ip || (fcnp->cn_flags | tcnp->cn_flags) & CNP_ISDOTDOT ||
(ip->i_flag & IN_RENAME)) {
vn_unlock(fvp);
error = EINVAL;
goto abortit;
}
ip->i_flag |= IN_RENAME;
oldparent = dp->i_number;
doingdirectory++;
}
dp = VTOI(tdvp);
xp = NULL;
if (tvp)
xp = VTOI(tvp);
ext2_inc_nlink(ip);
ip->i_flag |= IN_CHANGE;
if ((error = ext2_update(fvp, !DOINGASYNC(fvp))) != 0) {
vn_unlock(fvp);
goto bad;
}
error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred);
vn_unlock(fvp);
if (oldparent != dp->i_number)
newparent = dp->i_number;
if (doingdirectory && newparent) {
if (error)
goto bad;
if (xp != NULL) {
vput(tvp);
xp = NULL;
}
vref(tdvp);
error = ext2_checkpath(ip, dp, tcnp->cn_cred);
tcnp->cn_flags |= CNP_PDIRUNLOCK;
if (error) {
vrele(tdvp);
goto out;
}
error = relookup(tdvp, &tvp, tcnp);
if (error) {
if (tcnp->cn_flags & CNP_PDIRUNLOCK)
vrele(tdvp);
else
vput(tdvp);
goto out;
}
dp = VTOI(tdvp);
if (tvp)
xp = VTOI(tvp);
}
if (xp == NULL) {
if (dp->i_devvp != ip->i_devvp)
panic("ext2_rename: EXDEV");
if (doingdirectory && newparent) {
if ((nlink_t)dp->i_nlink >= LINK_MAX) {
error = EMLINK;
goto bad;
}
error = ext2_inc_nlink(dp);
if (error)
goto bad;
dp->i_flag |= IN_CHANGE;
error = ext2_update(tdvp, !DOINGASYNC(tdvp));
if (error)
goto bad;
}
error = ext2_direnter(ip, tdvp, tcnp);
if (error) {
if (doingdirectory && newparent) {
ext2_dec_nlink(dp);
dp->i_flag |= IN_CHANGE;
(void)ext2_update(tdvp, 1);
}
goto bad;
}
vput(tdvp);
} else {
if (xp->i_devvp != dp->i_devvp || xp->i_devvp != ip->i_devvp)
panic("ext2_rename: EXDEV");
if (xp->i_number == ip->i_number)
panic("ext2_rename: same file");
if ((dp->i_mode & S_ISTXT) && tcnp->cn_cred->cr_uid != 0 &&
tcnp->cn_cred->cr_uid != dp->i_uid &&
xp->i_uid != tcnp->cn_cred->cr_uid) {
error = EPERM;
goto bad;
}
if ((xp->i_mode & IFMT) == IFDIR) {
if (!ext2_dirempty(xp, dp->i_number, tcnp->cn_cred)) {
error = ENOTEMPTY;
goto bad;
}
if (!doingdirectory) {
error = ENOTDIR;
goto bad;
}
} else if (doingdirectory) {
error = EISDIR;
goto bad;
}
error = ext2_dirrewrite(dp, ip, tcnp);
if (error)
goto bad;
if (doingdirectory && !newparent) {
ext2_dec_nlink(dp);
dp->i_flag |= IN_CHANGE;
}
vput(tdvp);
ext2_dec_nlink(xp);
if (doingdirectory) {
if (xp->i_nlink > 2)
panic("ext2_rename: linked directory");
error = ext2_truncate(tvp, (off_t)0, IO_SYNC,
tcnp->cn_cred);
xp->i_nlink = 0;
}
xp->i_flag |= IN_CHANGE;
vput(tvp);
xp = NULL;
}
fcnp->cn_flags &= ~CNP_MODMASK;
fcnp->cn_flags |= CNP_LOCKPARENT;
error = relookup(fdvp, &fvp, fcnp);
if (error) {
if (doingdirectory)
panic("ext2_rename: lost dir entry");
vrele(ap->a_fvp);
if (fcnp->cn_flags & CNP_PDIRUNLOCK)
vrele(fdvp);
else
vput(fdvp);
return (0);
}
KKASSERT((fcnp->cn_flags & CNP_PDIRUNLOCK) == 0);
if (fvp == NULL) {
if (doingdirectory)
panic("ext2_rename: lost dir entry");
vrele(ap->a_fvp);
vput(fvp);
vput(fdvp);
return (0);
}
xp = VTOI(fvp);
dp = VTOI(fdvp);
if (xp != ip) {
} else {
if (doingdirectory && newparent) {
ext2_dec_nlink(dp);
dp->i_flag |= IN_CHANGE;
dirbuf = malloc(dp->i_e2fs->e2fs_bsize, M_TEMP, M_WAITOK | M_ZERO);
error = vn_rdwr(UIO_READ, fvp, (caddr_t)&dirbuf,
sizeof (struct dirtemplate), (off_t)0,
UIO_SYSSPACE, IO_NODELOCKED,
tcnp->cn_cred, NULL);
if (error == 0) {
namlen = dirbuf->dotdot_type;
if (namlen != 2 ||
dirbuf->dotdot_name[0] != '.' ||
dirbuf->dotdot_name[1] != '.') {
ext2_dirbad(xp, (doff_t)12,
"rename: mangled dir");
} else {
dirbuf->dotdot_ino = htole32(newparent);
ext2_dirent_csum_set(ip,
(struct ext2fs_direct_2 *)dirbuf);
ext2_dx_csum_set(ip,
(struct ext2fs_direct_2 *)dirbuf);
vn_rdwr(UIO_WRITE, fvp,
(caddr_t)&dirbuf,
sizeof (struct dirtemplate),
(off_t)0, UIO_SYSSPACE,
IO_NODELOCKED | IO_SYNC,
tcnp->cn_cred, NULL);
}
}
free(dirbuf, M_TEMP);
}
error = ext2_dirremove(fdvp, fcnp);
if (!error) {
ext2_dec_nlink(xp);
xp->i_flag |= IN_CHANGE;
}
xp->i_flag &= ~IN_RENAME;
}
if (dp)
vput(fdvp);
if (xp)
vput(fvp);
vrele(ap->a_fvp);
return (error);
bad:
if (xp)
vput(ITOV(xp));
vput(ITOV(dp));
out:
if (doingdirectory)
ip->i_flag &= ~IN_RENAME;
if (vn_lock(fvp, LK_EXCLUSIVE) == 0) {
ext2_dec_nlink(ip);
ip->i_flag |= IN_CHANGE;
ip->i_flag &= ~IN_RENAME;
vput(fvp);
} else
vrele(fvp);
return (error);
}
static int
ext2_mkdir(struct vop_old_mkdir_args *ap)
{
struct m_ext2fs *fs;
struct vnode *dvp = ap->a_dvp;
struct vattr *vap = ap->a_vap;
struct componentname *cnp = ap->a_cnp;
struct inode *ip, *dp;
struct vnode *tvp;
struct dirtemplate dirtemplate, *dtp;
char *buf = NULL;
int error, dmode;
dp = VTOI(dvp);
if ((nlink_t)dp->i_nlink >= EXT4_LINK_MAX &&
!EXT2_HAS_RO_COMPAT_FEATURE(dp->i_e2fs, EXT2F_ROCOMPAT_DIR_NLINK)) {
error = EMLINK;
goto out;
}
dmode = vap->va_mode & 0777;
dmode |= IFDIR;
error = ext2_valloc(dvp, dmode, cnp->cn_cred, &tvp);
if (error)
goto out;
ip = VTOI(tvp);
fs = ip->i_e2fs;
ip->i_gid = dp->i_gid;
#ifdef SUIDDIR
{
if ((dvp->v_mount->mnt_flag & MNT_SUIDDIR) &&
(dp->i_mode & ISUID) && dp->i_uid) {
dmode |= ISUID;
ip->i_uid = dp->i_uid;
} else {
ip->i_uid = cnp->cn_cred->cr_uid;
}
}
#else
ip->i_uid = cnp->cn_cred->cr_uid;
#endif
ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE;
ip->i_mode = dmode;
tvp->v_type = VDIR;
ip->i_nlink = 2;
if (cnp->cn_flags & CNP_ISWHITEOUT)
ip->i_flags |= UF_OPAQUE;
error = ext2_update(tvp, 1);
vinitvmio(tvp, 0, PAGE_SIZE, -1);
ext2_inc_nlink(dp);
dp->i_flag |= IN_CHANGE;
error = ext2_update(dvp, !DOINGASYNC(dvp));
if (error)
goto bad;
if (EXT2_HAS_INCOMPAT_FEATURE(ip->i_e2fs,
EXT2F_INCOMPAT_FTYPE))
dtp = &mastertemplate;
else
dtp = &omastertemplate;
dirtemplate = *dtp;
dirtemplate.dot_ino = htole32(ip->i_number);
dirtemplate.dotdot_ino = htole32(dp->i_number);
#undef DIRBLKSIZ
#define DIRBLKSIZ VTOI(dvp)->i_e2fs->e2fs_bsize
dirtemplate.dotdot_reclen = htole16(DIRBLKSIZ - 12);
buf = malloc(DIRBLKSIZ, M_TEMP, M_WAITOK | M_ZERO);
if (EXT2_HAS_RO_COMPAT_FEATURE(fs, EXT2F_ROCOMPAT_METADATA_CKSUM)) {
dirtemplate.dotdot_reclen =
htole16(le16toh(dirtemplate.dotdot_reclen) -
sizeof(struct ext2fs_direct_tail));
ext2_init_dirent_tail(EXT2_DIRENT_TAIL(buf, DIRBLKSIZ));
}
memcpy(buf, &dirtemplate, sizeof(dirtemplate));
ext2_dirent_csum_set(ip, (struct ext2fs_direct_2 *)buf);
error = vn_rdwr(UIO_WRITE, tvp, (caddr_t)buf,
DIRBLKSIZ, (off_t)0, UIO_SYSSPACE,
IO_NODELOCKED | IO_SYNC, cnp->cn_cred, NULL);
if (error) {
ext2_dec_nlink(dp);
dp->i_flag |= IN_CHANGE;
goto bad;
}
if (DIRBLKSIZ > VFSTOEXT2(dvp->v_mount)->um_mountp->mnt_stat.f_bsize)
panic("ext2_mkdir: blksize");
else {
ip->i_size = DIRBLKSIZ;
ip->i_flag |= IN_CHANGE;
}
error = ext2_direnter(ip, dvp, cnp);
if (error) {
ext2_dec_nlink(dp);
dp->i_flag |= IN_CHANGE;
}
bad:
if (error) {
ip->i_nlink = 0;
ip->i_flag |= IN_CHANGE;
vput(tvp);
} else
*ap->a_vpp = tvp;
out:
free(buf, M_TEMP);
return (error);
#undef DIRBLKSIZ
#define DIRBLKSIZ DEV_BSIZE
}
static int
ext2_rmdir(struct vop_old_rmdir_args *ap)
{
struct vnode *vp = ap->a_vp;
struct vnode *dvp = ap->a_dvp;
struct componentname *cnp = ap->a_cnp;
struct inode *ip, *dp;
int error;
ip = VTOI(vp);
dp = VTOI(dvp);
if (!ext2_dirempty(ip, dp->i_number, cnp->cn_cred)) {
error = ENOTEMPTY;
goto out;
}
if ((dp->i_flags & APPEND)
|| (ip->i_flags & (NOUNLINK | IMMUTABLE | APPEND))) {
error = EPERM;
goto out;
}
error = ext2_dirremove(dvp, cnp);
if (error)
goto out;
ext2_dec_nlink(dp);
dp->i_flag |= IN_CHANGE;
vn_unlock(dvp);
ip->i_nlink = 0;
error = ext2_truncate(vp, (off_t)0, IO_SYNC, cnp->cn_cred);
vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY);
out:
return (error);
}
static int
ext2_symlink(struct vop_old_symlink_args *ap)
{
struct vnode *vp, **vpp = ap->a_vpp;
struct inode *ip;
int len, error;
error = ext2_makeinode(IFLNK | ap->a_vap->va_mode, ap->a_dvp,
vpp, ap->a_cnp);
if (error)
return (error);
vp = *vpp;
len = strlen(ap->a_target);
if (len < vp->v_mount->mnt_maxsymlinklen) {
ip = VTOI(vp);
bcopy(ap->a_target, (char *)ip->i_shortlink, len);
ip->i_size = len;
ip->i_flag |= IN_CHANGE | IN_UPDATE;
} else {
if (vp->v_object == NULL)
vinitvmio(vp, 0, PAGE_SIZE, -1);
error = vn_rdwr(UIO_WRITE, vp, ap->a_target, len, (off_t)0,
UIO_SYSSPACE, IO_NODELOCKED, ap->a_cnp->cn_cred, NULL);
}
if (error)
vput(vp);
return (error);
}
static int
ext2_readlink(struct vop_readlink_args *ap)
{
struct vnode *vp = ap->a_vp;
struct inode *ip = VTOI(vp);
int isize;
isize = ip->i_size;
if (isize < vp->v_mount->mnt_maxsymlinklen) {
uiomove((char *)ip->i_shortlink, isize, ap->a_uio);
return (0);
}
return (VOP_READ(vp, ap->a_uio, 0, ap->a_cred));
}
static int
ext2_strategy(struct vop_strategy_args *ap)
{
struct bio *bio = ap->a_bio;
struct bio *nbio;
struct buf *bp = bio->bio_buf;
struct vnode *vp = ap->a_vp;
struct inode *ip;
int error;
ip = VTOI(vp);
if (vp->v_type == VBLK || vp->v_type == VCHR)
panic("ext2_strategy: spec");
nbio = push_bio(bio);
if (nbio->bio_offset == NOOFFSET) {
error = VOP_BMAP(vp, bio->bio_offset, &nbio->bio_offset, NULL,
NULL, bp->b_cmd);
if (error) {
bp->b_error = error;
bp->b_flags |= B_ERROR;
biodone(bio);
return (error);
}
if (nbio->bio_offset == NOOFFSET)
vfs_bio_clrbuf(bp);
}
if (nbio->bio_offset == NOOFFSET) {
biodone(bio);
return (0);
}
vn_strategy(ip->i_devvp, nbio);
return (0);
}
static int
ext2_print(struct vop_print_args *ap)
{
struct vnode *vp = ap->a_vp;
struct inode *ip = VTOI(vp);
printf("tag VT_EXT2FS, ino %lu, on dev %s (%d, %d)",
(u_long)ip->i_number, devtoname(ip->i_dev), major(ip->i_dev),
minor(ip->i_dev));
if (vp->v_type == VFIFO)
fifo_printinfo(vp);
lockmgr_printinfo(&vp->v_lock);
printf("\n");
return (0);
}
static
int
ext2fifo_read(struct vop_read_args *ap)
{
int error, resid;
struct inode *ip;
struct uio *uio;
uio = ap->a_uio;
resid = uio->uio_resid;
error = VOCALL(&fifo_vnode_vops, &ap->a_head);
ip = VTOI(ap->a_vp);
if ((ap->a_vp->v_mount->mnt_flag & MNT_NOATIME) == 0 && ip != NULL &&
(uio->uio_resid != resid || (error == 0 && resid != 0)))
VTOI(ap->a_vp)->i_flag |= IN_ACCESS;
return (error);
}
static
int
ext2fifo_write(struct vop_write_args *ap)
{
int error, resid;
struct inode *ip;
struct uio *uio;
uio = ap->a_uio;
resid = uio->uio_resid;
error = VOCALL(&fifo_vnode_vops, &ap->a_head);
ip = VTOI(ap->a_vp);
if (ip != NULL && (uio->uio_resid != resid || (error == 0 && resid != 0)))
VTOI(ap->a_vp)->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
static int
ext2fifo_close(struct vop_close_args *ap)
{
struct vnode *vp = ap->a_vp;
if (VREFCNT(vp) > 1)
ext2_itimes(vp);
return (VOCALL(&fifo_vnode_vops, &ap->a_head));
}
static void
filt_ext2detach(struct knote *kn)
{
struct vnode *vp = (struct vnode *)kn->kn_hook;
lwkt_gettoken(&vp->v_token);
knote_remove(&vp->v_pollinfo.vpi_kqinfo.ki_note, kn);
lwkt_reltoken(&vp->v_token);
}
static int
filt_ext2read(struct knote *kn, long hint)
{
struct vnode *vp = (struct vnode *)kn->kn_hook;
struct inode *ip = VTOI(vp);
off_t off;
if (hint == NOTE_REVOKE) {
kn->kn_flags |= (EV_EOF | EV_NODATA | EV_ONESHOT);
return (1);
}
off = ip->i_size - kn->kn_fp->f_offset;
kn->kn_data = (off < INTPTR_MAX) ? off : INTPTR_MAX;
if (kn->kn_sfflags & NOTE_OLDAPI)
return (1);
return (kn->kn_data != 0);
}
static int
filt_ext2write(struct knote *kn, long hint)
{
if (hint == NOTE_REVOKE)
kn->kn_flags |= (EV_EOF | EV_NODATA | EV_ONESHOT);
kn->kn_data = 0;
return (1);
}
static int
filt_ext2vnode(struct knote *kn, long hint)
{
if (kn->kn_sfflags & hint)
kn->kn_fflags |= hint;
if (hint == NOTE_REVOKE) {
kn->kn_flags |= (EV_EOF | EV_NODATA);
return (1);
}
return (kn->kn_fflags != 0);
}
static struct filterops ext2read_filtops =
{ FILTEROP_ISFD | FILTEROP_MPSAFE, NULL, filt_ext2detach, filt_ext2read };
static struct filterops ext2write_filtops =
{ FILTEROP_ISFD | FILTEROP_MPSAFE, NULL, filt_ext2detach, filt_ext2write };
static struct filterops ext2vnode_filtops =
{ FILTEROP_ISFD | FILTEROP_MPSAFE, NULL, filt_ext2detach, filt_ext2vnode };
static int
ext2_kqfilter(struct vop_kqfilter_args *ap)
{
struct vnode *vp = ap->a_vp;
struct knote *kn = ap->a_kn;
switch (kn->kn_filter) {
case EVFILT_READ:
kn->kn_fop = &ext2read_filtops;
break;
case EVFILT_WRITE:
kn->kn_fop = &ext2write_filtops;
break;
case EVFILT_VNODE:
kn->kn_fop = &ext2vnode_filtops;
break;
default:
return (EOPNOTSUPP);
}
kn->kn_hook = (caddr_t)vp;
lwkt_gettoken(&vp->v_token);
knote_insert(&vp->v_pollinfo.vpi_kqinfo.ki_note, kn);
lwkt_reltoken(&vp->v_token);
return (0);
}
static int
ext2fifo_kqfilter(struct vop_kqfilter_args *ap)
{
int error;
error = VOCALL(&fifo_vnode_vops, &ap->a_head);
if (error)
error = ext2_kqfilter(ap);
return (error);
}
static int
ext2_pathconf(struct vop_pathconf_args *ap)
{
int error = 0;
switch (ap->a_name) {
case _PC_LINK_MAX:
if (EXT2_HAS_RO_COMPAT_FEATURE(VTOI(ap->a_vp)->i_e2fs,
EXT2F_ROCOMPAT_DIR_NLINK))
*ap->a_retval = INT_MAX;
else
*ap->a_retval = EXT4_LINK_MAX;
break;
case _PC_NAME_MAX:
*ap->a_retval = NAME_MAX;
break;
case _PC_PATH_MAX:
*ap->a_retval = PATH_MAX;
break;
case _PC_PIPE_BUF:
if (ap->a_vp->v_type == VDIR || ap->a_vp->v_type == VFIFO)
*ap->a_retval = PIPE_BUF;
else
error = EINVAL;
break;
case _PC_CHOWN_RESTRICTED:
*ap->a_retval = 1;
break;
case _PC_NO_TRUNC:
*ap->a_retval = 1;
break;
case _PC_MIN_HOLE_SIZE:
*ap->a_retval = ap->a_vp->v_mount->mnt_stat.f_iosize;
break;
case _PC_PRIO_IO:
*ap->a_retval = 0;
break;
case _PC_SYNC_IO:
*ap->a_retval = 0;
break;
case _PC_ALLOC_SIZE_MIN:
*ap->a_retval = ap->a_vp->v_mount->mnt_stat.f_bsize;
break;
case _PC_FILESIZEBITS:
*ap->a_retval = 64;
break;
case _PC_REC_INCR_XFER_SIZE:
*ap->a_retval = ap->a_vp->v_mount->mnt_stat.f_iosize;
break;
case _PC_REC_MAX_XFER_SIZE:
*ap->a_retval = -1;
break;
case _PC_REC_MIN_XFER_SIZE:
*ap->a_retval = ap->a_vp->v_mount->mnt_stat.f_iosize;
break;
case _PC_REC_XFER_ALIGN:
*ap->a_retval = PAGE_SIZE;
break;
case _PC_SYMLINK_MAX:
*ap->a_retval = MAXPATHLEN;
break;
default:
error = vop_stdpathconf(ap);
break;
}
return (error);
}
int
ext2_vinit(struct mount *mntp, struct vnode **vpp)
{
struct inode *ip;
struct vnode *vp;
vp = *vpp;
ip = VTOI(vp);
switch (vp->v_type = IFTOVT(ip->i_mode)) {
case VCHR:
case VBLK:
vp->v_ops = &mntp->mnt_vn_spec_ops;
addaliasu(vp, umajor(ip->i_rdev), uminor(ip->i_rdev));
break;
case VFIFO:
vp->v_ops = &mntp->mnt_vn_fifo_ops;
break;
case VDIR:
case VREG:
vinitvmio(vp, ip->i_size, PAGE_SIZE, -1);
break;
case VLNK:
if ((ip->i_size >= vp->v_mount->mnt_maxsymlinklen) &&
ip->i_blocks != 0) {
vinitvmio(vp, ip->i_size, PAGE_SIZE, -1);
}
break;
default:
break;
}
if (ip->i_mode != 0 && vp->v_type == VNON)
return (EINVAL);
if (ip->i_number == EXT2_ROOTINO)
vp->v_flag |= VROOT;
ip->i_modrev = init_va_filerev();
*vpp = vp;
return (0);
}
static int
ext2_makeinode(int mode, struct vnode *dvp, struct vnode **vpp,
struct componentname *cnp)
{
struct inode *ip, *pdir;
struct vnode *tvp;
int error;
pdir = VTOI(dvp);
*vpp = NULL;
if ((mode & IFMT) == 0)
mode |= IFREG;
error = ext2_valloc(dvp, mode, cnp->cn_cred, &tvp);
if (error) {
return (error);
}
ip = VTOI(tvp);
ip->i_gid = pdir->i_gid;
#ifdef SUIDDIR
{
if ((dvp->v_mount->mnt_flag & MNT_SUIDDIR) &&
(pdir->i_mode & ISUID) &&
(pdir->i_uid != cnp->cn_cred->cr_uid) && pdir->i_uid) {
ip->i_uid = pdir->i_uid;
mode &= ~07111;
} else {
ip->i_uid = cnp->cn_cred->cr_uid;
}
}
#else
ip->i_uid = cnp->cn_cred->cr_uid;
#endif
ip->i_flag |= IN_ACCESS | IN_CHANGE | IN_UPDATE;
ip->i_mode = mode;
tvp->v_type = IFTOVT(mode);
ip->i_nlink = 1;
if ((ip->i_mode & ISGID) && !groupmember(ip->i_gid, cnp->cn_cred)) {
if (caps_priv_check(cnp->cn_cred, SYSCAP_NOVFS_RETAINSUGID))
ip->i_mode &= ~ISGID;
}
if (cnp->cn_flags & CNP_ISWHITEOUT)
ip->i_flags |= UF_OPAQUE;
if (tvp->v_type == VREG || tvp->v_type == VDIR)
vinitvmio(tvp, 0, PAGE_SIZE, -1);
error = ext2_update(tvp, !DOINGASYNC(tvp));
if (error)
goto bad;
error = ext2_direnter(ip, dvp, cnp);
if (error)
goto bad;
*vpp = tvp;
return (0);
bad:
ip->i_nlink = 0;
ip->i_flag |= IN_CHANGE;
vput(tvp);
return (error);
}
static int
ext2_read(struct vop_read_args *ap)
{
struct vnode *vp;
struct inode *ip;
struct uio *uio;
struct m_ext2fs *fs;
struct buf *bp;
daddr_t lbn;
off_t nextlbn;
off_t nextloffset;
off_t bytesinfile;
long size, xfersize, blkoffset;
int error, orig_resid, seqcount;
int ioflag;
vp = ap->a_vp;
uio = ap->a_uio;
ioflag = ap->a_ioflag;
seqcount = ap->a_ioflag >> IO_SEQSHIFT;
ip = VTOI(vp);
#ifdef INVARIANTS
if (uio->uio_rw != UIO_READ)
panic("%s: mode", "ext2_read");
if (vp->v_type == VLNK) {
if ((int)ip->i_size < vp->v_mount->mnt_maxsymlinklen)
panic("%s: short symlink", "ext2_read");
} else if (vp->v_type != VREG && vp->v_type != VDIR)
panic("%s: type %d", "ext2_read", vp->v_type);
#endif
orig_resid = uio->uio_resid;
KASSERT(orig_resid >= 0, ("ext2_read: uio->uio_resid < 0"));
if (orig_resid == 0)
return (0);
KASSERT(uio->uio_offset >= 0, ("ext2_read: uio->uio_offset < 0"));
fs = ip->i_e2fs;
if (uio->uio_offset < ip->i_size &&
uio->uio_offset >= fs->e2fs_maxfilesize)
return (EOVERFLOW);
for (error = 0, bp = NULL; uio->uio_resid > 0; bp = NULL) {
if ((bytesinfile = ip->i_size - uio->uio_offset) <= 0)
break;
lbn = lblkno(fs, uio->uio_offset);
nextlbn = lbn + 1;
nextloffset = lblktodoff(fs, nextlbn);
size = blksize(fs, ip, lbn);
blkoffset = blkoff(fs, uio->uio_offset);
xfersize = fs->e2fs_fsize - blkoffset;
if (uio->uio_resid < xfersize)
xfersize = uio->uio_resid;
if (bytesinfile < xfersize)
xfersize = bytesinfile;
if (nextloffset >= ip->i_size)
error = bread(vp, lblktodoff(fs, lbn), size, &bp);
else if ((vp->v_mount->mnt_flag & MNT_NOCLUSTERR) == 0) {
error = cluster_read(vp, (off_t)ip->i_size,
lblktodoff(fs, lbn), size, uio->uio_resid,
(ap->a_ioflag >> IO_SEQSHIFT) * MAXBSIZE, &bp);
} else if (seqcount > 1) {
u_int nextsize = blksize(fs, ip, nextlbn);
error = breadn(vp, lblktodoff(fs, lbn), size,
&nextloffset, &nextsize, 1, &bp);
} else
error = bread(vp, lblktodoff(fs, lbn), size, &bp);
if (error) {
brelse(bp);
bp = NULL;
break;
}
size -= bp->b_resid;
if (size < xfersize) {
if (size == 0)
break;
xfersize = size;
}
error = uiomove((char *)bp->b_data + blkoffset,
(int)xfersize, uio);
if (error)
break;
bqrelse(bp);
}
if (bp != NULL)
bqrelse(bp);
if ((error == 0 || uio->uio_resid != orig_resid) &&
(vp->v_mount->mnt_flag & (MNT_NOATIME | MNT_RDONLY)) == 0)
ip->i_flag |= IN_ACCESS;
return (error);
}
static int
ext2_ioctl(struct vop_ioctl_args *ap)
{
switch (ap->a_command) {
case FIOSEEKDATA:
case FIOSEEKHOLE:
return (vn_bmap_seekhole(ap->a_vp, ap->a_command,
(off_t *)ap->a_data, ap->a_cred));
default:
return (ENOTTY);
}
}
static int
ext2_write(struct vop_write_args *ap)
{
struct vnode *vp;
struct uio *uio;
struct inode *ip;
struct m_ext2fs *fs;
struct buf *bp;
struct thread *td;
daddr_t lbn;
off_t osize;
int blkoffset, error, flags, ioflag, resid, size, seqcount, xfersize;
ioflag = ap->a_ioflag;
uio = ap->a_uio;
vp = ap->a_vp;
seqcount = ioflag >> IO_SEQSHIFT;
ip = VTOI(vp);
#ifdef INVARIANTS
if (uio->uio_rw != UIO_WRITE)
panic("%s: mode", "ext2_write");
#endif
switch (vp->v_type) {
case VREG:
if (ioflag & IO_APPEND)
uio->uio_offset = ip->i_size;
if ((ip->i_flags & APPEND) && uio->uio_offset != ip->i_size)
return (EPERM);
case VLNK:
break;
case VDIR:
if ((ioflag & IO_SYNC) == 0)
panic("ext2_write: nonsync dir write");
break;
default:
panic("ext2_write: type %p %d (%jd,%jd)", (void *)vp,
vp->v_type, (intmax_t)uio->uio_offset,
(intmax_t)uio->uio_resid);
}
KASSERT(uio->uio_resid >= 0, ("ext2_write: uio->uio_resid < 0"));
KASSERT(uio->uio_offset >= 0, ("ext2_write: uio->uio_offset < 0"));
fs = ip->i_e2fs;
if ((uoff_t)uio->uio_offset + uio->uio_resid > fs->e2fs_maxfilesize)
return (EFBIG);
td = uio->uio_td;
if (vp->v_type == VREG && td && td->td_proc &&
uio->uio_offset + uio->uio_resid >
td->td_proc->p_rlimit[RLIMIT_FSIZE].rlim_cur) {
lwpsignal(td->td_proc, td->td_lwp, SIGXFSZ);
return (EFBIG);
}
resid = uio->uio_resid;
osize = ip->i_size;
if (seqcount > BA_SEQMAX)
flags = BA_SEQMAX << BA_SEQSHIFT;
else
flags = seqcount << BA_SEQSHIFT;
if ((ioflag & IO_SYNC) && !DOINGASYNC(vp))
flags |= IO_SYNC;
for (error = 0; uio->uio_resid > 0;) {
lbn = lblkno(fs, uio->uio_offset);
blkoffset = blkoff(fs, uio->uio_offset);
xfersize = fs->e2fs_fsize - blkoffset;
if (uio->uio_resid < xfersize)
xfersize = uio->uio_resid;
if (uio->uio_offset + xfersize > ip->i_size)
vnode_pager_setsize(vp, uio->uio_offset + xfersize);
if (fs->e2fs_bsize > xfersize)
flags |= BA_CLRBUF;
else
flags &= ~BA_CLRBUF;
error = ext2_balloc(ip, lbn, blkoffset + xfersize,
ap->a_cred, &bp, flags);
if (error != 0)
break;
if ((ioflag & (IO_SYNC | IO_INVAL)) == (IO_SYNC | IO_INVAL))
bp->b_flags |= B_NOCACHE;
if (uio->uio_offset + xfersize > ip->i_size)
ip->i_size = uio->uio_offset + xfersize;
size = blksize(fs, ip, lbn) - bp->b_resid;
if (size < xfersize)
xfersize = size;
error =
uiomove((char *)bp->b_data + blkoffset, (int)xfersize, uio);
if ((ioflag & IO_VMIO) &&
LIST_FIRST(&bp->b_dep) == NULL)
bp->b_flags |= B_RELBUF;
if (error != 0 && (bp->b_flags & B_CACHE) == 0 &&
fs->e2fs_bsize == xfersize)
vfs_bio_clrbuf(bp);
if (ioflag & IO_SYNC) {
(void)bwrite(bp);
} else if (vm_paging_severe() ||
buf_dirty_count_severe() ||
(ioflag & IO_ASYNC))
{
bp->b_flags |= B_CLUSTEROK;
bawrite(bp);
} else if (xfersize + blkoffset == fs->e2fs_fsize) {
if ((vp->v_mount->mnt_flag & MNT_NOCLUSTERW) == 0) {
bp->b_flags |= B_CLUSTEROK;
cluster_write(bp, (off_t)ip->i_size,
vp->v_mount->mnt_stat.f_iosize, seqcount);
} else {
bawrite(bp);
}
} else if (ioflag & IO_DIRECT) {
bp->b_flags |= B_CLUSTEROK;
bawrite(bp);
} else {
bp->b_flags |= B_CLUSTEROK;
bdwrite(bp);
}
if (error || xfersize == 0)
break;
}
if ((ip->i_mode & (ISUID | ISGID)) && resid > uio->uio_resid &&
ap->a_cred) {
if (caps_priv_check(ap->a_cred, SYSCAP_NOVFS_RETAINSUGID))
ip->i_mode &= ~(ISUID | ISGID);
}
if (error) {
if (ioflag & IO_UNIT) {
(void)ext2_truncate(vp, osize, ioflag & IO_SYNC,
ap->a_cred);
uio->uio_offset -= resid - uio->uio_resid;
uio->uio_resid = resid;
}
}
if (uio->uio_resid != resid) {
ip->i_flag |= IN_CHANGE | IN_UPDATE;
if (ioflag & IO_SYNC)
error = ext2_update(vp, 1);
}
return (error);
}
struct vop_ops ext2_vnodeops = {
.vop_default = vop_defaultop,
.vop_access = ext2_access,
.vop_bmap = ext2_bmap,
.vop_old_lookup = ext2_lookup,
.vop_close = ext2_close,
.vop_old_create = ext2_create,
.vop_fsync = ext2_fsync,
.vop_getpages = vop_stdgetpages,
.vop_putpages = vop_stdputpages,
.vop_getattr = ext2_getattr,
.vop_inactive = ext2_inactive,
.vop_ioctl = ext2_ioctl,
.vop_old_link = ext2_link,
.vop_old_lookup = ext2_lookup,
.vop_old_mkdir = ext2_mkdir,
.vop_old_mknod = ext2_mknod,
.vop_open = ext2_open,
.vop_pathconf = ext2_pathconf,
.vop_print = ext2_print,
.vop_read = ext2_read,
.vop_readdir = ext2_readdir,
.vop_readlink = ext2_readlink,
.vop_reallocblks = ext2_reallocblks,
.vop_reclaim = ext2_reclaim,
.vop_old_remove = ext2_remove,
.vop_old_rename = ext2_rename,
.vop_old_rmdir = ext2_rmdir,
.vop_setattr = ext2_setattr,
.vop_strategy = ext2_strategy,
.vop_old_symlink = ext2_symlink,
.vop_write = ext2_write,
};
struct vop_ops ext2_specops = {
.vop_default = vop_defaultop,
.vop_access = ext2_access,
.vop_close = ext2_close,
.vop_fsync = ext2_fsync,
.vop_getattr = ext2_getattr,
.vop_inactive = ext2_inactive,
.vop_pathconf = ext2_pathconf,
.vop_print = ext2_print,
.vop_read = vop_stdnoread,
.vop_reclaim = ext2_reclaim,
.vop_setattr = ext2_setattr,
.vop_write = vop_stdnowrite
};
struct vop_ops ext2_fifoops = {
.vop_default = fifo_vnoperate,
.vop_access = ext2_access,
.vop_close = ext2fifo_close,
.vop_fsync = ext2_fsync,
.vop_getattr = ext2_getattr,
.vop_inactive = ext2_inactive,
.vop_kqfilter = ext2fifo_kqfilter,
.vop_pathconf = ext2_pathconf,
.vop_print = ext2_print,
.vop_read = ext2fifo_read,
.vop_reclaim = ext2_reclaim,
.vop_setattr = ext2_setattr,
.vop_write = ext2fifo_write
};
VNODEOP_SET(ext2_vnodeops);
VNODEOP_SET(ext2_specops);
VNODEOP_SET(ext2_fifoops);