#include "lf_hfs_attrlist.h"
#include "lf_hfs_locks.h"
#include "lf_hfs.h"
#include "lf_hfs_vfsutils.h"
#include "lf_hfs_vnops.h"
#include "lf_hfs_fileops_handler.h"
#include "lf_hfs_logger.h"
#include "lf_hfs_chash.h"
static void SetAttrIntoStruct(UVFSDirEntryAttr* psAttrEntry, struct cat_attr* pAttr, struct cat_desc* psDesc, struct hfsmount* psHfsm, struct cat_fork* pDataFork)
{
psAttrEntry->dea_attrs.fa_validmask = VALID_OUT_ATTR_MASK;
psAttrEntry->dea_attrs.fa_atime.tv_sec = pAttr->ca_atime;
psAttrEntry->dea_attrs.fa_ctime.tv_sec = pAttr->ca_ctime;
psAttrEntry->dea_attrs.fa_mtime.tv_sec = pAttr->ca_mtime;
psAttrEntry->dea_attrs.fa_birthtime.tv_sec = pAttr->ca_btime;
psAttrEntry->dea_attrs.fa_atime.tv_nsec = psAttrEntry->dea_attrs.fa_mtime.tv_nsec = psAttrEntry->dea_attrs.fa_ctime.tv_nsec = psAttrEntry->dea_attrs.fa_birthtime.tv_nsec = 0;
psAttrEntry->dea_attrs.fa_fileid = psDesc->cd_cnid;
psAttrEntry->dea_attrs.fa_parentid = psDesc->cd_parentcnid;
psAttrEntry->dea_attrs.fa_mode = pAttr->ca_mode & ALL_UVFS_MODES;
psAttrEntry->dea_attrs.fa_bsd_flags = pAttr->ca_bsdflags;
if (VTTOUVFS(IFTOVT(pAttr->ca_mode)) == UVFS_FA_TYPE_DIR)
{
psAttrEntry->dea_attrs.fa_nlink = 2 + pAttr->ca_entries;
if (psHfsm->jnl && psDesc->cd_cnid == kHFSRootFolderID)
{
psAttrEntry->dea_attrs.fa_nlink -= 2;
}
psAttrEntry->dea_attrs.fa_size = (pAttr->ca_entries + 2) * AVERAGE_HFSDIRENTRY_SIZE ;
} else {
psAttrEntry->dea_attrs.fa_nlink = pAttr->ca_linkcount;
psAttrEntry->dea_attrs.fa_size = pDataFork->cf_size;
}
psAttrEntry->dea_attrs.fa_allocsize = pDataFork->cf_blocks * HFSTOVCB(psHfsm)->blockSize;
psAttrEntry->dea_attrs.fa_type = VTTOUVFS(IFTOVT(pAttr->ca_mode));
psAttrEntry->dea_attrs.fa_gid = pAttr->ca_gid;
psAttrEntry->dea_attrs.fa_uid = pAttr->ca_uid ;
}
static void AddNewAttrEntry(ReadDirBuff_s* psReadDirBuffer, struct hfsmount* psHfsm, struct cat_desc* psDesc, struct cat_attr* pAttr, struct cat_fork* pDataFork, uint32_t* puUioSize, int iIndex, UVFSDirEntryAttr** ppsPrevAttrEntry)
{
UVFSDirEntryAttr* psAttrEntry = (UVFSDirEntryAttr*) (psReadDirBuffer->pvBuffer + READDIR_BUF_OFFSET(psReadDirBuffer));
SetAttrIntoStruct(psAttrEntry, pAttr, psDesc, psHfsm, pDataFork);
psAttrEntry->dea_namelen = psDesc->cd_namelen;
psAttrEntry->dea_nextrec = _UVFS_DIRENTRYATTR_RECLEN(UVFS_DIRENTRYATTR_NAMEOFF, psDesc->cd_namelen);
psAttrEntry->dea_spare0 = 0;
psAttrEntry->dea_nameoff = UVFS_DIRENTRYATTR_NAMEOFF;
const u_int8_t * name = psDesc->cd_nameptr;
memcpy( UVFS_DIRENTRYATTR_NAMEPTR(psAttrEntry), name, psDesc->cd_namelen );
UVFS_DIRENTRYATTR_NAMEPTR(psAttrEntry)[psAttrEntry->dea_namelen] = 0;
*puUioSize = psAttrEntry->dea_nextrec;
if (*ppsPrevAttrEntry != NULL)
{
(*ppsPrevAttrEntry)->dea_nextcookie = iIndex | ((u_int64_t)psDesc->cd_cnid << 32);
}
*ppsPrevAttrEntry = psAttrEntry;
psReadDirBuffer->uBufferResid -= *puUioSize;
return;
}
static bool CompareTimes(const struct timespec* psTimeA, const struct timespec* psTimeB)
{
if (psTimeA->tv_sec == psTimeB->tv_sec)
return psTimeA->tv_nsec == psTimeB->tv_nsec;
else
return psTimeA->tv_sec > psTimeB->tv_sec;
}
static bool DirScanIsMatch(ScanDirRequest_s* psScanDirRequest, struct cat_desc* psDesc, struct cat_attr* pAttr, struct hfsmount* psHfsm, struct cat_fork* pDataFork)
{
bool bIsMatch = true;
UVFSDirEntryAttr* psDirEntry = psScanDirRequest->psMatchingResult->smr_entry;
const char* pcName = (const char *) psDesc->cd_nameptr;
if (pcName && ((psDesc->cd_namelen == (sizeof(HFSPLUSMETADATAFOLDER) - 1) &&
memcmp(pcName, HFSPLUSMETADATAFOLDER, sizeof(HFSPLUSMETADATAFOLDER))) ||
(psDesc->cd_namelen == (sizeof(HFSPLUS_DIR_METADATA_FOLDER) - 1) &&
memcmp(pcName, HFSPLUS_DIR_METADATA_FOLDER, sizeof(HFSPLUS_DIR_METADATA_FOLDER)))))
{
return false;
} else if (pcName == NULL)
{
LFHFS_LOG(LEVEL_ERROR, "got NULL name during scandir: %#x!", psDesc->cd_cnid);
return false;
}
bool bAllowHiddenFiles = (psScanDirRequest->psMatchingCriteria->smr_attribute_filter->fa_validmask & LI_FA_VALID_BSD_FLAGS) &&
(psScanDirRequest->psMatchingCriteria->smr_attribute_filter->fa_bsd_flags & UF_HIDDEN);
if (bAllowHiddenFiles == false) {
if ( (pAttr->ca_flags & UF_HIDDEN) || (pcName[0] == '.') ) {
return false;
}
if ( S_ISDIR(pAttr->ca_mode)) {
if (pAttr->ca_finderdirinfo.frFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask)) {
return false;
}
} else { if (pAttr->ca_finderfileinfo.fdFlags & OSSwapHostToBigConstInt16(kFinderInvisibleMask)) {
return false;
}
}
}
if (psScanDirRequest->psMatchingCriteria->smr_filename_contains != NULL)
{
bool bAtLeastOneNameContainsMatched = false;
char** ppcNameContainsStr = psScanDirRequest->psMatchingCriteria->smr_filename_contains;
while ( (*ppcNameContainsStr) && (strlen(*ppcNameContainsStr) != 0) && !bAtLeastOneNameContainsMatched)
{
uint64_t uNameContainsLength = strlen(*ppcNameContainsStr);
if (uNameContainsLength <= strlen(pcName))
{
if(!hfs_strstr((const u_int8_t*) pcName, strlen(pcName), (const u_int8_t*) *ppcNameContainsStr, uNameContainsLength))
{
bAtLeastOneNameContainsMatched |= true;
}
}
ppcNameContainsStr++;
}
bIsMatch = bAtLeastOneNameContainsMatched;
}
if (!bIsMatch) goto check_if_directory;
if (psScanDirRequest->psMatchingCriteria->smr_filename_ends_with != NULL)
{
bool bAtLeastOneNameEndWithMatched = false;
char** ppcNameEndsWithStr = psScanDirRequest->psMatchingCriteria->smr_filename_ends_with;
while ( (*ppcNameEndsWithStr) && (strlen(*ppcNameEndsWithStr) != 0) && !bAtLeastOneNameEndWithMatched)
{
uint64_t uNameEndsWithLength = strlen(*ppcNameEndsWithStr);
if (uNameEndsWithLength <= strlen(pcName))
{
if ( !hfs_apendixcmp((const u_int8_t*) pcName, strlen(pcName),(const u_int8_t*) *ppcNameEndsWithStr, uNameEndsWithLength) )
{
bAtLeastOneNameEndWithMatched |= true;
}
}
ppcNameEndsWithStr++;
}
bIsMatch = bAtLeastOneNameEndWithMatched;
}
if (!bIsMatch) goto check_if_directory;
if (psScanDirRequest->psMatchingCriteria->smr_attribute_filter->fa_validmask != 0)
{
if (psScanDirRequest->psMatchingCriteria->smr_attribute_filter->fa_validmask & UVFS_FA_VALID_TYPE)
{
uint32_t uEntryType = VTTOUVFS(IFTOVT(pAttr->ca_mode));
if ((psScanDirRequest->psMatchingCriteria->smr_attribute_filter->fa_type & uEntryType) != uEntryType)
{
bIsMatch = false;
}
}
if (!bIsMatch) goto check_if_directory;
if (psScanDirRequest->psMatchingCriteria->smr_attribute_filter->fa_validmask & UVFS_FA_VALID_MTIME)
{
struct timespec mTime = {0};
mTime.tv_sec = pAttr->ca_mtime;
if (!CompareTimes(&mTime, &psScanDirRequest->psMatchingCriteria->smr_attribute_filter->fa_mtime))
{
bIsMatch = false;
}
}
}
if (bIsMatch)
{
psScanDirRequest->psMatchingResult->smr_result_type = SEARCH_RESULT_MATCH;
}
check_if_directory:
if (VTTOUVFS(IFTOVT(pAttr->ca_mode)) == UVFS_FA_TYPE_DIR)
{
psScanDirRequest->psMatchingResult->smr_result_type |= SEARCH_RESULT_PUSH;
bIsMatch = true;
}
if (bIsMatch)
{
UVFSDirEntryAttr* psAttrEntry = psScanDirRequest->psMatchingResult->smr_entry;
SetAttrIntoStruct(psAttrEntry, pAttr, psDesc, psHfsm, pDataFork);
psDirEntry->dea_namelen = strlen( pcName );
psDirEntry->dea_spare0 = 0;
psDirEntry->dea_nameoff = UVFS_DIRENTRYATTR_NAMEOFF;
memcpy( UVFS_DIRENTRYATTR_NAMEPTR(psDirEntry), pcName, psDesc->cd_namelen);
UVFS_DIRENTRYATTR_NAMEPTR(psDirEntry)[psDirEntry->dea_namelen] = 0;
}
return bIsMatch;
}
int
hfs_scandir(struct vnode *dvp, ScanDirRequest_s* psScanDirRequest)
{
int error = 0;
struct cat_entrylist *ce_list = NULL;
struct cnode* dcp = VTOC(dvp);
struct hfsmount* hfsmp = VTOHFS(dvp);
uint64_t uCookie = psScanDirRequest->psMatchingCriteria->smr_start_cookie;
int reachedeof = 0;
if ((error = hfs_lock(dcp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT)))
{
return (error);
}
ce_list = hfs_mallocz(CE_LIST_SIZE(2));
ce_list->maxentries = 2;
bool bContinueIterating = true;
while (bContinueIterating)
{
int lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
int index = uCookie & HFS_INDEX_MASK;
unsigned int tag = (unsigned int) uCookie & ~HFS_INDEX_MASK;
directoryhint_t* dirhint = hfs_getdirhint(dcp, ((index - 1) & HFS_INDEX_MASK) | tag, TRUE);
dirhint->dh_index &= HFS_INDEX_MASK;
if (dirhint->dh_index == HFS_INDEX_MASK)
{
dirhint->dh_index = -1;
}
error = cat_getentriesattr(hfsmp, dirhint, ce_list, &reachedeof);
hfs_systemfile_unlock(hfsmp, lockflags);
if (error == ENOENT)
{
error = 0;
if (ce_list->realentries == 0)
{
psScanDirRequest->psMatchingResult->smr_entry->dea_nextcookie = UVFS_DIRCOOKIE_EOF;
hfs_reldirhint(dcp, dirhint);
goto exit;
}
}
else if (error)
{
hfs_reldirhint(dcp, dirhint);
goto exit;
}
dcp->c_touch_acctime = true;
if ((dcp->c_entries == 0) && (ce_list->realentries > 0))
{
dcp->c_entries++;
dcp->c_flag |= C_MODIFIED;
LFHFS_LOG(LEVEL_DEBUG, "hfs_scandir: repairing valence to non-zero! \n");
hfs_update(dvp, 0);
}
struct cnode *cp = NULL;
struct cat_desc* psDesc = &ce_list->entry[0].ce_desc;
struct cat_attr* psAttr = &ce_list->entry[0].ce_attr;
struct hfsmount* psHfsmp = VTOHFS(dvp);
struct cat_fork sDataFork;
bzero(&sDataFork, sizeof(sDataFork));
sDataFork.cf_size = ce_list->entry[0].ce_datasize;
sDataFork.cf_blocks = ce_list->entry[0].ce_datablks;
struct vnode *vp = hfs_chash_getvnode(hfsmp, psAttr->ca_fileid, false, false, false);
if (vp != NULL)
{
cp = VTOC(vp);
if (!(cp->c_flag & C_HARDLINK) && cp->c_desc.cd_nameptr != NULL)
psDesc = &cp->c_desc;
psAttr = &cp->c_attr;
if (cp->c_datafork)
{
sDataFork.cf_size = cp->c_datafork->ff_size;
sDataFork.cf_blocks = cp->c_datafork->ff_blocks;
}
}
bool bIsAMatch = DirScanIsMatch(psScanDirRequest, psDesc, psAttr, psHfsmp, &sDataFork);
if (vp != NULL)
{
hfs_unlock(cp);
cp = NULL;
hfs_vnop_reclaim(vp);
}
index++;
if (ce_list->skipentries)
{
index += ce_list->skipentries;
ce_list->skipentries = 0;
}
uCookie = reachedeof ? UVFS_DIRCOOKIE_EOF : (index | ((u_int64_t)ce_list->entry[1].ce_desc.cd_cnid << 32));
if (bIsAMatch)
{
bContinueIterating = false;
psScanDirRequest->psMatchingResult->smr_entry->dea_nextcookie = uCookie;
psScanDirRequest->psMatchingResult->smr_entry->dea_nextrec = 0;
}
if (reachedeof)
hfs_reldirhint(dcp, dirhint);
else
hfs_insertdirhint(dcp, dirhint);
for (uint32_t i =0; i < ce_list->realentries; i++)
{
cat_releasedesc(&ce_list->entry[i].ce_desc);
}
ce_list->realentries = 0;
}
exit:
hfs_unlock(dcp);
dcp = NULL;
if (ce_list)
{
for (int i = 0; i < (int)ce_list->realentries; ++i)
{
cat_releasedesc(&ce_list->entry[i].ce_desc);
}
hfs_free(ce_list);
}
return (error);
}
int
hfs_readdirattr_internal(struct vnode *dvp, ReadDirBuff_s* psReadDirBuffer, int maxcount, uint32_t *newstate, int *eofflag, int *actualcount, uint64_t uCookie)
{
int error = 0;
struct cat_desc *lastdescp = NULL;
struct cat_entrylist *ce_list = NULL;
UVFSDirEntryAttr* psPrevAttrEntry = NULL;
int reachedeof = 0;
*(actualcount) = *(eofflag) = 0;
if ((error = hfs_lock(VTOC(dvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT)))
{
return (error);
}
struct cnode* dcp = VTOC(dvp);
struct hfsmount* hfsmp = VTOHFS(dvp);
u_int32_t dirchg = dcp->c_dirchangecnt;
int index = uCookie & HFS_INDEX_MASK;
unsigned int tag = (unsigned int) uCookie & ~HFS_INDEX_MASK;
directoryhint_t* dirhint = hfs_getdirhint(dcp, ((index - 1) & HFS_INDEX_MASK) | tag, TRUE);
dirhint->dh_index &= HFS_INDEX_MASK;
if (dirhint->dh_index == HFS_INDEX_MASK)
{
dirhint->dh_index = -1;
}
if (maxcount < 1)
{
error = EINVAL;
goto exit2;
}
ce_list = hfs_mallocz(CE_LIST_SIZE(maxcount));
ce_list->maxentries = maxcount;
int lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
error = cat_getentriesattr(hfsmp, dirhint, ce_list, &reachedeof);
hfs_systemfile_unlock(hfsmp, lockflags);
if ((error == ENOENT) || (reachedeof != 0))
{
*(eofflag) = true;
error = 0;
}
if (error)
{
goto exit1;
}
dcp->c_touch_acctime = true;
if ((dcp->c_entries == 0) && (ce_list->realentries > 0))
{
dcp->c_entries++;
dcp->c_flag |= C_MODIFIED;
LFHFS_LOG(LEVEL_DEBUG, "hfs_readdirattr_internal: repairing valence to non-zero! \n");
hfs_update(dvp, 0);
}
hfs_unlock(dcp);
dcp = NULL;
for (int i = 0; i < (int)ce_list->realentries; ++i)
{
struct cnode *cp = NULL;
struct vnode *vp = NULL;
struct cat_desc * cdescp;
struct cat_attr * cattrp;
struct cat_fork c_datafork;
struct cat_fork c_rsrcfork;
bzero(&c_datafork, sizeof(c_datafork));
bzero(&c_rsrcfork, sizeof(c_rsrcfork));
cdescp = &ce_list->entry[i].ce_desc;
cattrp = &ce_list->entry[i].ce_attr;
c_datafork.cf_size = ce_list->entry[i].ce_datasize;
c_datafork.cf_blocks = ce_list->entry[i].ce_datablks;
c_rsrcfork.cf_size = ce_list->entry[i].ce_rsrcsize;
c_rsrcfork.cf_blocks = ce_list->entry[i].ce_rsrcblks;
vp = hfs_chash_getvnode(hfsmp, cattrp->ca_fileid, false, false, false);
if (vp != NULL)
{
cp = VTOC(vp);
if (!(cp->c_flag & C_HARDLINK))
cdescp = &cp->c_desc;
cattrp = &cp->c_attr;
if (cp->c_datafork)
{
c_datafork.cf_size = cp->c_datafork->ff_size;
c_datafork.cf_blocks = cp->c_datafork->ff_blocks;
}
if (cp->c_rsrcfork)
{
c_rsrcfork.cf_size = cp->c_rsrcfork->ff_size;
c_rsrcfork.cf_blocks = cp->c_rsrcfork->ff_blocks;
}
hfs_unlock(cp);
cp = NULL;
hfs_vnop_reclaim(vp);
}
u_int32_t currattrbufsize;
AddNewAttrEntry(psReadDirBuffer, hfsmp, cdescp, cattrp, &c_datafork, &currattrbufsize, index, &psPrevAttrEntry);
if (currattrbufsize == 0)
{
break;
}
lastdescp = &ce_list->entry[i].ce_desc;
index++;
*actualcount += 1;
if ((--maxcount <= 0) || (psReadDirBuffer->uBufferResid < _UVFS_DIRENTRYATTR_RECLEN(UVFS_DIRENTRYATTR_NAMEOFF, 128)))
{
break;
}
}
if (*eofflag && (*actualcount < (int)ce_list->realentries))
*eofflag = 0;
if (ce_list->skipentries)
{
index += ce_list->skipentries;
ce_list->skipentries = 0;
}
if ((*(eofflag) == 0) && (lastdescp != NULL))
{
if ((dirhint->dh_desc.cd_flags & CD_HASBUF) && (dirhint->dh_desc.cd_nameptr != NULL))
{
dirhint->dh_desc.cd_flags &= ~CD_HASBUF;
if (dirhint->dh_desc.cd_nameptr != NULL)
hfs_free((void *) dirhint->dh_desc.cd_nameptr);
dirhint->dh_desc.cd_nameptr = NULL;
}
if (lastdescp->cd_nameptr != NULL)
{
dirhint->dh_desc.cd_namelen = lastdescp->cd_namelen;
dirhint->dh_desc.cd_nameptr = hfs_malloc(sizeof(char)*lastdescp->cd_namelen);
if (dirhint->dh_desc.cd_nameptr == NULL)
{
error = ENOMEM;
goto exit2;
}
memcpy((void *) dirhint->dh_desc.cd_nameptr,(void *) lastdescp->cd_nameptr,lastdescp->cd_namelen);
dirhint->dh_desc.cd_flags |= CD_HASBUF;
}
else
{
dirhint->dh_desc.cd_namelen = 0;
dirhint->dh_desc.cd_nameptr = NULL;
}
dirhint->dh_index = index - 1;
dirhint->dh_desc.cd_cnid = lastdescp->cd_cnid;
dirhint->dh_desc.cd_hint = lastdescp->cd_hint;
dirhint->dh_desc.cd_encoding = lastdescp->cd_encoding;
}
for (int i = 0; i < (int)ce_list->realentries; ++i)
{
cat_releasedesc(&ce_list->entry[i].ce_desc);
}
ce_list->realentries = 0;
(void) hfs_lock(VTOC(dvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS);
dcp = VTOC(dvp);
exit1:
while (tag == 0){
tag = (++dcp->c_dirhinttag) << HFS_INDEX_BITS;
}
if (psPrevAttrEntry != NULL)
{
psPrevAttrEntry->dea_nextcookie = *eofflag ? UVFS_DIRCOOKIE_EOF : index | tag;
psPrevAttrEntry->dea_nextrec = 0;
}
dirhint->dh_index |= tag;
exit2:
if (newstate)
*newstate = dirchg;
if (dirhint)
{
if ((error != 0) || *(eofflag))
{
hfs_reldirhint(dcp, dirhint);
}
else
{
hfs_insertdirhint(dcp, dirhint);
}
}
if (ce_list)
hfs_free(ce_list);
hfs_unlock(dcp);
return (error);
}