#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"
#include "msdosfs_kdebug.h"
#ifndef DEBUG
#define DEBUG 0
#endif
#define DOS_FILESIZE_MAX 0xffffffff
union msdosfs_dirbuf {
struct dirent dirent;
struct direntry direntry;
};
int msdosfs_vnop_create(struct vnop_create_args *);
int msdosfs_vnop_mknod(struct vnop_mknod_args *);
int msdosfs_vnop_open(struct vnop_open_args *ap);
int msdosfs_vnop_close(struct vnop_close_args *);
int msdosfs_vnop_getattr(struct vnop_getattr_args *);
int msdosfs_vnop_setattr(struct vnop_setattr_args *);
int msdosfs_vnop_read(struct vnop_read_args *);
int msdosfs_vnop_write(struct vnop_write_args *);
int msdosfs_vnop_pagein(struct vnop_pagein_args *);
int msdosfs_vnop_fsync(struct vnop_fsync_args *);
int msdosfs_vnop_remove(struct vnop_remove_args *);
int msdosfs_vnop_rename(struct vnop_rename_args *);
int msdosfs_vnop_mkdir(struct vnop_mkdir_args *);
int msdosfs_vnop_rmdir(struct vnop_rmdir_args *);
int msdosfs_vnop_readdir(struct vnop_readdir_args *);
int msdosfs_vnop_strategy(struct vnop_strategy_args *);
int msdosfs_vnop_pathconf(struct vnop_pathconf_args *ap);
int msdosfs_vnop_symlink(struct vnop_symlink_args *ap);
int msdosfs_vnop_readlink(struct vnop_readlink_args *ap);
int msdosfs_vnop_ioctl(struct vnop_ioctl_args *ap);
int msdosfs_vnop_pageout(struct vnop_pageout_args *ap);
void msdosfs_lock_two(struct denode *dep1, struct denode *dep2);
void msdosfs_sort_denodes(struct denode *deps[4]);
void msdosfs_lock_four(struct denode *dep0, struct denode *dep1, struct denode *dep2, struct denode *dep3);
void msdosfs_unlock_four(struct denode *dep0, struct denode *dep1, struct denode *dep2, struct denode *dep3);
void msdosfs_md5_digest(void *text, unsigned length, char digest[33]);
ssize_t msdosfs_dirbuf_size(union msdosfs_dirbuf *buf, size_t name_length, int flags);
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);
}
}
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;
}
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);
}
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);
}
int msdosfs_vnop_create(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 = NULL;
struct timespec ts;
int error;
uint32_t offset = 0;
uint32_t long_count = 0;
int needs_generation;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_CREATE|DBG_FUNC_START, pdep->de_pmp, pdep, 0, 0, 0);
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;
}
bzero(&ndirent, sizeof(ndirent));
error = msdosfs_findslots(pdep, cnp, ndirent.de_Name, &needs_generation, &ndirent.de_LowerCase, &offset, &long_count, context);
if (error)
goto exit;
if (pdep->de_StartCluster == MSDOSFSROOT && offset >= pdep->de_FileSize)
{
printf("msdosfs_vnop_create: Cannot grow the root directory on FAT12 or FAT16; returning ENOSPC.\n");
error = ENOSPC;
goto exit;
}
error = msdosfs_uniqdosname(pdep, ndirent.de_Name, needs_generation ? offset - long_count * sizeof(struct dosdirentry) : 1, context);
if (error)
{
if (DEBUG) panic("msdosfs_vnop_create: msdosfs_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 = msdosfs_createde(&ndirent, pdep, &dep, cnp, offset, long_count, context);
if (error)
{
if (DEBUG && error != ENOSPC) panic("msdosfs_vnop_create: msdosfs_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);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_CREATE|DBG_FUNC_END, error, dep, offset, long_count, 0);
return error;
}
int msdosfs_vnop_mknod(struct vnop_mknod_args *ap)
{
#pragma unused (ap)
return EINVAL;
}
int msdosfs_vnop_open(struct vnop_open_args *ap)
{
#pragma unused (ap)
return 0;
}
int msdosfs_vnop_close(struct vnop_close_args *ap)
{
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_CLOSE|DBG_FUNC_START, dep, 0, 0, 0, 0);
lck_mtx_lock(dep->de_lock);
cluster_push(vp, IO_CLOSE);
msdosfs_deupdat(dep, 0, ap->a_context);
msdosfs_meta_flush(dep->de_pmp, FALSE);
lck_mtx_unlock(dep->de_lock);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_CLOSE|DBG_FUNC_END, 0, 0, 0, 0, 0);
return 0;
}
int msdosfs_vnop_getattr(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;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_GETATTR|DBG_FUNC_START, dep, vap->va_active, 0, 0, 0);
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);
msdosfs_dos2unixtime(dep->de_CDate, dep->de_CTime, 0, &vap->va_create_time);
msdosfs_dos2unixtime(dep->de_ADate, 0, 0, &vap->va_access_time);
msdosfs_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, msdosfs_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);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_GETATTR|DBG_FUNC_END, 0, vap->va_supported, 0, 0, 0);
return 0;
}
int msdosfs_vnop_setattr(struct vnop_setattr_args *ap)
{
struct denode *dep = VTODE(ap->a_vp);
struct vnode_attr *vap = ap->a_vap;
int error = 0;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_SETATTR|DBG_FUNC_START, dep, vap->va_active, vap->va_data_size, vap->va_flags, 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 = msdosfs_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)) {
msdosfs_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)) {
msdosfs_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)) {
msdosfs_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 = msdosfs_deupdat(dep, 1, ap->a_context);
msdosfs_meta_flush(dep->de_pmp, FALSE);
exit:
lck_mtx_unlock(dep->de_lock);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_SETATTR|DBG_FUNC_END, error, vap->va_supported, 0, 0, 0);
return error;
}
int msdosfs_vnop_read(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);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_READ|DBG_FUNC_START, dep, uio_offset(uio), uio_resid(uio), dep->de_FileSize, 0);
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 = msdosfs_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);
}
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_READ|DBG_FUNC_END, error, uio_offset(uio), uio_resid(uio), dep->de_FileSize, 0);
lck_mtx_unlock(dep->de_lock);
return error;
}
int msdosfs_vnop_write(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_vnop_write: bad file type");
return EINVAL;
}
lck_mtx_lock(dep->de_lock);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_WRITE|DBG_FUNC_START, dep, uio_offset(uio), uio_resid(uio), dep->de_FileSize, 0);
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;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_WRITE, error, count, pmp->pm_freeclustercount, 0, 0);
}
else
{
error = msdosfs_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) {
msdosfs_detrunc(dep, original_size, ioflag, context);
uio_setoffset(uio, original_offset);
uio_setresid(uio, original_resid);
} else {
msdosfs_detrunc(dep, dep->de_FileSize, ioflag, context);
if (uio_resid(uio) != original_resid)
error = 0;
}
} else if (ioflag & IO_SYNC)
error = msdosfs_deupdat(dep, 1, context);
msdosfs_meta_flush(pmp, (ioflag & IO_SYNC));
exit:
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_WRITE|DBG_FUNC_END, error, uio_offset(uio), uio_resid(uio), dep->de_FileSize, 0);
lck_mtx_unlock(dep->de_lock);
return error;
}
int msdosfs_vnop_pagein(struct vnop_pagein_args *ap)
{
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
int error;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_PAGEIN|DBG_FUNC_START, dep, ap->a_f_offset, ap->a_size, dep->de_FileSize, 0);
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);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_PAGEIN|DBG_FUNC_END, error, 0, 0, 0, 0);
return error;
}
int msdosfs_vnop_pageout(struct vnop_pageout_args *ap)
{
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
int error;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_PAGEOUT|DBG_FUNC_START, dep, ap->a_f_offset, ap->a_size, dep->de_FileSize, 0);
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;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_PAGEOUT|DBG_FUNC_END, error, 0, 0, 0, 0);
return error;
}
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 = msdosfs_deupdat(dep, sync, context);
return error;
}
int msdosfs_vnop_fsync(struct vnop_fsync_args *ap)
{
int error;
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
if (dep == NULL)
return 0;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_FSYNC|DBG_FUNC_START, dep, 0, 0, 0, 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);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_FSYNC|DBG_FUNC_END, error, 0, 0, 0, 0);
return error;
}
int msdosfs_vnop_remove(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;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_REMOVE|DBG_FUNC_START, ddep, dep, 0, 0, 0);
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_vnop_remove: de_refcnt went negative");
error = msdosfs_removede(ddep, dep->de_diroffset, ap->a_context);
if (DEBUG && error) panic("msdosfs_vnop_remove: msdosfs_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);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_REMOVE|DBG_FUNC_END, error, 0, 0, 0, 0);
return error;
}
int msdosfs_vnop_rename(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;
int needs_generation;
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;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_RENAME|DBG_FUNC_START, fddep, fdep, tddep, tdep, 0);
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 = msdosfs_findslots(tddep, tcnp, toname, &needs_generation, &new_deLowerCase, &to_diroffset, &to_long_count, context);
if (error)
goto exit;
if (tddep->de_StartCluster == MSDOSFSROOT && to_diroffset >= tddep->de_FileSize)
{
printf("msdosfs_vnop_rename: Cannot grow the root directory on FAT12 or FAT16; returning ENOSPC.\n");
error = ENOSPC;
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 = msdosfs_doscheckpath(fdep, tddep, context);
if (error) goto exit;
}
from_offset = fdep->de_diroffset;
if (tvp != NULL && tdep != NULL) {
uint32_t dest_offset;
if (tdep->de_Attributes & ATTR_DIRECTORY) {
if (!msdosfs_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_vnop_rename: de_refcnt went negative");
error = msdosfs_removede(tddep, dest_offset, context);
if (error)
{
if (DEBUG) panic("msdosfs_vnop_rename: msdosfs_removede (destination) returned %d\n", error);
goto flush_exit;
}
}
if (change_case)
{
bcopy(fdep->de_Name, toname, SHORT_NAME_LEN);
}
else
{
error = msdosfs_uniqdosname(tddep, toname, needs_generation ? to_diroffset - to_long_count * sizeof(struct dosdirentry) : 1, context);
if (error)
{
if (DEBUG) panic("msdosfs_vnop_rename: msdosfs_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 = msdosfs_createde(fdep, tddep, NULL, tcnp, to_diroffset, to_long_count, context);
if (error)
{
if (DEBUG) panic("msdosfs_vnop_rename: msdosfs_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 = msdosfs_removede(fddep, from_offset, context);
if (error) {
if (DEBUG) panic("msdosfs_vnop_rename: msdosfs_removede (source) returned %d\n", error);
goto flush_exit;
}
error = msdosfs_pcbmap(tddep, de_cluster(pmp, to_diroffset), 1,
NULL, &fdep->de_dirclust, NULL);
if (error) {
if (DEBUG) panic("msdosfs_vnop_rename: msdosfs_pcbmap returned %d\n", error);
goto flush_exit;
}
fdep->de_diroffset = to_diroffset;
msdosfs_hash_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_vnop_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_vnop_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);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_RENAME|DBG_FUNC_END, error, 0, 0, 0, 0);
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 }
}
};
int msdosfs_vnop_mkdir(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;
int needs_generation;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_MKDIR|DBG_FUNC_START, pdep, 0, 0, 0, 0);
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;
}
bzero(&ndirent, sizeof(ndirent));
error = msdosfs_findslots(pdep, cnp, ndirent.de_Name, &needs_generation, &ndirent.de_LowerCase, &offset, &long_count, context);
if (error)
goto exit;
if (pdep->de_StartCluster == MSDOSFSROOT && offset >= pdep->de_FileSize)
{
printf("msdosfs_vnop_mkdir: Cannot grow the root directory on FAT12 or FAT16; returning ENOSPC.\n");
error = ENOSPC;
goto exit;
}
error = msdosfs_clusteralloc(pmp, 0, 1, CLUST_EOFE, &newcluster, NULL);
if (error)
goto exit;
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 = msdosfs_uniqdosname(pdep, ndirent.de_Name, needs_generation ? offset - long_count * sizeof(struct dosdirentry) : 1, 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 = msdosfs_createde(&ndirent, pdep, &dep, cnp, offset, long_count, context);
if (error)
{
if (DEBUG)
panic("msodsfs_mkdir: msdosfs_createde failed\n");
msdosfs_freeclusterchain(pmp, newcluster);
}
else
{
*ap->a_vpp = DETOV(dep);
cache_purge_negatives(dvp);
}
exit:
msdosfs_meta_flush(pmp, FALSE);
lck_mtx_unlock(pdep->de_lock);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_MKDIR|DBG_FUNC_END, error, 0, 0, 0, 0);
return error;
}
int msdosfs_vnop_rmdir(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);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_RMDIR|DBG_FUNC_START, dp, ip, 0, 0, 0);
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;
}
if (!msdosfs_dosdirempty(ip, context)) {
error = ENOTEMPTY;
goto exit;
}
ip->de_refcnt--;
if (DEBUG && ip->de_refcnt < 0)
panic("msdosfs_vnop_rmdir: de_refcnt went negative");
error = msdosfs_removede(dp, ip->de_diroffset, context);
if (error)
{
if (DEBUG) panic("msdosfs_vnop_rmdir: msdosfs_removede returned %d\n", error);
goto flush_exit;
}
error = msdosfs_dir_invalidate(ip);
if (error)
{
if (DEBUG) panic("msdosfs_vnop_rmdir: msdosfs_dir_invalidate returned %d\n", error);
goto flush_exit;
}
error = msdosfs_detrunc(ip, 0, 0, context);
if (DEBUG && error) panic("msdosfs_vnop_rmdir: msdosfs_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);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_RMDIR|DBG_FUNC_END, error, 0, 0, 0, 0);
return error;
}
ssize_t msdosfs_dirbuf_size(union msdosfs_dirbuf *buf, size_t name_length, int flags)
{
if (flags & VNODE_READDIR_EXTENDED)
{
buf->direntry.d_namlen = name_length;
buf->direntry.d_reclen = (offsetof(struct direntry, d_name) + name_length + 4) & ~3;
return buf->direntry.d_reclen;
}
else
{
buf->dirent.d_namlen = name_length;
buf->dirent.d_reclen = (offsetof(struct dirent, d_name) + name_length + 4) & ~3;
return buf->dirent.d_reclen;
}
}
int msdosfs_vnop_readdir(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;
uint32_t blsize;
int32_t entry_offset;
uint32_t cn;
uint32_t fileno;
int32_t bias = 0;
daddr64_t bn, dir_cluster;
struct buf *bp;
struct dosdirentry *dentp;
off_t offset;
off_t long_name_offset;
int chksum = -1;
u_int16_t ucfn[WIN_MAXLEN + 1];
u_int16_t unichars = 0;
size_t outbytes;
char *bdata;
union msdosfs_dirbuf buf;
char *buf_name;
size_t max_name;
ssize_t buf_reclen;
uint8_t buf_type;
int eofflag = 0;
int numdirent = 0;
if (ap->a_numdirent)
*ap->a_numdirent = 0;
if (ap->a_eofflag)
*ap->a_eofflag = 0;
if (ap->a_flags & VNODE_READDIR_REQSEEKOFF)
return EINVAL;
if (ap->a_flags & VNODE_READDIR_EXTENDED)
{
buf_name = &buf.direntry.d_name[0];
max_name = sizeof(buf.direntry.d_name);
buf.direntry.d_seekoff = 0;
}
else
{
buf_name = &buf.dirent.d_name[0];
max_name = sizeof(buf.dirent.d_name);
}
if (!vnode_isdir(vp))
return ENOTDIR;
long_name_offset = offset = uio_offset(uio);
if (offset & (sizeof(struct dosdirentry) - 1))
return EINVAL;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_READDIR|DBG_FUNC_START, dep, uio_offset(uio), uio_resid(uio), ap->a_flags, 0);
lck_mtx_lock(dep->de_lock);
if (dep->de_StartCluster == MSDOSFSROOT
|| (FAT32(pmp) && dep->de_StartCluster == pmp->pm_rootdirblk)) {
bias = 2 * sizeof(struct dosdirentry);
while (offset < bias) {
if (ap->a_flags & VNODE_READDIR_EXTENDED) {
buf.direntry.d_fileno = msdosfs_defileid(dep);
buf.direntry.d_type = DT_DIR;
} else {
buf.dirent.d_fileno = msdosfs_defileid(dep);
buf.dirent.d_type = DT_DIR;
}
if (offset == 0) {
strlcpy(buf_name, ".", max_name);
outbytes = 1;
} else {
strlcpy(buf_name, "..", max_name);
outbytes = 2;
}
buf_reclen = msdosfs_dirbuf_size(&buf, outbytes, ap->a_flags);
if (uio_resid(uio) < buf_reclen)
goto out;
error = uiomove((caddr_t) &buf, buf_reclen, uio);
if (error)
goto out;
++numdirent;
offset += sizeof(struct dosdirentry);
}
}
while (uio_resid(uio) > 0) {
dir_cluster = de_cluster(pmp, offset - bias);
entry_offset = (offset - bias) & pmp->pm_crbomask;
if (dep->de_FileSize <= (offset - bias)) {
eofflag = 1;
break;
}
error = msdosfs_pcbmap(dep, dir_cluster, 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;
}
bdata = (char *)buf_dataptr(bp);
blsize -= buf_resid(bp);
for (dentp = (struct dosdirentry *)(bdata + entry_offset);
(char *)dentp < bdata + blsize;
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_MASK) == ATTR_WIN95) {
if (dentp->deName[0] & WIN_LAST)
long_name_offset = offset;
chksum = msdosfs_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;
}
buf_type = DT_DIR;
} else {
if (fileno == 0)
fileno = FILENO_EMPTY;
if (getuint32(dentp->deFileSize) == sizeof(struct symlink))
buf_type = DT_UNKNOWN;
else
buf_type = DT_REG;
}
if (chksum != msdosfs_winChksum(dentp->deName)) {
chksum = -1;
unichars = msdosfs_dos2unicodefn(dentp->deName, ucfn,
dentp->deLowerCase);
}
(void) utf8_encodestr(ucfn, unichars * 2, (u_int8_t*)buf_name,
&outbytes, max_name, 0,
UTF_DECOMPOSED|UTF_SFM_CONVERSIONS);
if (ap->a_flags & VNODE_READDIR_EXTENDED)
{
buf.direntry.d_ino = fileno;
buf.direntry.d_type = buf_type;
}
else
{
buf.dirent.d_ino = fileno;
buf.dirent.d_type = buf_type;
}
buf_reclen = msdosfs_dirbuf_size(&buf, outbytes, ap->a_flags);
if (uio_resid(uio) < buf_reclen) {
buf_brelse(bp);
goto out;
}
error = uiomove((caddr_t) &buf, buf_reclen, uio);
if (error) {
buf_brelse(bp);
goto out;
}
chksum = -1;
++numdirent;
}
buf_brelse(bp);
}
out:
if (chksum != -1)
offset = long_name_offset;
uio_setoffset(uio, offset);
exit:
lck_mtx_unlock(dep->de_lock);
if (ap->a_eofflag)
*ap->a_eofflag = eofflag;
if (ap->a_numdirent)
*ap->a_numdirent = numdirent;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_READDIR|DBG_FUNC_END, error, uio_offset(uio), numdirent, eofflag, 0);
return error;
}
int msdosfs_vnop_blktooff(struct vnop_blktooff_args *ap)
{
if (ap->a_vp == NULL)
return EINVAL;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_BLKTOOFF|DBG_FUNC_START, ap->a_lblkno, 0, 0, 0, 0);
*ap->a_offset = ap->a_lblkno * PAGE_SIZE_64;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_BLKTOOFF|DBG_FUNC_END, 0, *ap->a_offset, 0, 0, 0);
return 0;
}
int msdosfs_vnop_offtoblk(struct vnop_offtoblk_args *ap)
{
if (ap->a_vp == NULL)
return EINVAL;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_OFFTOBLK|DBG_FUNC_START, ap->a_offset, 0, 0, 0, 0);
*ap->a_lblkno = ap->a_offset / PAGE_SIZE_64;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_OFFTOBLK|DBG_FUNC_END, 0, *ap->a_lblkno, 0, 0, 0);
return 0;
}
int msdosfs_vnop_blockmap(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;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_BLOCKMAP|DBG_FUNC_START, dep, ap->a_foffset, ap->a_size, 0, 0);
if (ap->a_size == 0)
panic("msdosfs_vnop_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 = msdosfs_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;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_BLOCKMAP|DBG_FUNC_END, error, bn, runsize, 0, 0);
return error;
}
int msdosfs_vnop_strategy(struct vnop_strategy_args *ap)
{
buf_t bp = ap->a_bp;
vnode_t vp = buf_vnode(bp);
struct denode *dep = VTODE(vp);
int error;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_STRATEGY|DBG_FUNC_START, dep, buf_lblkno(bp), buf_resid(bp), buf_flags(bp), 0);
error = buf_strategy(dep->de_devvp, ap);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_STRATEGY|DBG_FUNC_END, error, buf_error(bp), buf_resid(bp), buf_flags(bp), 0);
return error;
}
int msdosfs_vnop_pathconf(struct vnop_pathconf_args *ap)
{
int error = 0;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_PATHCONF|DBG_FUNC_START, ap->a_name, 0, 0, 0, 0);
switch (ap->a_name) {
case _PC_LINK_MAX:
*ap->a_retval = 1;
break;
case _PC_NAME_MAX:
*ap->a_retval = WIN_MAXLEN;
break;
case _PC_PATH_MAX:
*ap->a_retval = PATH_MAX;
break;
case _PC_CHOWN_RESTRICTED:
*ap->a_retval = 1;
break;
case _PC_NO_TRUNC:
*ap->a_retval = 0;
break;
case _PC_CASE_SENSITIVE:
*ap->a_retval = 0;
break;
case _PC_CASE_PRESERVING:
*ap->a_retval = 1;
break;
case _PC_FILESIZEBITS:
*ap->a_retval = 32;
break;
default:
error = EINVAL;
break;
}
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_PATHCONF|DBG_FUNC_END, error, *ap->a_retval, 0, 0, 0);
return error;
}
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;
}
}
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;
}
int msdosfs_vnop_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;
int needs_generation;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_SYMLINK|DBG_FUNC_START, dep, 0, 0, 0, 0);
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;
}
bzero(&ndirent, sizeof(ndirent));
error = msdosfs_findslots(dep, cnp, ndirent.de_Name, &needs_generation, &ndirent.de_LowerCase, &offset, &long_count, context);
if (error)
goto exit;
if (dep->de_StartCluster == MSDOSFSROOT && offset >= dep->de_FileSize)
{
printf("msdosfs_vnop_symlink: Cannot grow the root directory on FAT12 or FAT16; returning ENOSPC.\n");
error = ENOSPC;
goto exit;
}
length = strlen(target);
if (length > SYMLINK_LINK_MAX)
{
error = ENAMETOOLONG;
goto exit;
}
clusters = de_clcount(pmp, sizeof(*link));
error = msdosfs_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_vnop_symlink: buf_bwrite returned %d\n", error);
goto exit;
}
error = msdosfs_uniqdosname(dep, ndirent.de_Name, needs_generation ? offset - long_count * sizeof(struct dosdirentry) : 1, context);
if (error)
{
if (DEBUG) panic("msdosfs_vnop_symlink: msdosfs_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 = msdosfs_createde(&ndirent, dep, &new_dep, cnp, offset, long_count, context);
if (error)
{
if (DEBUG) panic("msdosfs_vnop_symlink: msdosfs_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) msdosfs_freeclusterchain(pmp, cn);
msdosfs_meta_flush(pmp, FALSE);
lck_mtx_unlock(dep->de_lock);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_SYMLINK|DBG_FUNC_END, error, 0, 0, 0, 0);
return error;
}
int msdosfs_vnop_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;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_READLINK|DBG_FUNC_START, dep, 0, 0, 0, 0);
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_vnop_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);
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_READLINK|DBG_FUNC_END, error, 0, 0, 0, 0);
return error;
}
int msdosfs_vnop_ioctl(struct vnop_ioctl_args *ap)
{
int error;
vnode_t vp = ap->a_vp;
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_IOCTL|DBG_FUNC_START, VTODE(vp), ap->a_command, 0, 0, 0);
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_vnop_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:
KERNEL_DEBUG_CONSTANT(MSDOSFS_VNOP_IOCTL|DBG_FUNC_START, error, 0, 0, 0, 0);
return error;
}
typedef int vnop_t(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_vnop_lookup },
{ &vnop_create_desc, (vnop_t *) msdosfs_vnop_create },
{ &vnop_mknod_desc, (vnop_t *) msdosfs_vnop_mknod },
{ &vnop_open_desc, (vnop_t *) msdosfs_vnop_open },
{ &vnop_close_desc, (vnop_t *) msdosfs_vnop_close },
{ &vnop_getattr_desc, (vnop_t *) msdosfs_vnop_getattr },
{ &vnop_setattr_desc, (vnop_t *) msdosfs_vnop_setattr },
{ &vnop_read_desc, (vnop_t *) msdosfs_vnop_read },
{ &vnop_write_desc, (vnop_t *) msdosfs_vnop_write },
{ &vnop_fsync_desc, (vnop_t *) msdosfs_vnop_fsync },
{ &vnop_remove_desc, (vnop_t *) msdosfs_vnop_remove },
{ &vnop_rename_desc, (vnop_t *) msdosfs_vnop_rename },
{ &vnop_mkdir_desc, (vnop_t *) msdosfs_vnop_mkdir },
{ &vnop_rmdir_desc, (vnop_t *) msdosfs_vnop_rmdir },
{ &vnop_readdir_desc, (vnop_t *) msdosfs_vnop_readdir },
{ &vnop_inactive_desc, (vnop_t *) msdosfs_vnop_inactive },
{ &vnop_reclaim_desc, (vnop_t *) msdosfs_vnop_reclaim },
{ &vnop_pathconf_desc, (vnop_t *) msdosfs_vnop_pathconf },
{ &vnop_pagein_desc, (vnop_t *) msdosfs_vnop_pagein },
{ &vnop_pageout_desc, (vnop_t *) msdosfs_vnop_pageout },
{ &vnop_blktooff_desc, (vnop_t *) msdosfs_vnop_blktooff },
{ &vnop_offtoblk_desc, (vnop_t *) msdosfs_vnop_offtoblk },
{ &vnop_blockmap_desc, (vnop_t *) msdosfs_vnop_blockmap },
{ &vnop_strategy_desc, (vnop_t *) msdosfs_vnop_strategy },
{ &vnop_symlink_desc, (vnop_t *) msdosfs_vnop_symlink },
{ &vnop_readlink_desc, (vnop_t *) msdosfs_vnop_readlink },
{ &vnop_ioctl_desc, (vnop_t *) msdosfs_vnop_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 };