#include <sys/param.h>
#include <sys/systm.h>
#include <sys/namei.h>
#include <sys/resourcevar.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/lock.h>
#include <sys/signalvar.h>
#include <sys/specdev.h>
#include <sys/malloc.h>
#include <sys/pool.h>
#include <sys/dirent.h>
#include <sys/lockf.h>
#include <sys/unistd.h>
#include <msdosfs/bpb.h>
#include <msdosfs/direntry.h>
#include <msdosfs/denode.h>
#include <msdosfs/msdosfsmount.h>
#include <msdosfs/fat.h>
static uint32_t fileidhash(uint64_t);
int msdosfs_bmaparray(struct vnode *, uint32_t, daddr_t *, int *);
int msdosfs_kqfilter(void *);
int filt_msdosfsread(struct knote *, long);
int filt_msdosfswrite(struct knote *, long);
int filt_msdosfsvnode(struct knote *, long);
void filt_msdosfsdetach(struct knote *);
int
msdosfs_create(void *v)
{
struct vop_create_args *ap = v;
struct componentname *cnp = ap->a_cnp;
struct denode ndirent;
struct denode *dep;
struct denode *pdep = VTODE(ap->a_dvp);
int error;
struct timespec ts;
#ifdef MSDOSFS_DEBUG
printf("msdosfs_create(cnp %p, vap %p\n", cnp, ap->a_vap);
#endif
if (pdep->de_StartCluster == MSDOSFSROOT
&& pdep->de_fndoffset >= pdep->de_FileSize) {
error = ENOSPC;
goto bad;
}
#ifdef DIAGNOSTIC
if ((cnp->cn_flags & HASBUF) == 0)
panic("msdosfs_create: no name");
#endif
bzero(&ndirent, sizeof(ndirent));
if ((error = uniqdosname(pdep, cnp, ndirent.de_Name)) != 0)
goto bad;
ndirent.de_Attributes = (ap->a_vap->va_mode & VWRITE) ?
ATTR_ARCHIVE : ATTR_ARCHIVE | ATTR_READONLY;
ndirent.de_StartCluster = 0;
ndirent.de_FileSize = 0;
ndirent.de_dev = pdep->de_dev;
ndirent.de_devvp = pdep->de_devvp;
ndirent.de_pmp = pdep->de_pmp;
ndirent.de_flag = DE_ACCESS | DE_CREATE | DE_UPDATE;
getnanotime(&ts);
DETIMES(&ndirent, &ts, &ts, &ts);
if ((error = createde(&ndirent, pdep, &dep, cnp)) != 0)
goto bad;
if ((cnp->cn_flags & SAVESTART) == 0)
pool_put(&namei_pool, cnp->cn_pnbuf);
VN_KNOTE(ap->a_dvp, NOTE_WRITE);
*ap->a_vpp = DETOV(dep);
return (0);
bad:
pool_put(&namei_pool, cnp->cn_pnbuf);
return (error);
}
int
msdosfs_mknod(void *v)
{
struct vop_mknod_args *ap = v;
pool_put(&namei_pool, ap->a_cnp->cn_pnbuf);
VN_KNOTE(ap->a_dvp, NOTE_WRITE);
return (EINVAL);
}
int
msdosfs_open(void *v)
{
return (0);
}
int
msdosfs_close(void *v)
{
struct vop_close_args *ap = v;
struct vnode *vp = ap->a_vp;
struct denode *dep = VTODE(vp);
struct timespec ts;
if (vp->v_usecount > 1 && !VOP_ISLOCKED(vp)) {
getnanotime(&ts);
DETIMES(dep, &ts, &ts, &ts);
}
return (0);
}
int
msdosfs_access(void *v)
{
struct vop_access_args *ap = v;
struct denode *dep = VTODE(ap->a_vp);
struct msdosfsmount *pmp = dep->de_pmp;
mode_t dosmode;
dosmode = (S_IRUSR | S_IRGRP | S_IROTH);
if ((dep->de_Attributes & ATTR_READONLY) == 0)
dosmode |= (S_IWUSR | S_IWGRP | S_IWOTH);
if (dep->de_Attributes & ATTR_DIRECTORY)
dosmode |= (S_IXUSR | S_IXGRP | S_IXOTH);
dosmode &= pmp->pm_mask;
return (vaccess(ap->a_vp->v_type, dosmode, pmp->pm_uid, pmp->pm_gid,
ap->a_mode, ap->a_cred));
}
int
msdosfs_getattr(void *v)
{
struct vop_getattr_args *ap = v;
struct denode *dep = VTODE(ap->a_vp);
struct msdosfsmount *pmp = dep->de_pmp;
struct vattr *vap = ap->a_vap;
struct timespec ts;
uint32_t fileid;
getnanotime(&ts);
DETIMES(dep, &ts, &ts, &ts);
vap->va_fsid = dep->de_dev;
fileid = dep->de_StartCluster;
if (dep->de_Attributes & ATTR_DIRECTORY) {
if (dep->de_StartCluster == MSDOSFSROOT)
fileid = FAT32(pmp) ? pmp->pm_rootdirblk : 1;
} else {
if (dep->de_FileSize == 0) {
uint32_t dirsperblk;
uint64_t fileid64;
dirsperblk = pmp->pm_BytesPerSec /
sizeof(struct direntry);
fileid64 = (dep->de_dirclust == MSDOSFSROOT) ?
roottobn(pmp, 0) : cntobn(pmp, dep->de_dirclust);
fileid64 *= dirsperblk;
fileid64 += dep->de_diroffset / sizeof(struct direntry);
fileid = fileidhash(fileid64);
}
}
vap->va_fileid = fileid;
vap->va_mode = (S_IRUSR|S_IRGRP|S_IROTH);
if ((dep->de_Attributes & ATTR_READONLY) == 0)
vap->va_mode |= (S_IWUSR|S_IWGRP|S_IWOTH);
if (dep->de_Attributes & ATTR_DIRECTORY) {
vap->va_mode |= S_IFDIR;
vap->va_mode |= (vap->va_mode & S_IRUSR) ? S_IXUSR : 0;
vap->va_mode |= (vap->va_mode & S_IRGRP) ? S_IXGRP : 0;
vap->va_mode |= (vap->va_mode & S_IROTH) ? S_IXOTH : 0;
}
vap->va_mode &= dep->de_pmp->pm_mask;
vap->va_nlink = 1;
vap->va_gid = dep->de_pmp->pm_gid;
vap->va_uid = dep->de_pmp->pm_uid;
vap->va_rdev = 0;
vap->va_size = dep->de_FileSize;
dos2unixtime(dep->de_MDate, dep->de_MTime, 0, &vap->va_mtime);
if (dep->de_pmp->pm_flags & MSDOSFSMNT_LONGNAME) {
dos2unixtime(dep->de_ADate, 0, 0, &vap->va_atime);
dos2unixtime(dep->de_CDate, dep->de_CTime, dep->de_CTimeHundredth, &vap->va_ctime);
} else {
vap->va_atime = vap->va_mtime;
vap->va_ctime = vap->va_mtime;
}
vap->va_flags = 0;
if ((dep->de_Attributes & ATTR_ARCHIVE) == 0)
vap->va_flags |= SF_ARCHIVED;
vap->va_gen = 0;
vap->va_blocksize = dep->de_pmp->pm_bpcluster;
vap->va_bytes = (dep->de_FileSize + dep->de_pmp->pm_crbomask) &
~(dep->de_pmp->pm_crbomask);
vap->va_type = ap->a_vp->v_type;
return (0);
}
int
msdosfs_setattr(void *v)
{
struct vop_setattr_args *ap = v;
struct vnode *vp = ap->a_vp;
struct denode *dep = VTODE(ap->a_vp);
struct msdosfsmount *pmp = dep->de_pmp;
struct vattr *vap = ap->a_vap;
struct ucred *cred = ap->a_cred;
int error = 0;
#ifdef MSDOSFS_DEBUG
printf("msdosfs_setattr(): vp %p, vap %p, cred %p, p %p\n",
ap->a_vp, vap, cred, ap->a_p);
#endif
if ((vap->va_type != VNON) || (vap->va_nlink != VNOVAL) ||
(vap->va_fsid != VNOVAL) || (vap->va_fileid != VNOVAL) ||
(vap->va_blocksize != VNOVAL) || (vap->va_rdev != VNOVAL) ||
(vap->va_bytes != VNOVAL) || (vap->va_gen != VNOVAL)) {
#ifdef MSDOSFS_DEBUG
printf("msdosfs_setattr(): returning EINVAL\n");
printf(" va_type %d, va_nlink %x, va_fsid %ld, "
"va_fileid %llx\n", vap->va_type, vap->va_nlink,
vap->va_fsid, (unsigned long long)vap->va_fileid);
printf(" va_blocksize %lx, va_rdev %x, va_bytes %llx, "
"va_gen %lx\n", vap->va_blocksize,
(unsigned int)vap->va_rdev,
(unsigned long long)vap->va_bytes, vap->va_gen);
#endif
return (EINVAL);
}
if (vap->va_flags != VNOVAL) {
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EINVAL);
if (cred->cr_uid != pmp->pm_uid) {
error = suser_ucred(cred);
if (error)
return (error);
}
if (vap->va_flags & SF_SETTABLE) {
error = suser_ucred(cred);
if (error)
return (error);
}
if (vap->va_flags & ~SF_ARCHIVED)
return EOPNOTSUPP;
if (vap->va_flags & SF_ARCHIVED)
dep->de_Attributes &= ~ATTR_ARCHIVE;
else if (!(dep->de_Attributes & ATTR_DIRECTORY))
dep->de_Attributes |= ATTR_ARCHIVE;
dep->de_flag |= DE_MODIFIED;
}
if (vap->va_uid != (uid_t)VNOVAL || vap->va_gid != (gid_t)VNOVAL) {
uid_t uid;
gid_t gid;
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EINVAL);
uid = vap->va_uid;
if (uid == (uid_t)VNOVAL)
uid = pmp->pm_uid;
gid = vap->va_gid;
if (gid == (gid_t)VNOVAL)
gid = pmp->pm_gid;
if (cred->cr_uid != pmp->pm_uid || uid != pmp->pm_uid ||
(gid != pmp->pm_gid && !groupmember(gid, cred))) {
error = suser_ucred(cred);
if (error)
return (error);
}
if (uid != pmp->pm_uid || gid != pmp->pm_gid)
return EINVAL;
}
if (vap->va_size != VNOVAL) {
switch (vp->v_type) {
case VDIR:
return (EISDIR);
case VREG:
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EINVAL);
break;
default:
break;
}
error = detrunc(dep, vap->va_size, 0, cred, ap->a_p);
if (error)
return error;
}
if ((vap->va_vaflags & VA_UTIMES_CHANGE) ||
vap->va_atime.tv_nsec != VNOVAL ||
vap->va_mtime.tv_nsec != VNOVAL) {
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EINVAL);
if (cred->cr_uid != pmp->pm_uid &&
(error = suser_ucred(cred)) &&
((vap->va_vaflags & VA_UTIMES_NULL) == 0 ||
(error = VOP_ACCESS(ap->a_vp, VWRITE, cred, ap->a_p))))
return (error);
if (vp->v_type != VDIR) {
if ((pmp->pm_flags & MSDOSFSMNT_NOWIN95) == 0 &&
vap->va_atime.tv_nsec != VNOVAL) {
dep->de_flag &= ~DE_ACCESS;
unix2dostime(&vap->va_atime, &dep->de_ADate,
NULL, NULL);
}
if (vap->va_mtime.tv_nsec != VNOVAL) {
dep->de_flag &= ~DE_UPDATE;
unix2dostime(&vap->va_mtime, &dep->de_MDate,
&dep->de_MTime, NULL);
}
dep->de_Attributes |= ATTR_ARCHIVE;
dep->de_flag |= DE_MODIFIED;
}
}
if (vap->va_mode != (mode_t)VNOVAL) {
if (vp->v_mount->mnt_flag & MNT_RDONLY)
return (EINVAL);
if (cred->cr_uid != pmp->pm_uid) {
error = suser_ucred(cred);
if (error)
return (error);
}
if (vp->v_type != VDIR) {
if (vap->va_mode & VWRITE)
dep->de_Attributes &= ~ATTR_READONLY;
else
dep->de_Attributes |= ATTR_READONLY;
dep->de_Attributes |= ATTR_ARCHIVE;
dep->de_flag |= DE_MODIFIED;
}
}
VN_KNOTE(ap->a_vp, NOTE_ATTRIB);
return (deupdat(dep, 1));
}
int
msdosfs_read(void *v)
{
struct vop_read_args *ap = v;
struct vnode *vp = ap->a_vp;
struct denode *dep = VTODE(vp);
struct msdosfsmount *pmp = dep->de_pmp;
struct uio *uio = ap->a_uio;
int isadir, error = 0;
uint32_t n, diff, size, on;
struct buf *bp;
uint32_t cn;
daddr_t bn;
if (uio->uio_resid == 0)
return (0);
if (uio->uio_offset < 0)
return (EINVAL);
isadir = dep->de_Attributes & ATTR_DIRECTORY;
do {
if (uio->uio_offset >= dep->de_FileSize)
return (0);
cn = de_cluster(pmp, uio->uio_offset);
size = pmp->pm_bpcluster;
on = uio->uio_offset & pmp->pm_crbomask;
n = ulmin(pmp->pm_bpcluster - on, uio->uio_resid);
diff = dep->de_FileSize - (uint32_t)uio->uio_offset;
if (diff < n)
n = diff;
if (isadir) {
error = pcbmap(dep, cn, &bn, 0, &size);
if (error)
return (error);
error = bread(pmp->pm_devvp, bn, size, &bp);
} else {
if (de_cn2off(pmp, cn + 1) >= dep->de_FileSize)
error = bread(vp, cn, size, &bp);
else
error = bread_cluster(vp, cn, size, &bp);
}
n = min(n, pmp->pm_bpcluster - bp->b_resid);
if (error) {
brelse(bp);
return (error);
}
error = uiomove(bp->b_data + on, n, uio);
brelse(bp);
} while (error == 0 && uio->uio_resid > 0 && n != 0);
if (!isadir && !(vp->v_mount->mnt_flag & MNT_NOATIME))
dep->de_flag |= DE_ACCESS;
return (error);
}
int
msdosfs_write(void *v)
{
struct vop_write_args *ap = v;
uint32_t n, croffset;
size_t resid;
ssize_t overrun;
int extended = 0;
uint32_t osize;
int error = 0;
uint32_t count, lastcn, cn;
struct buf *bp;
int ioflag = ap->a_ioflag;
struct uio *uio = ap->a_uio;
struct vnode *vp = ap->a_vp;
struct vnode *thisvp;
struct denode *dep = VTODE(vp);
struct msdosfsmount *pmp = dep->de_pmp;
struct ucred *cred = ap->a_cred;
#ifdef MSDOSFS_DEBUG
printf("msdosfs_write(vp %p, uio %p, ioflag %08x, cred %p\n",
vp, uio, ioflag, cred);
printf("msdosfs_write(): diroff %d, dirclust %d, startcluster %d\n",
dep->de_diroffset, dep->de_dirclust, dep->de_StartCluster);
#endif
switch (vp->v_type) {
case VREG:
if (ioflag & IO_APPEND)
uio->uio_offset = dep->de_FileSize;
thisvp = vp;
break;
case VDIR:
return EISDIR;
default:
panic("msdosfs_write(): bad file type");
}
if (uio->uio_offset < 0)
return (EINVAL);
if (uio->uio_resid == 0)
return (0);
if (uio->uio_offset > MSDOSFS_FILESIZE_MAX ||
uio->uio_resid > (MSDOSFS_FILESIZE_MAX - uio->uio_offset))
return (EFBIG);
if ((error = vn_fsizechk(vp, uio, ioflag, &overrun)))
return (error);
if (uio->uio_offset > dep->de_FileSize) {
if ((error = deextend(dep, uio->uio_offset, cred)) != 0)
goto out;
}
resid = uio->uio_resid;
osize = dep->de_FileSize;
if (uio->uio_offset + resid > osize) {
extended = 1;
count = de_clcount(pmp, uio->uio_offset + resid) -
de_clcount(pmp, osize);
if ((error = extendfile(dep, count, NULL, NULL, 0)) &&
(error != ENOSPC || (ioflag & IO_UNIT)))
goto errexit;
lastcn = dep->de_fc[FC_LASTFC].fc_frcn;
} else
lastcn = de_clcount(pmp, osize) - 1;
do {
croffset = uio->uio_offset & pmp->pm_crbomask;
cn = de_cluster(pmp, uio->uio_offset);
if (cn > lastcn) {
error = ENOSPC;
break;
}
if (croffset == 0 &&
(de_cluster(pmp, uio->uio_offset + uio->uio_resid) > cn ||
(uio->uio_offset + uio->uio_resid) >= dep->de_FileSize)) {
bp = getblk(thisvp, cn, pmp->pm_bpcluster, 0, INFSLP);
clrbuf(bp);
if (bp->b_blkno == bp->b_lblkno) {
error = pcbmap(dep, bp->b_lblkno, &bp->b_blkno, 0, 0);
if (error)
bp->b_blkno = -1;
}
if (bp->b_blkno == -1) {
brelse(bp);
if (!error)
error = EIO;
break;
}
} else {
error = bread(thisvp, cn, pmp->pm_bpcluster, &bp);
if (error) {
brelse(bp);
break;
}
}
n = ulmin(uio->uio_resid, pmp->pm_bpcluster - croffset);
if (uio->uio_offset + n > dep->de_FileSize) {
dep->de_FileSize = uio->uio_offset + n;
uvm_vnp_setsize(vp, dep->de_FileSize);
}
uvm_vnp_uncache(vp);
error = uiomove(bp->b_data + croffset, n, uio);
#if 0
if (ioflag & IO_NOCACHE)
bp->b_flags |= B_NOCACHE;
#endif
if (ioflag & IO_SYNC)
(void) bwrite(bp);
else if (n + croffset == pmp->pm_bpcluster)
bawrite(bp);
else
bdwrite(bp);
dep->de_flag |= DE_UPDATE;
} while (error == 0 && uio->uio_resid > 0);
if (resid > uio->uio_resid)
VN_KNOTE(ap->a_vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
if (dep->de_FileSize < osize)
VN_KNOTE(ap->a_vp, NOTE_TRUNCATE);
errexit:
if (error) {
if (ioflag & IO_UNIT) {
detrunc(dep, osize, ioflag & IO_SYNC, NOCRED, curproc);
uio->uio_offset -= resid - uio->uio_resid;
uio->uio_resid = resid;
} else {
detrunc(dep, dep->de_FileSize, ioflag & IO_SYNC, NOCRED, curproc);
if (uio->uio_resid != resid)
error = 0;
}
} else if (ioflag & IO_SYNC)
error = deupdat(dep, 1);
out:
uio->uio_resid += overrun;
return (error);
}
int
msdosfs_ioctl(void *v)
{
return (ENOTTY);
}
int
msdosfs_fsync(void *v)
{
struct vop_fsync_args *ap = v;
struct vnode *vp = ap->a_vp;
vflushbuf(vp, ap->a_waitfor == MNT_WAIT);
return (deupdat(VTODE(vp), ap->a_waitfor == MNT_WAIT));
}
int
msdosfs_remove(void *v)
{
struct vop_remove_args *ap = v;
struct denode *dep = VTODE(ap->a_vp);
struct denode *ddep = VTODE(ap->a_dvp);
int error;
if (ap->a_vp->v_type == VDIR)
error = EPERM;
else
error = removede(ddep, dep);
VN_KNOTE(ap->a_vp, NOTE_DELETE);
VN_KNOTE(ap->a_dvp, NOTE_WRITE);
#ifdef MSDOSFS_DEBUG
printf("msdosfs_remove(), dep %p, v_usecount %d\n", dep,
ap->a_vp->v_usecount);
#endif
return (error);
}
int
msdosfs_link(void *v)
{
struct vop_link_args *ap = v;
VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
vput(ap->a_dvp);
return (EOPNOTSUPP);
}
int
msdosfs_rename(void *v)
{
struct vop_rename_args *ap = v;
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 denode *ip, *xp, *dp, *zp;
u_char toname[11], oldname[11];
uint32_t from_diroffset, to_diroffset;
u_char to_count;
int doingdirectory = 0, newparent = 0;
int error;
uint32_t cn, pcl;
daddr_t bn;
struct msdosfsmount *pmp;
struct direntry *dotdotp;
struct buf *bp;
pmp = VFSTOMSDOSFS(fdvp->v_mount);
#ifdef DIAGNOSTIC
if ((tcnp->cn_flags & HASBUF) == 0 ||
(fcnp->cn_flags & HASBUF) == 0)
panic("msdosfs_rename: no name");
#endif
if ((fvp->v_mount != tdvp->v_mount) ||
(tvp && (fvp->v_mount != tvp->v_mount))) {
error = EXDEV;
abortit:
VOP_ABORTOP(tdvp, tcnp);
if (tdvp == tvp)
vrele(tdvp);
else
vput(tdvp);
if (tvp)
vput(tvp);
VOP_ABORTOP(fdvp, fcnp);
vrele(fdvp);
vrele(fvp);
return (error);
}
if (tvp == fvp) {
error = 0;
goto abortit;
}
if ((error = vn_lock(fvp, LK_EXCLUSIVE | LK_RETRY)) != 0)
goto abortit;
dp = VTODE(fdvp);
ip = VTODE(fvp);
if (ip->de_Attributes & ATTR_DIRECTORY) {
if ((fcnp->cn_namelen == 1 && fcnp->cn_nameptr[0] == '.') ||
dp == ip ||
(fcnp->cn_flags & ISDOTDOT) ||
(tcnp->cn_flags & ISDOTDOT) ||
(ip->de_flag & DE_RENAME)) {
VOP_UNLOCK(fvp);
error = EINVAL;
goto abortit;
}
ip->de_flag |= DE_RENAME;
doingdirectory++;
}
VN_KNOTE(fdvp, NOTE_WRITE);
dp = VTODE(tdvp);
xp = tvp ? VTODE(tvp) : NULL;
to_diroffset = dp->de_fndoffset;
to_count = dp->de_fndcnt;
error = VOP_ACCESS(fvp, VWRITE, tcnp->cn_cred, tcnp->cn_proc);
VOP_UNLOCK(fvp);
if (VTODE(fdvp)->de_StartCluster != VTODE(tdvp)->de_StartCluster)
newparent = 1;
vrele(fdvp);
if (doingdirectory && newparent) {
if (error)
goto bad1;
if (xp != NULL)
vput(tvp);
if ((error = doscheckpath(ip, dp)) != 0)
goto out;
if ((tcnp->cn_flags & SAVESTART) == 0)
panic("msdosfs_rename: lost to startdir");
if ((error = vfs_relookup(tdvp, &tvp, tcnp)) != 0)
goto out;
dp = VTODE(tdvp);
xp = tvp ? VTODE(tvp) : NULL;
}
VN_KNOTE(tdvp, NOTE_WRITE);
if (xp != NULL) {
if (xp->de_Attributes & ATTR_DIRECTORY) {
if (!dosdirempty(xp)) {
error = ENOTEMPTY;
goto bad1;
}
if (!doingdirectory) {
error = ENOTDIR;
goto bad1;
}
cache_purge(tdvp);
} else if (doingdirectory) {
error = EISDIR;
goto bad1;
}
if ((error = removede(dp, xp)) != 0)
goto bad1;
VN_KNOTE(tvp, NOTE_DELETE);
vput(tvp);
xp = NULL;
}
if ((error = uniqdosname(VTODE(tdvp), tcnp, toname)) != 0)
goto bad1;
fcnp->cn_flags &= ~MODMASK;
fcnp->cn_flags |= LOCKPARENT | LOCKLEAF;
if ((fcnp->cn_flags & SAVESTART) == 0)
panic("msdosfs_rename: lost from startdir");
if (!newparent)
VOP_UNLOCK(tdvp);
(void) vfs_relookup(fdvp, &fvp, fcnp);
if (fvp == NULL) {
if (doingdirectory)
panic("rename: lost dir entry");
vrele(ap->a_fvp);
if (newparent)
VOP_UNLOCK(tdvp);
vrele(tdvp);
return 0;
}
xp = VTODE(fvp);
zp = VTODE(fdvp);
from_diroffset = zp->de_fndoffset;
if (xp != ip) {
if (doingdirectory)
panic("rename: lost dir entry");
vrele(ap->a_fvp);
if (newparent)
VOP_UNLOCK(fdvp);
xp = NULL;
} else {
vrele(fvp);
xp = NULL;
bcopy(ip->de_Name, oldname, 11);
bcopy(toname, ip->de_Name, 11);
dp->de_fndoffset = to_diroffset;
dp->de_fndcnt = to_count;
error = createde(ip, dp, NULL, tcnp);
if (error) {
bcopy(oldname, ip->de_Name, 11);
if (newparent)
VOP_UNLOCK(fdvp);
goto bad;
}
ip->de_refcnt++;
zp->de_fndoffset = from_diroffset;
if ((error = removede(zp, ip)) != 0) {
if (newparent)
VOP_UNLOCK(fdvp);
goto bad;
}
cache_purge(fvp);
if (!doingdirectory) {
error = pcbmap(dp, de_cluster(pmp, to_diroffset), 0,
&ip->de_dirclust, 0);
if (error) {
if (newparent)
VOP_UNLOCK(fdvp);
goto bad;
}
ip->de_diroffset = to_diroffset;
if (ip->de_dirclust != MSDOSFSROOT)
ip->de_diroffset &= pmp->pm_crbomask;
}
reinsert(ip);
if (newparent)
VOP_UNLOCK(fdvp);
}
if (doingdirectory && newparent) {
cn = ip->de_StartCluster;
if (cn == MSDOSFSROOT) {
panic("msdosfs_rename: updating .. in root directory?");
} else
bn = cntobn(pmp, cn);
error = bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, &bp);
if (error) {
brelse(bp);
goto bad;
}
dotdotp = (struct direntry *)bp->b_data;
putushort(dotdotp[0].deStartCluster, cn);
pcl = dp->de_StartCluster;
if (FAT32(pmp) && pcl == pmp->pm_rootdirblk)
pcl = 0;
putushort(dotdotp[1].deStartCluster, pcl);
if (FAT32(pmp)) {
putushort(dotdotp[0].deHighClust, cn >> 16);
putushort(dotdotp[1].deHighClust, pcl >> 16);
}
if ((error = bwrite(bp)) != 0) {
goto bad;
}
}
VN_KNOTE(fvp, NOTE_RENAME);
bad:
VOP_UNLOCK(fvp);
vrele(fdvp);
bad1:
if (xp)
vput(tvp);
vput(tdvp);
out:
ip->de_flag &= ~DE_RENAME;
vrele(fvp);
return (error);
}
struct {
struct direntry dot;
struct direntry dotdot;
} dosdirtemplate = {
{ ". ", " ",
ATTR_DIRECTORY,
CASE_LOWER_BASE | CASE_LOWER_EXT,
0,
{ 0, 0 }, { 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 210, 4 }, { 210, 4 },
{ 0, 0 },
{ 0, 0, 0, 0 }
},
{ ".. ", " ",
ATTR_DIRECTORY,
CASE_LOWER_BASE | CASE_LOWER_EXT,
0,
{ 0, 0 }, { 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 210, 4 }, { 210, 4 },
{ 0, 0 },
{ 0, 0, 0, 0 }
}
};
int
msdosfs_mkdir(void *v)
{
struct vop_mkdir_args *ap = v;
struct componentname *cnp = ap->a_cnp;
struct denode ndirent;
struct denode *dep;
struct denode *pdep = VTODE(ap->a_dvp);
int error;
daddr_t bn;
uint32_t newcluster, pcl;
struct direntry *denp;
struct msdosfsmount *pmp = pdep->de_pmp;
struct buf *bp;
struct timespec ts;
if (pdep->de_StartCluster == MSDOSFSROOT
&& pdep->de_fndoffset >= pdep->de_FileSize) {
error = ENOSPC;
goto bad2;
}
error = clusteralloc(pmp, 0, 1, &newcluster, NULL);
if (error)
goto bad2;
bzero(&ndirent, sizeof(ndirent));
ndirent.de_pmp = pmp;
ndirent.de_flag = DE_ACCESS | DE_CREATE | DE_UPDATE;
getnanotime(&ts);
DETIMES(&ndirent, &ts, &ts, &ts);
bn = cntobn(pmp, newcluster);
bp = getblk(pmp->pm_devvp, bn, pmp->pm_bpcluster, 0, INFSLP);
bzero(bp->b_data, pmp->pm_bpcluster);
bcopy(&dosdirtemplate, bp->b_data, sizeof dosdirtemplate);
denp = (struct direntry *)bp->b_data;
putushort(denp[0].deStartCluster, newcluster);
putushort(denp[0].deCDate, ndirent.de_CDate);
putushort(denp[0].deCTime, ndirent.de_CTime);
denp[0].deCTimeHundredth = ndirent.de_CTimeHundredth;
putushort(denp[0].deADate, ndirent.de_ADate);
putushort(denp[0].deMDate, ndirent.de_MDate);
putushort(denp[0].deMTime, ndirent.de_MTime);
pcl = pdep->de_StartCluster;
if (FAT32(pmp) && pcl == pmp->pm_rootdirblk)
pcl = 0;
putushort(denp[1].deStartCluster, pcl);
putushort(denp[1].deCDate, ndirent.de_CDate);
putushort(denp[1].deCTime, ndirent.de_CTime);
denp[1].deCTimeHundredth = ndirent.de_CTimeHundredth;
putushort(denp[1].deADate, ndirent.de_ADate);
putushort(denp[1].deMDate, ndirent.de_MDate);
putushort(denp[1].deMTime, ndirent.de_MTime);
if (FAT32(pmp)) {
putushort(denp[0].deHighClust, newcluster >> 16);
putushort(denp[1].deHighClust, pdep->de_StartCluster >> 16);
}
if ((error = bwrite(bp)) != 0)
goto bad;
#ifdef DIAGNOSTIC
if ((cnp->cn_flags & HASBUF) == 0)
panic("msdosfs_mkdir: no name");
#endif
if ((error = uniqdosname(pdep, cnp, ndirent.de_Name)) != 0)
goto bad;
ndirent.de_Attributes = ATTR_DIRECTORY;
ndirent.de_StartCluster = newcluster;
ndirent.de_FileSize = 0;
ndirent.de_dev = pdep->de_dev;
ndirent.de_devvp = pdep->de_devvp;
if ((error = createde(&ndirent, pdep, &dep, cnp)) != 0)
goto bad;
if ((cnp->cn_flags & SAVESTART) == 0)
pool_put(&namei_pool, cnp->cn_pnbuf);
VN_KNOTE(ap->a_dvp, NOTE_WRITE | NOTE_LINK);
vput(ap->a_dvp);
*ap->a_vpp = DETOV(dep);
return (0);
bad:
clusterfree(pmp, newcluster, NULL);
bad2:
pool_put(&namei_pool, cnp->cn_pnbuf);
vput(ap->a_dvp);
return (error);
}
int
msdosfs_rmdir(void *v)
{
struct vop_rmdir_args *ap = v;
struct vnode *vp = ap->a_vp;
struct vnode *dvp = ap->a_dvp;
struct componentname *cnp = ap->a_cnp;
struct denode *ip, *dp;
int error;
ip = VTODE(vp);
dp = VTODE(dvp);
error = 0;
if (!dosdirempty(ip) || ip->de_flag & DE_RENAME) {
error = ENOTEMPTY;
goto out;
}
VN_KNOTE(dvp, NOTE_WRITE | NOTE_LINK);
if ((error = removede(dp, ip)) != 0)
goto out;
cache_purge(dvp);
vput(dvp);
dvp = NULL;
error = detrunc(ip, (uint32_t)0, IO_SYNC, cnp->cn_cred, cnp->cn_proc);
cache_purge(vp);
out:
if (dvp)
vput(dvp);
VN_KNOTE(vp, NOTE_DELETE);
vput(vp);
return (error);
}
int
msdosfs_symlink(void *v)
{
struct vop_symlink_args *ap = v;
VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
vput(ap->a_dvp);
return (EOPNOTSUPP);
}
int
msdosfs_readdir(void *v)
{
struct vop_readdir_args *ap = v;
int error = 0;
int diff;
long n;
int blsize;
long on;
long lost;
long count;
uint32_t dirsperblk;
uint32_t cn, lbn;
uint32_t fileno;
long bias = 0;
daddr_t bn;
struct buf *bp;
struct denode *dep = VTODE(ap->a_vp);
struct msdosfsmount *pmp = dep->de_pmp;
struct direntry *dentp;
struct dirent dirbuf;
struct uio *uio = ap->a_uio;
off_t offset, wlast = -1;
int chksum = -1;
#ifdef MSDOSFS_DEBUG
printf("msdosfs_readdir(): vp %p, uio %p, cred %p, eofflagp %p\n",
ap->a_vp, uio, ap->a_cred, ap->a_eofflag);
#endif
if ((dep->de_Attributes & ATTR_DIRECTORY) == 0)
return (ENOTDIR);
bzero(&dirbuf, sizeof(dirbuf));
count = uio->uio_resid & ~(sizeof(struct direntry) - 1);
offset = uio->uio_offset;
if (count < sizeof(struct direntry) ||
(offset & (sizeof(struct direntry) - 1)))
return (EINVAL);
lost = uio->uio_resid - count;
uio->uio_resid = count;
dirsperblk = pmp->pm_BytesPerSec / sizeof(struct direntry);
if (dep->de_StartCluster == MSDOSFSROOT
|| (FAT32(pmp) && dep->de_StartCluster == pmp->pm_rootdirblk)) {
#if 0
printf("msdosfs_readdir(): going after . or .. in root dir, offset %d\n",
offset);
#endif
bias = 2 * sizeof(struct direntry);
if (offset < bias) {
for (n = (int)offset / sizeof(struct direntry);
n < 2; n++) {
if (FAT32(pmp))
dirbuf.d_fileno = pmp->pm_rootdirblk;
else
dirbuf.d_fileno = 1;
dirbuf.d_type = DT_DIR;
switch (n) {
case 0:
dirbuf.d_namlen = 1;
strlcpy(dirbuf.d_name, ".",
sizeof dirbuf.d_name);
break;
case 1:
dirbuf.d_namlen = 2;
strlcpy(dirbuf.d_name, "..",
sizeof dirbuf.d_name);
break;
}
dirbuf.d_reclen = DIRENT_SIZE(&dirbuf);
dirbuf.d_off = offset +
sizeof(struct direntry);
if (uio->uio_resid < dirbuf.d_reclen)
goto out;
error = uiomove(&dirbuf, dirbuf.d_reclen, uio);
if (error)
goto out;
offset = dirbuf.d_off;
}
}
}
while (uio->uio_resid > 0) {
lbn = de_cluster(pmp, offset - bias);
on = (offset - bias) & pmp->pm_crbomask;
n = min(pmp->pm_bpcluster - on, uio->uio_resid);
diff = dep->de_FileSize - (offset - bias);
if (diff <= 0)
break;
n = min(n, diff);
if ((error = pcbmap(dep, lbn, &bn, &cn, &blsize)) != 0)
break;
error = bread(pmp->pm_devvp, bn, blsize, &bp);
if (error) {
brelse(bp);
return (error);
}
n = min(n, blsize - bp->b_resid);
for (dentp = (struct direntry *)(bp->b_data + on);
(char *)dentp < bp->b_data + on + n;
dentp++, offset += sizeof(struct direntry)) {
#if 0
printf("rd: dentp %08x prev %08x crnt %08x deName %02x attr %02x\n",
dentp, prev, crnt, dentp->deName[0], dentp->deAttributes);
#endif
if (dentp->deName[0] == SLOT_EMPTY) {
brelse(bp);
goto out;
}
if (dentp->deName[0] == SLOT_DELETED) {
chksum = -1;
wlast = -1;
continue;
}
if (dentp->deAttributes == ATTR_WIN95) {
struct winentry *wep;
if (pmp->pm_flags & MSDOSFSMNT_SHORTNAME)
continue;
wep = (struct winentry *)dentp;
chksum = win2unixfn(wep, &dirbuf, chksum);
if (wep->weCnt & WIN_LAST)
wlast = offset;
continue;
}
if (dentp->deAttributes & ATTR_VOLUME) {
chksum = -1;
wlast = -1;
continue;
}
fileno = getushort(dentp->deStartCluster);
if (FAT32(pmp))
fileno |= getushort(dentp->deHighClust) << 16;
if (dentp->deAttributes & ATTR_DIRECTORY) {
if (fileno == MSDOSFSROOT) {
fileno = FAT32(pmp) ?
pmp->pm_rootdirblk : 1;
}
dirbuf.d_fileno = fileno;
dirbuf.d_type = DT_DIR;
} else {
if (getulong(dentp->deFileSize) == 0) {
uint64_t fileno64;
fileno64 = (cn == MSDOSFSROOT) ?
roottobn(pmp, 0) : cntobn(pmp, cn);
fileno64 *= dirsperblk;
fileno64 += dentp -
(struct direntry *)bp->b_data;
fileno = fileidhash(fileno64);
}
dirbuf.d_fileno = fileno;
dirbuf.d_type = DT_REG;
}
if (chksum != winChksum(dentp->deName))
dirbuf.d_namlen = dos2unixfn(dentp->deName,
(u_char *)dirbuf.d_name,
pmp->pm_flags & MSDOSFSMNT_SHORTNAME);
else
dirbuf.d_name[dirbuf.d_namlen] = 0;
chksum = -1;
dirbuf.d_reclen = DIRENT_SIZE(&dirbuf);
dirbuf.d_off = offset + sizeof(struct direntry);
if (uio->uio_resid < dirbuf.d_reclen) {
brelse(bp);
if (wlast != -1)
offset = wlast;
goto out;
}
wlast = -1;
error = uiomove(&dirbuf, dirbuf.d_reclen, uio);
if (error) {
brelse(bp);
goto out;
}
}
brelse(bp);
}
out:
uio->uio_offset = offset;
uio->uio_resid += lost;
if (dep->de_FileSize - (offset - bias) <= 0)
*ap->a_eofflag = 1;
else
*ap->a_eofflag = 0;
return (error);
}
int
msdosfs_readlink(void *v)
{
#if 0
struct vop_readlink_args
*ap;
#endif
return (EINVAL);
}
int
msdosfs_lock(void *v)
{
struct vop_lock_args *ap = v;
struct vnode *vp = ap->a_vp;
return rrw_enter(&VTODE(vp)->de_lock, ap->a_flags & LK_RWFLAGS);
}
int
msdosfs_unlock(void *v)
{
struct vop_unlock_args *ap = v;
struct vnode *vp = ap->a_vp;
rrw_exit(&VTODE(vp)->de_lock);
return 0;
}
int
msdosfs_islocked(void *v)
{
struct vop_islocked_args *ap = v;
return rrw_status(&VTODE(ap->a_vp)->de_lock);
}
int
msdosfs_bmap(void *v)
{
struct vop_bmap_args *ap = v;
struct denode *dep = VTODE(ap->a_vp);
uint32_t cn;
if (ap->a_vpp != NULL)
*ap->a_vpp = dep->de_devvp;
if (ap->a_bnp == NULL)
return (0);
cn = ap->a_bn;
if (cn != ap->a_bn)
return (EFBIG);
return (msdosfs_bmaparray(ap->a_vp, cn, ap->a_bnp, ap->a_runp));
}
int
msdosfs_bmaparray(struct vnode *vp, uint32_t cn, daddr_t *bnp, int *runp)
{
struct denode *dep = VTODE(vp);
struct msdosfsmount *pmp = dep->de_pmp;
struct mount *mp;
int error, maxrun = 0, run;
daddr_t runbn;
mp = vp->v_mount;
if (runp) {
*runp = 0;
maxrun = min(MAXBSIZE / mp->mnt_stat.f_iosize - 1,
pmp->pm_maxcluster - cn);
}
if ((error = pcbmap(dep, cn, bnp, 0, 0)) != 0)
return (error);
for (run = 1; run <= maxrun; run++) {
error = pcbmap(dep, cn + run, &runbn, 0, 0);
if (error != 0 || (runbn != *bnp + de_cn2bn(pmp, run)))
break;
}
if (runp)
*runp = run - 1;
return (0);
}
int
msdosfs_strategy(void *v)
{
struct vop_strategy_args *ap = v;
struct buf *bp = ap->a_bp;
struct denode *dep = VTODE(bp->b_vp);
struct vnode *vp;
int error = 0;
int s;
if (bp->b_vp->v_type == VBLK || bp->b_vp->v_type == VCHR)
panic("msdosfs_strategy: spec");
if (bp->b_blkno == bp->b_lblkno) {
error = pcbmap(dep, bp->b_lblkno, &bp->b_blkno, 0, 0);
if (error)
bp->b_blkno = -1;
if (bp->b_blkno == -1)
clrbuf(bp);
}
if (bp->b_blkno == -1) {
s = splbio();
biodone(bp);
splx(s);
return (error);
}
vp = dep->de_devvp;
bp->b_dev = vp->v_rdev;
VOP_STRATEGY(vp, bp);
return (0);
}
int
msdosfs_print(void *v)
{
#if defined(DEBUG) || defined(DIAGNOSTIC) || defined(VFSLCKDEBUG)
struct vop_print_args *ap = v;
struct denode *dep = VTODE(ap->a_vp);
printf(
"tag VT_MSDOSFS, startcluster %u, dircluster %u, diroffset %u ",
dep->de_StartCluster, dep->de_dirclust, dep->de_diroffset);
printf(" dev %d, %d, %s\n",
major(dep->de_dev), minor(dep->de_dev),
VOP_ISLOCKED(ap->a_vp) ? "(LOCKED)" : "");
#ifdef DIAGNOSTIC
printf("\n");
#endif
#endif
return (0);
}
int
msdosfs_advlock(void *v)
{
struct vop_advlock_args *ap = v;
struct denode *dep = VTODE(ap->a_vp);
return (lf_advlock(&dep->de_lockf, dep->de_FileSize, ap->a_id, ap->a_op,
ap->a_fl, ap->a_flags));
}
int
msdosfs_pathconf(void *v)
{
struct vop_pathconf_args *ap = v;
struct msdosfsmount *pmp = VTODE(ap->a_vp)->de_pmp;
int error = 0;
switch (ap->a_name) {
case _PC_LINK_MAX:
*ap->a_retval = 1;
break;
case _PC_NAME_MAX:
*ap->a_retval = pmp->pm_flags & MSDOSFSMNT_LONGNAME ? WIN_MAXLEN : 12;
break;
case _PC_CHOWN_RESTRICTED:
*ap->a_retval = 1;
break;
case _PC_NO_TRUNC:
*ap->a_retval = 0;
break;
case _PC_TIMESTAMP_RESOLUTION:
*ap->a_retval = 2000000000;
break;
default:
error = EINVAL;
break;
}
return (error);
}
static uint32_t
fileidhash(uint64_t fileid)
{
uint64_t c1 = 0x6e5ea73858134343LL;
uint64_t c2 = 0xb34e8f99a2ec9ef5LL;
fileid ^= ((c1 ^ fileid) >> 32);
fileid *= c1;
fileid ^= ((c2 ^ fileid) >> 31);
fileid *= c2;
fileid ^= ((c1 ^ fileid) >> 32);
return (uint32_t)(fileid | 0x80000000);
}
const struct vops msdosfs_vops = {
.vop_lookup = msdosfs_lookup,
.vop_create = msdosfs_create,
.vop_mknod = msdosfs_mknod,
.vop_open = msdosfs_open,
.vop_close = msdosfs_close,
.vop_access = msdosfs_access,
.vop_getattr = msdosfs_getattr,
.vop_setattr = msdosfs_setattr,
.vop_read = msdosfs_read,
.vop_write = msdosfs_write,
.vop_ioctl = msdosfs_ioctl,
.vop_kqfilter = msdosfs_kqfilter,
.vop_fsync = msdosfs_fsync,
.vop_remove = msdosfs_remove,
.vop_link = msdosfs_link,
.vop_rename = msdosfs_rename,
.vop_mkdir = msdosfs_mkdir,
.vop_rmdir = msdosfs_rmdir,
.vop_symlink = msdosfs_symlink,
.vop_readdir = msdosfs_readdir,
.vop_readlink = msdosfs_readlink,
.vop_abortop = vop_generic_abortop,
.vop_inactive = msdosfs_inactive,
.vop_reclaim = msdosfs_reclaim,
.vop_lock = msdosfs_lock,
.vop_unlock = msdosfs_unlock,
.vop_bmap = msdosfs_bmap,
.vop_strategy = msdosfs_strategy,
.vop_print = msdosfs_print,
.vop_islocked = msdosfs_islocked,
.vop_pathconf = msdosfs_pathconf,
.vop_advlock = msdosfs_advlock,
.vop_bwrite = vop_generic_bwrite,
.vop_revoke = vop_generic_revoke,
};
const struct filterops msdosfsread_filtops = {
.f_flags = FILTEROP_ISFD,
.f_attach = NULL,
.f_detach = filt_msdosfsdetach,
.f_event = filt_msdosfsread,
};
const struct filterops msdosfswrite_filtops = {
.f_flags = FILTEROP_ISFD,
.f_attach = NULL,
.f_detach = filt_msdosfsdetach,
.f_event = filt_msdosfswrite,
};
const struct filterops msdosfsvnode_filtops = {
.f_flags = FILTEROP_ISFD,
.f_attach = NULL,
.f_detach = filt_msdosfsdetach,
.f_event = filt_msdosfsvnode,
};
int
msdosfs_kqfilter(void *v)
{
struct vop_kqfilter_args *ap = v;
struct vnode *vp = ap->a_vp;
struct knote *kn = ap->a_kn;
switch (kn->kn_filter) {
case EVFILT_READ:
kn->kn_fop = &msdosfsread_filtops;
break;
case EVFILT_WRITE:
kn->kn_fop = &msdosfswrite_filtops;
break;
case EVFILT_VNODE:
kn->kn_fop = &msdosfsvnode_filtops;
break;
default:
return (EINVAL);
}
kn->kn_hook = (caddr_t)vp;
klist_insert_locked(&vp->v_klist, kn);
return (0);
}
void
filt_msdosfsdetach(struct knote *kn)
{
struct vnode *vp = (struct vnode *)kn->kn_hook;
klist_remove_locked(&vp->v_klist, kn);
}
int
filt_msdosfsread(struct knote *kn, long hint)
{
struct vnode *vp = (struct vnode *)kn->kn_hook;
struct denode *dep = VTODE(vp);
if (hint == NOTE_REVOKE) {
kn->kn_flags |= (EV_EOF | EV_ONESHOT);
return (1);
}
kn->kn_data = dep->de_FileSize - foffset(kn->kn_fp);
if (kn->kn_data == 0 && kn->kn_sfflags & NOTE_EOF) {
kn->kn_fflags |= NOTE_EOF;
return (1);
}
if (kn->kn_flags & (__EV_POLL | __EV_SELECT))
return (1);
return (kn->kn_data != 0);
}
int
filt_msdosfswrite(struct knote *kn, long hint)
{
if (hint == NOTE_REVOKE) {
kn->kn_flags |= (EV_EOF | EV_ONESHOT);
return (1);
}
kn->kn_data = 0;
return (1);
}
int
filt_msdosfsvnode(struct knote *kn, long hint)
{
if (kn->kn_sfflags & hint)
kn->kn_fflags |= hint;
if (hint == NOTE_REVOKE) {
kn->kn_flags |= EV_EOF;
return (1);
}
return (kn->kn_fflags != 0);
}