#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mount.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/buf.h>
#include <sys/vnode.h>
#include <sys/ubc.h>
#include <sys/namei.h>
#include <mach/boolean.h>
#include <libkern/OSMalloc.h>
#include "bpb.h"
#include "msdosfsmount.h"
#include "direntry.h"
#include "denode.h"
#include "fat.h"
#ifndef DEBUG
#define DEBUG 0
#endif
OSMallocTag msdosfs_node_tag;
static lck_mtx_t *msdosfs_hash_lock = NULL;
static struct denode **dehashtbl;
static unsigned long dehash;
#define DEHASH(dev, dcl, doff) (dehashtbl[(minor(dev) + (dcl) + (doff)) & dehash])
union _qcvt {
quad_t qcvt;
int32_t val[2];
};
#define SETHIGH(q, h) { \
union _qcvt tmp; \
tmp.qcvt = (q); \
tmp.val[_QUAD_HIGHWORD] = (h); \
(q) = tmp.qcvt; \
}
#define SETLOW(q, l) { \
union _qcvt tmp; \
tmp.qcvt = (q); \
tmp.val[_QUAD_LOWWORD] = (l); \
(q) = tmp.qcvt; \
}
static struct denode *
msdosfs_hashget __P((dev_t dev, uint32_t dirclust,
uint32_t diroff));
static void msdosfs_hashins __P((struct denode *dep));
static void msdosfs_hashrem __P((struct denode *dep));
__private_extern__ void
msdosfs_hash_init(void)
{
msdosfs_hash_lock = lck_mtx_alloc_init(msdosfs_lck_grp, msdosfs_lck_attr);
msdosfs_node_tag = OSMalloc_Tagalloc("msdosfs denode", OSMT_DEFAULT);
dehashtbl = hashinit(desiredvnodes, M_TEMP, &dehash);
}
__private_extern__ void
msdosfs_hash_uninit(void)
{
OSMalloc_Tagfree(msdosfs_node_tag);
if (msdosfs_hash_lock)
lck_mtx_free(msdosfs_hash_lock, msdosfs_lck_grp);
}
static struct denode *
msdosfs_hashget(dev_t dev, uint32_t dirclust, uint32_t diroff)
{
int error;
struct denode *dep;
vnode_t vp;
u_int32_t vid;
struct denode *found = 0;
loop:
for (dep = DEHASH(dev, dirclust, diroff); dep; dep = dep->de_next)
{
if (dirclust == dep->de_dirclust
&& diroff == dep->de_diroffset
&& dev == dep->de_dev
&& dep->de_refcnt != 0)
{
if (ISSET(dep->de_flag, DE_INIT))
{
SET(dep->de_flag, DE_WAITINIT);
msleep(dep, msdosfs_hash_lock, PINOD, "msdosfs_hashget", 0);
goto loop;
}
if (dep->de_refcnt <= 0)
{
if (DEBUG)
printf("msdosfs_hashget: found deleted object\n");
msdosfs_hashrem(dep);
goto loop;
}
vp = DETOV(dep);
vid = vnode_vid(vp);
lck_mtx_unlock(msdosfs_hash_lock);
error = vnode_getwithvid(vp, vid);
lck_mtx_lock(msdosfs_hash_lock);
if (error)
goto loop;
if (DEBUG)
{
if (found)
panic("msdosfs_hashget: multiple denodes");
found = dep;
continue;
}
return dep;
}
}
if (DEBUG)
return found;
return NULL;
}
static void
msdosfs_hashins(dep)
struct denode *dep;
{
struct denode **depp, *deq;
if (DEBUG)
{
struct denode *found;
found = msdosfs_hashget(dep->de_dev, dep->de_dirclust, dep->de_diroffset);
if (found)
panic("msdosfs_hashins: denode already in hash? found=%p", found);
}
depp = &DEHASH(dep->de_dev, dep->de_dirclust, dep->de_diroffset);
deq = *depp;
if (deq)
deq->de_prev = &dep->de_next;
dep->de_next = deq;
dep->de_prev = depp;
*depp = dep;
}
static void
msdosfs_hashrem(dep)
struct denode *dep;
{
struct denode *deq;
deq = dep->de_next;
if (deq)
deq->de_prev = dep->de_prev;
*dep->de_prev = deq;
}
__private_extern__ int
deget(pmp, dirclust, diroffset, dvp, cnp, depp, context)
struct msdosfsmount *pmp;
uint32_t dirclust;
uint32_t diroffset;
vnode_t dvp;
struct componentname *cnp;
struct denode **depp;
vfs_context_t context;
{
int error;
dev_t dev = pmp->pm_dev;
struct mount *mntp = pmp->pm_mountp;
struct denode *dep;
struct dosdirentry *direntptr;
struct buf *bp = NULL;
struct timeval tv;
struct vnode_fsparam vfsp;
enum vtype vtype;
if (FAT32(pmp) && dirclust == MSDOSFSROOT)
dirclust = pmp->pm_rootdirblk;
lck_mtx_lock(msdosfs_hash_lock);
dep = msdosfs_hashget(dev, dirclust, diroffset);
if (dep != NULL) {
*depp = dep;
if (dvp && cnp && (cnp->cn_flags & MAKEENTRY) && (dep->de_flag & DE_ROOT) == 0)
cache_enter(dvp, DETOV(dep), cnp);
if (dep->de_parent == NULL && dvp != NULLVP && (dep->de_flag & DE_ROOT) == 0)
{
if (DEBUG) printf("deget: fixing de_parent\n");
dep->de_parent = VTODE(dvp);
}
lck_mtx_unlock(msdosfs_hash_lock);
return 0;
}
dep = OSMalloc(sizeof(struct denode), msdosfs_node_tag);
if (dep == NULL) {
*depp = NULL;
lck_mtx_unlock(msdosfs_hash_lock);
return ENOMEM;
}
bzero(dep, sizeof *dep);
dep->de_lock = lck_mtx_alloc_init(msdosfs_lck_grp, msdosfs_lck_attr);
dep->de_cluster_lock = lck_mtx_alloc_init(msdosfs_lck_grp, msdosfs_lck_attr);
dep->de_pmp = pmp;
dep->de_devvp = pmp->pm_devvp;
dep->de_dev = dev;
dep->de_dirclust = dirclust;
dep->de_diroffset = diroffset;
dep->de_refcnt = 1;
SET(dep->de_flag, DE_INIT);
msdosfs_hashins(dep);
lck_mtx_unlock(msdosfs_hash_lock);
vfsp.vnfs_markroot = 0;
if ((dirclust == MSDOSFSROOT
|| (FAT32(pmp) && dirclust == pmp->pm_rootdirblk))
&& diroffset == MSDOSFSROOT_OFS) {
vfsp.vnfs_markroot = 1;
SET(dep->de_flag, DE_ROOT);
bcopy(" ", dep->de_Name, 11);
dep->de_Attributes = ATTR_DIRECTORY;
dep->de_LowerCase = 0;
if (FAT32(pmp))
dep->de_StartCluster = pmp->pm_rootdirblk;
else {
dep->de_StartCluster = MSDOSFSROOT;
dep->de_FileSize = pmp->pm_rootdirsize * pmp->pm_BlockSize;
}
dep->de_CHun = 0;
dep->de_CTime = 0x0000;
dep->de_CDate = (0 << DD_YEAR_SHIFT) | (1 << DD_MONTH_SHIFT)
| (1 << DD_DAY_SHIFT);
dep->de_ADate = dep->de_CDate;
dep->de_MTime = dep->de_CTime;
dep->de_MDate = dep->de_CDate;
if (pmp->pm_label_cluster != CLUST_EOFE) {
error = readep(pmp, pmp->pm_label_cluster, pmp->pm_label_offset,
&bp, &direntptr, context);
if (!error) {
dep->de_CHun = direntptr->deCHundredth;
dep->de_CTime = getuint16(direntptr->deCTime);
dep->de_CDate = getuint16(direntptr->deCDate);
dep->de_ADate = getuint16(direntptr->deADate);
dep->de_MTime = getuint16(direntptr->deMTime);
dep->de_MDate = getuint16(direntptr->deMDate);
buf_brelse(bp);
bp = NULL;
}
}
} else {
error = readep(pmp, dirclust, diroffset, &bp, &direntptr, context);
if (error) goto fail;
bcopy(direntptr->deName, dep->de_Name, 11);
dep->de_Attributes = direntptr->deAttributes;
dep->de_LowerCase = direntptr->deLowerCase;
dep->de_StartCluster = getuint16(direntptr->deStartCluster);
if (FAT32(pmp))
dep->de_StartCluster |= getuint16(direntptr->deHighClust) << 16;
dep->de_FileSize = getuint32(direntptr->deFileSize);
if (direntptr->deAttributes & ATTR_DIRECTORY)
{
buf_brelse(bp);
bp = NULL;
if (DEBUG)
{
if (dep->de_StartCluster < CLUST_FIRST || dep->de_StartCluster > pmp->pm_maxcluster)
panic("deget: directory de_StartCluster=%u", dep->de_StartCluster);
}
error = readep(pmp, dep->de_StartCluster, 0, &bp, &direntptr, context);
if (error) goto fail;
}
dep->de_CHun = direntptr->deCHundredth;
dep->de_CTime = getuint16(direntptr->deCTime);
dep->de_CDate = getuint16(direntptr->deCDate);
dep->de_ADate = getuint16(direntptr->deADate);
dep->de_MTime = getuint16(direntptr->deMTime);
dep->de_MDate = getuint16(direntptr->deMDate);
buf_brelse(bp);
bp = NULL;
}
if (dep->de_Attributes & ATTR_DIRECTORY) {
uint32_t size;
vtype = VDIR;
if (dep->de_StartCluster != MSDOSFSROOT) {
error = pcbmap(dep, 0xFFFFFFFF, 1, NULL, &size, &dep->de_LastCluster);
if (error == E2BIG) {
dep->de_FileSize = de_cn2off(pmp, size);
error = 0;
}
if (error)
goto fail;
}
} else {
vtype = msdosfs_check_link(dep, context);
}
getmicrouptime(&tv);
SETHIGH(dep->de_modrev, tv.tv_sec);
SETLOW(dep->de_modrev, tv.tv_usec * 4294);
dep->de_parent = (dvp != NULLVP) ? VTODE(dvp) : NULL;
vfsp.vnfs_mp = mntp;
vfsp.vnfs_vtype = vtype;
vfsp.vnfs_str = "msdosfs";
vfsp.vnfs_dvp = dvp;
vfsp.vnfs_fsnode = dep;
vfsp.vnfs_cnp = cnp;
vfsp.vnfs_vops = msdosfs_vnodeop_p;
vfsp.vnfs_rdev = 0;
vfsp.vnfs_filesize = dep->de_FileSize;
if (dvp && cnp && (cnp->cn_flags & MAKEENTRY))
vfsp.vnfs_flags = 0;
else
vfsp.vnfs_flags = VNFS_NOCACHE;
vfsp.vnfs_marksystem = 0;
error = vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vfsp, &dep->de_vnode);
if (error)
goto fail;
vnode_addfsref(dep->de_vnode);
*depp = dep;
CLR(dep->de_flag, DE_INIT);
if (ISSET(dep->de_flag, DE_WAITINIT))
wakeup(dep);
return 0;
fail:
if (bp)
buf_brelse(bp);
lck_mtx_lock(msdosfs_hash_lock);
msdosfs_hashrem(dep);
lck_mtx_unlock(msdosfs_hash_lock);
if (ISSET(dep->de_flag, DE_WAITINIT))
wakeup(dep);
OSFree(dep, sizeof *dep, msdosfs_node_tag);
return error;
}
__private_extern__ int
deupdat(dep, waitfor, context)
struct denode *dep;
int waitfor;
vfs_context_t context;
{
#pragma unused (waitfor)
int error = 0;
int isRoot = 0;
uint32_t dirclust, diroffset;
struct buf *bp;
struct dosdirentry *dirp;
struct timespec ts;
struct msdosfsmount *pmp = dep->de_pmp;
if (vnode_vfsisrdonly(DETOV(dep)))
return (0);
getnanotime(&ts);
DETIMES(dep, &ts, &ts, &ts);
if ((dep->de_flag & DE_MODIFIED) == 0)
return 0;
isRoot = dep->de_flag & DE_ROOT;
if (isRoot && pmp->pm_label_cluster == CLUST_EOFE)
return 0;
if (dep->de_refcnt <= 0)
return 0;
if (dep->de_flag & DE_MODIFIED)
{
dep->de_flag &= ~DE_MODIFIED;
if (dep->de_Attributes & ATTR_DIRECTORY)
{
if (isRoot)
{
dirclust = pmp->pm_label_cluster;
diroffset = pmp->pm_label_offset;
}
else
{
dirclust = dep->de_StartCluster;
diroffset = 0;
}
}
else
{
dirclust = dep->de_dirclust;
diroffset = dep->de_diroffset;
}
error = readep(pmp, dirclust, diroffset, &bp, &dirp, context);
if (error) return (error);
if (isRoot)
DE_EXTERNALIZE_ROOT(dirp, dep);
else
{
if (DEBUG)
{
if ((dirp->deAttributes ^ dep->de_Attributes) & ATTR_DIRECTORY)
panic("deupdate: attributes are wrong");
if ((dep->de_Attributes & ATTR_DIRECTORY) == 0 &&
bcmp(dirp->deName, dep->de_Name, SHORT_NAME_LEN))
{
panic("deupdat: file name is wrong");
}
}
DE_EXTERNALIZE(dirp, dep);
}
error = buf_bdwrite(bp);
}
return error;
}
__private_extern__ int
detrunc(dep, length, flags, context)
struct denode *dep;
uint32_t length;
int flags;
vfs_context_t context;
{
int error;
int allerror;
int cluster_locked = 0;
uint32_t eofentry;
uint32_t chaintofree;
int isadir = dep->de_Attributes & ATTR_DIRECTORY;
struct msdosfsmount *pmp = dep->de_pmp;
vnode_t vp = DETOV(dep);
if (vnode_isvroot(vp) && !FAT32(pmp)) {
printf("detrunc(): can't truncate root directory, clust %d, offset %d\n",
dep->de_dirclust, dep->de_diroffset);
return (EINVAL);
}
if (dep->de_FileSize < length) {
return deextend(dep, length, flags, context);
}
lck_mtx_lock(dep->de_cluster_lock);
cluster_locked = 1;
if (length == 0) {
chaintofree = dep->de_StartCluster;
dep->de_StartCluster = 0;
dep->de_LastCluster = 0;
dep->de_cluster_count = 0;
} else {
uint32_t length_clusters;
length_clusters = de_clcount(pmp, length);
lck_mtx_lock(dep->de_pmp->pm_fat_lock);
error = pcbmap_internal(dep, length_clusters - 1, 1, NULL,
&eofentry, NULL);
lck_mtx_unlock(dep->de_pmp->pm_fat_lock);
if (error)
{
allerror = error;
goto exit;
}
if (length_clusters >= dep->de_cluster_logical)
{
if (length_clusters < (dep->de_cluster_logical + dep->de_cluster_count))
dep->de_cluster_count = length_clusters - dep->de_cluster_logical;
}
else
{
dep->de_cluster_count = 0;
}
}
allerror = buf_invalidateblks(vp, ((length > 0) ? BUF_WRITE_DATA : 0), 0, 0);
dep->de_FileSize = length;
if (!isadir)
dep->de_flag |= DE_UPDATE|DE_MODIFIED;
error = deupdat(dep, 1, context);
if (error && (allerror == 0))
allerror = error;
if (length != 0) {
error = fatentry(FAT_GET_AND_SET, pmp, eofentry,
&chaintofree, CLUST_EOFE);
if (error)
{
dep->de_cluster_count = 0;
allerror = error;
goto exit;
}
dep->de_LastCluster = eofentry;
}
if (chaintofree != 0 && !MSDOSFSEOF(pmp, chaintofree))
freeclusterchain(pmp, chaintofree);
lck_mtx_unlock(dep->de_cluster_lock);
cluster_locked = 0;
ubc_setsize(vp, (off_t)length);
exit:
if (cluster_locked)
lck_mtx_unlock(dep->de_cluster_lock);
return (allerror);
}
__private_extern__ int
deextend(dep, length, flags, context)
struct denode *dep;
uint32_t length;
int flags;
vfs_context_t context;
{
struct msdosfsmount *pmp = dep->de_pmp;
uint32_t count;
int error;
if (vnode_isvroot(DETOV(dep)) && !FAT32(pmp))
return (EINVAL);
if (dep->de_Attributes & ATTR_DIRECTORY)
return (EISDIR);
if (length <= dep->de_FileSize)
panic("deextend: file too large");
count = de_clcount(pmp, length) - de_clcount(pmp, dep->de_FileSize);
if (count > 0) {
if (count > pmp->pm_freeclustercount)
return (ENOSPC);
error = extendfile(dep, count);
if (error) {
(void) detrunc(dep, dep->de_FileSize, 0, context);
return (error);
}
}
if (!(flags & IO_NOZEROFILL)) {
error = cluster_write(DETOV(dep), (struct uio *) 0,
(off_t)dep->de_FileSize, (off_t)(length),
(off_t)dep->de_FileSize, (off_t)0,
(flags | IO_HEADZEROFILL));
if (error)
return (error);
}
ubc_setsize(DETOV(dep), (off_t)length);
dep->de_FileSize = length;
dep->de_flag |= DE_UPDATE|DE_MODIFIED;
return (deupdat(dep, 1, context));
}
__private_extern__ void
reinsert(dep)
struct denode *dep;
{
lck_mtx_lock(msdosfs_hash_lock);
msdosfs_hashrem(dep);
msdosfs_hashins(dep);
lck_mtx_unlock(msdosfs_hash_lock);
}
__private_extern__ int
msdosfs_reclaim(ap)
struct vnop_reclaim_args *ap;
{
vnode_t vp = ap->a_vp;
struct denode *dep = VTODE(vp);
if (dep == NULL)
return 0;
lck_mtx_lock(msdosfs_hash_lock);
msdosfs_hashrem(dep);
lck_mtx_unlock(msdosfs_hash_lock);
cache_purge(vp);
lck_mtx_free(dep->de_cluster_lock, msdosfs_lck_grp);
lck_mtx_free(dep->de_lock, msdosfs_lck_grp);
OSFree(dep, sizeof(struct denode), msdosfs_node_tag);
vnode_clearfsnode(vp);
vnode_removefsref(vp);
return 0;
}
__private_extern__ int
msdosfs_inactive(ap)
struct vnop_inactive_args *ap;
{
vnode_t vp = ap->a_vp;
vfs_context_t context = ap->a_context;
struct denode *dep = VTODE(vp);
int error = 0;
int needs_flush = 0;
if (dep == NULL)
return 0;
lck_mtx_lock(dep->de_lock);
if (dep->de_Name[0] == SLOT_DELETED)
goto out;
if (dep->de_refcnt <= 0 && ( !vnode_vfsisrdonly(vp))) {
error = detrunc(dep, (uint32_t) 0, 0, context);
needs_flush = 1;
dep->de_flag |= DE_UPDATE;
dep->de_Name[0] = SLOT_DELETED;
vnode_recycle(vp);
}
deupdat(dep, 1, context);
out:
if (needs_flush)
msdosfs_meta_flush(dep->de_pmp, FALSE);
lck_mtx_unlock(dep->de_lock);
return (error);
}
__private_extern__ uint32_t defileid(struct denode *dep)
{
uint32_t fileid;
fileid = dep->de_StartCluster;
if ((dep->de_Attributes & ATTR_DIRECTORY) && (dep->de_StartCluster == MSDOSFSROOT))
fileid = FILENO_ROOT;
if (fileid == 0)
fileid = FILENO_EMPTY;
return fileid;
}