#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <sys/vnode.h>
#include <sys/mount.h>
#include <sys/buf.h>
#include <sys/fcntl.h>
#include <sys/malloc.h>
#include <sys/stat.h>
#include <sys/ubc.h>
#include <sys/utfconv.h>
#include <sys/disk.h>
#include <sys/sysctl.h>
#include <mach/kmod.h>
#include <libkern/OSBase.h>
#include <libkern/OSAtomic.h>
#include <kern/clock.h>
#include <kern/thread.h>
#include <kern/thread_call.h>
#include <miscfs/specfs/specdev.h>
#include <IOKit/IOTypes.h>
#include <libkern/OSMalloc.h>
#include <libkern/OSKextLib.h>
#include <TargetConditionals.h>
#include "bpb.h"
#include "bootsect.h"
#include "direntry.h"
#include "denode.h"
#include "msdosfsmount.h"
#include "fat.h"
#ifndef MSDOSFS_AUTO_UNLOAD
#if TARGET_OS_EMBEDDED
#define MSDOSFS_AUTO_UNLOAD 0
#else
#define MSDOSFS_AUTO_UNLOAD 1
#endif
#endif
#define MSDOSFS_DFLTBSIZE 4096
#define rounddown(x,y) (((x)/(y))*(y))
extern u_int16_t dos2unicode[32];
extern int32_t msdos_secondsWest;
lck_grp_attr_t *msdosfs_lck_grp_attr = NULL;
lck_grp_t *msdosfs_lck_grp = NULL;
lck_attr_t *msdosfs_lck_attr = NULL;
OSMallocTag msdosfs_malloc_tag = NULL;
#if DEBUG
SYSCTL_DECL(_vfs_generic);
SYSCTL_NODE(_vfs_generic, OID_AUTO, msdosfs, CTLFLAG_RW, 0, "msdosfs (FAT) file system");
SYSCTL_INT(_vfs_generic_msdosfs, OID_AUTO, meta_delay, CTLFLAG_RW, &msdosfs_meta_delay, 0, "max delay before flushing metadata (ms)");
#endif
static int update_mp __P((struct mount *mp, struct msdosfs_args *argp));
static int mountmsdosfs __P((vnode_t devvp, struct mount *mp, vfs_context_t context));
static int msdosfs_mount __P((struct mount *mp, vnode_t devvp, user_addr_t data, vfs_context_t));
static int msdosfs_root __P((struct mount *, vnode_t *, vfs_context_t));
static int msdosfs_statfs __P((struct mount *, struct vfsstatfs *, vfs_context_t));
static int msdosfs_vfs_getattr(mount_t mp, struct vfs_attr *attr, vfs_context_t context);
static int msdosfs_vfs_setattr(mount_t mp, struct vfs_attr *attr, vfs_context_t context);
static int msdosfs_sync __P((struct mount *, int, vfs_context_t));
static int msdosfs_unmount __P((struct mount *, int, vfs_context_t));
static int scan_root_dir(struct mount *mp, vfs_context_t context);
int msdosfs_module_start(kmod_info_t *ki, void *data);
int msdosfs_module_stop (kmod_info_t *ki, void *data);
static int
msdosfs_init(struct vfsconf *vfsp)
{
#pragma unused (vfsp)
msdosfs_lck_grp_attr = lck_grp_attr_alloc_init();
msdosfs_lck_grp = lck_grp_alloc_init("msdosfs", msdosfs_lck_grp_attr);
msdosfs_lck_attr = lck_attr_alloc_init();
msdosfs_malloc_tag = OSMalloc_Tagalloc("msdosfs", OSMT_DEFAULT);
msdosfs_hash_init();
return 0;
}
static int
msdosfs_uninit(void)
{
msdosfs_hash_uninit();
OSMalloc_Tagfree(msdosfs_malloc_tag);
lck_attr_free(msdosfs_lck_attr);
lck_grp_free(msdosfs_lck_grp);
lck_grp_attr_free(msdosfs_lck_grp_attr);
return 0;
}
static int
update_mp(mp, argp)
struct mount *mp;
struct msdosfs_args *argp;
{
struct msdosfsmount *pmp = VFSTOMSDOSFS(mp);
pmp->pm_gid = argp->gid;
pmp->pm_uid = argp->uid;
pmp->pm_mask = argp->mask & ALLPERMS;
pmp->pm_flags |= argp->flags & MSDOSFSMNT_MNTOPT;
if (argp->flags & MSDOSFSMNT_SECONDSWEST)
msdos_secondsWest = argp->secondsWest;
if (argp->flags & MSDOSFSMNT_LABEL)
bcopy(argp->label, pmp->pm_label, sizeof(pmp->pm_label));
return 0;
}
static int
msdosfs_mount(mp, devvp, data, context)
struct mount *mp;
vnode_t devvp;
user_addr_t data;
vfs_context_t context;
{
struct msdosfs_args args;
struct msdosfsmount *pmp = NULL;
int error, flags;
#if MSDOSFS_AUTO_UNLOAD
OSKextRetainKextWithLoadTag(OSKextGetCurrentLoadTag());
#endif
error = copyin(data, &args, sizeof(struct msdosfs_args));
if (error)
goto error_exit;
if (args.magic != MSDOSFS_ARGSMAGIC)
args.flags = 0;
if (vfs_isupdate(mp)) {
pmp = VFSTOMSDOSFS(mp);
error = 0;
if (!(pmp->pm_flags & MSDOSFSMNT_RONLY) && vfs_isrdonly(mp)) {
flags = WRITECLOSE;
if (vfs_isforce(mp))
flags |= FORCECLOSE;
error = vflush(mp, NULLVP, flags);
}
if (!error && vfs_isreload(mp))
error = ENOTSUP;
if (error)
goto error_exit;
if ((pmp->pm_flags & MSDOSFSMNT_RONLY) && vfs_iswriteupgrade(mp)) {
pmp->pm_flags &= ~MSDOSFSMNT_RONLY;
error = markvoldirty(pmp, 1);
if (error) {
pmp->pm_flags |= MSDOSFSMNT_RONLY;
goto error_exit;
}
}
}
if ( !vfs_isupdate(mp)) {
error = mountmsdosfs(devvp, mp, context);
if (error)
goto error_exit;
}
if (error == 0)
error = update_mp(mp, &args);
if (error == 0)
(void) msdosfs_statfs(mp, vfs_statfs(mp), context);
if (error)
msdosfs_unmount(mp, MNT_FORCE, context);
return error;
error_exit:
#if MSDOSFS_AUTO_UNLOAD
OSKextReleaseKextWithLoadTag(OSKextGetCurrentLoadTag());
#endif
return error;
}
static int
mountmsdosfs(devvp, mp, context)
vnode_t devvp;
struct mount *mp;
vfs_context_t context;
{
struct msdosfsmount *pmp;
struct buf *bp;
dev_t dev = vnode_specrdev(devvp);
union bootsector *bsp;
struct byte_bpb33 *b33;
struct byte_bpb50 *b50;
struct byte_bpb710 *b710;
uint32_t fat_sectors;
uint32_t clusters;
uint32_t fsinfo = 0;
int error;
struct vfsstatfs *vfsstatfs;
u_int8_t SecPerClust;
error = buf_invalidateblks(devvp, BUF_WRITE_DATA, 0, 0);
if (error)
return (error);
vfs_setlocklocal(mp);
bp = NULL;
pmp = NULL;
error = (int)buf_meta_bread(devvp, 0, 4096, vfs_context_ucred(context), &bp);
if (error)
goto error_exit;
buf_markaged(bp);
bsp = (union bootsector *)buf_dataptr(bp);
b33 = (struct byte_bpb33 *)bsp->bs33.bsBPB;
b50 = (struct byte_bpb50 *)bsp->bs50.bsBPB;
b710 = (struct byte_bpb710 *)bsp->bs710.bsBPB;
if (bsp->bs50.bsJump[0] != 0xE9
&& (bsp->bs50.bsJump[0] != 0xEB || bsp->bs50.bsJump[2] != 0x90))
{
error = EINVAL;
goto error_exit;
}
MALLOC(pmp, struct msdosfsmount *, sizeof(*pmp), M_TEMP, M_WAITOK);
bzero((caddr_t)pmp, sizeof *pmp);
pmp->pm_mountp = mp;
pmp->pm_fat_lock = lck_mtx_alloc_init(msdosfs_lck_grp, msdosfs_lck_attr);
pmp->pm_rename_lock = lck_mtx_alloc_init(msdosfs_lck_grp, msdosfs_lck_attr);
SecPerClust = b50->bpbSecPerClust;
pmp->pm_BytesPerSec = getuint16(b50->bpbBytesPerSec);
pmp->pm_ResSectors = getuint16(b50->bpbResSectors);
pmp->pm_FATs = b50->bpbFATs;
pmp->pm_RootDirEnts = getuint16(b50->bpbRootDirEnts);
pmp->pm_Sectors = getuint16(b50->bpbSectors);
fat_sectors = getuint16(b50->bpbFATsecs);
pmp->pm_SecPerTrack = getuint16(b50->bpbSecPerTrack);
pmp->pm_Heads = getuint16(b50->bpbHeads);
pmp->pm_Media = b50->bpbMedia;
pmp->pm_label_cluster = CLUST_EOFE;
error = VNOP_IOCTL(devvp, DKIOCGETBLOCKSIZE, (caddr_t) &pmp->pm_BlockSize, 0, context);
if (error) {
error = ENXIO;
goto error_exit;
}
pmp->pm_BlocksPerSec = pmp->pm_BytesPerSec / pmp->pm_BlockSize;
error = VNOP_IOCTL(devvp, DKIOCGETPHYSICALBLOCKSIZE, (caddr_t) &pmp->pm_PhysBlockSize, 0, context);
if (error)
pmp->pm_PhysBlockSize = pmp->pm_BlockSize;
if (pmp->pm_Sectors == 0) {
pmp->pm_HugeSectors = getuint32(b50->bpbHugeSectors);
} else {
pmp->pm_HugeSectors = pmp->pm_Sectors;
}
if (pmp->pm_RootDirEnts == 0) {
if (pmp->pm_Sectors != 0
|| fat_sectors != 0
|| getuint16(b710->bpbFSVers) != 0) {
error = EINVAL;
printf("mountmsdosfs(): bad FAT32 filesystem\n");
goto error_exit;
}
pmp->pm_fatmask = FAT32_MASK;
pmp->pm_fatmult = 4;
pmp->pm_fatdiv = 1;
fat_sectors = getuint32(b710->bpbBigFATsecs);
if (getuint16(b710->bpbExtFlags) & FATMIRROR)
pmp->pm_curfat = getuint16(b710->bpbExtFlags) & FATNUM;
else
pmp->pm_flags |= MSDOSFS_FATMIRROR;
} else
pmp->pm_flags |= MSDOSFS_FATMIRROR;
if ( (SecPerClust == 0)
|| (SecPerClust & (SecPerClust - 1))
|| (pmp->pm_BytesPerSec < DEV_BSIZE)
|| (pmp->pm_BytesPerSec & (pmp->pm_BytesPerSec - 1))
|| (pmp->pm_HugeSectors == 0)
) {
error = EINVAL;
goto error_exit;
}
if (FAT32(pmp)) {
pmp->pm_rootdirblk = getuint32(b710->bpbRootClust);
pmp->pm_firstcluster = pmp->pm_ResSectors
+ (pmp->pm_FATs * fat_sectors);
fsinfo = getuint16(b710->bpbFSInfo);
} else {
pmp->pm_rootdirblk = (pmp->pm_ResSectors + (pmp->pm_FATs * fat_sectors));
pmp->pm_rootdirsize = (pmp->pm_RootDirEnts * sizeof(struct dosdirentry)
+ pmp->pm_BytesPerSec - 1)
/ pmp->pm_BytesPerSec;
pmp->pm_firstcluster = pmp->pm_rootdirblk + pmp->pm_rootdirsize;
pmp->pm_rootdirblk *= pmp->pm_BlocksPerSec;
pmp->pm_rootdirsize *= pmp->pm_BlocksPerSec;
}
pmp->pm_maxcluster = (pmp->pm_HugeSectors - pmp->pm_firstcluster) /
SecPerClust + 1;
if (FAT32(pmp) && (pmp->pm_rootdirblk < CLUST_FIRST ||
pmp->pm_rootdirblk > pmp->pm_maxcluster))
{
printf("mountmsdosfs: root starting cluster (%u) out of range\n",
pmp->pm_rootdirblk);
error = EINVAL;
goto error_exit;
}
pmp->pm_firstcluster *= pmp->pm_BlocksPerSec;
if (pmp->pm_fatmask == 0) {
if ((pmp->pm_maxcluster - 1)
<= ((CLUST_RSRVD - CLUST_FIRST) & FAT12_MASK)) {
pmp->pm_fatmask = FAT12_MASK;
pmp->pm_fatmult = 3;
pmp->pm_fatdiv = 2;
} else {
pmp->pm_fatmask = FAT16_MASK;
pmp->pm_fatmult = 2;
pmp->pm_fatdiv = 1;
}
}
clusters = fat_sectors * pmp->pm_BytesPerSec;
clusters *= pmp->pm_fatdiv;
clusters /= pmp->pm_fatmult;
if (pmp->pm_maxcluster >= clusters) {
printf("Warning: number of clusters (%d) exceeds FAT "
"capacity (%d)\n", pmp->pm_maxcluster + 1, clusters);
pmp->pm_maxcluster = clusters - 1;
}
if (FAT12(pmp))
pmp->pm_fatblocksize = 3 * pmp->pm_BytesPerSec;
else
pmp->pm_fatblocksize = PAGE_SIZE;
pmp->pm_fat_bytes = fat_sectors * pmp->pm_BytesPerSec;
pmp->pm_bnshift = ffs(pmp->pm_BlockSize) - 1;
pmp->pm_bpcluster = (u_int32_t) SecPerClust * (u_int32_t) pmp->pm_BytesPerSec;
pmp->pm_crbomask = pmp->pm_bpcluster - 1;
pmp->pm_cnshift = ffs(pmp->pm_bpcluster) - 1;
if (pmp->pm_bpcluster ^ (1 << pmp->pm_cnshift)) {
error = EINVAL;
goto error_exit;
}
{
u_int32_t temp_size;
struct vfsioattr ioattr;
pmp->pm_iosize = ubc_upl_maxbufsize();
vfs_ioattr(mp, &ioattr);
if (ioattr.io_maxreadcnt < pmp->pm_iosize)
pmp->pm_iosize = ioattr.io_maxreadcnt;
if (ioattr.io_maxwritecnt < pmp->pm_iosize)
pmp->pm_iosize = ioattr.io_maxwritecnt;
temp_size = ioattr.io_segreadcnt * ioattr.io_maxsegreadsize;
if (temp_size < pmp->pm_iosize)
pmp->pm_iosize = temp_size;
temp_size = ioattr.io_segwritecnt * ioattr.io_maxsegwritesize;
if (temp_size < pmp->pm_iosize)
pmp->pm_iosize = temp_size;
if (pmp->pm_iosize < pmp->pm_bpcluster)
pmp->pm_iosize = pmp->pm_bpcluster;
}
{
struct extboot *extboot;
int i;
u_char uc;
pmp->pm_label[0] = '\0';
if (FAT32(pmp)) {
extboot = (struct extboot *) bsp->bs710.bsExt;
} else {
extboot = (struct extboot *) bsp->bs50.bsExt;
}
if (extboot->exBootSignature == EXBOOTSIG) {
for (i=0; i<SHORT_NAME_LEN; i++) {
uc = extboot->exVolumeLabel[i];
if (i==0 && uc == SLOT_E5)
uc = 0xE5;
pmp->pm_label[i] = (uc < 0x80 || uc > 0x9F ? uc : dos2unicode[uc - 0x80]);
}
for (i=10; i>=0 && pmp->pm_label[i]==' '; --i)
;
pmp->pm_label[i+1] = '\0';
}
}
buf_brelse(bp);
bp = NULL;
if (fsinfo) {
struct fsinfo *fp;
u_int32_t log_per_phys;
fsinfo *= pmp->pm_BytesPerSec / pmp->pm_BlockSize;
log_per_phys = pmp->pm_PhysBlockSize / pmp->pm_BlockSize;
if ((rounddown(fsinfo,log_per_phys) + log_per_phys) <= pmp->pm_ResSectors) {
pmp->pm_fsinfo_sector = rounddown(fsinfo,log_per_phys);
pmp->pm_fsinfo_size = pmp->pm_PhysBlockSize;
pmp->pm_fsinfo_offset = (fsinfo % log_per_phys) * pmp->pm_BlockSize;
} else {
pmp->pm_fsinfo_sector = fsinfo;
pmp->pm_fsinfo_size = pmp->pm_BlockSize;
pmp->pm_fsinfo_offset = 0;
}
error = buf_meta_bread(devvp, pmp->pm_fsinfo_sector, pmp->pm_fsinfo_size, vfs_context_ucred(context), &bp);
if (error)
goto error_exit;
fp = (struct fsinfo *)(buf_dataptr(bp) + pmp->pm_fsinfo_offset);
if (!bcmp(fp->fsisig1, "RRaA", 4)
&& !bcmp(fp->fsisig2, "rrAa", 4)
&& !bcmp(fp->fsisig3, "\0\0\125\252", 4)) {
pmp->pm_nxtfree = getuint32(fp->fsinxtfree);
} else {
printf("mountmsdosfs: FSInfo has bad signature\n");
pmp->pm_fsinfo_size = 0;
}
buf_brelse(bp);
bp = NULL;
}
if (vfs_issynchronous(mp))
pmp->pm_flags |= MSDOSFSMNT_WAITONFAT;
pmp->pm_dev = dev;
pmp->pm_devvp = devvp;
error = msdosfs_fat_init_vol(pmp);
if (error != 0)
goto error_exit;
pmp->pm_sync_timer = thread_call_allocate(msdosfs_meta_sync_callback, pmp);
if (pmp->pm_sync_timer == NULL)
{
error = ENOMEM;
goto error_exit;
}
vfs_setfsprivate(mp, (void *)pmp);
error = scan_root_dir(mp, context);
if (error)
{
if (error == EIO && vfs_isrdwr(mp))
{
(void) markvoldirty(pmp, 1);
}
goto error_exit;
}
if (vfs_isrdonly(mp))
pmp->pm_flags |= MSDOSFSMNT_RONLY;
else {
if ((error = markvoldirty(pmp, 1)) != 0)
goto error_exit;
}
vfsstatfs = vfs_statfs(mp);
vfsstatfs->f_bsize = pmp->pm_bpcluster;
vfsstatfs->f_iosize = pmp->pm_iosize;
vfsstatfs->f_blocks = pmp->pm_maxcluster - 1;
vfsstatfs->f_fsid.val[0] = dev;
vfsstatfs->f_fsid.val[1] = vfs_typenum(mp);
vfs_setflags(mp, MNT_IGNORE_OWNERSHIP);
return 0;
error_exit:
if (bp)
buf_brelse(bp);
if (pmp) {
if (pmp->pm_sync_timer)
{
thread_call_cancel(pmp->pm_sync_timer);
thread_call_free(pmp->pm_sync_timer);
pmp->pm_sync_timer = NULL;
}
(void) vflush(mp, NULLVP, SKIPSYSTEM|FORCECLOSE);
msdosfs_fat_uninit_vol(pmp);
(void) vflush(mp, NULLVP, FORCECLOSE);
lck_mtx_free(pmp->pm_fat_lock, msdosfs_lck_grp);
lck_mtx_free(pmp->pm_rename_lock, msdosfs_lck_grp);
FREE(pmp, M_TEMP);
vfs_setfsprivate(mp, (void *)NULL);
}
return (error);
}
static int
msdosfs_start(struct mount *mp, int flags, vfs_context_t context)
{
#pragma unused (mp)
#pragma unused (flags)
#pragma unused (context)
return (0);
}
static int
msdosfs_unmount(mp, mntflags, context)
struct mount *mp;
int mntflags;
vfs_context_t context;
{
struct msdosfsmount *pmp;
int error, flags;
int force;
pmp = VFSTOMSDOSFS(mp);
flags = SKIPSYSTEM;
force = 0;
if (mntflags & MNT_FORCE) {
flags |= FORCECLOSE;
force = 1;
}
if (pmp->pm_sync_timer)
{
struct timespec ts = {0, 100000000};
if (thread_call_cancel(pmp->pm_sync_timer))
OSDecrementAtomic(&pmp->pm_sync_incomplete);
thread_call_free(pmp->pm_sync_timer);
pmp->pm_sync_timer = NULL;
while(pmp->pm_sync_incomplete > 0)
{
msleep(&pmp->pm_sync_incomplete, NULL, PWAIT, "msdosfs_unmount", &ts);
}
if (pmp->pm_sync_incomplete < 0)
panic("msdosfs_unmount: pm_sync_incomplete underflow!\n");
}
error = vflush(mp, NULLVP, flags);
if (error && !force)
goto error_exit;
if ((pmp->pm_flags & (MSDOSFSMNT_RONLY | MSDOSFS_CORRUPT)) == 0) {
error = markvoldirty(pmp, 0);
if (error && !force)
goto error_exit;
}
msdosfs_fat_uninit_vol(pmp);
(void) vflush(mp, NULLVP, FORCECLOSE);
VNOP_FSYNC(pmp->pm_devvp, MNT_WAIT, context);
lck_mtx_free(pmp->pm_fat_lock, msdosfs_lck_grp);
lck_mtx_free(pmp->pm_rename_lock, msdosfs_lck_grp);
FREE(pmp, M_TEMP);
vfs_setfsprivate(mp, (void *)NULL);
#if MSDOSFS_AUTO_UNLOAD
OSKextReleaseKextWithLoadTag(OSKextGetCurrentLoadTag());
#endif
error = 0;
error_exit:
return (error);
}
static int
msdosfs_root(mp, vpp, context)
struct mount *mp;
vnode_t *vpp;
vfs_context_t context;
{
struct msdosfsmount *pmp = VFSTOMSDOSFS(mp);
struct denode *ndep;
int error;
error = deget(pmp, MSDOSFSROOT, MSDOSFSROOT_OFS, NULLVP, NULL, &ndep, context);
if (error)
return (error);
*vpp = DETOV(ndep);
return (0);
}
static int
msdosfs_statfs(mp, sbp, context)
struct mount *mp;
struct vfsstatfs *sbp;
vfs_context_t context;
{
#pragma unused (context)
struct msdosfsmount *pmp;
pmp = VFSTOMSDOSFS(mp);
sbp->f_bfree = pmp->pm_freeclustercount;
sbp->f_bavail = pmp->pm_freeclustercount;
sbp->f_bused = sbp->f_blocks - sbp->f_bavail;
sbp->f_files = pmp->pm_RootDirEnts;
sbp->f_ffree = 0;
vfs_name(mp, sbp->f_fstypename);
if (pmp->pm_fatmask == FAT12_MASK) {
sbp->f_fssubtype = 0;
} else if (pmp->pm_fatmask == FAT16_MASK) {
sbp->f_fssubtype = 1;
} else {
sbp->f_fssubtype = 2;
}
return (0);
}
static int
msdosfs_vfs_getattr(mount_t mp, struct vfs_attr *attr, vfs_context_t context)
{
#pragma unused (context)
struct vfsstatfs *stats;
struct msdosfsmount *pmp;
stats = vfs_statfs(mp);
pmp = VFSTOMSDOSFS(mp);
VFSATTR_RETURN(attr, f_bsize, pmp->pm_bpcluster);
VFSATTR_RETURN(attr, f_iosize, pmp->pm_iosize);
VFSATTR_RETURN(attr, f_blocks, pmp->pm_maxcluster - 1);
VFSATTR_RETURN(attr, f_bfree, pmp->pm_freeclustercount);
VFSATTR_RETURN(attr, f_bavail, pmp->pm_freeclustercount);
VFSATTR_RETURN(attr, f_bused, attr->f_blocks - attr->f_bfree);
if (VFSATTR_IS_ACTIVE(attr, f_fsid)) {
attr->f_fsid.val[0] = pmp->pm_dev;
attr->f_fsid.val[1] = vfs_typenum(mp);
VFSATTR_SET_SUPPORTED(attr, f_fsid);
}
if (VFSATTR_IS_ACTIVE(attr, f_capabilities)) {
attr->f_capabilities.capabilities[VOL_CAPABILITIES_FORMAT] =
VOL_CAP_FMT_SYMBOLICLINKS |
VOL_CAP_FMT_NO_ROOT_TIMES |
VOL_CAP_FMT_CASE_PRESERVING |
VOL_CAP_FMT_FAST_STATFS |
VOL_CAP_FMT_HIDDEN_FILES ;
attr->f_capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] =
VOL_CAP_INT_VOL_RENAME |
VOL_CAP_INT_ADVLOCK |
VOL_CAP_INT_FLOCK ;
attr->f_capabilities.capabilities[VOL_CAPABILITIES_RESERVED1] = 0;
attr->f_capabilities.capabilities[VOL_CAPABILITIES_RESERVED2] = 0;
attr->f_capabilities.valid[VOL_CAPABILITIES_FORMAT] =
VOL_CAP_FMT_PERSISTENTOBJECTIDS |
VOL_CAP_FMT_SYMBOLICLINKS |
VOL_CAP_FMT_HARDLINKS |
VOL_CAP_FMT_JOURNAL |
VOL_CAP_FMT_JOURNAL_ACTIVE |
VOL_CAP_FMT_NO_ROOT_TIMES |
VOL_CAP_FMT_SPARSE_FILES |
VOL_CAP_FMT_ZERO_RUNS |
VOL_CAP_FMT_CASE_SENSITIVE |
VOL_CAP_FMT_CASE_PRESERVING |
VOL_CAP_FMT_FAST_STATFS |
VOL_CAP_FMT_2TB_FILESIZE |
VOL_CAP_FMT_OPENDENYMODES |
VOL_CAP_FMT_HIDDEN_FILES ;
attr->f_capabilities.valid[VOL_CAPABILITIES_INTERFACES] =
VOL_CAP_INT_SEARCHFS |
VOL_CAP_INT_ATTRLIST |
VOL_CAP_INT_NFSEXPORT |
VOL_CAP_INT_READDIRATTR |
VOL_CAP_INT_EXCHANGEDATA |
VOL_CAP_INT_COPYFILE |
VOL_CAP_INT_ALLOCATE |
VOL_CAP_INT_VOL_RENAME |
VOL_CAP_INT_ADVLOCK |
VOL_CAP_INT_FLOCK |
VOL_CAP_INT_MANLOCK ;
attr->f_capabilities.valid[VOL_CAPABILITIES_RESERVED1] = 0;
attr->f_capabilities.valid[VOL_CAPABILITIES_RESERVED2] = 0;
VFSATTR_SET_SUPPORTED(attr, f_capabilities);
}
if (VFSATTR_IS_ACTIVE(attr, f_attributes)) {
attr->f_attributes.validattr.commonattr =
ATTR_CMN_NAME |
ATTR_CMN_DEVID |
ATTR_CMN_FSID |
ATTR_CMN_OBJTYPE |
ATTR_CMN_OBJTAG |
ATTR_CMN_OBJID |
ATTR_CMN_PAROBJID |
ATTR_CMN_CRTIME |
ATTR_CMN_MODTIME |
ATTR_CMN_CHGTIME |
ATTR_CMN_ACCTIME |
ATTR_CMN_OWNERID |
ATTR_CMN_GRPID |
ATTR_CMN_ACCESSMASK |
ATTR_CMN_FLAGS |
ATTR_CMN_USERACCESS |
0;
attr->f_attributes.validattr.volattr =
ATTR_VOL_FSTYPE |
ATTR_VOL_SIZE |
ATTR_VOL_SPACEFREE |
ATTR_VOL_SPACEAVAIL |
ATTR_VOL_MINALLOCATION |
ATTR_VOL_ALLOCATIONCLUMP |
ATTR_VOL_IOBLOCKSIZE |
ATTR_VOL_MOUNTPOINT |
ATTR_VOL_NAME |
ATTR_VOL_MOUNTFLAGS |
ATTR_VOL_MOUNTEDDEVICE |
ATTR_VOL_CAPABILITIES |
ATTR_VOL_ATTRIBUTES;
attr->f_attributes.validattr.dirattr =
ATTR_DIR_LINKCOUNT |
ATTR_DIR_MOUNTSTATUS;
attr->f_attributes.validattr.fileattr =
ATTR_FILE_LINKCOUNT |
ATTR_FILE_TOTALSIZE |
ATTR_FILE_ALLOCSIZE |
ATTR_FILE_DEVTYPE |
ATTR_FILE_DATALENGTH |
ATTR_FILE_DATAALLOCSIZE |
ATTR_FILE_RSRCLENGTH |
ATTR_FILE_RSRCALLOCSIZE;
attr->f_attributes.validattr.forkattr = 0;
attr->f_attributes.nativeattr.commonattr =
ATTR_CMN_NAME |
ATTR_CMN_DEVID |
ATTR_CMN_FSID |
ATTR_CMN_OBJTYPE |
ATTR_CMN_OBJTAG |
ATTR_CMN_OBJID |
ATTR_CMN_PAROBJID |
ATTR_CMN_CRTIME |
ATTR_CMN_MODTIME |
ATTR_CMN_ACCTIME |
ATTR_CMN_FLAGS |
ATTR_CMN_USERACCESS |
0;
attr->f_attributes.nativeattr.volattr =
ATTR_VOL_FSTYPE |
ATTR_VOL_SIZE |
ATTR_VOL_SPACEFREE |
ATTR_VOL_SPACEAVAIL |
ATTR_VOL_MINALLOCATION |
ATTR_VOL_ALLOCATIONCLUMP |
ATTR_VOL_IOBLOCKSIZE |
ATTR_VOL_MOUNTPOINT |
ATTR_VOL_NAME |
ATTR_VOL_MOUNTFLAGS |
ATTR_VOL_MOUNTEDDEVICE |
ATTR_VOL_CAPABILITIES |
ATTR_VOL_ATTRIBUTES;
attr->f_attributes.nativeattr.dirattr = 0;
attr->f_attributes.nativeattr.fileattr =
ATTR_FILE_TOTALSIZE |
ATTR_FILE_ALLOCSIZE |
ATTR_FILE_DEVTYPE |
ATTR_FILE_DATALENGTH |
ATTR_FILE_DATAALLOCSIZE |
ATTR_FILE_RSRCLENGTH |
ATTR_FILE_RSRCALLOCSIZE;
attr->f_attributes.nativeattr.forkattr = 0;
VFSATTR_SET_SUPPORTED(attr, f_attributes);
}
if (VFSATTR_IS_ACTIVE(attr, f_fssubtype)) {
if (pmp->pm_fatmask == FAT12_MASK) {
attr->f_fssubtype = 0;
} else if (pmp->pm_fatmask == FAT16_MASK) {
attr->f_fssubtype = 1;
} else {
attr->f_fssubtype = 2;
}
VFSATTR_SET_SUPPORTED(attr, f_fssubtype);
}
if (VFSATTR_IS_ACTIVE(attr, f_vol_name)) {
strlcpy(attr->f_vol_name, (char*)pmp->pm_label, MAXPATHLEN);
VFSATTR_SET_SUPPORTED(attr, f_vol_name);
}
return 0;
}
static int msdosfs_vfs_setattr(mount_t mp, struct vfs_attr *attr, vfs_context_t context)
{
int error = 0;
struct msdosfsmount *pmp = VFSTOMSDOSFS(mp);
if (VFSATTR_IS_ACTIVE(attr, f_vol_name))
{
struct buf *bp = NULL;
size_t i;
int len;
size_t unichars;
u_int16_t c;
u_int16_t volName[SHORT_NAME_LEN];
u_char label[SHORT_NAME_LEN];
len = strlen(attr->f_vol_name);
if (len > 63)
return EINVAL;
error = utf8_decodestr((u_int8_t*)attr->f_vol_name, len, volName,
&unichars, sizeof(volName), 0, UTF_PRECOMPOSED);
if (error)
return error;
unichars /= 2;
if (unichars > SHORT_NAME_LEN)
return EINVAL;
for (i=0; i<SHORT_NAME_LEN; ++i)
label[i] = ' ';
for (i=0; i<unichars; ++i) {
c = volName[i];
if (c < 0x100)
c = l2u[c];
if (c != ' ')
c = unicode2dos(c);
if (c < 3)
return EINVAL;
label[i] = c;
}
bcopy(attr->f_vol_name, pmp->pm_label, len);
pmp->pm_label[len] = '\0';
error = (int)buf_meta_bread(pmp->pm_devvp, 0, pmp->pm_BlockSize, vfs_context_ucred(context), &bp);
if (!error) {
if (FAT32(pmp))
bcopy(label, (char*)buf_dataptr(bp)+71, SHORT_NAME_LEN);
else
bcopy(label, (char*)buf_dataptr(bp)+43, SHORT_NAME_LEN);
buf_bdwrite(bp);
bp = NULL;
}
if (bp)
buf_brelse(bp);
bp = NULL;
if (pmp->pm_label_cluster != CLUST_EOFE) {
error = readep(pmp, pmp->pm_label_cluster, pmp->pm_label_offset, &bp, NULL, context);
if (!error) {
bcopy(label, (char *)buf_dataptr(bp) + pmp->pm_label_offset, SHORT_NAME_LEN);
buf_bdwrite(bp);
bp = NULL;
}
if (bp)
buf_brelse(bp);
bp=NULL;
}
if (error == 0)
VFSATTR_SET_SUPPORTED(attr, f_vol_name);
}
return error;
}
struct msdosfs_sync_cargs {
vfs_context_t context;
int waitfor;
int error;
};
static int
msdosfs_sync_callback(vnode_t vp, void *cargs)
{
struct msdosfs_sync_cargs *args;
struct denode *dep;
int error;
dep = VTODE(vp);
if (dep == NULL)
return VNODE_RETURNED;
args = (struct msdosfs_sync_cargs *)cargs;
if (vnode_issystem(vp))
return VNODE_RETURNED;
lck_mtx_lock(dep->de_lock);
error = msdosfs_fsync_internal(vp, args->waitfor, 0, args->context);
if (error)
args->error = error;
lck_mtx_unlock(dep->de_lock);
return VNODE_RETURNED;
}
static int
msdosfs_sync(mp, waitfor, context)
struct mount *mp;
int waitfor;
vfs_context_t context;
{
struct msdosfsmount *pmp = VFSTOMSDOSFS(mp);
int error, allerror = 0;
struct msdosfs_sync_cargs args;
error = msdosfs_update_fsinfo(pmp, waitfor, context);
if (error)
allerror = error;
error = VNOP_FSYNC(pmp->pm_fat_active_vp, waitfor, context);
if (error)
allerror = error;
if (pmp->pm_fat_mirror_vp)
error = VNOP_FSYNC(pmp->pm_fat_mirror_vp, waitfor, context);
if (error)
allerror = error;
args.context = context;
args.waitfor = waitfor;
args.error = 0;
vnode_iterate(mp, VNODE_ITERATE_ACTIVE, msdosfs_sync_callback, (void *)&args);
if (args.error)
allerror = args.error;
error = VNOP_FSYNC(pmp->pm_devvp, waitfor, context);
if (error)
allerror = error;
return (allerror);
}
struct vfsops msdosfs_vfsops = {
msdosfs_mount,
msdosfs_start,
msdosfs_unmount,
msdosfs_root,
NULL,
msdosfs_vfs_getattr,
msdosfs_sync,
NULL,
NULL,
NULL,
msdosfs_init,
NULL,
msdosfs_vfs_setattr,
{0}
};
extern struct vnodeopv_desc msdosfs_vnodeop_opv_desc;
extern struct vnodeopv_desc msdosfs_fat_vnodeop_opv_desc;
static struct vnodeopv_desc *msdosfs_vnodeop_opv_desc_list[2] =
{
&msdosfs_vnodeop_opv_desc,
&msdosfs_fat_vnodeop_opv_desc
};
static vfstable_t msdosfs_vfsconf;
__private_extern__ int
msdosfs_module_start(kmod_info_t *ki, void *data)
{
#pragma unused(ki)
#pragma unused(data)
errno_t error;
struct vfs_fsentry vfe;
vfe.vfe_vfsops = &msdosfs_vfsops;
vfe.vfe_vopcnt = 2;
vfe.vfe_opvdescs = msdosfs_vnodeop_opv_desc_list;
strlcpy(vfe.vfe_fsname, "msdos", sizeof(vfe.vfe_fsname));
vfe.vfe_flags = VFS_TBLTHREADSAFE | VFS_TBLNOTYPENUM | VFS_TBLLOCALVOL | VFS_TBL64BITREADY;
vfe.vfe_reserv[0] = 0;
vfe.vfe_reserv[1] = 0;
error = vfs_fsadd(&vfe, &msdosfs_vfsconf);
#if DEBUG
if (!error)
{
sysctl_register_oid(&sysctl__vfs_generic_msdosfs);
sysctl_register_oid(&sysctl__vfs_generic_msdosfs_meta_delay);
}
#endif
return error ? KERN_FAILURE : KERN_SUCCESS;
}
__private_extern__ int
msdosfs_module_stop(kmod_info_t *ki, void *data)
{
#pragma unused(ki)
#pragma unused(data)
errno_t error;
error = vfs_fsremove(msdosfs_vfsconf);
if (!error)
{
msdosfs_uninit();
#if DEBUG
sysctl_unregister_oid(&sysctl__vfs_generic_msdosfs_meta_delay);
sysctl_unregister_oid(&sysctl__vfs_generic_msdosfs);
#endif
}
return error ? KERN_FAILURE : KERN_SUCCESS;
}
static int scan_root_dir(struct mount *mp, vfs_context_t context)
{
int error;
struct msdosfsmount *pmp;
vnode_t vp = NULL;
struct buf *bp = NULL;
uint32_t frcn;
daddr64_t bn;
uint32_t cluster;
uint32_t blsize;
unsigned blkoff;
unsigned diroff;
struct dosdirentry *dep = NULL;
struct denode *root;
u_int16_t unichars;
u_int16_t ucfn[12];
u_char uc;
int i;
size_t outbytes;
char *bdata = NULL;
pmp = VFSTOMSDOSFS(mp);
error = msdosfs_root(mp, &vp, context);
if (error)
return error;
root = VTODE(vp);
diroff = 0;
for (frcn=0; ; frcn++) {
error = pcbmap(root, frcn, 1, &bn, &cluster, &blsize);
if (error) {
if (error == E2BIG)
error = 0;
goto not_found;
}
for (blkoff = 0; blkoff < blsize; blkoff += sizeof(struct dosdirentry), diroff += sizeof(struct dosdirentry)) {
if (bp == NULL) {
error = (int)buf_meta_bread(pmp->pm_devvp, bn, blsize, vfs_context_ucred(context), &bp);
if (error) {
goto not_found;
}
bdata = (char *)buf_dataptr(bp);
}
dep = (struct dosdirentry *) (bdata + blkoff);
if (dep->deName[0] == SLOT_DELETED)
continue;
if (dep->deName[0] == SLOT_EMPTY) {
goto not_found;
}
if (dep->deAttributes == ATTR_WIN95)
continue;
if (dep->deAttributes & ATTR_VOLUME) {
pmp->pm_label_cluster = cluster;
pmp->pm_label_offset = blkoff;
root->de_CHun = dep->deCHundredth;
root->de_CTime = getuint16(dep->deCTime);
root->de_CDate = getuint16(dep->deCDate);
root->de_ADate = getuint16(dep->deADate);
root->de_MTime = getuint16(dep->deMTime);
root->de_MDate = getuint16(dep->deMDate);
for (i=0; i<SHORT_NAME_LEN; i++) {
uc = dep->deName[i];
if (i==0 && uc == SLOT_E5)
uc = 0xE5;
ucfn[i] = (uc < 0x80 || uc > 0x9F ? (u_int16_t)uc : dos2unicode[uc - 0x80]);
}
for (i=10; i>=0 && ucfn[i]==' '; --i)
;
unichars = i+1;
error = utf8_encodestr(ucfn, unichars * 2,
pmp->pm_label, &outbytes,
sizeof(pmp->pm_label), 0, UTF_DECOMPOSED);
goto found;
}
if (!bcmp(dep->deName, "HIBERFILSYS", SHORT_NAME_LEN))
{
struct denode *hibernate = NULL;
buf_t hibernate_bp = NULL;
char *hibernate_data = NULL;
if (getuint32(dep->deFileSize) < 4096)
{
if (DEBUG) printf("msdosfs: Volume is hibernated; hiberfile.sys < 4096 bytes\n");
goto hibernated;
}
buf_brelse(bp);
bp = NULL;
error = deget(pmp, cluster, diroff, NULL, NULL, &hibernate, context);
if (error)
{
printf("msdosfs: error %d trying to open hiberfil.sys\n", error);
error = 0;
goto hibernated;
}
error = buf_meta_bread(DETOV(hibernate), 0, 4096, vfs_context_ucred(context), &hibernate_bp);
if (error)
{
printf("msdosfs: error %d trying to read hiberfil.sys\n", error);
error = 0;
goto hibernated;
}
hibernate_data = (char *) buf_dataptr(hibernate_bp);
if (!strncmp(hibernate_data, "hibr", 4))
{
if (DEBUG) printf("msdosfs: Volume is hibernated; signature = 'hibr'\n");
goto hibernated;
}
if (!strncmp(hibernate_data, "HIBR", 4))
{
if (DEBUG) printf("msdosfs: Volume is hibernated; signature = 'HIBR'\n");
goto hibernated;
}
for (i=0; i<4096; ++i)
{
if (hibernate_data[i])
{
if (DEBUG) printf("msdosfs: Volume is hibernated (non-zero header)\n");
goto hibernated;
}
}
if (DEBUG) printf("msdosfs: Volume is not hibernated (hiberfil.sys header all zeroes)\n");
goto not_hibernated;
hibernated:
printf("msdosfs: Mounting hibernated volume read-only\n");
vfs_setflags(mp, MNT_RDONLY);
not_hibernated:
if (hibernate_bp)
buf_brelse(hibernate_bp);
if (hibernate)
{
vnode_recycle(DETOV(hibernate));
vnode_put(DETOV(hibernate));
}
}
}
if (bp != NULL) {
buf_brelse(bp);
bp = NULL;
}
}
found:
not_found:
if (bp)
buf_brelse(bp);
if (vp) {
if (error)
vnode_recycle(vp);
vnode_put(vp);
}
return error;
}