#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/ubc.h>
#include <mach/boolean.h>
#include <libkern/OSBase.h>
#include <libkern/OSAtomic.h>
#include <kern/thread_call.h>
#include <libkern/OSMalloc.h>
#include <IOKit/IOTypes.h>
#include "bpb.h"
#include "msdosfsmount.h"
#include "direntry.h"
#include "denode.h"
#include "fat.h"
uint32_t msdosfs_meta_delay = 50;
#ifndef DEBUG
#define DEBUG 0
#endif
struct msdosfs_fat_node {
struct msdosfsmount *pmp;
u_int32_t start_block;
u_int32_t file_size;
};
static int msdosfs_fat_cache_flush(struct msdosfsmount *pmp, int ioflags);
static int clusterfree_internal(struct msdosfsmount *pmp,
uint32_t cluster, uint32_t *oldcnp);
static int fatentry_internal(int function, struct msdosfsmount *pmp,
uint32_t cn, uint32_t *oldcontents, uint32_t newcontents);
static int clusteralloc_internal(struct msdosfsmount *pmp,
uint32_t start, uint32_t count, uint32_t fillwith,
uint32_t *retcluster, uint32_t *got);
static int chainalloc __P((struct msdosfsmount *pmp, uint32_t start,
uint32_t count, uint32_t fillwith,
uint32_t *retcluster, uint32_t *got));
static int chainlength __P((struct msdosfsmount *pmp, uint32_t start,
uint32_t count));
static int fatchain __P((struct msdosfsmount *pmp, uint32_t start,
uint32_t count, uint32_t fillwith));
static int fillinusemap(struct msdosfsmount *pmp);
static __inline void
usemap_alloc __P((struct msdosfsmount *pmp, uint32_t cn));
static __inline void
usemap_free __P((struct msdosfsmount *pmp, uint32_t cn));
typedef int vnop_t __P((void *));
static int msdosfs_fat_pagein(struct vnop_pagein_args *ap)
{
struct msdosfs_fat_node *node = vnode_fsnode(ap->a_vp);
return cluster_pagein(ap->a_vp, ap->a_pl, ap->a_pl_offset, ap->a_f_offset,
ap->a_size, node->file_size, ap->a_flags);
}
static int msdosfs_fat_pageout(struct vnop_pageout_args *ap)
{
struct msdosfs_fat_node *node = vnode_fsnode(ap->a_vp);
return cluster_pageout(ap->a_vp, ap->a_pl, ap->a_pl_offset, ap->a_f_offset,
ap->a_size, node->file_size, ap->a_flags);
}
static int msdosfs_fat_strategy(struct vnop_strategy_args *ap)
{
struct msdosfs_fat_node *node = vnode_fsnode(buf_vnode(ap->a_bp));
return buf_strategy(node->pmp->pm_devvp, ap);
}
static int msdosfs_fat_blockmap(struct vnop_blockmap_args *ap)
{
struct msdosfs_fat_node *node = vnode_fsnode(ap->a_vp);
if (ap->a_bpn == NULL)
return 0;
*ap->a_bpn = node->start_block + (ap->a_foffset / node->pmp->pm_BlockSize);
if (ap->a_run)
{
if (ap->a_foffset + ap->a_size > node->file_size)
*ap->a_run = node->file_size - ap->a_foffset;
else
*ap->a_run = ap->a_size;
}
return 0;
}
static int msdosfs_fat_fsync(struct vnop_fsync_args *ap)
{
int error = 0;
int ioflags = 0;
vnode_t vp = ap->a_vp;
struct msdosfs_fat_node *node = vnode_fsnode(ap->a_vp);
struct msdosfsmount *pmp = node->pmp;
if (ap->a_waitfor == MNT_WAIT)
ioflags = IO_SYNC;
if (pmp->pm_fat_flags & FAT_CACHE_DIRTY)
{
lck_mtx_lock(pmp->pm_fat_lock);
error = msdosfs_fat_cache_flush(pmp, ioflags);
lck_mtx_unlock(pmp->pm_fat_lock);
}
if (!error)
cluster_push(vp, ioflags);
return error;
}
static int msdosfs_fat_inactive(struct vnop_inactive_args *ap)
{
vnode_recycle(ap->a_vp);
return 0;
}
static int msdosfs_fat_reclaim(struct vnop_reclaim_args *ap)
{
vnode_t vp = ap->a_vp;
struct msdosfs_fat_node *node = vnode_fsnode(vp);
vnode_clearfsnode(vp);
OSFree(node, sizeof(*node), msdosfs_malloc_tag);
return 0;
}
static int (**msdosfs_fat_vnodeop_p)(void *);
static struct vnodeopv_entry_desc msdosfs_fat_vnodeop_entries[] = {
{ &vnop_default_desc, (vnop_t *) vn_default_error },
{ &vnop_pagein_desc, (vnop_t *) msdosfs_fat_pagein },
{ &vnop_pageout_desc, (vnop_t *) msdosfs_fat_pageout },
{ &vnop_strategy_desc, (vnop_t *) msdosfs_fat_strategy },
{ &vnop_blockmap_desc, (vnop_t *) msdosfs_fat_blockmap },
{ &vnop_blktooff_desc, (vnop_t *) msdosfs_blktooff },
{ &vnop_offtoblk_desc, (vnop_t *) msdosfs_offtoblk },
{ &vnop_fsync_desc, (vnop_t *) msdosfs_fat_fsync },
{ &vnop_inactive_desc, (vnop_t *) msdosfs_fat_inactive },
{ &vnop_reclaim_desc, (vnop_t *) msdosfs_fat_reclaim },
{ &vnop_bwrite_desc, (vnop_t *) vn_bwrite },
{ NULL, NULL }
};
__private_extern__
struct vnodeopv_desc msdosfs_fat_vnodeop_opv_desc =
{ &msdosfs_fat_vnodeop_p, msdosfs_fat_vnodeop_entries };
__private_extern__ int
msdosfs_fat_init_vol(struct msdosfsmount *pmp)
{
errno_t error;
struct vnode_fsparam vfsp;
struct msdosfs_fat_node *node;
lck_mtx_lock(pmp->pm_fat_lock);
node = OSMalloc(sizeof(*node), msdosfs_malloc_tag);
if (node == NULL)
{
error = ENOMEM;
goto exit;
}
node->pmp = pmp;
node->start_block = pmp->pm_ResSectors * pmp->pm_BlocksPerSec;
node->file_size = pmp->pm_fat_bytes;
vfsp.vnfs_mp = pmp->pm_mountp;
vfsp.vnfs_vtype = VREG;
vfsp.vnfs_str = "msdosfs";
vfsp.vnfs_dvp = NULL;
vfsp.vnfs_fsnode = node;
vfsp.vnfs_vops = msdosfs_fat_vnodeop_p;
vfsp.vnfs_markroot = 0;
vfsp.vnfs_marksystem = 1;
vfsp.vnfs_rdev = 0;
vfsp.vnfs_filesize = pmp->pm_fat_bytes;
vfsp.vnfs_cnp = NULL;
vfsp.vnfs_flags = VNFS_CANTCACHE;
error = vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vfsp, &pmp->pm_fat_active_vp);
if (error)
{
OSFree(node, sizeof(*node), msdosfs_malloc_tag);
goto exit;
}
vnode_ref(pmp->pm_fat_active_vp);
vnode_put(pmp->pm_fat_active_vp);
if (pmp->pm_flags & MSDOSFS_FATMIRROR)
{
node = OSMalloc(sizeof(*node), msdosfs_malloc_tag);
if (node == NULL)
{
error = ENOMEM;
goto exit;
}
node->pmp = pmp;
node->start_block = pmp->pm_ResSectors * pmp->pm_BlocksPerSec + pmp->pm_fat_bytes / pmp->pm_BlockSize;
node->file_size = (pmp->pm_FATs - 1) * pmp->pm_fat_bytes;
vfsp.vnfs_mp = pmp->pm_mountp;
vfsp.vnfs_vtype = VREG;
vfsp.vnfs_str = "msdosfs";
vfsp.vnfs_dvp = NULL;
vfsp.vnfs_fsnode = node;
vfsp.vnfs_vops = msdosfs_fat_vnodeop_p;
vfsp.vnfs_markroot = 0;
vfsp.vnfs_marksystem = 1;
vfsp.vnfs_rdev = 0;
vfsp.vnfs_filesize = node->file_size;
vfsp.vnfs_cnp = NULL;
vfsp.vnfs_flags = VNFS_CANTCACHE;
error = vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vfsp, &pmp->pm_fat_mirror_vp);
if (error)
{
OSFree(node, sizeof(*node), msdosfs_malloc_tag);
goto exit;
}
vnode_ref(pmp->pm_fat_mirror_vp);
vnode_put(pmp->pm_fat_mirror_vp);
}
pmp->pm_fat_cache = OSMalloc(pmp->pm_fatblocksize, msdosfs_malloc_tag);
if (pmp->pm_fat_cache == NULL)
{
error = ENOMEM;
goto exit;
}
pmp->pm_fat_cache_offset = -1;
pmp->pm_fat_flags = 0;
MALLOC(pmp->pm_inusemap, u_int *,
((pmp->pm_maxcluster / N_INUSEBITS) + 1) *
sizeof(u_int), M_TEMP, M_WAITOK);
if (pmp->pm_inusemap == NULL)
{
error = ENOMEM;
goto exit;
}
error = fillinusemap(pmp);
exit:
lck_mtx_unlock(pmp->pm_fat_lock);
return error;
}
__private_extern__ void
msdosfs_fat_uninit_vol(struct msdosfsmount *pmp)
{
if (pmp->pm_fat_flags & FAT_CACHE_DIRTY)
{
int error;
lck_mtx_lock(pmp->pm_fat_lock);
error = msdosfs_fat_cache_flush(pmp, IO_SYNC);
if (error)
printf("msdosfs_fat_uninit_vol: error %d from msdosfs_fat_cache_flush\n", error);
pmp->pm_fat_flags = 0;
lck_mtx_unlock(pmp->pm_fat_lock);
}
if (pmp->pm_fat_active_vp)
{
cluster_push(pmp->pm_fat_active_vp, IO_SYNC);
vnode_recycle(pmp->pm_fat_active_vp);
vnode_rele(pmp->pm_fat_active_vp);
pmp->pm_fat_active_vp = NULL;
}
if (pmp->pm_fat_mirror_vp)
{
cluster_push(pmp->pm_fat_mirror_vp, IO_SYNC);
vnode_recycle(pmp->pm_fat_mirror_vp);
vnode_rele(pmp->pm_fat_mirror_vp);
pmp->pm_fat_mirror_vp = NULL;
}
if (pmp->pm_fat_cache)
{
OSFree(pmp->pm_fat_cache, pmp->pm_fatblocksize, msdosfs_malloc_tag);
pmp->pm_fat_cache = NULL;
}
if (pmp->pm_inusemap)
{
FREE(pmp->pm_inusemap, M_TEMP);
pmp->pm_inusemap = NULL;
}
}
__private_extern__
int msdosfs_update_fsinfo(struct msdosfsmount *pmp, int waitfor, vfs_context_t context)
{
int error;
buf_t bp;
struct fsinfo *fp;
if (pmp->pm_fsinfo_size == 0 || !(pmp->pm_fat_flags & FSINFO_DIRTY))
return 0;
error = buf_meta_bread(pmp->pm_devvp, pmp->pm_fsinfo_sector, pmp->pm_fsinfo_size,
vfs_context_ucred(context), &bp);
if (error)
{
printf("msdosfs_update_fsinfo: error %d reading FSInfo\n", error);
pmp->pm_fsinfo_size = 0;
buf_brelse(bp);
}
else
{
fp = (struct fsinfo *) (buf_dataptr(bp) + pmp->pm_fsinfo_offset);
putuint32(fp->fsinfree, pmp->pm_freeclustercount);
if (waitfor || pmp->pm_flags & MSDOSFSMNT_WAITONFAT)
error = buf_bwrite(bp);
else
error = buf_bawrite(bp);
}
pmp->pm_fat_flags &= ~FSINFO_DIRTY;
return error;
}
static int
msdosfs_fat_cache_flush(struct msdosfsmount *pmp, int ioflags)
{
int error;
u_int32_t fat_file_size;
u_int32_t block_size;
uio_t uio;
if (DEBUG) lck_mtx_assert(pmp->pm_fat_lock, LCK_MTX_ASSERT_OWNED);
if ((pmp->pm_fat_flags & FAT_CACHE_DIRTY) == 0)
return 0;
fat_file_size = pmp->pm_fat_bytes;
block_size = pmp->pm_fatblocksize;
if (pmp->pm_fat_cache_offset + block_size > pmp->pm_fat_bytes)
block_size = pmp->pm_fat_bytes - pmp->pm_fat_cache_offset;
uio = uio_create(1, pmp->pm_fat_cache_offset, UIO_SYSSPACE, UIO_WRITE);
if (uio == NULL)
return ENOMEM;
uio_addiov(uio, CAST_USER_ADDR_T(pmp->pm_fat_cache), block_size);
error = cluster_write(pmp->pm_fat_active_vp, uio, fat_file_size, fat_file_size, 0, 0, ioflags);
if (!error)
pmp->pm_fat_flags &= ~FAT_CACHE_DIRTY;
if (pmp->pm_flags & MSDOSFS_FATMIRROR)
{
int i;
fat_file_size = (pmp->pm_FATs - 1) * pmp->pm_fat_bytes;
for (i=0; i<pmp->pm_FATs-1; ++i)
{
uio_reset(uio, pmp->pm_fat_cache_offset + i * pmp->pm_fat_bytes, UIO_SYSSPACE, UIO_WRITE);
uio_addiov(uio, CAST_USER_ADDR_T(pmp->pm_fat_cache), block_size);
error = cluster_write(pmp->pm_fat_mirror_vp, uio, fat_file_size, fat_file_size, 0, 0, ioflags);
}
}
uio_free(uio);
return error;
}
static void *
msdosfs_fat_map(struct msdosfsmount *pmp, u_int32_t cn, u_int32_t *offp, u_int32_t *sizep, int *error_out)
{
int error = 0;
u_int32_t offset;
u_int32_t block_offset;
u_int32_t block_size;
if (DEBUG) lck_mtx_assert(pmp->pm_fat_lock, LCK_MTX_ASSERT_OWNED);
offset = cn * pmp->pm_fatmult / pmp->pm_fatdiv;
block_offset = offset / pmp->pm_fatblocksize * pmp->pm_fatblocksize;
block_size = pmp->pm_fatblocksize;
if (block_offset + block_size > pmp->pm_fat_bytes)
block_size = pmp->pm_fat_bytes - block_offset;
if (pmp->pm_fat_cache_offset != block_offset)
{
uio_t uio;
if (pmp->pm_fat_flags & FAT_CACHE_DIRTY)
error = msdosfs_fat_cache_flush(pmp, 0);
if (!error)
{
pmp->pm_fat_cache_offset = block_offset;
uio = uio_create(1, block_offset, UIO_SYSSPACE, UIO_READ);
if (uio == NULL)
{
error = ENOMEM;
}
else
{
uio_addiov(uio, CAST_USER_ADDR_T(pmp->pm_fat_cache), block_size);
error = cluster_read(pmp->pm_fat_active_vp, uio, pmp->pm_fat_bytes, 0);
uio_free(uio);
}
}
}
offset -= block_offset;
if (offp)
*offp = offset;
if (sizep)
{
u_int32_t last_offset;
last_offset = (pmp->pm_maxcluster + 1) * pmp->pm_fatmult / pmp->pm_fatdiv;
if (last_offset - block_offset < block_size)
block_size = last_offset - block_offset;
*sizep = block_size;
}
if (error_out)
*error_out = error;
if (error)
{
pmp->pm_fat_cache_offset = -1;
pmp->pm_fat_flags &= ~FAT_CACHE_DIRTY;
return NULL;
}
return (char *) pmp->pm_fat_cache + offset;
}
static void msdosfs_meta_flush_internal(struct msdosfsmount *pmp, int sync)
{
int error;
lck_mtx_lock(pmp->pm_fat_lock);
if (pmp->pm_fat_flags & FAT_CACHE_DIRTY)
{
error = msdosfs_fat_cache_flush(pmp, 0);
if (error)
{
printf("msdosfs_meta_flush_internal: error %d flushing FAT cache!\n", error);
pmp->pm_fat_flags &= ~FAT_CACHE_DIRTY;
}
}
cluster_push(pmp->pm_fat_active_vp, IO_SYNC);
if (pmp->pm_fat_mirror_vp)
cluster_push(pmp->pm_fat_mirror_vp, IO_SYNC);
lck_mtx_unlock(pmp->pm_fat_lock);
buf_flushdirtyblks(pmp->pm_devvp, sync, 0, "msdosfs_meta_flush");
}
__private_extern__ void
msdosfs_meta_flush(struct msdosfsmount *pmp, int sync)
{
if (sync) goto flush_now;
if (vfs_flags(pmp->pm_mountp) & MNT_ASYNC)
return;
if (pmp->pm_sync_timer)
{
if (pmp->pm_sync_scheduled == 0)
{
AbsoluteTime deadline;
clock_interval_to_deadline(msdosfs_meta_delay, kMillisecondScale, &deadline);
OSIncrementAtomic(&pmp->pm_sync_scheduled);
if (thread_call_enter_delayed(pmp->pm_sync_timer, deadline))
OSDecrementAtomic(&pmp->pm_sync_scheduled);
else
OSIncrementAtomic(&pmp->pm_sync_incomplete);
}
return;
}
flush_now:
msdosfs_meta_flush_internal(pmp, sync);
}
__private_extern__ void msdosfs_meta_sync_callback(void *arg0, void *unused)
{
#pragma unused(unused)
struct msdosfsmount *pmp = arg0;
OSDecrementAtomic(&pmp->pm_sync_scheduled);
msdosfs_meta_flush_internal(pmp, 0);
OSDecrementAtomic(&pmp->pm_sync_incomplete);
wakeup(&pmp->pm_sync_incomplete);
}
__private_extern__ int
pcbmap_internal(
struct denode *dep,
uint32_t findcn,
uint32_t numclusters,
daddr64_t *bnp,
uint32_t *cnp,
uint32_t *sp)
{
int error=0;
uint32_t i;
uint32_t cn;
uint32_t prevcn=0;
uint32_t cluster_logical;
uint32_t cluster_physical;
uint32_t cluster_count;
struct msdosfsmount *pmp = dep->de_pmp;
void *entry;
if (numclusters == 0)
panic("pcbmap: numclusters == 0");
if (bnp == NULL && cnp == NULL && sp == NULL)
goto exit;
cn = dep->de_StartCluster;
if (cn == MSDOSFSROOT) {
if (dep->de_Attributes & ATTR_DIRECTORY) {
if (de_cn2off(pmp, findcn) >= dep->de_FileSize) {
if (cnp)
*cnp = de_bn2cn(pmp, pmp->pm_rootdirsize);
error = E2BIG;
goto exit;
}
if (bnp)
*bnp = pmp->pm_rootdirblk + de_cn2bn(pmp, findcn);
if (cnp)
*cnp = MSDOSFSROOT;
if (sp)
*sp = min(pmp->pm_bpcluster,
dep->de_FileSize - de_cn2off(pmp, findcn));
goto exit;
} else {
if (cnp)
*cnp = 0;
error = E2BIG;
goto exit;
}
}
if (findcn >= dep->de_cluster_logical &&
findcn < (dep->de_cluster_logical + dep->de_cluster_count))
{
i = findcn - dep->de_cluster_logical;
cn = dep->de_cluster_physical + i;
if (bnp)
*bnp = cntobn(pmp, cn);
if (cnp)
*cnp = cn;
if (sp)
*sp = min(dep->de_cluster_count - i, numclusters) * pmp->pm_bpcluster;
goto exit;
}
cluster_logical = 0;
cluster_physical = 0;
cluster_count = 0;
if (dep->de_cluster_count && findcn > dep->de_cluster_logical)
{
cluster_logical = dep->de_cluster_logical;
cluster_physical = dep->de_cluster_physical;
cluster_count = dep->de_cluster_count;
prevcn = dep->de_cluster_physical+dep->de_cluster_count-1;
error = fatentry_internal(FAT_GET, pmp, prevcn, &cn, 0);
if (error)
goto exit;
}
while ((cluster_logical + cluster_count) <= findcn)
{
if ((cn | ~pmp->pm_fatmask) >= CLUST_RSRVD)
goto hiteof;
if (cluster_logical > pmp->pm_maxcluster)
{
printf("msdosfs: pcbmap: Corrupt cluster chain detected\n");
pmp->pm_flags |= MSDOSFS_CORRUPT;
error = EIO;
goto exit;
}
if (cn < CLUST_FIRST || cn > pmp->pm_maxcluster)
{
if (DEBUG)
panic("pcbmap_internal: invalid cluster: cn=%u, name='%11.11s'", cn, dep->de_Name);
return EIO;
}
cluster_logical += cluster_count;
cluster_physical = cn;
cluster_count = 0;
do {
entry = msdosfs_fat_map(pmp, cn, NULL, NULL, &error);
if (!entry)
goto exit;
prevcn = cn;
if (FAT32(pmp))
cn = getuint32(entry);
else
cn = getuint16(entry);
if (FAT12(pmp) && (prevcn & 1))
cn >>= 4;
cn &= pmp->pm_fatmask;
if ((cn | ~pmp->pm_fatmask) >= CLUST_RSRVD)
cn |= ~pmp->pm_fatmask;
cluster_count++;
} while (cn == prevcn + 1);
}
dep->de_cluster_logical = cluster_logical;
dep->de_cluster_physical = cluster_physical;
dep->de_cluster_count = cluster_count;
i = findcn - cluster_logical;
cn = cluster_physical + i;
if (bnp)
*bnp = cntobn(pmp, cn);
if (cnp)
*cnp = cn;
if (sp)
*sp = min(cluster_count-i, numclusters) * pmp->pm_bpcluster;
exit:
return error;
hiteof:
if (cnp)
*cnp = cluster_logical + cluster_count;
if (sp)
*sp = prevcn;
error = E2BIG;
goto exit;
}
__private_extern__ int
pcbmap(
struct denode *dep,
uint32_t findcn,
uint32_t numclusters,
daddr64_t *bnp,
uint32_t *cnp,
uint32_t *sp)
{
int error;
lck_mtx_lock(dep->de_cluster_lock);
lck_mtx_lock(dep->de_pmp->pm_fat_lock);
error = pcbmap_internal(dep, findcn, numclusters, bnp, cnp, sp);
lck_mtx_unlock(dep->de_pmp->pm_fat_lock);
lck_mtx_unlock(dep->de_cluster_lock);
return error;
}
static __inline void
usemap_alloc(pmp, cn)
struct msdosfsmount *pmp;
uint32_t cn;
{
pmp->pm_inusemap[cn / N_INUSEBITS] |= 1 << (cn % N_INUSEBITS);
pmp->pm_freeclustercount--;
}
static __inline void
usemap_free(pmp, cn)
struct msdosfsmount *pmp;
uint32_t cn;
{
pmp->pm_freeclustercount++;
pmp->pm_inusemap[cn / N_INUSEBITS] &= ~(1 << (cn % N_INUSEBITS));
}
static int
clusterfree_internal(pmp, cluster, oldcnp)
struct msdosfsmount *pmp;
uint32_t cluster;
uint32_t *oldcnp;
{
int error;
uint32_t oldcn;
usemap_free(pmp, cluster);
error = fatentry_internal(FAT_GET_AND_SET, pmp, cluster, &oldcn, MSDOSFSFREE);
if (error) {
usemap_alloc(pmp, cluster);
return (error);
}
if (oldcnp)
*oldcnp = oldcn;
return (0);
}
__private_extern__ int
clusterfree(pmp, cluster, oldcnp)
struct msdosfsmount *pmp;
uint32_t cluster;
uint32_t *oldcnp;
{
int error;
lck_mtx_lock(pmp->pm_fat_lock);
error = clusterfree_internal(pmp, cluster, oldcnp);
lck_mtx_unlock(pmp->pm_fat_lock);
return error;
}
static int
fatentry_internal(function, pmp, cn, oldcontents, newcontents)
int function;
struct msdosfsmount *pmp;
uint32_t cn;
uint32_t *oldcontents;
uint32_t newcontents;
{
int error = 0;
uint32_t readcn;
void *entry;
if (cn < CLUST_FIRST || cn > pmp->pm_maxcluster)
{
if (DEBUG)
{
printf("msdosfs: fatentry_internal: cn=%u, function=%d, new=%u\n", cn, function, newcontents);
}
return (EIO);
}
entry = msdosfs_fat_map(pmp, cn, NULL, NULL, &error);
if (!entry)
return error;
if (function & FAT_GET) {
if (FAT32(pmp))
readcn = getuint32(entry);
else
readcn = getuint16(entry);
if (FAT12(pmp) & (cn & 1))
readcn >>= 4;
readcn &= pmp->pm_fatmask;
if ((readcn | ~pmp->pm_fatmask) >= CLUST_RSRVD)
readcn |= ~pmp->pm_fatmask;
*oldcontents = readcn;
}
if (function & FAT_SET) {
switch (pmp->pm_fatmask) {
case FAT12_MASK:
readcn = getuint16(entry);
if (cn & 1) {
readcn &= 0x000f;
readcn |= newcontents << 4;
} else {
readcn &= 0xf000;
readcn |= newcontents & 0xfff;
}
putuint16(entry, readcn);
break;
case FAT16_MASK:
putuint16(entry, newcontents);
break;
case FAT32_MASK:
readcn = getuint32(entry);
readcn &= ~FAT32_MASK;
readcn |= newcontents & FAT32_MASK;
putuint32(entry, readcn);
break;
}
pmp->pm_fat_flags |= FAT_CACHE_DIRTY | FSINFO_DIRTY;
}
return 0;
}
__private_extern__ int
fatentry(function, pmp, cn, oldcontents, newcontents)
int function;
struct msdosfsmount *pmp;
uint32_t cn;
uint32_t *oldcontents;
uint32_t newcontents;
{
int error;
lck_mtx_lock(pmp->pm_fat_lock);
error = fatentry_internal(function, pmp, cn, oldcontents, newcontents);
lck_mtx_unlock(pmp->pm_fat_lock);
return error;
}
static int
fatchain(pmp, start, count, fillwith)
struct msdosfsmount *pmp;
uint32_t start;
uint32_t count;
uint32_t fillwith;
{
int error = 0;
u_int32_t bo, bsize;
uint32_t readcn, newc;
char *entry;
char *block_end;
if (start < CLUST_FIRST || start + count - 1 > pmp->pm_maxcluster)
{
printf("msdosfs: fatchain: start=%u, count=%u, fill=%u\n", start, count, fillwith);
return (EIO);
}
while (count > 0) {
entry = msdosfs_fat_map(pmp, start, &bo, &bsize, &error);
if (!entry)
break;
block_end = entry - bo + bsize;
while (count > 0) {
start++;
newc = --count > 0 ? start : fillwith;
switch (pmp->pm_fatmask) {
case FAT12_MASK:
readcn = getuint16(entry);
if (start & 1) {
readcn &= 0xf000;
readcn |= newc & 0xfff;
} else {
readcn &= 0x000f;
readcn |= newc << 4;
}
putuint16(entry, readcn);
entry++;
if (!(start & 1))
entry++;
break;
case FAT16_MASK:
putuint16(entry, newc);
entry += 2;
break;
case FAT32_MASK:
readcn = getuint32(entry);
readcn &= ~pmp->pm_fatmask;
readcn |= newc & pmp->pm_fatmask;
putuint32(entry, readcn);
entry += 4;
break;
}
if (entry >= block_end)
break;
}
pmp->pm_fat_flags |= FAT_CACHE_DIRTY | FSINFO_DIRTY;
}
return error;
}
static int
chainlength(pmp, start, count)
struct msdosfsmount *pmp;
uint32_t start;
uint32_t count;
{
uint32_t idx, max_idx;
u_int map;
uint32_t len;
max_idx = pmp->pm_maxcluster / N_INUSEBITS;
idx = start / N_INUSEBITS;
start %= N_INUSEBITS;
map = pmp->pm_inusemap[idx];
map &= ~((1 << start) - 1);
if (map) {
len = ffs(map) - 1 - start;
return (len > count ? count : len);
}
len = N_INUSEBITS - start;
if (len >= count)
return (count);
while (++idx <= max_idx) {
if (len >= count)
break;
map = pmp->pm_inusemap[idx];
if (map) {
len += ffs(map) - 1;
break;
}
len += N_INUSEBITS;
}
return (len > count ? count : len);
}
static int
chainalloc(pmp, start, count, fillwith, retcluster, got)
struct msdosfsmount *pmp;
uint32_t start;
uint32_t count;
uint32_t fillwith;
uint32_t *retcluster;
uint32_t *got;
{
int error;
uint32_t cl, n;
for (cl = start, n = count; n-- > 0;)
usemap_alloc(pmp, cl++);
error = fatchain(pmp, start, count, fillwith);
if (error != 0)
return (error);
if (retcluster)
*retcluster = start;
if (got)
*got = count;
return (0);
}
static int
clusteralloc_internal(pmp, start, count, fillwith, retcluster, got)
struct msdosfsmount *pmp;
uint32_t start;
uint32_t count;
uint32_t fillwith;
uint32_t *retcluster;
uint32_t *got;
{
uint32_t idx;
uint32_t len, foundl, cn, l;
uint32_t foundcn;
u_int map;
if (start) {
if ((len = chainlength(pmp, start, count)) >= count)
return (chainalloc(pmp, start, count, fillwith, retcluster, got));
} else
len = 0;
foundl = 0;
foundcn = 0;
for (cn = 0; cn <= pmp->pm_maxcluster;) {
idx = cn / N_INUSEBITS;
map = pmp->pm_inusemap[idx];
map |= (1 << (cn % N_INUSEBITS)) - 1;
if (map != (u_int)-1) {
cn = idx * N_INUSEBITS + ffs(map^(u_int)-1) - 1;
if ((l = chainlength(pmp, cn, count)) >= count)
return (chainalloc(pmp, cn, count, fillwith, retcluster, got));
if (l > foundl) {
foundcn = cn;
foundl = l;
}
cn += l + 1;
continue;
}
cn += N_INUSEBITS - cn % N_INUSEBITS;
}
if (!foundl)
return (ENOSPC);
if (len)
return (chainalloc(pmp, start, len, fillwith, retcluster, got));
else
return (chainalloc(pmp, foundcn, foundl, fillwith, retcluster, got));
}
__private_extern__ int
clusteralloc(pmp, start, count, fillwith, retcluster, got)
struct msdosfsmount *pmp;
uint32_t start;
uint32_t count;
uint32_t fillwith;
uint32_t *retcluster;
uint32_t *got;
{
int error;
lck_mtx_lock(pmp->pm_fat_lock);
error = clusteralloc_internal(pmp, start, count, fillwith, retcluster, got);
lck_mtx_unlock(pmp->pm_fat_lock);
return error;
}
__private_extern__ int
freeclusterchain(pmp, cluster)
struct msdosfsmount *pmp;
uint32_t cluster;
{
int error=0;
uint32_t readcn;
void *entry;
lck_mtx_lock(pmp->pm_fat_lock);
while (cluster >= CLUST_FIRST && cluster <= pmp->pm_maxcluster) {
entry = msdosfs_fat_map(pmp, cluster, NULL, NULL, &error);
if (!entry)
break;
usemap_free(pmp, cluster);
switch (pmp->pm_fatmask) {
case FAT12_MASK:
readcn = getuint16(entry);
if (cluster & 1) {
cluster = readcn >> 4;
readcn &= 0x000f;
readcn |= MSDOSFSFREE << 4;
} else {
cluster = readcn;
readcn &= 0xf000;
readcn |= MSDOSFSFREE & 0xfff;
}
putuint16(entry, readcn);
break;
case FAT16_MASK:
cluster = getuint16(entry);
putuint16(entry, MSDOSFSFREE);
break;
case FAT32_MASK:
cluster = getuint32(entry);
putuint32(entry,
(MSDOSFSFREE & FAT32_MASK) | (cluster & ~FAT32_MASK));
break;
}
pmp->pm_fat_flags |= FAT_CACHE_DIRTY | FSINFO_DIRTY;
cluster &= pmp->pm_fatmask;
if ((cluster | ~pmp->pm_fatmask) >= CLUST_RSRVD)
cluster |= pmp->pm_fatmask;
}
lck_mtx_unlock(pmp->pm_fat_lock);
return error;
}
static int
fillinusemap(struct msdosfsmount *pmp)
{
uint32_t cn, readcn=0;
int error = 0;
char *entry, *last_entry;
u_int32_t offset, block_size;
(void) advisory_read(pmp->pm_fat_active_vp, pmp->pm_fat_bytes,
0, pmp->pm_fat_bytes);
memset(pmp->pm_inusemap, 0xFF, ((pmp->pm_maxcluster / N_INUSEBITS) + 1) * sizeof(u_int));
pmp->pm_freeclustercount = 0;
for (cn = CLUST_FIRST; cn <= pmp->pm_maxcluster; ) {
entry = msdosfs_fat_map(pmp, cn, &offset, &block_size, &error);
if (!entry)
break;
last_entry = entry - offset + block_size;
while (entry < last_entry && cn <= pmp->pm_maxcluster)
{
switch (pmp->pm_fatmask)
{
case FAT12_MASK:
readcn = getuint16(entry);
if (cn & 1)
{
readcn >>= 4;
entry += 2;
}
else
{
readcn &= FAT12_MASK;
entry++;
}
break;
case FAT16_MASK:
readcn = getuint16(entry);
entry += 2;
break;
case FAT32_MASK:
readcn = getuint32(entry);
entry += 4;
readcn &= FAT32_MASK;
break;
}
if (readcn == 0)
usemap_free(pmp, cn);
cn++;
}
}
return error;
}
__private_extern__ int
extendfile(dep, count)
struct denode *dep;
uint32_t count;
{
int error=0;
uint32_t cn, got, reqcnt;
struct msdosfsmount *pmp = dep->de_pmp;
struct buf *bp = NULL;
lck_mtx_lock(dep->de_cluster_lock);
lck_mtx_lock(pmp->pm_fat_lock);
if (dep->de_StartCluster == MSDOSFSROOT
&& (dep->de_Attributes & ATTR_DIRECTORY))
{
error = ENOSPC;
goto exit;
}
if (dep->de_LastCluster == 0 &&
dep->de_StartCluster != 0)
{
error = pcbmap_internal(dep, 0xFFFFFFFF, 1, NULL, NULL, &dep->de_LastCluster);
if (error != E2BIG)
goto exit;
error = 0;
if (dep->de_LastCluster == 0)
{
printf("msdosfs: extendfile: dep->de_LastCluster == 0!\n");
error = EIO;
goto exit;
}
}
reqcnt = count;
while (count > 0) {
if (dep->de_StartCluster == 0)
cn = 0;
else
cn = dep->de_LastCluster + 1;
error = clusteralloc_internal(pmp, cn, count, CLUST_EOFE, &cn, &got);
if (error)
goto exit;
if (reqcnt == count)
{
if (dep->de_StartCluster == 0)
{
dep->de_cluster_logical = 0;
dep->de_cluster_physical = cn;
dep->de_cluster_count = got;
}
else if (cn == (dep->de_LastCluster+1) &&
cn == (dep->de_cluster_physical + dep->de_cluster_count))
{
dep->de_cluster_count += got;
}
}
count -= got;
if (dep->de_StartCluster == 0) {
dep->de_StartCluster = cn;
} else {
error = fatentry_internal(FAT_SET, pmp,
dep->de_LastCluster,
0, cn);
if (error) {
clusterfree_internal(pmp, cn, NULL);
goto exit;
}
}
dep->de_LastCluster = cn + got - 1;
if (dep->de_Attributes & ATTR_DIRECTORY) {
while (got-- > 0) {
bp = buf_getblk(pmp->pm_devvp, cntobn(pmp, cn++),
pmp->pm_bpcluster, 0, 0, BLK_META);
buf_clear(bp);
buf_bdwrite(bp);
}
}
}
exit:
lck_mtx_unlock(pmp->pm_fat_lock);
lck_mtx_unlock(dep->de_cluster_lock);
return error;
}
__private_extern__ int markvoldirty(struct msdosfsmount *pmp, int dirty)
{
int error=0;
uint32_t fatval;
void *entry;
if (FAT12(pmp))
return 0;
if (pmp->pm_flags & MSDOSFSMNT_RONLY)
return EROFS;
lck_mtx_lock(pmp->pm_fat_lock);
entry = msdosfs_fat_map(pmp, 1, NULL, NULL, &error);
if (!entry)
goto done;
if (FAT32(pmp)) {
fatval = getuint32(entry);
if (dirty)
fatval &= 0xF7FFFFFF;
else
fatval |= 0x08000000;
putuint32(entry, fatval);
}
else {
fatval = getuint16(entry);
if (dirty)
fatval &= 0x7FFF;
else
fatval |= 0x8000;
putuint16(entry, fatval);
}
pmp->pm_fat_flags |= FAT_CACHE_DIRTY;
error = msdosfs_fat_cache_flush(pmp, IO_SYNC);
done:
lck_mtx_unlock(pmp->pm_fat_lock);
return error;
}