#include <sys/xattr.h>
#include <sys/utfconv.h>
#include <libkern/OSByteOrder.h>
#include <sys/stat.h>
#include "hfs.h"
#include "hfs_fsctl.h"
#include "hfs_endian.h"
#include "BTreesInternal.h"
#include "BTreesPrivate.h"
#include "FileMgrInternal.h"
#include "hfs_cprotect.h"
union HFSPlusRecord {
HFSPlusCatalogFolder folder_record;
HFSPlusCatalogFile file_record;
HFSPlusCatalogThread thread_record;
HFSPlusExtentRecord extent_record;
HFSPlusAttrRecord attr_record;
};
typedef union HFSPlusRecord HFSPlusRecord;
union HFSPlusKey {
HFSPlusExtentKey extent_key;
HFSPlusAttrKey attr_key;
};
typedef union HFSPlusKey HFSPlusKey;
typedef enum traverse_btree_flag {
TRAVERSE_BTREE_EXTENTS = 1,
TRAVERSE_BTREE_XATTR_CPROTECT = 2,
} traverse_btree_flag_t;
static errno_t hfs_fsinfo_metadata_blocks(struct hfsmount *hfsmp, struct hfs_fsinfo_metadata *fsinfo);
static errno_t hfs_fsinfo_metadata_extents(struct hfsmount *hfsmp, struct hfs_fsinfo_metadata *fsinfo);
static errno_t hfs_fsinfo_metadata_percentfree(struct hfsmount *hfsmp, struct hfs_fsinfo_metadata *fsinfo);
static errno_t fsinfo_file_extent_count_callback(struct hfsmount *hfsmp, HFSPlusKey *key, HFSPlusRecord *record, void *data);
static errno_t fsinfo_file_extent_size_catalog_callback(struct hfsmount *hfsmp, HFSPlusKey *key, HFSPlusRecord *record, void *data);
static errno_t fsinfo_file_extent_size_overflow_callback(struct hfsmount *hfsmp, HFSPlusKey *key, HFSPlusRecord *record, void *data);
static errno_t fsinfo_file_size_callback(struct hfsmount *hfsmp, HFSPlusKey *key, HFSPlusRecord *record, void *data);
static errno_t fsinfo_dir_valence_callback(struct hfsmount *hfsmp, HFSPlusKey *key, HFSPlusRecord *record, void *data);
static errno_t fsinfo_name_size_callback(struct hfsmount *hfsmp, HFSPlusKey *key, HFSPlusRecord *record, void *data);
static errno_t fsinfo_xattr_size_callback(struct hfsmount *hfsmp, HFSPlusKey *key, HFSPlusRecord *record, void *data);
static errno_t traverse_btree(struct hfsmount *hfsmp, uint32_t btree_fileID, int flags, void *fsinfo,
int (*callback)(struct hfsmount *, HFSPlusKey *, HFSPlusRecord *, void *));
static errno_t hfs_fsinfo_free_extents(struct hfsmount *hfsmp, struct hfs_fsinfo_data *fsinfo);
static void fsinfo_free_extents_callback(void *data, off_t free_extent_size);
#if CONFIG_PROTECT
static errno_t fsinfo_cprotect_count_callback(struct hfsmount *hfsmp, HFSPlusKey *key, HFSPlusRecord *record, void *data);
#endif
static errno_t fsinfo_symlink_size_callback(struct hfsmount *hfsmp, HFSPlusKey *key, HFSPlusRecord *record, void *data);
errno_t hfs_get_fsinfo(struct hfsmount *hfsmp, void *a_data)
{
int error = 0;
hfs_fsinfo *fsinfo_union;
uint32_t request_type;
uint32_t header_len = sizeof(hfs_fsinfo_header_t);
fsinfo_union = (hfs_fsinfo *)a_data;
request_type = fsinfo_union->header.request_type;
bzero((char *)fsinfo_union + header_len, sizeof(hfs_fsinfo) - header_len);
switch (request_type) {
case HFS_FSINFO_METADATA_BLOCKS_INFO:
error = hfs_fsinfo_metadata_blocks(hfsmp, &(fsinfo_union->metadata));
break;
case HFS_FSINFO_METADATA_EXTENTS:
error = hfs_fsinfo_metadata_extents(hfsmp, &(fsinfo_union->metadata));
break;
case HFS_FSINFO_METADATA_PERCENTFREE:
error = hfs_fsinfo_metadata_percentfree(hfsmp, &(fsinfo_union->metadata));
break;
case HFS_FSINFO_FILE_EXTENT_COUNT:
error = traverse_btree(hfsmp, kHFSCatalogFileID, TRAVERSE_BTREE_EXTENTS, &(fsinfo_union->data), fsinfo_file_extent_count_callback);
break;
case HFS_FSINFO_FILE_EXTENT_SIZE:
error = traverse_btree(hfsmp, kHFSCatalogFileID, 0, &(fsinfo_union->data), &fsinfo_file_extent_size_catalog_callback);
if (error) {
break;
}
error = traverse_btree(hfsmp, kHFSExtentsFileID, 0, &(fsinfo_union->data), &fsinfo_file_extent_size_overflow_callback);
break;
case HFS_FSINFO_FILE_SIZE:
error = traverse_btree(hfsmp, kHFSCatalogFileID, 0, &(fsinfo_union->data), &fsinfo_file_size_callback);
break;
case HFS_FSINFO_DIR_VALENCE:
error = traverse_btree(hfsmp, kHFSCatalogFileID, 0, &(fsinfo_union->data), &fsinfo_dir_valence_callback);
break;
case HFS_FSINFO_NAME_SIZE:
error = traverse_btree(hfsmp, kHFSCatalogFileID, 0, &(fsinfo_union->name), &fsinfo_name_size_callback);
break;
case HFS_FSINFO_XATTR_SIZE:
error = traverse_btree(hfsmp, kHFSAttributesFileID, 0, &(fsinfo_union->data), &fsinfo_xattr_size_callback);
break;
case HFS_FSINFO_FREE_EXTENTS:
error = hfs_fsinfo_free_extents(hfsmp, &(fsinfo_union->data));
break;
case HFS_FSINFO_SYMLINK_SIZE:
error = traverse_btree(hfsmp, kHFSCatalogFileID, 0, &(fsinfo_union->data), &fsinfo_symlink_size_callback);
break;
#if CONFIG_PROTECT
case HFS_FSINFO_FILE_CPROTECT_COUNT:
error = traverse_btree(hfsmp, kHFSAttributesFileID, TRAVERSE_BTREE_XATTR_CPROTECT, &(fsinfo_union->cprotect), &fsinfo_cprotect_count_callback);
break;
#endif
default:
return ENOTSUP;
};
return error;
}
static errno_t
hfs_fsinfo_metadata_blocks(struct hfsmount *hfsmp, struct hfs_fsinfo_metadata *fsinfo)
{
int lockflags = 0;
int ret_lockflags = 0;
lockflags = SFL_CATALOG | SFL_EXTENTS | SFL_BITMAP | SFL_ATTRIBUTE;
ret_lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_SHARED_LOCK);
fsinfo->extents = hfsmp->hfs_extents_cp->c_datafork->ff_blocks;
fsinfo->catalog = hfsmp->hfs_catalog_cp->c_datafork->ff_blocks;
fsinfo->allocation = hfsmp->hfs_allocation_cp->c_datafork->ff_blocks;
if (hfsmp->hfs_attribute_cp)
fsinfo->attribute = hfsmp->hfs_attribute_cp->c_datafork->ff_blocks;
else
fsinfo->attribute = 0;
hfs_systemfile_unlock(hfsmp, ret_lockflags);
fsinfo->journal = howmany(hfsmp->jnl_size, hfsmp->blockSize);
return 0;
}
static uint32_t
hfs_count_extents_fp(struct filefork *ff)
{
int i;
uint32_t count = 0;
for (i = 0; i < kHFSPlusExtentDensity; i++) {
if (ff->ff_data.cf_extents[i].blockCount == 0) {
break;
}
count++;
}
return count;
}
static errno_t
hfs_count_overflow_extents(struct hfsmount *hfsmp, uint32_t fileID, uint32_t *num_extents)
{
int error;
FCB *fcb;
struct BTreeIterator *iterator = NULL;
FSBufferDescriptor btdata;
HFSPlusExtentKey *extentKey;
HFSPlusExtentRecord extentData;
uint32_t extent_count = 0;
int i;
fcb = VTOF(hfsmp->hfs_extents_vp);
iterator = hfs_mallocz(sizeof(struct BTreeIterator));
extentKey = (HFSPlusExtentKey *) &iterator->key;
extentKey->keyLength = kHFSPlusExtentKeyMaximumLength;
extentKey->forkType = kHFSDataForkType;
extentKey->fileID = fileID;
extentKey->startBlock = 0;
btdata.bufferAddress = &extentData;
btdata.itemSize = sizeof(HFSPlusExtentRecord);
btdata.itemCount = 1;
error = BTSearchRecord(fcb, iterator, &btdata, NULL, iterator);
if (error && error != fsBTRecordNotFoundErr && error != fsBTEndOfIterationErr)
goto out;
error = 0;
for (;;) {
if (msleep(NULL, NULL, PINOD | PCATCH,
"hfs_fsinfo", NULL) == EINTR) {
error = EINTR;
break;
}
error = BTIterateRecord(fcb, kBTreeNextRecord, iterator, &btdata, NULL);
if (error != 0) {
if (error == fsBTRecordNotFoundErr || error == fsBTEndOfIterationErr) {
error = 0;
}
break;
}
if (extentKey->fileID != fileID) {
break;
}
if (extentKey->forkType != kHFSDataForkType)
break;
for (i = 0; i < kHFSPlusExtentDensity; i++) {
if (extentData[i].blockCount == 0) {
break;
}
extent_count++;
}
}
out:
hfs_free(iterator, sizeof(*iterator));
if (error == 0) {
*num_extents = extent_count;
}
return MacToVFSError(error);
}
static errno_t
hfs_fsinfo_metadata_extents(struct hfsmount *hfsmp, struct hfs_fsinfo_metadata *fsinfo)
{
int error = 0;
int lockflags = 0;
int ret_lockflags = 0;
uint32_t overflow_count;
lockflags = SFL_CATALOG | SFL_EXTENTS | SFL_BITMAP | SFL_ATTRIBUTE;
ret_lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_SHARED_LOCK);
fsinfo->extents = hfs_count_extents_fp(hfsmp->hfs_extents_cp->c_datafork);
fsinfo->catalog = hfs_count_extents_fp(hfsmp->hfs_catalog_cp->c_datafork);
if (fsinfo->catalog >= kHFSPlusExtentDensity) {
error = hfs_count_overflow_extents(hfsmp, kHFSCatalogFileID, &overflow_count);
if (error) {
goto out;
}
fsinfo->catalog += overflow_count;
}
fsinfo->allocation = hfs_count_extents_fp(hfsmp->hfs_allocation_cp->c_datafork);
if (fsinfo->allocation >= kHFSPlusExtentDensity) {
error = hfs_count_overflow_extents(hfsmp, kHFSAllocationFileID, &overflow_count);
if (error) {
goto out;
}
fsinfo->allocation += overflow_count;
}
if (hfsmp->hfs_attribute_cp) {
fsinfo->attribute = hfs_count_extents_fp(hfsmp->hfs_attribute_cp->c_datafork);
if (fsinfo->attribute >= kHFSPlusExtentDensity) {
error = hfs_count_overflow_extents(hfsmp, kHFSAttributesFileID, &overflow_count);
if (error) {
goto out;
}
fsinfo->attribute += overflow_count;
}
}
fsinfo->journal = 1;
out:
hfs_systemfile_unlock(hfsmp, ret_lockflags);
return error;
}
static inline uint32_t
hfs_percent(uint32_t X, uint32_t Y)
{
return (X * 100ll) / Y;
}
static errno_t
hfs_fsinfo_metadata_percentfree(struct hfsmount *hfsmp, struct hfs_fsinfo_metadata *fsinfo)
{
int lockflags = 0;
int ret_lockflags = 0;
BTreeControlBlockPtr btreePtr;
uint32_t free_nodes, total_nodes;
lockflags = SFL_CATALOG | SFL_EXTENTS | SFL_BITMAP | SFL_ATTRIBUTE;
ret_lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_SHARED_LOCK);
btreePtr = VTOF(hfsmp->hfs_extents_vp)->fcbBTCBPtr;
total_nodes = btreePtr->totalNodes;
free_nodes = btreePtr->freeNodes;
fsinfo->extents = hfs_percent(free_nodes, total_nodes);
btreePtr = VTOF(hfsmp->hfs_catalog_vp)->fcbBTCBPtr;
total_nodes = btreePtr->totalNodes;
free_nodes = btreePtr->freeNodes;
fsinfo->catalog = hfs_percent(free_nodes, total_nodes);
if (hfsmp->hfs_attribute_vp) {
btreePtr = VTOF(hfsmp->hfs_attribute_vp)->fcbBTCBPtr;
total_nodes = btreePtr->totalNodes;
free_nodes = btreePtr->freeNodes;
fsinfo->attribute = hfs_percent(free_nodes, total_nodes);
}
hfs_systemfile_unlock(hfsmp, ret_lockflags);
return 0;
}
static inline int
hfs_log2(uint64_t entry)
{
return (63 - __builtin_clzll(entry|1));
}
void hfs_fsinfo_data_add(struct hfs_fsinfo_data *fsinfo, uint64_t entry)
{
uint32_t bucket;
if (entry) {
bucket = MIN(hfs_log2(entry) + 1, HFS_FSINFO_DATA_MAX_BUCKETS-1);
++fsinfo->bucket[bucket];
} else {
fsinfo->bucket[0]++;
}
}
static errno_t
traverse_btree(struct hfsmount *hfsmp, uint32_t btree_fileID, int flags,
void *fsinfo, int (*callback)(struct hfsmount *, HFSPlusKey *, HFSPlusRecord *, void *))
{
int error = 0;
int lockflags = 0;
int ret_lockflags = 0;
FCB *fcb;
struct BTreeIterator *iterator = NULL;
struct FSBufferDescriptor btdata;
int btree_operation;
HFSPlusRecord record;
HFSPlusKey *key;
uint64_t start, timeout_abs;
switch(btree_fileID) {
case kHFSExtentsFileID:
fcb = VTOF(hfsmp->hfs_extents_vp);
lockflags = SFL_EXTENTS;
break;
case kHFSCatalogFileID:
fcb = VTOF(hfsmp->hfs_catalog_vp);
lockflags = SFL_CATALOG;
break;
case kHFSAttributesFileID:
if (hfsmp->hfs_attribute_vp == NULL)
return error;
fcb = VTOF(hfsmp->hfs_attribute_vp);
lockflags = SFL_ATTRIBUTE;
break;
default:
return EINVAL;
}
iterator = hfs_mallocz(sizeof(struct BTreeIterator));
key = (HFSPlusKey *)&iterator->key;
if (flags & TRAVERSE_BTREE_EXTENTS) {
lockflags |= SFL_EXTENTS;
}
btdata.bufferAddress = &record;
btdata.itemSize = sizeof(HFSPlusRecord);
btdata.itemCount = 1;
ret_lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_SHARED_LOCK);
btree_operation = kBTreeFirstRecord;
nanoseconds_to_absolutetime(HFS_FSINFO_MAX_LOCKHELD_TIME, &timeout_abs);
start = mach_absolute_time();
while (1) {
if (msleep(NULL, NULL, PINOD | PCATCH,
"hfs_fsinfo", NULL) == EINTR) {
error = EINTR;
break;
}
error = BTIterateRecord(fcb, btree_operation, iterator, &btdata, NULL);
if (error != 0) {
if (error == fsBTRecordNotFoundErr || error == fsBTEndOfIterationErr) {
error = 0;
}
break;
}
btree_operation = kBTreeNextRecord;
error = callback(hfsmp, key, &record, fsinfo);
if (error) {
break;
}
if ((mach_absolute_time() - start) >= timeout_abs) {
hfs_systemfile_unlock (hfsmp, ret_lockflags);
tsleep((caddr_t)hfsmp, PRIBIO, "hfs_fsinfo", 1);
ret_lockflags = hfs_systemfile_lock (hfsmp, lockflags, HFS_SHARED_LOCK);
start = mach_absolute_time();
}
}
hfs_systemfile_unlock(hfsmp, ret_lockflags);
hfs_free(iterator, sizeof(*iterator));
return MacToVFSError(error);
}
static errno_t
fsinfo_file_extent_count_callback(struct hfsmount *hfsmp,
__unused HFSPlusKey *key, HFSPlusRecord *record, void *data)
{
int i;
int error = 0;
uint32_t num_extents = 0;
uint32_t num_overflow = 0;
uint32_t blockCount;
if (record->file_record.recordType == kHFSPlusFileRecord) {
for (i = 0; i < kHFSPlusExtentDensity; i++) {
blockCount = record->file_record.dataFork.extents[i].blockCount;
if (blockCount == 0) {
break;
}
num_extents++;
}
if (num_extents >= kHFSPlusExtentDensity) {
error = hfs_count_overflow_extents(hfsmp, record->file_record.fileID, &num_overflow);
if (error) {
goto out;
}
num_extents += num_overflow;
}
hfs_fsinfo_data_add(data, num_extents);
}
out:
return error;
}
static errno_t fsinfo_file_extent_size_catalog_callback(struct hfsmount *hfsmp,
__unused HFSPlusKey *key, HFSPlusRecord *record, void *data)
{
int i;
uint32_t blockCount;
uint64_t extent_size;
if (record->file_record.recordType == kHFSPlusFileRecord) {
for (i = 0; i < kHFSPlusExtentDensity; i++) {
blockCount = record->file_record.dataFork.extents[i].blockCount;
if (blockCount == 0) {
break;
}
extent_size = hfs_blk_to_bytes(blockCount, hfsmp->blockSize);
hfs_fsinfo_data_add(data, extent_size);
}
}
return 0;
}
static errno_t fsinfo_file_extent_size_overflow_callback(struct hfsmount *hfsmp,
HFSPlusKey *key, HFSPlusRecord *record, void *data)
{
int i;
uint32_t blockCount;
uint64_t extent_size;
if (key->extent_key.fileID >= kHFSFirstUserCatalogNodeID) {
if (key->extent_key.forkType == kHFSDataForkType) {
for (i = 0; i < kHFSPlusExtentDensity; i++) {
blockCount = record->extent_record[i].blockCount;
if (blockCount == 0) {
break;
}
extent_size = hfs_blk_to_bytes(blockCount, hfsmp->blockSize);
hfs_fsinfo_data_add(data, extent_size);
}
}
}
return 0;
}
static errno_t fsinfo_file_size_callback(__unused struct hfsmount *hfsmp,
__unused HFSPlusKey *key, HFSPlusRecord *record, void *data)
{
if (record->file_record.recordType == kHFSPlusFileRecord) {
hfs_fsinfo_data_add(data, record->file_record.dataFork.logicalSize);
}
return 0;
}
static errno_t fsinfo_dir_valence_callback(__unused struct hfsmount *hfsmp,
__unused HFSPlusKey *key, HFSPlusRecord *record, void *data)
{
if (record->folder_record.recordType == kHFSPlusFolderRecord) {
hfs_fsinfo_data_add(data, record->folder_record.valence);
}
return 0;
}
static errno_t fsinfo_name_size_callback(__unused struct hfsmount *hfsmp,
__unused HFSPlusKey *key, HFSPlusRecord *record, void *data)
{
struct hfs_fsinfo_name *fsinfo = (struct hfs_fsinfo_name *)data;
uint32_t length;
if ((record->folder_record.recordType == kHFSPlusFolderThreadRecord) ||
(record->folder_record.recordType == kHFSPlusFileThreadRecord)) {
length = record->thread_record.nodeName.length;
if (length > kHFSPlusMaxFileNameChars) {
return EIO;
}
if (length == 0)
return EIO;
length = (length - 1)/ 5;
fsinfo->bucket[length]++;
}
return 0;
}
static errno_t fsinfo_xattr_size_callback(__unused struct hfsmount *hfsmp,
__unused HFSPlusKey *key, HFSPlusRecord *record, void *data)
{
if (record->attr_record.recordType == kHFSPlusAttrInlineData) {
hfs_fsinfo_data_add(data, record->attr_record.attrData.attrSize);
} else if (record->attr_record.recordType == kHFSPlusAttrForkData) {
hfs_fsinfo_data_add(data, record->attr_record.forkData.theFork.logicalSize);
}
return 0;
}
static void fsinfo_free_extents_callback(void *data, off_t free_extent_size)
{
hfs_fsinfo_data_add(data, free_extent_size / 4096);
}
static errno_t hfs_fsinfo_free_extents(struct hfsmount *hfsmp, struct hfs_fsinfo_data *fsinfo)
{
return hfs_find_free_extents(hfsmp, &fsinfo_free_extents_callback, fsinfo);
}
static errno_t fsinfo_symlink_size_callback(__unused struct hfsmount *hfsmp,
__unused HFSPlusKey *key, HFSPlusRecord *record, void *data)
{
if (record->file_record.recordType == kHFSPlusFileRecord) {
if (S_ISLNK(record->file_record.bsdInfo.fileMode))
hfs_fsinfo_data_add((struct hfs_fsinfo_data *)data, record->file_record.dataFork.logicalSize);
}
return 0;
}
#if CONFIG_PROTECT
static int fsinfo_cprotect_count_callback(struct hfsmount *hfsmp, HFSPlusKey *key,
HFSPlusRecord *record, void *data)
{
struct hfs_fsinfo_cprotect *fsinfo = (struct hfs_fsinfo_cprotect *)data;
static const uint16_t cp_xattrname_utf16[] = CONTENT_PROTECTION_XATTR_NAME_CHARS;
static const size_t cp_xattrname_utf16_len = sizeof(cp_xattrname_utf16)/2;
struct cp_xattr_v5 *xattr;
size_t xattr_len = sizeof(struct cp_xattr_v5);
struct cprotect cp_entry;
struct cprotect *cp_entryp = &cp_entry;
int error = 0;
if (record->attr_record.recordType != kHFSPlusAttrInlineData)
return 0;
if ((key->attr_key.attrNameLen != cp_xattrname_utf16_len) ||
(bcmp(key->attr_key.attrName, cp_xattrname_utf16, 2 * cp_xattrname_utf16_len))) {
return 0;
}
xattr = (struct cp_xattr_v5 *)((void *)(record->attr_record.attrData.attrData));
error = cp_read_xattr_v5(hfsmp, xattr, xattr_len, (cprotect_t *)&cp_entryp,
CP_GET_XATTR_BASIC_INFO);
if (error)
return 0;
if (!ISSET(cp_entry.cp_flags, CP_HAS_A_KEY))
return 0;
switch (CP_CLASS(cp_entry.cp_pclass)) {
case PROTECTION_CLASS_A:
fsinfo->class_A++;
break;
case PROTECTION_CLASS_B:
fsinfo->class_B++;
break;
case PROTECTION_CLASS_C:
fsinfo->class_C++;
break;
case PROTECTION_CLASS_D:
fsinfo->class_D++;
break;
case PROTECTION_CLASS_E:
fsinfo->class_E++;
break;
case PROTECTION_CLASS_F:
fsinfo->class_F++;
break;
};
return 0;
}
#endif