#include <sys/param.h>
#include <sys/systm.h>
#include <sys/vnode.h>
#include <sys/kernel.h>
#include <sys/stat.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/mount.h>
#include <sys/unistd.h>
#include <sys/vnode.h>
#include <miscfs/specfs/specdev.h>
#include <sys/malloc.h>
#include <sys/dirent.h>
#include <sys/signalvar.h>
#include <sys/ubc.h>
#include <sys/utfconv.h>
#include <sys/attr.h>
#include <sys/namei.h>
#include <libkern/crypto/md5.h>
#include <sys/disk.h>
#include <mach/boolean.h>
#include <libkern/OSMalloc.h>
#include "bpb.h"
#include "direntry.h"
#include "denode.h"
#include "msdosfsmount.h"
#include "fat.h"
#ifndef DEBUG
#define DEBUG 0
#endif
#define DOS_FILESIZE_MAX 0xffffffff
#define GENERIC_DIRSIZ(dp) \
((sizeof (struct dirent) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3))
static int msdosfs_create __P((struct vnop_create_args *));
static int msdosfs_mknod __P((struct vnop_mknod_args *));
static int msdosfs_open(struct vnop_open_args *ap);
static int msdosfs_close __P((struct vnop_close_args *));
static int msdosfs_getattr __P((struct vnop_getattr_args *));
static int msdosfs_setattr __P((struct vnop_setattr_args *));
static int msdosfs_read __P((struct vnop_read_args *));
static int msdosfs_write __P((struct vnop_write_args *));
static int msdosfs_pagein __P((struct vnop_pagein_args *));
static int msdosfs_fsync __P((struct vnop_fsync_args *));
static int msdosfs_remove __P((struct vnop_remove_args *));
static int msdosfs_rename __P((struct vnop_rename_args *));
static int msdosfs_mkdir __P((struct vnop_mkdir_args *));
static int msdosfs_rmdir __P((struct vnop_rmdir_args *));
static int msdosfs_readdir __P((struct vnop_readdir_args *));
static int msdosfs_strategy __P((struct vnop_strategy_args *));
static int msdosfs_pathconf __P((struct vnop_pathconf_args *ap));
static int msdosfs_symlink(struct vnop_symlink_args *ap);
static int msdosfs_readlink(struct vnop_readlink_args *ap);
static int msdosfs_ioctl(struct vnop_ioctl_args *ap);
static int msdosfs_pageout(struct vnop_pageout_args *ap);
__private_extern__ uid_t
get_pmuid(struct msdosfsmount *pmp, uid_t current_user)
{
if (vfs_flags(pmp->pm_mountp) & MNT_UNKNOWNPERMISSIONS)
return current_user;
else
return pmp->pm_uid;
}
static void
msdosfs_lock_two(struct denode *dep1, struct denode *dep2)
{
if (dep1 == NULL)
panic("msdosfs_lock_two: dep1 == NULL\n");
if (dep2 == NULL)
panic("msdosfs_lock_two: dep2 == NULL\n");
if (dep1 == dep2)
panic("msdosfs_lock_two: dep1 == dep2\n");
if (dep1 < dep2)
{
lck_mtx_lock(dep1->de_lock);
lck_mtx_lock(dep2->de_lock);
}
else
{
lck_mtx_lock(dep2->de_lock);
lck_mtx_lock(dep1->de_lock);
}
}
static void
msdosfs_sort_denodes(struct denode *deps[4])
{
int i, j;
struct denode *temp;
for (j=3; j>0; --j)
for (i=0; i<j; ++i)
if (deps[i] > deps[i+1])
{
temp = deps[i];
deps[i] = deps[i+1];
deps[i+1] = temp;
}
for (i=0; i<3; ++i)
if (deps[i] == deps[i+1])
deps[i] = NULL;
}
static void
msdosfs_lock_four(struct denode *dep0, struct denode *dep1, struct denode *dep2, struct denode *dep3)
{
int i;
struct denode *deps[4] = {dep0, dep1, dep2, dep3};
msdosfs_sort_denodes(deps);
for (i=0; i<4; ++i)
if (deps[i] != NULL)
lck_mtx_lock(deps[i]->de_lock);
}
static void
msdosfs_unlock_four(struct denode *dep0, struct denode *dep1, struct denode *dep2, struct denode *dep3)
{
int i;
struct denode *deps[4] = {dep0, dep1, dep2, dep3};
msdosfs_sort_denodes(deps);
for (i=0; i<4; ++i)
if (deps[i] != NULL)
lck_mtx_unlock(deps[i]->de_lock);
}
static int
msdosfs_create(ap)
struct vnop_create_args *ap;
{
vnode_t dvp = ap->a_dvp;
struct denode *pdep = VTODE(dvp);
struct componentname *cnp = ap->a_cnp;
vfs_context_t context = ap->a_context;
struct vnode_attr *vap = ap->a_vap;
struct denode ndirent;
struct denode *dep;
struct timespec ts;
int error;
uint32_t offset;
uint32_t long_count;
lck_mtx_lock(pdep->de_lock);
if (pdep->de_refcnt <= 0)
{
cache_purge(dvp);
error = ENOENT;
goto exit;
}
error = msdosfs_lookup_name(pdep, cnp, NULL, NULL, NULL, context);
if (error != ENOENT)
{
error = EEXIST;
goto exit;
}
error = findslots(pdep, cnp, &ndirent.de_LowerCase, &offset, &long_count, context);
if (error)
goto exit;
if (pdep->de_StartCluster == MSDOSFSROOT
&& offset >= pdep->de_FileSize)
{
error = ENOSPC;
goto exit;
}
bzero(&ndirent, sizeof(ndirent));
error = uniqdosname(pdep, cnp, ndirent.de_Name, &ndirent.de_LowerCase, context);
if (error)
{
if (DEBUG) panic("msdosfs_create: uniqdosname returned %d\n", error);
goto exit;
}
ndirent.de_Attributes = ATTR_ARCHIVE;
if (VATTR_IS_ACTIVE(vap, va_flags))
{
if ((vap->va_flags & (SF_IMMUTABLE | UF_IMMUTABLE)) != 0)
ndirent.de_Attributes |= ATTR_READONLY;
VATTR_SET_SUPPORTED(vap, va_flags);
}
if (cnp->cn_nameptr[0] == '.')
ndirent.de_Attributes |= ATTR_HIDDEN;
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);
error = createde(&ndirent, pdep, &dep, cnp, offset, long_count, context);
if (error)
{
if (DEBUG && error != ENOSPC) panic("msdosfs_create: createde returned %d\n", error);
goto exit;
}
*ap->a_vpp = DETOV(dep);
cache_purge_negatives(dvp);
exit:
msdosfs_meta_flush(pdep->de_pmp, FALSE);
lck_mtx_unlock(pdep->de_lock);
return error;
}
static int
msdosfs_mknod(ap)
struct vnop_mknod_args *ap;
{
#pragma unused (ap)
return EINVAL;
}
static int
msdosfs_open(
struct vnop_open_args *ap)
{
#pragma unused (ap)
return 0;
}
static int
msdosfs_close(ap)
struct vnop_close_args *ap;
{
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
lck_mtx_lock(dep->de_lock);
cluster_push(vp, IO_CLOSE);
deupdat(dep, 0, ap->a_context);
msdosfs_meta_flush(dep->de_pmp, FALSE);
lck_mtx_unlock(dep->de_lock);
return 0;
}
static int
msdosfs_getattr(ap)
struct vnop_getattr_args *ap;
{
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
struct msdosfsmount *pmp = dep->de_pmp;
struct vnode_attr *vap = ap->a_vap;
lck_mtx_lock(dep->de_lock);
VATTR_RETURN(vap, va_rdev, 0);
VATTR_RETURN(vap, va_nlink, 1);
VATTR_RETURN(vap, va_total_size, dep->de_FileSize);
VATTR_RETURN(vap, va_total_alloc, ((off_t)dep->de_FileSize + pmp->pm_crbomask) & ~((off_t)pmp->pm_crbomask));
VATTR_RETURN(vap, va_data_size, dep->de_FileSize);
VATTR_RETURN(vap, va_data_alloc, vap->va_total_alloc);
VATTR_RETURN(vap, va_iosize, pmp->pm_iosize);
VATTR_RETURN(vap, va_uid, 99);
VATTR_RETURN(vap, va_gid, 99);
VATTR_RETURN(vap, va_mode, ALLPERMS & pmp->pm_mask);
if (VATTR_IS_ACTIVE(vap, va_flags)) {
vap->va_flags = 0;
if ((dep->de_Attributes & (ATTR_ARCHIVE | ATTR_DIRECTORY)) == 0) vap->va_flags |= SF_ARCHIVED; if ((dep->de_Attributes & (ATTR_READONLY | ATTR_DIRECTORY)) == ATTR_READONLY)
vap->va_flags |= UF_IMMUTABLE; if (dep->de_Attributes & ATTR_HIDDEN)
vap->va_flags |= UF_HIDDEN;
VATTR_SET_SUPPORTED(vap, va_flags);
}
if (vap->va_active & (VNODE_ATTR_va_create_time |
VNODE_ATTR_va_access_time | VNODE_ATTR_va_modify_time |
VNODE_ATTR_va_change_time))
{
struct timespec ts;
getnanotime(&ts);
DETIMES(dep, &ts, &ts, &ts);
dos2unixtime(dep->de_CDate, dep->de_CTime, 0, &vap->va_create_time);
dos2unixtime(dep->de_ADate, 0, 0, &vap->va_access_time);
dos2unixtime(dep->de_MDate, dep->de_MTime, 0, &vap->va_modify_time);
vap->va_change_time = vap->va_modify_time;
vap->va_supported |= VNODE_ATTR_va_create_time |
VNODE_ATTR_va_access_time |
VNODE_ATTR_va_modify_time |
VNODE_ATTR_va_change_time;
}
if (VATTR_IS_ACTIVE(vap, va_fileid))
VATTR_RETURN(vap, va_fileid, defileid(dep));
VATTR_RETURN(vap, va_fsid, dep->de_dev);
VATTR_RETURN(vap, va_filerev, dep->de_modrev);
VATTR_RETURN(vap, va_gen, 0);
lck_mtx_unlock(dep->de_lock);
return 0;
}
static int
msdosfs_setattr(struct vnop_setattr_args *ap)
{
struct denode *dep = VTODE(ap->a_vp);
struct vnode_attr *vap = ap->a_vap;
int error = 0;
lck_mtx_lock(dep->de_lock);
if (VATTR_IS_ACTIVE(vap, va_data_size)) {
if (vnode_vtype(ap->a_vp) != VREG)
{
error = EPERM;
goto exit;
}
if (dep->de_FileSize != vap->va_data_size) {
if (vap->va_data_size > DOS_FILESIZE_MAX)
error = EFBIG;
else
error = detrunc(dep, vap->va_data_size, vap->va_vaflags, ap->a_context);
if (error)
goto exit;
}
VATTR_SET_SUPPORTED(vap, va_data_size);
}
if (VATTR_IS_ACTIVE(vap, va_flags)) {
if (vap->va_flags & ~(SF_ARCHIVED | SF_IMMUTABLE | UF_IMMUTABLE | UF_HIDDEN))
{
error = EINVAL;
goto exit;
}
if (vap->va_flags & SF_ARCHIVED)
dep->de_Attributes &= ~ATTR_ARCHIVE;
else if (!(dep->de_Attributes & ATTR_DIRECTORY))
dep->de_Attributes |= ATTR_ARCHIVE;
if (!(dep->de_Attributes & ATTR_DIRECTORY))
{
if (vap->va_flags & (SF_IMMUTABLE | UF_IMMUTABLE))
dep->de_Attributes |= ATTR_READONLY;
else
dep->de_Attributes &= ~ATTR_READONLY;
}
if (vap->va_flags & UF_HIDDEN)
dep->de_Attributes |= ATTR_HIDDEN;
else
dep->de_Attributes &= ~ATTR_HIDDEN;
dep->de_flag |= DE_MODIFIED;
VATTR_SET_SUPPORTED(vap, va_flags);
}
if (VATTR_IS_ACTIVE(vap, va_create_time) |
VATTR_IS_ACTIVE(vap, va_access_time) |
VATTR_IS_ACTIVE(vap, va_modify_time))
{
if (VATTR_IS_ACTIVE(vap, va_create_time)) {
unix2dostime(&vap->va_create_time, &dep->de_CDate, &dep->de_CTime, NULL);
VATTR_SET_SUPPORTED(vap, va_create_time);
}
if (VATTR_IS_ACTIVE(vap, va_access_time)) {
unix2dostime(&vap->va_access_time, &dep->de_ADate, NULL, NULL);
VATTR_SET_SUPPORTED(vap, va_access_time);
}
if (VATTR_IS_ACTIVE(vap, va_modify_time)) {
unix2dostime(&vap->va_modify_time, &dep->de_MDate, &dep->de_MTime, NULL);
VATTR_SET_SUPPORTED(vap, va_modify_time);
}
dep->de_Attributes |= ATTR_ARCHIVE;
dep->de_flag |= DE_MODIFIED;
}
error = deupdat(dep, 1, ap->a_context);
msdosfs_meta_flush(dep->de_pmp, FALSE);
exit:
lck_mtx_unlock(dep->de_lock);
return error;
}
static int
msdosfs_read(ap)
struct vnop_read_args *ap;
{
int error = 0;
int orig_resid;
vnode_t vp = ap->a_vp;
struct uio *uio = ap->a_uio;
vfs_context_t context = ap->a_context;
struct denode *dep = VTODE(vp);
struct msdosfsmount *pmp = dep->de_pmp;
if (uio_offset(uio) < 0)
return EINVAL;
if (uio_offset(uio) > DOS_FILESIZE_MAX)
return 0;
orig_resid = uio_resid(uio);
if (orig_resid <= 0)
return 0;
lck_mtx_lock(dep->de_lock);
if (vnode_isreg(vp)) {
error = cluster_read(vp, uio, (off_t)dep->de_FileSize, ap->a_ioflag);
if (error == 0 && (vfs_flags(pmp->pm_mountp) & (MNT_RDONLY | MNT_NOATIME)) == 0)
dep->de_flag |= DE_ACCESS;
}
else
{
uint32_t blsize;
u_int n;
uint32_t diff;
uint32_t on;
daddr64_t lbn;
buf_t bp;
do {
if (uio_offset(uio) >= dep->de_FileSize)
break;
lbn = de_cluster(pmp, uio_offset(uio));
error = pcbmap(dep, lbn, 1, &lbn, NULL, &blsize);
if (error == E2BIG) {
error = EINVAL;
break;
} else if (error)
break;
error = (int)buf_meta_bread(pmp->pm_devvp, lbn, blsize, vfs_context_ucred(context), &bp);
if (error) {
buf_brelse(bp);
break;
}
if (ISSET(ap->a_ioflag, IO_NOCACHE) && buf_fromcache(bp) == 0)
buf_markaged(bp);
on = uio_offset(uio) & pmp->pm_crbomask;
diff = pmp->pm_bpcluster - on;
n = diff > (uint32_t)uio_resid(uio) ? (uint32_t)uio_resid(uio) : diff;
diff = dep->de_FileSize - uio_offset(uio);
if (diff < n)
n = diff;
diff = blsize - buf_resid(bp);
if (diff < n)
n = diff;
error = uiomove((char *)buf_dataptr(bp) + on, (int) n, uio);
buf_brelse(bp);
} while (error == 0 && uio_resid(uio) > 0 && n != 0);
}
lck_mtx_unlock(dep->de_lock);
return error;
}
static int
msdosfs_write(ap)
struct vnop_write_args *ap;
{
int error;
vnode_t vp = ap->a_vp;
struct uio *uio = ap->a_uio;
int ioflag = ap->a_ioflag;
vfs_context_t context = ap->a_context;
struct denode *dep = VTODE(vp);
struct msdosfsmount *pmp = dep->de_pmp;
off_t zero_off;
u_int32_t original_size;
u_int32_t count;
u_int32_t filesize;
int lflag;
user_ssize_t original_resid;
off_t original_offset;
off_t offset;
switch (vnode_vtype(vp)) {
case VREG:
break;
case VDIR:
return EISDIR;
default:
panic("msdosfs_write: bad file type");
return EINVAL;
}
lck_mtx_lock(dep->de_lock);
original_resid = uio_resid(uio);
original_size = dep->de_FileSize;
original_offset = uio_offset(uio);
offset = original_offset;
if (ioflag & IO_APPEND) {
uio_setoffset(uio, dep->de_FileSize);
offset = dep->de_FileSize;
}
if (offset < 0)
{
error = EFBIG;
goto exit;
}
if (original_resid == 0)
{
error = 0;
goto exit;
}
if (offset + original_resid > DOS_FILESIZE_MAX)
{
error = EFBIG;
goto exit;
}
if (offset + original_resid > original_size) {
count = de_clcount(pmp, offset + original_resid) -
de_clcount(pmp, original_size);
if ((ioflag & IO_UNIT) && (count > pmp->pm_freeclustercount))
error = ENOSPC;
else
error = extendfile(dep, count);
if (error && (error != ENOSPC || (ioflag & IO_UNIT)))
goto errexit;
filesize = offset + original_resid;
} else {
filesize = original_size;
}
lflag = ioflag;
if (offset > original_size) {
zero_off = original_size;
lflag |= IO_HEADZEROFILL;
} else
zero_off = 0;
error = cluster_write(vp, uio, (off_t)original_size, (off_t)filesize,
(off_t)zero_off,
(off_t)0, lflag);
if (uio_offset(uio) > dep->de_FileSize) {
dep->de_FileSize = uio_offset(uio);
ubc_setsize(vp, (off_t)dep->de_FileSize);
}
if (original_resid > uio_resid(uio))
dep->de_flag |= DE_UPDATE;
errexit:
if (error) {
if (ioflag & IO_UNIT) {
detrunc(dep, original_size, ioflag, context);
uio_setoffset(uio, original_offset);
uio_setresid(uio, original_resid);
} else {
detrunc(dep, dep->de_FileSize, ioflag, context);
if (uio_resid(uio) != original_resid)
error = 0;
}
} else if (ioflag & IO_SYNC)
error = deupdat(dep, 1, context);
msdosfs_meta_flush(pmp, (ioflag & IO_SYNC));
exit:
lck_mtx_unlock(dep->de_lock);
return error;
}
static int
msdosfs_pagein(ap)
struct vnop_pagein_args *ap;
{
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
int error;
error = cluster_pagein(vp, ap->a_pl, ap->a_pl_offset, ap->a_f_offset,
ap->a_size, (off_t)dep->de_FileSize,
ap->a_flags);
return error;
}
static int
msdosfs_pageout(
struct vnop_pageout_args *ap)
{
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
int error;
error = cluster_pageout(vp, ap->a_pl, ap->a_pl_offset, ap->a_f_offset,
ap->a_size, (off_t)dep->de_FileSize,
ap->a_flags);
if (!error)
dep->de_flag |= DE_UPDATE;
return error;
}
__private_extern__ int
msdosfs_fsync_internal(vnode_t vp, int sync, int do_dirs, vfs_context_t context)
{
int error;
struct denode *dep = VTODE(vp);
cluster_push(vp, sync ? IO_SYNC : 0);
buf_flushdirtyblks(vp, sync, 0, "msdosfs_fsync_internal");
if (do_dirs && (dep->de_Attributes & ATTR_DIRECTORY))
(void) msdosfs_dir_flush(dep, sync);
error = deupdat(dep, sync, context);
return error;
}
static int
msdosfs_fsync(ap)
struct vnop_fsync_args *ap;
{
int error;
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
if (dep == NULL)
return 0;
lck_mtx_lock(dep->de_lock);
error = msdosfs_fsync_internal(vp, (ap->a_waitfor == MNT_WAIT), TRUE, ap->a_context);
msdosfs_meta_flush(dep->de_pmp, FALSE);
lck_mtx_unlock(dep->de_lock);
return error;
}
static int
msdosfs_remove(ap)
struct vnop_remove_args *ap;
{
vnode_t vp = ap->a_vp;
vnode_t dvp = ap->a_dvp;
struct denode *dep = VTODE(vp);
struct denode *ddep = VTODE(dvp);
int error;
uint32_t cluster, offset;
msdosfs_lock_two(ddep, dep);
if (ddep->de_refcnt <= 0)
{
cache_purge(dvp);
error = ENOENT;
goto exit;
}
if (dep->de_refcnt <= 0)
{
cache_purge(vp);
error = ENOENT;
goto exit;
}
error = msdosfs_lookup_name(ddep, ap->a_cnp, &cluster, &offset, NULL, ap->a_context);
if (error || cluster != dep->de_dirclust || offset != dep->de_diroffset)
{
cache_purge(vp);
error = ENOENT;
goto exit;
}
if (dep->de_Attributes & ATTR_READONLY)
{
error = EPERM;
goto exit;
}
if ((ap->a_flags & VNODE_REMOVE_NODELETEBUSY) && vnode_isinuse(vp, 0))
{
error = EBUSY;
goto exit;
}
cache_purge(vp);
dep->de_refcnt--;
if (DEBUG && dep->de_refcnt < 0)
panic("msdosfs_remove: de_refcnt went negative");
error = removede(ddep, dep->de_diroffset, ap->a_context);
if (DEBUG && error) panic("msdosfs_remove: removede returned %d\n", error);
msdosfs_meta_flush(ddep->de_pmp, FALSE);
exit:
lck_mtx_unlock(ddep->de_lock);
lck_mtx_unlock(dep->de_lock);
return error;
}
static int
msdosfs_rename(ap)
struct vnop_rename_args *ap;
{
vnode_t tdvp = ap->a_tdvp;
vnode_t fvp = ap->a_fvp;
vnode_t fdvp = ap->a_fdvp;
vnode_t tvp = ap->a_tvp;
struct componentname *tcnp = ap->a_tcnp;
vfs_context_t context = ap->a_context;
u_char toname[SHORT_NAME_LEN], oldname[SHORT_NAME_LEN];
uint32_t to_diroffset;
uint32_t to_long_count;
u_int32_t from_offset;
u_int8_t new_deLowerCase;
int doingdirectory = 0, newparent = 0;
int change_case;
int error;
uint32_t cn;
daddr64_t bn = 0;
struct denode *fddep;
struct denode *fdep;
struct denode *tddep;
struct denode *tdep;
struct msdosfsmount *pmp;
struct buf *bp;
uint32_t cluster, offset;
fddep = VTODE(fdvp);
fdep = VTODE(fvp);
tddep = VTODE(tdvp);
tdep = tvp ? VTODE(tvp) : NULL;
msdosfs_lock_four(fddep, fdep, tddep, tdep);
pmp = fddep->de_pmp;
if (fddep->de_refcnt <= 0)
{
cache_purge(fdvp);
error = ENOENT;
goto exit;
}
if (fdep->de_refcnt <= 0)
{
cache_purge(fvp);
error = ENOENT;
goto exit;
}
error = msdosfs_lookup_name(fddep, ap->a_fcnp, &cluster, &offset, NULL, context);
if (error || cluster != fdep->de_dirclust || offset != fdep->de_diroffset)
{
cache_purge(fvp);
error = ENOENT;
goto exit;
}
if (tddep->de_refcnt <= 0)
{
cache_purge(tdvp);
error = ENOENT;
goto exit;
}
if (tdep && tdep->de_refcnt <= 0)
{
cache_purge(tvp);
error = ENOENT;
goto exit;
}
error = msdosfs_lookup_name(tddep, tcnp, &cluster, &offset, NULL, context);
if (tdep)
{
if (error || cluster != tdep->de_dirclust || offset != tdep->de_diroffset)
{
error = ERESTART;
goto exit;
}
}
else
{
if (error != ENOENT)
{
error = EEXIST;
goto exit;
}
}
change_case = 0;
if (tvp == fvp) {
tvp = NULL;
change_case = 1;
}
if (tdep && (tdep->de_Attributes & (ATTR_READONLY | ATTR_DIRECTORY)) == ATTR_READONLY)
{
error = EPERM;
goto exit;
}
if ((fdep->de_Attributes & (ATTR_READONLY | ATTR_DIRECTORY)) == ATTR_READONLY)
{
error = EPERM;
goto exit;
}
error = findslots(tddep, tcnp, &new_deLowerCase, &to_diroffset, &to_long_count, context);
if (error)
goto exit;
if (fdep->de_Attributes & ATTR_DIRECTORY)
doingdirectory = 1;
if (fddep->de_StartCluster != tddep->de_StartCluster)
newparent = 1;
if (doingdirectory && newparent) {
lck_mtx_lock(pmp->pm_rename_lock);
error = doscheckpath(fdep, tddep, context);
if (error) goto exit;
}
from_offset = fdep->de_diroffset;
if (tvp != NULL) {
uint32_t dest_offset;
if (tdep->de_Attributes & ATTR_DIRECTORY) {
if (!dosdirempty(tdep, context)) {
error = ENOTEMPTY;
goto exit;
}
if (!doingdirectory) {
error = ENOTDIR;
goto exit;
}
} else {
if (doingdirectory) {
error = EISDIR;
goto exit;
}
}
dest_offset = tdep->de_diroffset;
cache_purge(tvp);
tdep->de_refcnt--;
if (DEBUG && tdep->de_refcnt < 0)
panic("msdosfs_rename: de_refcnt went negative");
error = removede(tddep, dest_offset, context);
if (error)
{
if (DEBUG) panic("msdosfs_rename: removede (destination) returned %d\n", error);
goto flush_exit;
}
}
if (change_case)
{
bcopy(fdep->de_Name, toname, SHORT_NAME_LEN);
}
else
{
error = uniqdosname(tddep, tcnp, toname, &new_deLowerCase, context);
if (error)
{
if (DEBUG) panic("msdosfs_rename: uniqdosname returned %d\n", error);
goto flush_exit;
}
}
cache_purge(fvp);
bcopy(fdep->de_Name, oldname, SHORT_NAME_LEN);
bcopy(toname, fdep->de_Name, SHORT_NAME_LEN);
fdep->de_LowerCase = new_deLowerCase;
if (tcnp->cn_nameptr[0] == '.')
fdep->de_Attributes |= ATTR_HIDDEN;
else
fdep->de_Attributes &= ~ATTR_HIDDEN;
error = createde(fdep, tddep, NULL, tcnp, to_diroffset, to_long_count, context);
if (error)
{
if (DEBUG) panic("msdosfs_rename: createde returned %d\n", error);
bcopy(oldname, fdep->de_Name, SHORT_NAME_LEN);
goto flush_exit;
}
else
{
cache_purge_negatives(tdvp);
}
fdep->de_parent = tddep;
error = removede(fddep, from_offset, context);
if (error) {
if (DEBUG) panic("msdosfs_rename: removede (source) returned %d\n", error);
goto flush_exit;
}
error = pcbmap(tddep, de_cluster(pmp, to_diroffset), 1,
NULL, &fdep->de_dirclust, NULL);
if (error) {
if (DEBUG) panic("msdosfs_rename: pcbmap returned %d\n", error);
goto flush_exit;
}
fdep->de_diroffset = to_diroffset;
reinsert(fdep);
if (doingdirectory && newparent) {
struct dosdirentry *dotdotp;
cn = fdep->de_StartCluster;
bn = cntobn(pmp, cn);
error = (int)buf_meta_bread(pmp->pm_devvp, bn, pmp->pm_bpcluster, vfs_context_ucred(context), &bp);
if (error) {
if (DEBUG) panic("msdosfs_rename: buf_meta_bread returned %d\n", error);
buf_brelse(bp);
goto flush_exit;
}
dotdotp = (struct dosdirentry *)buf_dataptr(bp) + 1;
putuint16(dotdotp->deStartCluster, tddep->de_StartCluster);
if (FAT32(pmp))
putuint16(dotdotp->deHighClust, tddep->de_StartCluster >> 16);
error = (int)buf_bdwrite(bp);
if (error) {
if (DEBUG) panic("msdosfs_rename: buf_bdwrite returned %d\n", error);
goto flush_exit;
}
}
flush_exit:
msdosfs_meta_flush(pmp, FALSE);
exit:
if (doingdirectory && newparent)
lck_mtx_unlock(pmp->pm_rename_lock);
msdosfs_unlock_four(fddep, fdep, tddep, tdep);
return error;
}
static struct {
struct dosdirentry dot;
struct dosdirentry dotdot;
} dosdirtemplate = {
{ ". ", " ",
ATTR_DIRECTORY,
0,
0, { 0, 0 }, { 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 210, 4 }, { 210, 4 },
{ 0, 0 },
{ 0, 0, 0, 0 }
},
{ ".. ", " ",
ATTR_DIRECTORY,
0,
0, { 0, 0 }, { 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 210, 4 }, { 210, 4 },
{ 0, 0 },
{ 0, 0, 0, 0 }
}
};
static int
msdosfs_mkdir(ap)
struct vnop_mkdir_args *ap;
{
vnode_t dvp = ap->a_dvp;
struct denode *pdep = VTODE(dvp);
struct componentname *cnp = ap->a_cnp;
vfs_context_t context = ap->a_context;
struct denode *dep;
struct dosdirentry *denp;
struct msdosfsmount *pmp = pdep->de_pmp;
struct buf *bp;
uint32_t newcluster, pcl;
daddr64_t bn;
int error;
struct denode ndirent;
struct timespec ts;
char *bdata;
uint32_t offset;
uint32_t long_count;
lck_mtx_lock(pdep->de_lock);
if (pdep->de_refcnt <= 0)
{
cache_purge(dvp);
error = ENOENT;
goto exit;
}
error = msdosfs_lookup_name(pdep, cnp, NULL, NULL, NULL, context);
if (error != ENOENT)
{
error = EEXIST;
goto exit;
}
error = findslots(pdep, cnp, &ndirent.de_LowerCase, &offset, &long_count, context);
if (error)
goto exit;
if (pdep->de_StartCluster == MSDOSFSROOT
&& offset >= pdep->de_FileSize)
{
error = ENOSPC;
goto exit;
}
error = clusteralloc(pmp, 0, 1, CLUST_EOFE, &newcluster, NULL);
if (error)
goto exit;
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 = buf_getblk(pmp->pm_devvp, bn, pmp->pm_bpcluster, 0, 0, BLK_META);
bdata = (char *)buf_dataptr(bp);
bzero(bdata, pmp->pm_bpcluster);
bcopy(&dosdirtemplate, bdata, sizeof dosdirtemplate);
denp = (struct dosdirentry *)bdata;
putuint16(denp[0].deStartCluster, newcluster);
putuint16(denp[0].deCDate, ndirent.de_CDate);
putuint16(denp[0].deCTime, ndirent.de_CTime);
denp[0].deCHundredth = ndirent.de_CHun;
putuint16(denp[0].deADate, ndirent.de_ADate);
putuint16(denp[0].deMDate, ndirent.de_MDate);
putuint16(denp[0].deMTime, ndirent.de_MTime);
pcl = pdep->de_StartCluster;
if (FAT32(pmp) && pcl == pmp->pm_rootdirblk)
pcl = 0;
putuint16(denp[1].deStartCluster, pcl);
putuint16(denp[1].deCDate, ndirent.de_CDate);
putuint16(denp[1].deCTime, ndirent.de_CTime);
denp[1].deCHundredth = ndirent.de_CHun;
putuint16(denp[1].deADate, ndirent.de_ADate);
putuint16(denp[1].deMDate, ndirent.de_MDate);
putuint16(denp[1].deMTime, ndirent.de_MTime);
if (FAT32(pmp)) {
putuint16(denp[0].deHighClust, newcluster >> 16);
putuint16(denp[1].deHighClust, pdep->de_StartCluster >> 16);
}
error = (int)buf_bdwrite(bp);
if (error)
goto exit;
error = uniqdosname(pdep, cnp, ndirent.de_Name, &ndirent.de_LowerCase, context);
if (error)
goto exit;
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 (cnp->cn_nameptr[0] == '.')
ndirent.de_Attributes |= ATTR_HIDDEN;
error = createde(&ndirent, pdep, &dep, cnp, offset, long_count, context);
if (error)
{
if (DEBUG)
panic("msodsfs_mkdir: createde failed\n");
clusterfree(pmp, newcluster, NULL);
}
else
{
*ap->a_vpp = DETOV(dep);
cache_purge_negatives(dvp);
}
exit:
msdosfs_meta_flush(pmp, FALSE);
lck_mtx_unlock(pdep->de_lock);
return error;
}
static int
msdosfs_rmdir(ap)
struct vnop_rmdir_args *ap;
{
vnode_t vp = ap->a_vp;
vnode_t dvp = ap->a_dvp;
vfs_context_t context = ap->a_context;
struct denode *ip, *dp;
int error;
uint32_t cluster, offset;
ip = VTODE(vp);
dp = VTODE(dvp);
msdosfs_lock_two(dp, ip);
if (dp->de_refcnt <= 0)
{
cache_purge(dvp);
error = ENOENT;
goto exit;
}
if (ip->de_refcnt <= 0)
{
cache_purge(vp);
error = ENOENT;
goto exit;
}
error = msdosfs_lookup_name(dp, ap->a_cnp, &cluster, &offset, NULL, context);
if (error || cluster != ip->de_dirclust || offset != ip->de_diroffset)
{
cache_purge(vp);
error = ENOENT;
goto exit;
}
if (dp == ip) {
error = EINVAL;
goto exit;
}
error = 0;
if (!dosdirempty(ip, context)) {
error = ENOTEMPTY;
goto exit;
}
ip->de_refcnt--;
if (DEBUG && ip->de_refcnt < 0)
panic("msdosfs_rmdir: de_refcnt went negative");
error = removede(dp, ip->de_diroffset, context);
if (error)
{
if (DEBUG) panic("msdosfs_rmdir: removede returned %d\n", error);
goto flush_exit;
}
error = msdosfs_dir_invalidate(ip);
if (error)
{
if (DEBUG) panic("msdosfs_rmdir: msdosfs_dir_invalidate returned %d\n", error);
goto flush_exit;
}
error = detrunc(ip, 0, 0, context);
if (DEBUG && error) panic("msdosfs_rmdir: detrunc returned %d\n", error);
cache_purge(vp);
flush_exit:
msdosfs_meta_flush(dp->de_pmp, FALSE);
exit:
lck_mtx_unlock(dp->de_lock);
lck_mtx_unlock(ip->de_lock);
return error;
}
static int
msdosfs_readdir(ap)
struct vnop_readdir_args *ap;
{
int error = 0;
vnode_t vp = ap->a_vp;
struct uio *uio = ap->a_uio;
struct denode *dep = VTODE(vp);
struct msdosfsmount *pmp = dep->de_pmp;
vfs_context_t context = ap->a_context;
int diff;
int32_t n;
uint32_t blsize;
int32_t on;
uint32_t cn;
uint32_t fileno;
int32_t bias = 0;
daddr64_t bn, lbn;
struct buf *bp;
struct dosdirentry *dentp;
struct dirent dirbuf;
off_t offset;
off_t long_name_offset;
int chksum = -1;
u_int16_t ucfn[WIN_MAXLEN + 1];
u_int16_t unichars;
size_t outbytes;
char *bdata;
if (ap->a_numdirent)
*ap->a_numdirent = 0;
if (ap->a_eofflag)
*ap->a_eofflag = 0;
if (ap->a_flags & (VNODE_READDIR_EXTENDED | VNODE_READDIR_REQSEEKOFF))
return EINVAL;
if (!vnode_isdir(vp))
return ENOTDIR;
bzero(dirbuf.d_name, sizeof(dirbuf.d_name));
long_name_offset = offset = uio_offset(uio);
if (offset & (sizeof(struct dosdirentry) - 1))
return EINVAL;
lck_mtx_lock(dep->de_lock);
if (dep->de_StartCluster == MSDOSFSROOT
|| (FAT32(pmp) && dep->de_StartCluster == pmp->pm_rootdirblk)) {
bias = 2 * sizeof(struct dosdirentry);
if (offset < bias) {
for (n = (int)offset / sizeof(struct dosdirentry);
n < 2; n++) {
dirbuf.d_fileno = defileid(dep);
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 = GENERIC_DIRSIZ(&dirbuf);
if (uio_resid(uio) < dirbuf.d_reclen)
goto out;
error = uiomove((caddr_t) &dirbuf,
dirbuf.d_reclen, uio);
if (error)
goto out;
if (ap->a_numdirent)
++(*ap->a_numdirent);
offset += sizeof(struct dosdirentry);
}
}
}
while (uio_resid(uio) > 0) {
lbn = de_cluster(pmp, offset - bias);
on = (offset - bias) & pmp->pm_crbomask;
n = min(pmp->pm_bpcluster - on, uio_resid(uio));
diff = dep->de_FileSize - (offset - bias);
if (diff <= 0) {
if (ap->a_eofflag)
*ap->a_eofflag = 1;
break;
}
n = min(n, diff);
error = pcbmap(dep, lbn, 1, &bn, &cn, &blsize);
if (error)
break;
error = (int)buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), &bp);
if (error) {
buf_brelse(bp);
goto exit;
}
n = min(n, blsize - buf_resid(bp));
bdata = (char *)buf_dataptr(bp);
for (dentp = (struct dosdirentry *)(bdata + on);
(char *)dentp < bdata + on + n;
dentp++, offset += sizeof(struct dosdirentry)) {
if (dentp->deName[0] == SLOT_EMPTY) {
buf_brelse(bp);
if (ap->a_eofflag)
*ap->a_eofflag = 1;
goto out;
}
if (dentp->deName[0] == SLOT_DELETED) {
chksum = -1;
continue;
}
if (dentp->deAttributes == ATTR_WIN95) {
if (dentp->deName[0] & WIN_LAST)
long_name_offset = offset;
chksum = getunicodefn((struct winentry *)dentp,
ucfn, &unichars, chksum);
continue;
}
if (dentp->deAttributes & ATTR_VOLUME) {
chksum = -1;
continue;
}
fileno = getuint16(dentp->deStartCluster);
if (FAT32(pmp))
fileno |= getuint16(dentp->deHighClust) << 16;
if (dentp->deAttributes & ATTR_DIRECTORY) {
if (fileno == MSDOSFSROOT) {
if (FAT32(pmp))
fileno = pmp->pm_rootdirblk;
else
fileno = FILENO_ROOT;
}
dirbuf.d_type = DT_DIR;
} else {
if (fileno == 0)
fileno = FILENO_EMPTY;
if (getuint32(dentp->deFileSize) == sizeof(struct symlink))
dirbuf.d_type = DT_UNKNOWN;
else
dirbuf.d_type = DT_REG;
}
dirbuf.d_fileno = fileno;
if (chksum != winChksum(dentp->deName)) {
chksum = -1;
unichars = dos2unicodefn(dentp->deName, ucfn,
dentp->deLowerCase);
}
(void) utf8_encodestr(ucfn, unichars * 2,
(u_int8_t*)dirbuf.d_name, &outbytes,
sizeof(dirbuf.d_name), 0,
UTF_DECOMPOSED|UTF_SFM_CONVERSIONS);
dirbuf.d_namlen = outbytes;
dirbuf.d_reclen = GENERIC_DIRSIZ(&dirbuf);
if (uio_resid(uio) < dirbuf.d_reclen) {
if (chksum != -1)
offset = long_name_offset;
buf_brelse(bp);
goto out;
}
error = uiomove((caddr_t) &dirbuf,
dirbuf.d_reclen, uio);
if (error) {
buf_brelse(bp);
goto out;
}
chksum = -1;
if (ap->a_numdirent)
++(*ap->a_numdirent);
}
buf_brelse(bp);
}
out:
uio_setoffset(uio, offset);
exit:
lck_mtx_unlock(dep->de_lock);
return error;
}
__private_extern__ int
msdosfs_blktooff(ap)
struct vnop_blktooff_args *ap;
{
if (ap->a_vp == NULL)
return EINVAL;
*ap->a_offset = ap->a_lblkno * PAGE_SIZE_64;
return 0;
}
__private_extern__ int
msdosfs_offtoblk(ap)
struct vnop_offtoblk_args *ap;
{
if (ap->a_vp == NULL)
return EINVAL;
*ap->a_lblkno = ap->a_offset / PAGE_SIZE_64;
return 0;
}
__private_extern__ int
msdosfs_blockmap(ap)
struct vnop_blockmap_args *ap;
{
int error;
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
struct msdosfsmount *pmp = dep->de_pmp;
uint32_t runsize;
uint32_t cn;
uint32_t numclusters;
daddr64_t bn;
if (ap->a_bpn == NULL)
return 0;
if (ap->a_size == 0)
panic("msdosfs_blockmap: a_size == 0");
cn = de_cluster(pmp, ap->a_foffset);
numclusters = de_cluster(pmp, ap->a_foffset + ap->a_size - 1) - cn + 1;
error = pcbmap(dep, cn, numclusters, &bn, NULL, &runsize);
bn += (((uint32_t)ap->a_foffset - de_cn2off(pmp, cn)) >> pmp->pm_bnshift);
runsize -= ((uint32_t)ap->a_foffset - (de_cn2off(pmp, cn)));
*ap->a_bpn = bn;
if (error == 0 && ap->a_run) {
if (runsize > ap->a_size)
* ap->a_run = ap->a_size;
else
* ap->a_run = runsize;
}
if (ap->a_poff)
*(int *)ap->a_poff = 0;
return error;
}
static int
msdosfs_strategy(ap)
struct vnop_strategy_args *ap;
{
buf_t bp = ap->a_bp;
vnode_t vp = buf_vnode(bp);
struct denode *dep = VTODE(vp);
int error;
error = buf_strategy(dep->de_devvp, ap);
return error;
}
static int
msdosfs_pathconf(ap)
struct vnop_pathconf_args *ap;
{
switch (ap->a_name) {
case _PC_LINK_MAX:
*ap->a_retval = 1;
return 0;
case _PC_NAME_MAX:
*ap->a_retval = WIN_MAXLEN;
return 0;
case _PC_PATH_MAX:
*ap->a_retval = PATH_MAX;
return 0;
case _PC_CHOWN_RESTRICTED:
*ap->a_retval = 1;
return 0;
case _PC_NO_TRUNC:
*ap->a_retval = 0;
return 0;
case _PC_CASE_SENSITIVE:
*ap->a_retval = 0;
return 0;
case _PC_CASE_PRESERVING:
*ap->a_retval = 1;
return 0;
case _PC_FILESIZEBITS:
*ap->a_retval = 32;
return 0;
default:
return EINVAL;
}
}
static void msdosfs_md5_digest(void *text, unsigned length, char digest[33])
{
int i;
MD5_CTX context;
unsigned char digest_raw[16];
MD5Init(&context);
MD5Update(&context, text, length);
MD5Final(digest_raw, &context);
for (i=0; i<16; ++i)
{
(void) snprintf(digest, 3, "%02x", digest_raw[i]);
digest += 2;
}
}
__private_extern__ enum vtype msdosfs_check_link(struct denode *dep, vfs_context_t context)
{
int error;
int i;
unsigned length;
char c;
enum vtype result;
struct msdosfsmount *pmp;
vnode_t vp = NULL;
buf_t bp = NULL;
struct symlink *link;
char digest[33];
struct vnode_fsparam vfsp;
if (dep->de_FileSize != sizeof(struct symlink))
{
result = VREG;
goto exit;
}
result = VREG;
pmp = dep->de_pmp;
vfsp.vnfs_mp = pmp->pm_mountp;
vfsp.vnfs_vtype = VNON;
vfsp.vnfs_str = "msdosfs";
vfsp.vnfs_dvp = NULL;
vfsp.vnfs_fsnode = dep;
vfsp.vnfs_cnp = NULL;
vfsp.vnfs_vops = msdosfs_vnodeop_p;
vfsp.vnfs_rdev = 0;
vfsp.vnfs_filesize = dep->de_FileSize;
vfsp.vnfs_flags = VNFS_NOCACHE;
vfsp.vnfs_markroot = 0;
vfsp.vnfs_marksystem = 0;
error = vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vfsp, &dep->de_vnode);
vp = dep->de_vnode;
if (error) goto exit;
error = buf_meta_bread(vp, 0, roundup(sizeof(*link),pmp->pm_bpcluster),
vfs_context_ucred(context), &bp);
if (error) goto exit;
link = (struct symlink *) buf_dataptr(bp);
if (strncmp(link->magic, symlink_magic, 5) != 0)
goto exit;
length = 0;
for (i=0; i<4; ++i)
{
c = link->length[i];
if (c < '0' || c > '9')
goto exit;
length = 10 * length + c - '0';
}
if (length > SYMLINK_LINK_MAX)
goto exit;
msdosfs_md5_digest(link->link, length, digest);
if (strncmp(digest, link->md5, 32) != 0)
goto exit;
result = VLNK;
dep->de_FileSize = length;
dep->de_flag |= DE_SYMLINK;
exit:
if (bp)
{
buf_markinvalid(bp);
buf_brelse(bp);
}
if (vp)
{
(void) vnode_clearfsnode(vp);
(void) vnode_recycle(vp);
(void) vnode_put(vp);
}
return result;
}
static int msdosfs_symlink(struct vnop_symlink_args *ap)
{
int error;
vnode_t dvp = ap->a_dvp;
struct denode *dep = VTODE(dvp);
struct msdosfsmount *pmp = dep->de_pmp;
struct componentname *cnp = ap->a_cnp;
struct vnode_attr *vap = ap->a_vap;
char *target = ap->a_target;
vfs_context_t context = ap->a_context;
unsigned length;
struct symlink *link = NULL;
uint32_t cn = 0;
uint32_t clusters, got;
buf_t bp = NULL;
struct denode ndirent;
struct denode *new_dep;
struct timespec ts;
uint32_t offset;
uint32_t long_count;
lck_mtx_lock(dep->de_lock);
if (dep->de_refcnt <= 0)
{
cache_purge(dvp);
error = ENOENT;
goto exit;
}
error = msdosfs_lookup_name(dep, cnp, NULL, NULL, NULL, context);
if (error != ENOENT)
{
error = EEXIST;
goto exit;
}
error = findslots(dep, cnp, &ndirent.de_LowerCase, &offset, &long_count, context);
if (error)
goto exit;
if (dep->de_StartCluster == MSDOSFSROOT
&& offset >= dep->de_FileSize)
{
error = ENOSPC;
goto exit;
}
length = strlen(target);
if (length > SYMLINK_LINK_MAX)
{
error = ENAMETOOLONG;
goto exit;
}
clusters = de_clcount(pmp, sizeof(*link));
error = clusteralloc(pmp, 0, clusters, CLUST_EOFE, &cn, &got);
if (error) goto exit;
if (got < clusters)
{
error = ENOSPC;
goto exit;
}
bp = buf_getblk(pmp->pm_devvp, cntobn(pmp, cn),
roundup(sizeof(*link),pmp->pm_bpcluster),
0, 0, BLK_META);
buf_clear(bp);
link = (struct symlink *) buf_dataptr(bp);
bcopy(symlink_magic, link->magic, sizeof(symlink_magic));
snprintf(link->length, 6, "%04d\n", length);
msdosfs_md5_digest(target, length, link->md5);
link->newline2 = '\n';
bcopy(target, link->link, length);
if (length < SYMLINK_LINK_MAX)
link->link[length++] = '\n';
if (length < SYMLINK_LINK_MAX)
memset(&link->link[length], ' ', SYMLINK_LINK_MAX-length);
error = buf_bwrite(bp);
bp = NULL;
buf_invalblkno(pmp->pm_devvp, cntobn(pmp, cn), BUF_WAIT);
if (error)
{
if (DEBUG) panic("msdosfs_symlink: buf_bwrite returned %d\n", error);
goto exit;
}
bzero(&ndirent, sizeof(ndirent));
error = uniqdosname(dep, cnp, ndirent.de_Name, &ndirent.de_LowerCase, context);
if (error)
{
if (DEBUG) panic("msdosfs_symlink: uniqdosname returned %d\n", error);
goto exit;
}
ndirent.de_Attributes = ATTR_ARCHIVE;
if (VATTR_IS_ACTIVE(vap, va_flags))
{
if ((vap->va_flags & (SF_IMMUTABLE | UF_IMMUTABLE)) != 0)
ndirent.de_Attributes |= ATTR_READONLY;
VATTR_SET_SUPPORTED(vap, va_flags);
}
ndirent.de_StartCluster = cn;
ndirent.de_FileSize = sizeof(*link);
ndirent.de_dev = dep->de_dev;
ndirent.de_devvp = dep->de_devvp;
ndirent.de_pmp = dep->de_pmp;
ndirent.de_flag = DE_ACCESS | DE_CREATE | DE_UPDATE;
getnanotime(&ts);
DETIMES(&ndirent, &ts, &ts, &ts);
error = createde(&ndirent, dep, &new_dep, cnp, offset, long_count, context);
if (error)
{
if (DEBUG) panic("msdosfs_symlink: createde returned %d\n", error);
goto exit;
}
*ap->a_vpp = DETOV(new_dep);
cache_purge_negatives(dvp);
exit:
if (bp)
{
buf_markinvalid(bp);
buf_brelse(bp);
}
if (error != 0 && cn != 0)
(void) freeclusterchain(pmp, cn);
msdosfs_meta_flush(pmp, FALSE);
lck_mtx_unlock(dep->de_lock);
return error;
}
static int msdosfs_readlink(struct vnop_readlink_args *ap)
{
int error;
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
struct msdosfsmount *pmp = dep->de_pmp;
buf_t bp = NULL;
struct symlink *link;
if (vnode_vtype(vp) != VLNK)
return EINVAL;
lck_mtx_lock(dep->de_lock);
if (dep->de_refcnt <= 0)
{
cache_purge(vp);
error = EAGAIN;
goto exit;
}
if (dep->de_StartCluster == 0)
panic("msdosfs_readlink: de_StartCluster == 0!\n");
error = buf_meta_bread(vp, 0, roundup(sizeof(*link),pmp->pm_bpcluster),
vfs_context_ucred(ap->a_context), &bp);
if (error) goto exit;
link = (struct symlink *) buf_dataptr(bp);
error = uiomove(link->link, dep->de_FileSize, ap->a_uio);
exit:
if (bp)
buf_brelse(bp);
lck_mtx_unlock(dep->de_lock);
return error;
}
static int msdosfs_ioctl(struct vnop_ioctl_args *ap)
{
int error;
vnode_t vp = ap->a_vp;
switch(ap->a_command) {
case F_FULLFSYNC:
{
struct vnop_fsync_args fsync_args;
bzero(&fsync_args, sizeof(fsync_args));
fsync_args.a_vp = ap->a_vp;
fsync_args.a_waitfor = MNT_WAIT;
fsync_args.a_context = ap->a_context;
error = msdosfs_fsync(&fsync_args);
if (error) {
goto exit;
}
error = VNOP_IOCTL(VTODE(vp)->de_pmp->pm_devvp, DKIOCSYNCHRONIZECACHE,
NULL, FWRITE, ap->a_context);
if (error) {
goto exit;
}
break;
}
default:
{
error = ENOTTY;
goto exit;
}
}
exit:
return error;
}
typedef int vnop_t __P((void *));
int (**msdosfs_vnodeop_p)(void *);
static struct vnodeopv_entry_desc msdosfs_vnodeop_entries[] = {
{ &vnop_default_desc, (vnop_t *) vn_default_error },
{ &vnop_lookup_desc, (vnop_t *) msdosfs_lookup },
{ &vnop_create_desc, (vnop_t *) msdosfs_create },
{ &vnop_mknod_desc, (vnop_t *) msdosfs_mknod },
{ &vnop_open_desc, (vnop_t *) msdosfs_open },
{ &vnop_close_desc, (vnop_t *) msdosfs_close },
{ &vnop_getattr_desc, (vnop_t *) msdosfs_getattr },
{ &vnop_setattr_desc, (vnop_t *) msdosfs_setattr },
{ &vnop_read_desc, (vnop_t *) msdosfs_read },
{ &vnop_write_desc, (vnop_t *) msdosfs_write },
{ &vnop_fsync_desc, (vnop_t *) msdosfs_fsync },
{ &vnop_remove_desc, (vnop_t *) msdosfs_remove },
{ &vnop_rename_desc, (vnop_t *) msdosfs_rename },
{ &vnop_mkdir_desc, (vnop_t *) msdosfs_mkdir },
{ &vnop_rmdir_desc, (vnop_t *) msdosfs_rmdir },
{ &vnop_readdir_desc, (vnop_t *) msdosfs_readdir },
{ &vnop_inactive_desc, (vnop_t *) msdosfs_inactive },
{ &vnop_reclaim_desc, (vnop_t *) msdosfs_reclaim },
{ &vnop_pathconf_desc, (vnop_t *) msdosfs_pathconf },
{ &vnop_pagein_desc, (vnop_t *) msdosfs_pagein },
{ &vnop_pageout_desc, (vnop_t *) msdosfs_pageout },
{ &vnop_blktooff_desc, (vnop_t *) msdosfs_blktooff },
{ &vnop_offtoblk_desc, (vnop_t *) msdosfs_offtoblk },
{ &vnop_blockmap_desc, (vnop_t *) msdosfs_blockmap },
{ &vnop_strategy_desc, (vnop_t *) msdosfs_strategy },
{ &vnop_symlink_desc, (vnop_t *) msdosfs_symlink },
{ &vnop_readlink_desc, (vnop_t *) msdosfs_readlink },
{ &vnop_ioctl_desc, (vnop_t *) msdosfs_ioctl},
{ &vnop_bwrite_desc, (vnop_t *) vn_bwrite },
{ NULL, NULL }
};
extern int (**msdosfs_fat_vnodeop_p)(void *);
extern struct vnodeopv_entry_desc msdosfs_fat_vnodeop_entries[];
struct vnodeopv_desc msdosfs_vnodeop_opv_desc =
{ &msdosfs_vnodeop_p, msdosfs_vnodeop_entries };