#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kauth.h>
#include <sys/ubc.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/stat.h>
#include <sys/quota.h>
#include <sys/disk.h>
#include <sys/paths.h>
#include <sys/utfconv.h>
#include <sys/kdebug.h>
#include <sys/fslog.h>
#include <sys/ubc.h>
#include <pexpert/pexpert.h>
#include <kern/locks.h>
#include "hfs_journal.h"
#include <miscfs/specfs/specdev.h>
#include "hfs_mount.h"
#include <libkern/crypto/md5.h>
#include <uuid/uuid.h>
#include "hfs_iokit.h"
#include "hfs.h"
#include "hfs_catalog.h"
#include "hfs_cnode.h"
#include "hfs_dbg.h"
#include "hfs_endian.h"
#include "hfs_hotfiles.h"
#include "hfs_quota.h"
#include "hfs_btreeio.h"
#include "hfs_kdebug.h"
#include "hfs_cprotect.h"
#include "FileMgrInternal.h"
#include "BTreesInternal.h"
#define HFS_MOUNT_DEBUG 1
extern int hfs_resize_debug;
lck_grp_attr_t * hfs_group_attr;
lck_attr_t * hfs_lock_attr;
lck_grp_t * hfs_mutex_group;
lck_grp_t * hfs_rwlock_group;
lck_grp_t * hfs_spinlock_group;
extern struct vnodeopv_desc hfs_vnodeop_opv_desc;
#if CONFIG_HFS_STD
extern struct vnodeopv_desc hfs_std_vnodeop_opv_desc;
static int hfs_flushMDB(struct hfsmount *hfsmp, int waitfor, int altflush);
#endif
int hfs_vfs_vget(struct mount *mp, ino64_t ino, struct vnode **vpp, vfs_context_t context);
static int hfs_changefs(struct mount *mp, struct hfs_mount_args *args);
static int hfs_fhtovp(struct mount *mp, int fhlen, unsigned char *fhp, struct vnode **vpp, vfs_context_t context);
static int hfs_flushfiles(struct mount *, int, struct proc *);
static int hfs_init(struct vfsconf *vfsp);
static void hfs_locks_destroy(struct hfsmount *hfsmp);
static int hfs_quotactl(struct mount *, int, uid_t, caddr_t, vfs_context_t context);
static int hfs_start(struct mount *mp, int flags, vfs_context_t context);
static int hfs_vptofh(struct vnode *vp, int *fhlenp, unsigned char *fhp, vfs_context_t context);
static void hfs_syncer_free(struct hfsmount *hfsmp);
void hfs_initialize_allocator (struct hfsmount *hfsmp);
int hfs_teardown_allocator (struct hfsmount *hfsmp);
int hfs_mount(struct mount *mp, vnode_t devvp, user_addr_t data, vfs_context_t context);
int hfs_mountfs(struct vnode *devvp, struct mount *mp, struct hfs_mount_args *args, int journal_replay_only, vfs_context_t context);
int hfs_reload(struct mount *mp);
int hfs_statfs(struct mount *mp, register struct vfsstatfs *sbp, vfs_context_t context);
int hfs_sync(struct mount *mp, int waitfor, vfs_context_t context);
int hfs_sysctl(int *name, u_int namelen, user_addr_t oldp, size_t *oldlenp,
user_addr_t newp, size_t newlen, vfs_context_t context);
int hfs_unmount(struct mount *mp, int mntflags, vfs_context_t context);
static int hfs_journal_replay(vnode_t devvp, vfs_context_t context);
#if HFS_LEAK_DEBUG
#include <libkern/OSAtomic.h>
#include <IOKit/IOLib.h>
int hfs_active_mounts;
#endif
int
hfs_mount(struct mount *mp, vnode_t devvp, user_addr_t data, vfs_context_t context)
{
#if HFS_LEAK_DEBUG
#warning HFS_LEAK_DEBUG is on
hfs_alloc_trace_enable();
#endif
struct proc *p = vfs_context_proc(context);
struct hfsmount *hfsmp = NULL;
struct hfs_mount_args args;
int retval = E_NONE;
u_int32_t cmdflags;
if (data && (retval = copyin(data, (caddr_t)&args, sizeof(args)))) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mount: copyin returned %d for fs\n", retval);
}
return (retval);
}
cmdflags = (u_int32_t)vfs_flags(mp) & MNT_CMDFLAGS;
if (cmdflags & MNT_UPDATE) {
hfs_assert(data);
hfsmp = VFSTOHFS(mp);
if (cmdflags & MNT_RELOAD) {
if (vfs_isrdonly(mp)) {
int error = hfs_reload(mp);
if (error && HFS_MOUNT_DEBUG) {
printf("hfs_mount: hfs_reload returned %d on %s \n", error, hfsmp->vcbVN);
}
return error;
}
else {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mount: MNT_RELOAD not supported on rdwr filesystem %s\n", hfsmp->vcbVN);
}
return (EINVAL);
}
}
if (((hfsmp->hfs_flags & HFS_READ_ONLY) == 0) &&
vfs_isrdonly(mp)) {
int flags;
hfs_lock_global (hfsmp, HFS_EXCLUSIVE_LOCK);
hfsmp->hfs_flags |= HFS_RDONLY_DOWNGRADE;
hfsmp->hfs_downgrading_thread = current_thread();
hfs_unlock_global (hfsmp);
hfs_syncer_free(hfsmp);
retval = hfs_sync(mp, MNT_WAIT, context);
if (retval && ((cmdflags & MNT_FORCE) == 0)) {
hfsmp->hfs_flags &= ~HFS_RDONLY_DOWNGRADE;
hfsmp->hfs_downgrading_thread = NULL;
if (HFS_MOUNT_DEBUG) {
printf("hfs_mount: VFS_SYNC returned %d during b-tree sync of %s \n", retval, hfsmp->vcbVN);
}
goto out;
}
flags = WRITECLOSE;
if (cmdflags & MNT_FORCE)
flags |= FORCECLOSE;
if ((retval = hfs_flushfiles(mp, flags, p))) {
hfsmp->hfs_flags &= ~HFS_RDONLY_DOWNGRADE;
hfsmp->hfs_downgrading_thread = NULL;
if (HFS_MOUNT_DEBUG) {
printf("hfs_mount: hfs_flushfiles returned %d on %s \n", retval, hfsmp->vcbVN);
}
goto out;
}
hfsmp->vcbAtrb |= kHFSVolumeUnmountedMask;
retval = hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT);
hfsmp->hfs_flags |= HFS_READ_ONLY;
if (hfsmp->jnl) {
hfs_lock_global (hfsmp, HFS_EXCLUSIVE_LOCK);
journal_close(hfsmp->jnl);
hfsmp->jnl = NULL;
hfs_unlock_global (hfsmp);
vfs_clearflags(hfsmp->hfs_mp, MNT_JOURNALED);
}
if (retval == 0) {
vnode_get(hfsmp->hfs_devvp);
retval = VNOP_FSYNC(hfsmp->hfs_devvp, MNT_WAIT, context);
vnode_put(hfsmp->hfs_devvp);
}
if (retval) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mount: FSYNC on devvp returned %d for fs %s\n", retval, hfsmp->vcbVN);
}
hfsmp->hfs_flags &= ~HFS_RDONLY_DOWNGRADE;
hfsmp->hfs_downgrading_thread = NULL;
hfsmp->hfs_flags &= ~HFS_READ_ONLY;
goto out;
}
if (hfsmp->hfs_flags & HFS_SUMMARY_TABLE) {
if (hfsmp->hfs_summary_table) {
int err = 0;
if (hfsmp->hfs_allocation_vp) {
err = hfs_lock (VTOC(hfsmp->hfs_allocation_vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
}
hfs_free(hfsmp->hfs_summary_table, hfsmp->hfs_summary_bytes);
hfsmp->hfs_summary_table = NULL;
hfsmp->hfs_flags &= ~HFS_SUMMARY_TABLE;
if (err == 0 && hfsmp->hfs_allocation_vp){
hfs_unlock (VTOC(hfsmp->hfs_allocation_vp));
}
}
}
hfsmp->hfs_downgrading_thread = NULL;
}
if (vfs_iswriteupgrade(mp)) {
if (!(vfs_flags(mp) & MNT_ROOTFS) &&
(hfsmp->vcbAtrb & kHFSVolumeInconsistentMask)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mount: attempting to mount inconsistent non-root volume %s\n", (hfsmp->vcbVN));
}
retval = EINVAL;
goto out;
}
if ( (HFSTOVCB(hfsmp)->vcbAtrb & kHFSVolumeJournaledMask)
&& hfsmp->jnl == NULL
&& hfsmp->jvp != NULL) {
int jflags;
if (hfsmp->hfs_flags & HFS_NEED_JNL_RESET) {
jflags = JOURNAL_RESET;
} else {
jflags = 0;
}
hfs_lock_global (hfsmp, HFS_EXCLUSIVE_LOCK);
hfsmp->jnl = journal_open(hfsmp->jvp,
hfs_blk_to_bytes(hfsmp->jnl_start, HFSTOVCB(hfsmp)->blockSize) + (off_t)HFSTOVCB(hfsmp)->hfsPlusIOPosOffset,
hfsmp->jnl_size,
hfsmp->hfs_devvp,
hfsmp->hfs_logical_block_size,
jflags,
0,
hfs_sync_metadata, hfsmp->hfs_mp,
hfsmp->hfs_mp);
if (hfsmp->jnl)
journal_trim_set_callback(hfsmp->jnl, hfs_trim_callback, hfsmp);
hfs_unlock_global (hfsmp);
if (hfsmp->jnl == NULL) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mount: journal_open == NULL; couldn't be opened on %s \n", (hfsmp->vcbVN));
}
retval = EINVAL;
goto out;
} else {
hfsmp->hfs_flags &= ~HFS_NEED_JNL_RESET;
vfs_setflags(hfsmp->hfs_mp, MNT_JOURNALED);
}
}
retval = hfs_erase_unused_nodes(hfsmp);
if (retval != E_NONE) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mount: hfs_erase_unused_nodes returned %d for fs %s\n", retval, hfsmp->vcbVN);
}
goto out;
}
hfsmp->hfs_flags &= ~HFS_RDONLY_DOWNGRADE;
hfsmp->hfs_downgrading_thread = NULL;
hfsmp->vcbAtrb &= ~kHFSVolumeUnmountedMask;
retval = hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT);
if (retval != E_NONE) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mount: hfs_flushvolumeheader returned %d for fs %s\n", retval, hfsmp->vcbVN);
}
goto out;
}
hfsmp->hfs_flags &= ~HFS_READ_ONLY;
if (!(hfsmp->hfs_flags & (HFS_READ_ONLY | HFS_STANDARD))) {
hfs_privatedir_init(hfsmp, FILE_HARDLINKS);
hfs_privatedir_init(hfsmp, DIR_HARDLINKS);
hfs_remove_orphans(hfsmp);
if (ISSET(hfsmp->hfs_flags, HFS_METADATA_ZONE)
&& (!ISSET(hfsmp->hfs_flags, HFS_SSD)
|| ISSET(hfsmp->hfs_flags, HFS_CS_HOTFILE_PIN))) {
hfs_recording_init(hfsmp);
}
if (vfs_extendedsecurity(HFSTOVFS(hfsmp)) == 0) {
vfs_setextendedsecurity(HFSTOVFS(hfsmp));
}
}
}
retval = hfs_changefs(mp, &args);
if (retval && HFS_MOUNT_DEBUG) {
printf("hfs_mount: hfs_changefs returned %d for %s\n", retval, hfsmp->vcbVN);
}
} else {
if (devvp == NULL) {
retval = EINVAL;
goto out;
}
vfs_setflags(mp, (u_int64_t)((unsigned int)MNT_DOVOLFS));
retval = hfs_mountfs(devvp, mp, data ? &args : NULL, 0, context);
if (retval) {
const char *name = vnode_getname(devvp);
printf("hfs_mount: hfs_mountfs returned error=%d for device %s\n", retval, (name ? name : "unknown-dev"));
if (name) {
vnode_putname(name);
}
goto out;
}
hfsmp = VFSTOHFS(mp);
hfsmp->hfs_defrag_max = HFS_INITIAL_DEFRAG_SIZE;
if (!data) {
hfsmp->hfs_uid = UNKNOWNUID;
hfsmp->hfs_gid = UNKNOWNGID;
hfsmp->hfs_dir_mask = (S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH);
hfsmp->hfs_file_mask = (S_IRWXU | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH);
hfsmp->reserveBlocks = ((u_int64_t)hfsmp->totalBlocks * HFS_MINFREE) / 100;
hfsmp->reserveBlocks = MIN(hfsmp->reserveBlocks, HFS_MAXRESERVE / hfsmp->blockSize);
}
#if HFS_LEAK_DEBUG
OSIncrementAtomic(&hfs_active_mounts);
#endif
}
out:
if (retval == 0) {
(void)hfs_statfs(mp, vfs_statfs(mp), context);
}
return (retval);
}
struct hfs_changefs_cargs {
struct hfsmount *hfsmp;
int namefix;
int permfix;
int permswitch;
};
static int
hfs_changefs_callback(struct vnode *vp, void *cargs)
{
ExtendedVCB *vcb;
struct cnode *cp;
struct cat_desc cndesc;
struct cat_attr cnattr;
struct hfs_changefs_cargs *args;
int lockflags;
int error;
args = (struct hfs_changefs_cargs *)cargs;
cp = VTOC(vp);
vcb = HFSTOVCB(args->hfsmp);
lockflags = hfs_systemfile_lock(args->hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
error = cat_lookup(args->hfsmp, &cp->c_desc, 0, 0, &cndesc, &cnattr, NULL, NULL);
hfs_systemfile_unlock(args->hfsmp, lockflags);
if (error) {
if (args->namefix)
cache_purge(vp);
return (VNODE_RETURNED);
}
if (args->permswitch || args->permfix) {
cp->c_uid = cnattr.ca_uid;
cp->c_gid = cnattr.ca_gid;
cp->c_mode = cnattr.ca_mode;
}
if (args->namefix) {
cache_purge(vp);
replace_desc(cp, &cndesc);
if (cndesc.cd_cnid == kHFSRootFolderID) {
strlcpy((char *)vcb->vcbVN, (const char *)cp->c_desc.cd_nameptr, NAME_MAX+1);
cp->c_desc.cd_encoding = args->hfsmp->hfs_encoding;
}
} else {
cat_releasedesc(&cndesc);
}
return (VNODE_RETURNED);
}
static int
hfs_changefs(struct mount *mp, struct hfs_mount_args *args)
{
int retval = 0;
int namefix, permfix, permswitch;
struct hfsmount *hfsmp;
ExtendedVCB *vcb;
struct hfs_changefs_cargs cargs;
u_int32_t mount_flags;
#if CONFIG_HFS_STD
u_int32_t old_encoding = 0;
hfs_to_unicode_func_t get_unicode_func;
unicode_to_hfs_func_t get_hfsname_func = NULL;
#endif
hfsmp = VFSTOHFS(mp);
vcb = HFSTOVCB(hfsmp);
mount_flags = (unsigned int)vfs_flags(mp);
hfsmp->hfs_flags |= HFS_IN_CHANGEFS;
permswitch = (((hfsmp->hfs_flags & HFS_UNKNOWN_PERMS) &&
((mount_flags & MNT_UNKNOWNPERMISSIONS) == 0)) ||
(((hfsmp->hfs_flags & HFS_UNKNOWN_PERMS) == 0) &&
(mount_flags & MNT_UNKNOWNPERMISSIONS)));
if (permswitch && (mount_flags & MNT_ROOTFS) && (mount_flags & MNT_UNKNOWNPERMISSIONS)) {
vfs_clearflags(mp, (u_int64_t)((unsigned int)MNT_UNKNOWNPERMISSIONS));
retval = EINVAL;
goto exit;
}
if (mount_flags & MNT_UNKNOWNPERMISSIONS)
hfsmp->hfs_flags |= HFS_UNKNOWN_PERMS;
else
hfsmp->hfs_flags &= ~HFS_UNKNOWN_PERMS;
namefix = permfix = 0;
if (mount_flags & MNT_NOATIME) {
(void) hfs_recording_suspend(hfsmp);
}
if (args->hfs_timezone.tz_minuteswest != VNOVAL) {
gTimeZone = args->hfs_timezone;
}
if ((args->hfs_uid != (uid_t)VNOVAL) && (hfsmp->hfs_uid != args->hfs_uid)) {
hfsmp->hfs_uid = args->hfs_uid;
if (vcb->vcbSigWord == kHFSPlusSigWord)
++permfix;
}
if ((args->hfs_gid != (gid_t)VNOVAL) && (hfsmp->hfs_gid != args->hfs_gid)) {
hfsmp->hfs_gid = args->hfs_gid;
if (vcb->vcbSigWord == kHFSPlusSigWord)
++permfix;
}
if (args->hfs_mask != (mode_t)VNOVAL) {
if (hfsmp->hfs_dir_mask != (args->hfs_mask & ALLPERMS)) {
hfsmp->hfs_dir_mask = args->hfs_mask & ALLPERMS;
hfsmp->hfs_file_mask = args->hfs_mask & ALLPERMS;
if ((args->flags != VNOVAL) && (args->flags & HFSFSMNT_NOXONFILES))
hfsmp->hfs_file_mask = (args->hfs_mask & DEFFILEMODE);
if (vcb->vcbSigWord == kHFSPlusSigWord)
++permfix;
}
}
#if CONFIG_HFS_STD
if ((vcb->vcbSigWord == kHFSSigWord) &&
(args->hfs_encoding != (u_int32_t)VNOVAL) &&
(hfsmp->hfs_encoding != args->hfs_encoding)) {
retval = hfs_getconverter(args->hfs_encoding, &get_unicode_func, &get_hfsname_func);
if (retval)
goto exit;
hfsmp->hfs_get_unicode = get_unicode_func;
old_encoding = hfsmp->hfs_encoding;
hfsmp->hfs_encoding = args->hfs_encoding;
++namefix;
}
#endif
if (!(namefix || permfix || permswitch))
goto exit;
if (permfix) {
vfs_setowner(mp,
hfsmp->hfs_uid == UNKNOWNUID ? KAUTH_UID_NONE : hfsmp->hfs_uid,
hfsmp->hfs_gid == UNKNOWNGID ? KAUTH_GID_NONE : hfsmp->hfs_gid);
}
cargs.hfsmp = hfsmp;
cargs.namefix = namefix;
cargs.permfix = permfix;
cargs.permswitch = permswitch;
vnode_iterate(mp, 0, hfs_changefs_callback, (void *)&cargs);
#if CONFIG_HFS_STD
if (namefix) {
hfsmp->hfs_get_hfsname = get_hfsname_func;
vcb->volumeNameEncodingHint = args->hfs_encoding;
(void) hfs_relconverter(old_encoding);
}
#endif
exit:
hfsmp->hfs_flags &= ~HFS_IN_CHANGEFS;
return (retval);
}
struct hfs_reload_cargs {
struct hfsmount *hfsmp;
int error;
};
static int
hfs_reload_callback(struct vnode *vp, void *cargs)
{
struct cnode *cp;
struct hfs_reload_cargs *args;
int lockflags;
args = (struct hfs_reload_cargs *)cargs;
(void) buf_invalidateblks(vp, 0, 0, 0);
cp = VTOC(vp);
if (vnode_isdir(vp))
hfs_reldirhints(cp, 0);
if (!vnode_issystem(vp) && !VNODE_IS_RSRC(vp) && (cp->c_fileid >= kHFSFirstUserCatalogNodeID)) {
struct cat_fork *datafork;
struct cat_desc desc;
datafork = cp->c_datafork ? &cp->c_datafork->ff_data : NULL;
lockflags = hfs_systemfile_lock(args->hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
args->error = cat_idlookup(args->hfsmp, cp->c_fileid, 0, 0, &desc, &cp->c_attr, datafork);
hfs_systemfile_unlock(args->hfsmp, lockflags);
if (args->error) {
return (VNODE_RETURNED_DONE);
}
(void) replace_desc(cp, &desc);
}
return (VNODE_RETURNED);
}
int
hfs_reload(struct mount *mountp)
{
register struct vnode *devvp;
struct buf *bp;
int error, i;
struct hfsmount *hfsmp;
struct HFSPlusVolumeHeader *vhp;
ExtendedVCB *vcb;
struct filefork *forkp;
struct cat_desc cndesc;
struct hfs_reload_cargs args;
daddr64_t priIDSector;
hfsmp = VFSTOHFS(mountp);
vcb = HFSTOVCB(hfsmp);
if (vcb->vcbSigWord == kHFSSigWord)
return (EINVAL);
devvp = hfsmp->hfs_devvp;
if (buf_invalidateblks(devvp, 0, 0, 0))
panic("hfs_reload: dirty1");
args.hfsmp = hfsmp;
args.error = 0;
vnode_iterate(mountp, VNODE_RELOAD | VNODE_WAIT, hfs_reload_callback, (void *)&args);
if (args.error)
return (args.error);
priIDSector = (daddr64_t)((vcb->hfsPlusIOPosOffset / hfsmp->hfs_logical_block_size) +
HFS_PRI_SECTOR(hfsmp->hfs_logical_block_size));
error = (int)buf_meta_bread(hfsmp->hfs_devvp,
HFS_PHYSBLK_ROUNDDOWN(priIDSector, hfsmp->hfs_log_per_phys),
hfsmp->hfs_physical_block_size, NOCRED, &bp);
if (error) {
if (bp != NULL)
buf_brelse(bp);
return (error);
}
vhp = (HFSPlusVolumeHeader *) (buf_dataptr(bp) + HFS_PRI_OFFSET(hfsmp->hfs_physical_block_size));
if ((SWAP_BE16(vhp->signature) != kHFSPlusSigWord &&
SWAP_BE16(vhp->signature) != kHFSXSigWord) ||
(SWAP_BE16(vhp->version) != kHFSPlusVersion &&
SWAP_BE16(vhp->version) != kHFSXVersion) ||
SWAP_BE32(vhp->blockSize) != vcb->blockSize) {
buf_brelse(bp);
return (EIO);
}
vcb->vcbLsMod = to_bsd_time(SWAP_BE32(vhp->modifyDate));
vcb->vcbAtrb = SWAP_BE32 (vhp->attributes);
vcb->vcbJinfoBlock = SWAP_BE32(vhp->journalInfoBlock);
vcb->vcbClpSiz = SWAP_BE32 (vhp->rsrcClumpSize);
vcb->vcbNxtCNID = SWAP_BE32 (vhp->nextCatalogID);
vcb->vcbVolBkUp = to_bsd_time(SWAP_BE32(vhp->backupDate));
vcb->vcbWrCnt = SWAP_BE32 (vhp->writeCount);
vcb->vcbFilCnt = SWAP_BE32 (vhp->fileCount);
vcb->vcbDirCnt = SWAP_BE32 (vhp->folderCount);
HFS_UPDATE_NEXT_ALLOCATION(vcb, SWAP_BE32 (vhp->nextAllocation));
vcb->totalBlocks = SWAP_BE32 (vhp->totalBlocks);
vcb->freeBlocks = SWAP_BE32 (vhp->freeBlocks);
vcb->encodingsBitmap = SWAP_BE64 (vhp->encodingsBitmap);
bcopy(vhp->finderInfo, vcb->vcbFndrInfo, sizeof(vhp->finderInfo));
vcb->localCreateDate = SWAP_BE32 (vhp->createDate);
forkp = VTOF((struct vnode *)vcb->extentsRefNum);
for (i = 0; i < kHFSPlusExtentDensity; i++) {
forkp->ff_extents[i].startBlock =
SWAP_BE32 (vhp->extentsFile.extents[i].startBlock);
forkp->ff_extents[i].blockCount =
SWAP_BE32 (vhp->extentsFile.extents[i].blockCount);
}
forkp->ff_size = SWAP_BE64 (vhp->extentsFile.logicalSize);
forkp->ff_blocks = SWAP_BE32 (vhp->extentsFile.totalBlocks);
forkp->ff_clumpsize = SWAP_BE32 (vhp->extentsFile.clumpSize);
forkp = VTOF((struct vnode *)vcb->catalogRefNum);
for (i = 0; i < kHFSPlusExtentDensity; i++) {
forkp->ff_extents[i].startBlock =
SWAP_BE32 (vhp->catalogFile.extents[i].startBlock);
forkp->ff_extents[i].blockCount =
SWAP_BE32 (vhp->catalogFile.extents[i].blockCount);
}
forkp->ff_size = SWAP_BE64 (vhp->catalogFile.logicalSize);
forkp->ff_blocks = SWAP_BE32 (vhp->catalogFile.totalBlocks);
forkp->ff_clumpsize = SWAP_BE32 (vhp->catalogFile.clumpSize);
if (hfsmp->hfs_attribute_vp) {
forkp = VTOF(hfsmp->hfs_attribute_vp);
for (i = 0; i < kHFSPlusExtentDensity; i++) {
forkp->ff_extents[i].startBlock =
SWAP_BE32 (vhp->attributesFile.extents[i].startBlock);
forkp->ff_extents[i].blockCount =
SWAP_BE32 (vhp->attributesFile.extents[i].blockCount);
}
forkp->ff_size = SWAP_BE64 (vhp->attributesFile.logicalSize);
forkp->ff_blocks = SWAP_BE32 (vhp->attributesFile.totalBlocks);
forkp->ff_clumpsize = SWAP_BE32 (vhp->attributesFile.clumpSize);
}
forkp = VTOF((struct vnode *)vcb->allocationsRefNum);
for (i = 0; i < kHFSPlusExtentDensity; i++) {
forkp->ff_extents[i].startBlock =
SWAP_BE32 (vhp->allocationFile.extents[i].startBlock);
forkp->ff_extents[i].blockCount =
SWAP_BE32 (vhp->allocationFile.extents[i].blockCount);
}
forkp->ff_size = SWAP_BE64 (vhp->allocationFile.logicalSize);
forkp->ff_blocks = SWAP_BE32 (vhp->allocationFile.totalBlocks);
forkp->ff_clumpsize = SWAP_BE32 (vhp->allocationFile.clumpSize);
buf_brelse(bp);
vhp = NULL;
forkp = VTOF((struct vnode *)vcb->extentsRefNum);
if ( (error = MacToVFSError( BTReloadData((FCB*)forkp) )) )
return (error);
forkp = VTOF((struct vnode *)vcb->catalogRefNum);
if ( (error = MacToVFSError( BTReloadData((FCB*)forkp) )) )
return (error);
if (hfsmp->hfs_attribute_vp) {
forkp = VTOF(hfsmp->hfs_attribute_vp);
if ( (error = MacToVFSError( BTReloadData((FCB*)forkp) )) )
return (error);
}
if ((error = cat_idlookup(hfsmp, kHFSRootFolderID, 0, 0, &cndesc, NULL, NULL)))
return (error);
vcb->volumeNameEncodingHint = cndesc.cd_encoding;
bcopy(cndesc.cd_nameptr, vcb->vcbVN, min(255, cndesc.cd_namelen));
cat_releasedesc(&cndesc);
hfs_privatedir_init(hfsmp, FILE_HARDLINKS);
hfs_privatedir_init(hfsmp, DIR_HARDLINKS);
hfs_generate_volume_notifications(hfsmp);
return (0);
}
__unused
static uint64_t tv_to_usecs(struct timeval *tv)
{
return tv->tv_sec * 1000000ULL + tv->tv_usec;
}
static bool hfs_has_elapsed (const struct timeval *a,
const struct timeval *b,
uint64_t usecs)
{
struct timeval diff;
timersub(b, a, &diff);
return diff.tv_sec * 1000000ULL + diff.tv_usec >= usecs;
}
void hfs_syncer(void *arg, __unused wait_result_t wr)
{
struct hfsmount *hfsmp = arg;
struct timeval now;
KDBG(HFSDBG_SYNCER | DBG_FUNC_START, obfuscate_addr(hfsmp));
hfs_syncer_lock(hfsmp);
while (ISSET(hfsmp->hfs_flags, HFS_RUN_SYNCER)
&& timerisset(&hfsmp->hfs_sync_req_oldest)) {
hfs_syncer_wait(hfsmp, &HFS_META_DELAY_TS);
if (!ISSET(hfsmp->hfs_flags, HFS_RUN_SYNCER)
|| !timerisset(&hfsmp->hfs_sync_req_oldest)) {
break;
}
microuptime(&now);
uint64_t idle_time = vfs_idle_time(hfsmp->hfs_mp);
if (!hfs_has_elapsed(&hfsmp->hfs_sync_req_oldest, &now,
HFS_MAX_META_DELAY)
&& idle_time < HFS_META_DELAY) {
continue;
}
timerclear(&hfsmp->hfs_sync_req_oldest);
hfs_syncer_unlock(hfsmp);
KDBG(HFSDBG_SYNCER_TIMED | DBG_FUNC_START, obfuscate_addr(hfsmp));
if (hfsmp->jnl) {
hfs_flush(hfsmp, HFS_FLUSH_JOURNAL_META);
} else {
hfs_sync(hfsmp->hfs_mp, MNT_WAIT, vfs_context_current());
}
KDBG(HFSDBG_SYNCER_TIMED | DBG_FUNC_END);
hfs_syncer_lock(hfsmp);
}
hfsmp->hfs_syncer_thread = NULL;
hfs_syncer_unlock(hfsmp);
hfs_syncer_wakeup(hfsmp);
KDBG(HFSDBG_SYNCER | DBG_FUNC_END);
}
void hfs_scan_blocks (struct hfsmount *hfsmp) {
int flags = hfs_systemfile_lock(hfsmp, SFL_BITMAP, HFS_EXCLUSIVE_LOCK);
(void) hfs_lock_mount (hfsmp);
hfsmp->scan_var |= HFS_ALLOCATOR_SCAN_INFLIGHT;
wakeup((caddr_t) &hfsmp->scan_var);
hfs_unlock_mount (hfsmp);
if (hfs_init_summary (hfsmp)) {
printf("hfs: could not initialize summary table for %s\n", hfsmp->vcbVN);
}
(void) ScanUnmapBlocks(hfsmp);
(void) hfs_lock_mount (hfsmp);
hfsmp->scan_var &= ~HFS_ALLOCATOR_SCAN_INFLIGHT;
hfsmp->scan_var |= HFS_ALLOCATOR_SCAN_COMPLETED;
wakeup((caddr_t) &hfsmp->scan_var);
hfs_unlock_mount (hfsmp);
buf_invalidateblks(hfsmp->hfs_allocation_vp, 0, 0, 0);
hfs_systemfile_unlock(hfsmp, flags);
}
int
hfs_mountfs(struct vnode *devvp, struct mount *mp, struct hfs_mount_args *args,
int journal_replay_only, vfs_context_t context)
{
struct proc *p = vfs_context_proc(context);
int retval = E_NONE;
struct hfsmount *hfsmp = NULL;
struct buf *bp;
dev_t dev;
HFSMasterDirectoryBlock *mdbp = NULL;
int ronly;
#if QUOTA
int i;
#endif
int mntwrapper;
kauth_cred_t cred;
u_int64_t disksize;
daddr64_t log_blkcnt;
u_int32_t log_blksize;
u_int32_t phys_blksize;
u_int32_t minblksize;
u_int32_t iswritable;
daddr64_t mdb_offset;
int isvirtual = 0;
int isroot = !journal_replay_only && args == NULL;
u_int32_t device_features = 0;
int isssd;
ronly = mp && vfs_isrdonly(mp);
dev = vnode_specrdev(devvp);
cred = p ? vfs_context_ucred(context) : NOCRED;
mntwrapper = 0;
bp = NULL;
hfsmp = NULL;
mdbp = NULL;
minblksize = kHFSBlockSize;
if (mp)
vfs_setlocklocal(mp);
if (VNOP_IOCTL(devvp, DKIOCGETBLOCKSIZE, (caddr_t)&log_blksize, 0, context)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: DKIOCGETBLOCKSIZE failed\n");
}
retval = ENXIO;
goto error_exit;
}
if (log_blksize == 0 || log_blksize > 1024*1024*1024) {
printf("hfs: logical block size 0x%x looks bad. Not mounting.\n", log_blksize);
retval = ENXIO;
goto error_exit;
}
retval = VNOP_IOCTL(devvp, DKIOCGETPHYSICALBLOCKSIZE, (caddr_t)&phys_blksize, 0, context);
if (retval) {
if ((retval != ENOTSUP) && (retval != ENOTTY)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: DKIOCGETPHYSICALBLOCKSIZE failed\n");
}
retval = ENXIO;
goto error_exit;
}
phys_blksize = log_blksize;
}
if (phys_blksize == 0 || phys_blksize > MAXBSIZE) {
printf("hfs: physical block size 0x%x looks bad. Not mounting.\n", phys_blksize);
retval = ENXIO;
goto error_exit;
}
if (log_blksize > 512) {
u_int32_t size512 = 512;
if (VNOP_IOCTL(devvp, DKIOCSETBLOCKSIZE, (caddr_t)&size512, FWRITE, context)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: DKIOCSETBLOCKSIZE failed \n");
}
retval = ENXIO;
goto error_exit;
}
}
if (VNOP_IOCTL(devvp, DKIOCGETBLOCKCOUNT, (caddr_t)&log_blkcnt, 0, context)) {
(void)VNOP_IOCTL(devvp, DKIOCSETBLOCKSIZE, (caddr_t)&log_blksize, FWRITE, context);
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: DKIOCGETBLOCKCOUNT failed\n");
}
retval = ENXIO;
goto error_exit;
}
disksize = (u_int64_t)log_blkcnt * (u_int64_t)512;
if (log_blkcnt > 0x000000007fffffff && (log_blkcnt & 7) == 0) {
minblksize = log_blksize = 4096;
if (phys_blksize < log_blksize)
phys_blksize = log_blksize;
}
if (log_blksize > PAGE_SIZE) {
log_blksize = PAGE_SIZE;
}
if (log_blksize > 512) {
if (VNOP_IOCTL(devvp, DKIOCSETBLOCKSIZE, (caddr_t)&log_blksize, FWRITE, context)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: DKIOCSETBLOCKSIZE (2) failed\n");
}
retval = ENXIO;
goto error_exit;
}
if (VNOP_IOCTL(devvp, DKIOCGETBLOCKCOUNT, (caddr_t)&log_blkcnt, 0, context)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: DKIOCGETBLOCKCOUNT (2) failed\n");
}
retval = ENXIO;
goto error_exit;
}
}
mdb_offset = (daddr64_t)HFS_PRI_SECTOR(log_blksize);
if ((retval = (int)buf_meta_bread(devvp,
HFS_PHYSBLK_ROUNDDOWN(mdb_offset, (phys_blksize/log_blksize)),
phys_blksize, cred, &bp))) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: buf_meta_bread failed with %d\n", retval);
}
goto error_exit;
}
mdbp = hfs_malloc(kMDBSize);
bcopy((char *)buf_dataptr(bp) + HFS_PRI_OFFSET(phys_blksize), mdbp, kMDBSize);
buf_brelse(bp);
bp = NULL;
hfsmp = hfs_mallocz(sizeof(struct hfsmount));
hfs_chashinit_finish(hfsmp);
hfs_idhash_init (hfsmp);
if (VNOP_IOCTL(devvp, DKIOCGETFEATURES, (caddr_t)&device_features, 0, context) == 0) {
if (device_features & DK_FEATURE_UNMAP) {
hfsmp->hfs_flags |= HFS_UNMAP;
}
if(device_features & DK_FEATURE_BARRIER)
hfsmp->hfs_flags |= HFS_FEATURE_BARRIER;
}
if (VNOP_IOCTL(devvp, DKIOCISSOLIDSTATE, (caddr_t)&isssd, 0, context) == 0) {
if (isssd) {
hfsmp->hfs_flags |= HFS_SSD;
}
}
dk_corestorage_info_t cs_info;
memset(&cs_info, 0, sizeof(dk_corestorage_info_t));
if (VNOP_IOCTL(devvp, DKIOCCORESTORAGE, (caddr_t)&cs_info, 0, context) == 0) {
hfsmp->hfs_flags |= HFS_CS;
if (isroot && (cs_info.flags & DK_CORESTORAGE_PIN_YOUR_METADATA)) {
hfsmp->hfs_flags |= HFS_CS_METADATA_PIN;
}
if (isroot && (cs_info.flags & DK_CORESTORAGE_ENABLE_HOTFILES)) {
hfsmp->hfs_flags |= HFS_CS_HOTFILE_PIN;
hfsmp->hfs_cs_hotfile_size = cs_info.hotfile_size;
}
if ((cs_info.flags & DK_CORESTORAGE_PIN_YOUR_SWAPFILE)) {
hfsmp->hfs_flags |= HFS_CS_SWAPFILE_PIN;
struct vfsioattr ioattr;
vfs_ioattr(mp, &ioattr);
ioattr.io_flags |= VFS_IOATTR_FLAGS_SWAPPIN_SUPPORTED;
ioattr.io_max_swappin_available = cs_info.swapfile_pinning;
vfs_setioattr(mp, &ioattr);
}
}
lck_mtx_init(&hfsmp->hfs_mutex, hfs_mutex_group, hfs_lock_attr);
lck_mtx_init(&hfsmp->hfc_mutex, hfs_mutex_group, hfs_lock_attr);
lck_rw_init(&hfsmp->hfs_global_lock, hfs_rwlock_group, hfs_lock_attr);
lck_spin_init(&hfsmp->vcbFreeExtLock, hfs_spinlock_group, hfs_lock_attr);
if (mp)
vfs_setfsprivate(mp, hfsmp);
hfsmp->hfs_mp = mp;
hfsmp->hfs_raw_dev = vnode_specrdev(devvp);
hfsmp->hfs_devvp = devvp;
vnode_ref(devvp);
hfsmp->hfs_logical_block_size = log_blksize;
hfsmp->hfs_logical_block_count = log_blkcnt;
hfsmp->hfs_logical_bytes = (uint64_t) log_blksize * (uint64_t) log_blkcnt;
hfsmp->hfs_physical_block_size = phys_blksize;
hfsmp->hfs_log_per_phys = (phys_blksize / log_blksize);
hfsmp->hfs_flags |= HFS_WRITEABLE_MEDIA;
if (ronly)
hfsmp->hfs_flags |= HFS_READ_ONLY;
if (mp && ((unsigned int)vfs_flags(mp)) & MNT_UNKNOWNPERMISSIONS)
hfsmp->hfs_flags |= HFS_UNKNOWN_PERMS;
#if QUOTA
for (i = 0; i < MAXQUOTAS; i++)
dqfileinit(&hfsmp->hfs_qfiles[i]);
#endif
if (args) {
hfsmp->hfs_uid = (args->hfs_uid == (uid_t)VNOVAL) ? UNKNOWNUID : args->hfs_uid;
if (hfsmp->hfs_uid == 0xfffffffd) hfsmp->hfs_uid = UNKNOWNUID;
hfsmp->hfs_gid = (args->hfs_gid == (gid_t)VNOVAL) ? UNKNOWNGID : args->hfs_gid;
if (hfsmp->hfs_gid == 0xfffffffd) hfsmp->hfs_gid = UNKNOWNGID;
vfs_setowner(mp, hfsmp->hfs_uid, hfsmp->hfs_gid);
if (args->hfs_mask != (mode_t)VNOVAL) {
hfsmp->hfs_dir_mask = args->hfs_mask & ALLPERMS;
if (args->flags & HFSFSMNT_NOXONFILES) {
hfsmp->hfs_file_mask = (args->hfs_mask & DEFFILEMODE);
} else {
hfsmp->hfs_file_mask = args->hfs_mask & ALLPERMS;
}
} else {
hfsmp->hfs_dir_mask = UNKNOWNPERMISSIONS & ALLPERMS;
hfsmp->hfs_file_mask = UNKNOWNPERMISSIONS & DEFFILEMODE;
}
if ((args->flags != (int)VNOVAL) && (args->flags & HFSFSMNT_WRAPPER))
mntwrapper = 1;
} else {
if (mp && ((unsigned int)vfs_flags(mp)) & MNT_UNKNOWNPERMISSIONS) {
hfsmp->hfs_uid = UNKNOWNUID;
hfsmp->hfs_gid = UNKNOWNGID;
vfs_setowner(mp, hfsmp->hfs_uid, hfsmp->hfs_gid);
hfsmp->hfs_dir_mask = UNKNOWNPERMISSIONS & ALLPERMS;
hfsmp->hfs_file_mask = UNKNOWNPERMISSIONS & DEFFILEMODE;
}
}
if (VNOP_IOCTL(devvp, DKIOCISWRITABLE, (caddr_t)&iswritable, 0, context) == 0) {
if (iswritable)
hfsmp->hfs_flags |= HFS_WRITEABLE_MEDIA;
else
hfsmp->hfs_flags &= ~HFS_WRITEABLE_MEDIA;
}
rl_init(&hfsmp->hfs_reserved_ranges[0]);
rl_init(&hfsmp->hfs_reserved_ranges[1]);
struct timeval tv;
microtime(&tv);
hfsmp->hfs_mount_time = tv.tv_sec;
if ((SWAP_BE16(mdbp->drSigWord) == kHFSSigWord) &&
(mntwrapper || (SWAP_BE16(mdbp->drEmbedSigWord) != kHFSPlusSigWord))) {
#if CONFIG_HFS_STD
if (journal_replay_only) {
retval = 0;
goto error_exit;
}
if (vfs_isrdwr(mp)) {
retval = EROFS;
goto error_exit;
}
printf("hfs_mountfs: Mounting HFS Standard volumes was deprecated in Mac OS 10.7 \n");
hfsmp->hfs_flags |= HFS_READ_ONLY;
hfsmp->hfs_flags &= ~HFS_WRITEABLE_MEDIA;
if ((vfs_flags(mp) & MNT_ROOTFS)) {
retval = EINVAL;
goto error_exit;
}
if (log_blksize > kHFSBlockSize) {
log_blksize = kHFSBlockSize;
if (VNOP_IOCTL(devvp, DKIOCSETBLOCKSIZE, (caddr_t)&log_blksize, FWRITE, context)) {
retval = ENXIO;
goto error_exit;
}
if (VNOP_IOCTL(devvp, DKIOCGETBLOCKCOUNT, (caddr_t)&log_blkcnt, 0, context)) {
retval = ENXIO;
goto error_exit;
}
hfsmp->hfs_logical_block_size = log_blksize;
hfsmp->hfs_logical_block_count = log_blkcnt;
hfsmp->hfs_logical_bytes = (uint64_t) log_blksize * (uint64_t) log_blkcnt;
hfsmp->hfs_physical_block_size = log_blksize;
hfsmp->hfs_log_per_phys = 1;
}
if (args) {
hfsmp->hfs_encoding = args->hfs_encoding;
HFSTOVCB(hfsmp)->volumeNameEncodingHint = args->hfs_encoding;
gTimeZone = args->hfs_timezone;
}
retval = hfs_getconverter(hfsmp->hfs_encoding, &hfsmp->hfs_get_unicode,
&hfsmp->hfs_get_hfsname);
if (retval)
goto error_exit;
retval = hfs_MountHFSVolume(hfsmp, mdbp, p);
if (retval)
(void) hfs_relconverter(hfsmp->hfs_encoding);
#else
retval = EINVAL;
goto error_exit;
#endif
}
else {
HFSPlusVolumeHeader *vhp;
off_t embeddedOffset;
int jnl_disable = 0;
if (SWAP_BE16(mdbp->drEmbedSigWord) == kHFSPlusSigWord) {
embeddedOffset = SWAP_BE16(mdbp->drAlBlSt) * kHFSBlockSize;
embeddedOffset += (u_int64_t)SWAP_BE16(mdbp->drEmbedExtent.startBlock) *
(u_int64_t)SWAP_BE32(mdbp->drAlBlkSiz);
hfsmp->hfs_flags &= ~HFS_CS_METADATA_PIN;
if ((embeddedOffset % log_blksize) != 0) {
printf("hfs_mountfs: embedded volume offset not"
" a multiple of physical block size (%d);"
" switching to 512\n", log_blksize);
log_blksize = 512;
if (VNOP_IOCTL(devvp, DKIOCSETBLOCKSIZE,
(caddr_t)&log_blksize, FWRITE, context)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: DKIOCSETBLOCKSIZE (3) failed\n");
}
retval = ENXIO;
goto error_exit;
}
if (VNOP_IOCTL(devvp, DKIOCGETBLOCKCOUNT,
(caddr_t)&log_blkcnt, 0, context)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: DKIOCGETBLOCKCOUNT (3) failed\n");
}
retval = ENXIO;
goto error_exit;
}
hfsmp->hfs_logical_block_count *=
hfsmp->hfs_logical_block_size / log_blksize;
hfsmp->hfs_logical_block_size = log_blksize;
hfsmp->hfs_physical_block_size = log_blksize;
phys_blksize = log_blksize;
hfsmp->hfs_log_per_phys = 1;
}
disksize = (u_int64_t)SWAP_BE16(mdbp->drEmbedExtent.blockCount) *
(u_int64_t)SWAP_BE32(mdbp->drAlBlkSiz);
hfsmp->hfs_logical_block_count = disksize / log_blksize;
hfsmp->hfs_logical_bytes = (uint64_t) hfsmp->hfs_logical_block_count * (uint64_t) hfsmp->hfs_logical_block_size;
mdb_offset = (daddr64_t)((embeddedOffset / log_blksize) + HFS_PRI_SECTOR(log_blksize));
if (bp) {
buf_markinvalid(bp);
buf_brelse(bp);
bp = NULL;
}
retval = (int)buf_meta_bread(devvp, HFS_PHYSBLK_ROUNDDOWN(mdb_offset, hfsmp->hfs_log_per_phys),
phys_blksize, cred, &bp);
if (retval) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: buf_meta_bread (2) failed with %d\n", retval);
}
goto error_exit;
}
bcopy((char *)buf_dataptr(bp) + HFS_PRI_OFFSET(phys_blksize), mdbp, 512);
buf_brelse(bp);
bp = NULL;
vhp = (HFSPlusVolumeHeader*) mdbp;
}
else {
embeddedOffset = 0;
vhp = (HFSPlusVolumeHeader*) mdbp;
}
retval = hfs_ValidateHFSPlusVolumeHeader(hfsmp, vhp);
if (retval)
goto error_exit;
if (SWAP_BE32(vhp->blockSize) < hfsmp->hfs_physical_block_size) {
phys_blksize = hfsmp->hfs_logical_block_size;
hfsmp->hfs_physical_block_size = hfsmp->hfs_logical_block_size;
hfsmp->hfs_log_per_phys = 1;
retval = buf_invalidateblks(devvp, 0, 0, 0);
if (retval)
goto error_exit;
}
if (isroot && ((SWAP_BE32(vhp->attributes) & kHFSVolumeUnmountedMask) != 0)) {
vfs_set_root_unmounted_cleanly();
}
if (!journal_replay_only
&& !(vfs_flags(mp) & MNT_ROOTFS)
&& (SWAP_BE32(vhp->attributes) & kHFSVolumeInconsistentMask)
&& !(hfsmp->hfs_flags & HFS_READ_ONLY)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: failed to mount non-root inconsistent disk\n");
}
retval = EINVAL;
goto error_exit;
}
hfsmp->jnl = NULL;
hfsmp->jvp = NULL;
if (args != NULL && (args->flags & HFSFSMNT_EXTENDED_ARGS) &&
args->journal_disable) {
jnl_disable = 1;
}
if ( (SWAP_BE32(vhp->lastMountedVersion) == kHFSJMountVersion)
&& (SWAP_BE32(vhp->attributes) & kHFSVolumeJournaledMask)
&& !jnl_disable) {
if ((retval = hfs_early_journal_init(hfsmp, vhp, args, embeddedOffset, mdb_offset, mdbp, cred)) == 0) {
if (mp)
vfs_setflags(mp, (u_int64_t)((unsigned int)MNT_JOURNALED));
} else {
if (retval == EROFS) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: hfs_early_journal_init indicated external jnl \n");
}
retval = EINVAL;
goto error_exit;
}
if (!ronly) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: hfs_early_journal_init failed, setting to FSK \n");
}
HFSPlusVolumeHeader *jvhp;
hfsmp->hfs_flags |= HFS_NEED_JNL_RESET;
if (mdb_offset == 0) {
mdb_offset = (daddr64_t)((embeddedOffset / log_blksize) + HFS_PRI_SECTOR(log_blksize));
}
bp = NULL;
retval = (int)buf_meta_bread(devvp,
HFS_PHYSBLK_ROUNDDOWN(mdb_offset, hfsmp->hfs_log_per_phys),
phys_blksize, cred, &bp);
if (retval == 0) {
jvhp = (HFSPlusVolumeHeader *)(buf_dataptr(bp) + HFS_PRI_OFFSET(phys_blksize));
if (SWAP_BE16(jvhp->signature) == kHFSPlusSigWord || SWAP_BE16(jvhp->signature) == kHFSXSigWord) {
printf ("hfs(1): Journal replay fail. Writing lastMountVersion as FSK!\n");
jvhp->lastMountedVersion = SWAP_BE32(kFSKMountVersion);
buf_bwrite(bp);
} else {
buf_brelse(bp);
}
bp = NULL;
} else if (bp) {
buf_brelse(bp);
bp = NULL;
}
}
if (mp && !(vfs_flags(mp) & MNT_ROOTFS)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: hfs_early_journal_init failed, erroring out \n");
}
retval = EINVAL;
goto error_exit;
}
}
}
if (journal_replay_only) {
retval = 0;
goto error_exit;
}
#if CONFIG_HFS_STD
(void) hfs_getconverter(0, &hfsmp->hfs_get_unicode, &hfsmp->hfs_get_hfsname);
#endif
retval = hfs_MountHFSPlusVolume(hfsmp, vhp, embeddedOffset, disksize, p, args, cred);
if ((retval == ENXIO) && (log_blksize > 512) && (log_blksize != minblksize)) {
printf("hfs_mountfs: could not use physical block size "
"(%d) switching to 512\n", log_blksize);
log_blksize = 512;
if (VNOP_IOCTL(devvp, DKIOCSETBLOCKSIZE, (caddr_t)&log_blksize, FWRITE, context)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: DKIOCSETBLOCKSIZE (4) failed \n");
}
retval = ENXIO;
goto error_exit;
}
if (VNOP_IOCTL(devvp, DKIOCGETBLOCKCOUNT, (caddr_t)&log_blkcnt, 0, context)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: DKIOCGETBLOCKCOUNT (4) failed \n");
}
retval = ENXIO;
goto error_exit;
}
set_fsblocksize(devvp);
hfsmp->hfs_logical_block_count *= hfsmp->hfs_logical_block_size / log_blksize;
hfsmp->hfs_logical_block_size = log_blksize;
hfsmp->hfs_log_per_phys = hfsmp->hfs_physical_block_size / log_blksize;
hfsmp->hfs_logical_bytes = (uint64_t) hfsmp->hfs_logical_block_count * (uint64_t) hfsmp->hfs_logical_block_size;
if (hfsmp->jnl && hfsmp->jvp == devvp) {
journal_close(hfsmp->jnl);
hfsmp->jnl = NULL;
if (hfs_early_journal_init(hfsmp, vhp, args, embeddedOffset, mdb_offset, mdbp, cred) == 0) {
vfs_setflags(mp, (u_int64_t)((unsigned int)MNT_JOURNALED));
} else {
if (!ronly) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: hfs_early_journal_init (2) resetting.. \n");
}
HFSPlusVolumeHeader *jvhp;
hfsmp->hfs_flags |= HFS_NEED_JNL_RESET;
if (mdb_offset == 0) {
mdb_offset = (daddr64_t)((embeddedOffset / log_blksize) + HFS_PRI_SECTOR(log_blksize));
}
bp = NULL;
retval = (int)buf_meta_bread(devvp, HFS_PHYSBLK_ROUNDDOWN(mdb_offset, hfsmp->hfs_log_per_phys),
phys_blksize, cred, &bp);
if (retval == 0) {
jvhp = (HFSPlusVolumeHeader *)(buf_dataptr(bp) + HFS_PRI_OFFSET(phys_blksize));
if (SWAP_BE16(jvhp->signature) == kHFSPlusSigWord || SWAP_BE16(jvhp->signature) == kHFSXSigWord) {
printf ("hfs(2): Journal replay fail. Writing lastMountVersion as FSK!\n");
jvhp->lastMountedVersion = SWAP_BE32(kFSKMountVersion);
buf_bwrite(bp);
} else {
buf_brelse(bp);
}
bp = NULL;
} else if (bp) {
buf_brelse(bp);
bp = NULL;
}
}
if ( !(vfs_flags(mp) & MNT_ROOTFS)) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: hfs_early_journal_init (2) failed \n");
}
retval = EINVAL;
goto error_exit;
}
}
}
retval = hfs_MountHFSPlusVolume(hfsmp, vhp, embeddedOffset, disksize, p, args, cred);
if (retval && HFS_MOUNT_DEBUG) {
printf("hfs_MountHFSPlusVolume (late) returned %d\n",retval);
}
}
#if CONFIG_HFS_STD
if (retval)
(void) hfs_relconverter(0);
#endif
}
hfsmp->hfs_last_mounted_mtime = hfsmp->hfs_mtime;
if ( retval ) {
if (HFS_MOUNT_DEBUG) {
printf("hfs_mountfs: encountered failure %d \n", retval);
}
goto error_exit;
}
struct vfsstatfs *vsfs = vfs_statfs(mp);
vsfs->f_fsid.val[0] = dev;
vsfs->f_fsid.val[1] = vfs_typenum(mp);
vfs_setmaxsymlen(mp, 0);
#if CONFIG_HFS_STD
if (ISSET(hfsmp->hfs_flags, HFS_STANDARD)) {
mount_set_noreaddirext (mp);
}
#endif
if (args) {
hfsmp->hfs_freespace_notify_dangerlimit =
MIN(HFS_VERYLOWDISKTRIGGERLEVEL / HFSTOVCB(hfsmp)->blockSize,
(HFSTOVCB(hfsmp)->totalBlocks / 100) * HFS_VERYLOWDISKTRIGGERFRACTION);
hfsmp->hfs_freespace_notify_warninglimit =
MIN(HFS_LOWDISKTRIGGERLEVEL / HFSTOVCB(hfsmp)->blockSize,
(HFSTOVCB(hfsmp)->totalBlocks / 100) * HFS_LOWDISKTRIGGERFRACTION);
hfsmp->hfs_freespace_notify_nearwarninglimit =
MIN(HFS_NEARLOWDISKTRIGGERLEVEL / HFSTOVCB(hfsmp)->blockSize,
(HFSTOVCB(hfsmp)->totalBlocks / 100) * HFS_NEARLOWDISKTRIGGERFRACTION);
hfsmp->hfs_freespace_notify_desiredlevel =
MIN(HFS_LOWDISKSHUTOFFLEVEL / HFSTOVCB(hfsmp)->blockSize,
(HFSTOVCB(hfsmp)->totalBlocks / 100) * HFS_LOWDISKSHUTOFFFRACTION);
} else {
hfsmp->hfs_freespace_notify_dangerlimit =
MIN(HFS_ROOTVERYLOWDISKTRIGGERLEVEL / HFSTOVCB(hfsmp)->blockSize,
(HFSTOVCB(hfsmp)->totalBlocks / 100) * HFS_ROOTVERYLOWDISKTRIGGERFRACTION);
hfsmp->hfs_freespace_notify_warninglimit =
MIN(HFS_ROOTLOWDISKTRIGGERLEVEL / HFSTOVCB(hfsmp)->blockSize,
(HFSTOVCB(hfsmp)->totalBlocks / 100) * HFS_ROOTLOWDISKTRIGGERFRACTION);
hfsmp->hfs_freespace_notify_nearwarninglimit =
MIN(HFS_ROOTNEARLOWDISKTRIGGERLEVEL / HFSTOVCB(hfsmp)->blockSize,
(HFSTOVCB(hfsmp)->totalBlocks / 100) * HFS_ROOTNEARLOWDISKTRIGGERFRACTION);
hfsmp->hfs_freespace_notify_desiredlevel =
MIN(HFS_ROOTLOWDISKSHUTOFFLEVEL / HFSTOVCB(hfsmp)->blockSize,
(HFSTOVCB(hfsmp)->totalBlocks / 100) * HFS_ROOTLOWDISKSHUTOFFFRACTION);
};
if (VNOP_IOCTL(devvp, DKIOCISVIRTUAL, (caddr_t)&isvirtual, 0, context) == 0) {
if (isvirtual) {
hfsmp->hfs_flags |= HFS_VIRTUAL_DEVICE;
}
}
if (!isroot
&& !ISSET(hfsmp->hfs_flags, HFS_VIRTUAL_DEVICE)
&& hfs_is_ejectable(vfs_statfs(mp)->f_mntfromname)) {
SET(hfsmp->hfs_flags, HFS_RUN_SYNCER);
}
const char *dev_name = (hfsmp->hfs_devvp
? vnode_getname_printable(hfsmp->hfs_devvp) : NULL);
printf("hfs: mounted %s on device %s\n",
(hfsmp->vcbVN[0] ? (const char*) hfsmp->vcbVN : "unknown"),
dev_name ?: "unknown device");
if (dev_name)
vnode_putname_printable(dev_name);
hfsmp->hfs_notification_conditions = 0;
hfs_generate_volume_notifications(hfsmp);
if (ronly == 0) {
(void) hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT);
}
hfs_free(mdbp, kMDBSize);
return (0);
error_exit:
if (bp)
buf_brelse(bp);
hfs_free(mdbp, kMDBSize);
hfs_close_jvp(hfsmp);
if (hfsmp) {
if (hfsmp->hfs_devvp) {
vnode_rele(hfsmp->hfs_devvp);
}
hfs_locks_destroy(hfsmp);
hfs_delete_chash(hfsmp);
hfs_idhash_destroy (hfsmp);
hfs_free(hfsmp, sizeof(*hfsmp));
if (mp)
vfs_setfsprivate(mp, NULL);
}
return (retval);
}
static int
hfs_start(__unused struct mount *mp, __unused int flags, __unused vfs_context_t context)
{
return (0);
}
int
hfs_unmount(struct mount *mp, int mntflags, vfs_context_t context)
{
struct proc *p = vfs_context_proc(context);
struct hfsmount *hfsmp = VFSTOHFS(mp);
int retval = E_NONE;
int flags;
int force;
int started_tr = 0;
flags = 0;
force = 0;
if (mntflags & MNT_FORCE) {
flags |= FORCECLOSE;
force = 1;
}
const char *dev_name = (hfsmp->hfs_devvp
? vnode_getname_printable(hfsmp->hfs_devvp) : NULL);
printf("hfs: unmount initiated on %s on device %s\n",
(hfsmp->vcbVN[0] ? (const char*) hfsmp->vcbVN : "unknown"),
dev_name ?: "unknown device");
if (dev_name)
vnode_putname_printable(dev_name);
if ((retval = hfs_flushfiles(mp, flags, p)) && !force)
return (retval);
if (hfsmp->hfs_flags & HFS_METADATA_ZONE)
(void) hfs_recording_suspend(hfsmp);
hfs_syncer_free(hfsmp);
if (hfsmp->hfs_flags & HFS_SUMMARY_TABLE) {
if (hfsmp->hfs_summary_table) {
int err = 0;
if (hfsmp->hfs_allocation_vp) {
err = hfs_lock (VTOC(hfsmp->hfs_allocation_vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
}
hfs_free(hfsmp->hfs_summary_table, hfsmp->hfs_summary_bytes);
hfsmp->hfs_summary_table = NULL;
hfsmp->hfs_flags &= ~HFS_SUMMARY_TABLE;
if (err == 0 && hfsmp->hfs_allocation_vp){
hfs_unlock (VTOC(hfsmp->hfs_allocation_vp));
}
}
}
if ((hfsmp->hfs_flags & HFS_READ_ONLY) == 0) {
retval = hfs_start_transaction(hfsmp);
if (retval == 0) {
started_tr = 1;
} else if (!force) {
goto err_exit;
}
if (hfsmp->hfs_startup_vp) {
(void) hfs_lock(VTOC(hfsmp->hfs_startup_vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
retval = hfs_fsync(hfsmp->hfs_startup_vp, MNT_WAIT, 0, p);
hfs_unlock(VTOC(hfsmp->hfs_startup_vp));
if (retval && !force)
goto err_exit;
}
if (hfsmp->hfs_attribute_vp) {
(void) hfs_lock(VTOC(hfsmp->hfs_attribute_vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
retval = hfs_fsync(hfsmp->hfs_attribute_vp, MNT_WAIT, 0, p);
hfs_unlock(VTOC(hfsmp->hfs_attribute_vp));
if (retval && !force)
goto err_exit;
}
(void) hfs_lock(VTOC(hfsmp->hfs_catalog_vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
retval = hfs_fsync(hfsmp->hfs_catalog_vp, MNT_WAIT, 0, p);
hfs_unlock(VTOC(hfsmp->hfs_catalog_vp));
if (retval && !force)
goto err_exit;
(void) hfs_lock(VTOC(hfsmp->hfs_extents_vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
retval = hfs_fsync(hfsmp->hfs_extents_vp, MNT_WAIT, 0, p);
hfs_unlock(VTOC(hfsmp->hfs_extents_vp));
if (retval && !force)
goto err_exit;
if (hfsmp->hfs_allocation_vp) {
(void) hfs_lock(VTOC(hfsmp->hfs_allocation_vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
retval = hfs_fsync(hfsmp->hfs_allocation_vp, MNT_WAIT, 0, p);
hfs_unlock(VTOC(hfsmp->hfs_allocation_vp));
if (retval && !force)
goto err_exit;
}
if (hfsmp->hfc_filevp && vnode_issystem(hfsmp->hfc_filevp)) {
retval = hfs_fsync(hfsmp->hfc_filevp, MNT_WAIT, 0, p);
if (retval && !force)
goto err_exit;
}
if (hfsmp->vcbAtrb & kHFSVolumeInconsistentMask) {
HFSTOVCB(hfsmp)->vcbAtrb &= ~kHFSVolumeUnmountedMask;
} else {
HFSTOVCB(hfsmp)->vcbAtrb |= kHFSVolumeUnmountedMask;
}
if (hfsmp->hfs_flags & HFS_HAS_SPARSE_DEVICE) {
int i;
u_int32_t min_start = hfsmp->totalBlocks;
lck_spin_lock(&hfsmp->vcbFreeExtLock);
for(i=0; i < (int)hfsmp->vcbFreeExtCnt; i++) {
if (hfsmp->vcbFreeExt[i].startBlock < min_start) {
min_start = hfsmp->vcbFreeExt[i].startBlock;
}
}
lck_spin_unlock(&hfsmp->vcbFreeExtLock);
if (min_start < hfsmp->nextAllocation) {
hfsmp->nextAllocation = min_start;
}
}
retval = hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT);
if (retval) {
HFSTOVCB(hfsmp)->vcbAtrb &= ~kHFSVolumeUnmountedMask;
if (!force)
goto err_exit;
}
if (started_tr) {
hfs_end_transaction(hfsmp);
started_tr = 0;
}
}
if (hfsmp->jnl) {
hfs_flush(hfsmp, HFS_FLUSH_FULL);
}
(void) hfsUnmount(hfsmp, p);
#if CONFIG_HFS_STD
if (HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord) {
(void) hfs_relconverter(hfsmp->hfs_encoding);
}
#endif
if (hfsmp->jnl) {
journal_close(hfsmp->jnl);
hfsmp->jnl = NULL;
}
VNOP_FSYNC(hfsmp->hfs_devvp, MNT_WAIT, context);
hfs_close_jvp(hfsmp);
(void) vflush(mp, NULLVP, FORCECLOSE);
#if HFS_SPARSE_DEV
if ((hfsmp->hfs_flags & HFS_HAS_SPARSE_DEVICE) && hfsmp->hfs_backingvp) {
struct vnode * tmpvp;
hfsmp->hfs_flags &= ~HFS_HAS_SPARSE_DEVICE;
tmpvp = hfsmp->hfs_backingvp;
hfsmp->hfs_backingvp = NULLVP;
vnode_rele(tmpvp);
}
#endif
vnode_rele(hfsmp->hfs_devvp);
hfs_locks_destroy(hfsmp);
hfs_delete_chash(hfsmp);
hfs_idhash_destroy(hfsmp);
hfs_assert(TAILQ_EMPTY(&hfsmp->hfs_reserved_ranges[HFS_TENTATIVE_BLOCKS])
&& TAILQ_EMPTY(&hfsmp->hfs_reserved_ranges[HFS_LOCKED_BLOCKS]));
hfs_assert(!hfsmp->lockedBlocks);
hfs_free(hfsmp, sizeof(*hfsmp));
#if HFS_LEAK_DEBUG
if (OSDecrementAtomic(&hfs_active_mounts) == 1) {
if (hfs_dump_allocations())
Debugger(NULL);
else {
printf("hfs: last unmount and nothing was leaked!\n");
msleep(hfs_unmount, NULL, PINOD, "hfs_unmount",
&(struct timespec){ 5, 0 });
}
}
#endif
return (0);
err_exit:
if (started_tr) {
hfs_end_transaction(hfsmp);
}
return retval;
}
int hfs_vfs_root(struct mount *mp, struct vnode **vpp, __unused vfs_context_t context)
{
return hfs_vget(VFSTOHFS(mp), (cnid_t)kHFSRootFolderID, vpp, 1, 0);
}
#if !QUOTA
static int
hfs_quotactl(__unused struct mount *mp, __unused int cmds, __unused uid_t uid, __unused caddr_t datap, __unused vfs_context_t context)
{
return (ENOTSUP);
}
#else
static int
hfs_quotactl(struct mount *mp, int cmds, uid_t uid, caddr_t datap, vfs_context_t context)
{
struct proc *p = vfs_context_proc(context);
int cmd, type, error;
if (uid == ~0U)
uid = kauth_cred_getuid(vfs_context_ucred(context));
cmd = cmds >> SUBCMDSHIFT;
switch (cmd) {
case Q_SYNC:
case Q_QUOTASTAT:
break;
case Q_GETQUOTA:
if (uid == kauth_cred_getuid(vfs_context_ucred(context)))
break;
default:
if ( (error = vfs_context_suser(context)) )
return (error);
}
type = cmds & SUBCMDMASK;
if ((u_int)type >= MAXQUOTAS)
return (EINVAL);
if (vfs_busy(mp, LK_NOWAIT))
return (0);
switch (cmd) {
case Q_QUOTAON:
error = hfs_quotaon(p, mp, type, datap);
break;
case Q_QUOTAOFF:
error = hfs_quotaoff(p, mp, type);
break;
case Q_SETQUOTA:
error = hfs_setquota(mp, uid, type, datap);
break;
case Q_SETUSE:
error = hfs_setuse(mp, uid, type, datap);
break;
case Q_GETQUOTA:
error = hfs_getquota(mp, uid, type, datap);
break;
case Q_SYNC:
error = hfs_qsync(mp);
break;
case Q_QUOTASTAT:
error = hfs_quotastat(mp, type, datap);
break;
default:
error = EINVAL;
break;
}
vfs_unbusy(mp);
return (error);
}
#endif
#define HFS_SUBTYPE_JOURNALED 0x01
#define HFS_SUBTYPE_CASESENSITIVE 0x02
#define HFS_SUBTYPE_STANDARDHFS 0x80
int
hfs_statfs(struct mount *mp, register struct vfsstatfs *sbp, __unused vfs_context_t context)
{
ExtendedVCB *vcb = VFSTOVCB(mp);
struct hfsmount *hfsmp = VFSTOHFS(mp);
u_int16_t subtype = 0;
sbp->f_bsize = (u_int32_t)vcb->blockSize;
sbp->f_iosize = (size_t)cluster_max_io_size(mp, 0);
sbp->f_blocks = (u_int64_t)((u_int32_t)vcb->totalBlocks);
sbp->f_bfree = (u_int64_t)((u_int32_t )hfs_freeblks(hfsmp, 0));
sbp->f_bavail = (u_int64_t)((u_int32_t )hfs_freeblks(hfsmp, 1));
sbp->f_files = (u_int64_t)HFS_MAX_FILES;
sbp->f_ffree = (u_int64_t)hfs_free_cnids(hfsmp);
if ((hfsmp->hfs_flags & HFS_STANDARD) == 0) {
if (hfsmp->jnl) {
subtype |= HFS_SUBTYPE_JOURNALED;
}
if (hfsmp->hfs_flags & HFS_CASE_SENSITIVE) {
subtype |= HFS_SUBTYPE_CASESENSITIVE;
}
}
#if CONFIG_HFS_STD
else {
subtype = HFS_SUBTYPE_STANDARDHFS;
}
#endif
sbp->f_fssubtype = subtype;
return (0);
}
void
hfs_sync_metadata(void *arg)
{
struct mount *mp = (struct mount *)arg;
struct hfsmount *hfsmp;
ExtendedVCB *vcb;
buf_t bp;
int retval;
daddr64_t priIDSector;
hfsmp = VFSTOHFS(mp);
vcb = HFSTOVCB(hfsmp);
priIDSector = (daddr64_t)((vcb->hfsPlusIOPosOffset / hfsmp->hfs_logical_block_size) +
HFS_PRI_SECTOR(hfsmp->hfs_logical_block_size));
retval = (int)buf_meta_bread(hfsmp->hfs_devvp,
HFS_PHYSBLK_ROUNDDOWN(priIDSector, hfsmp->hfs_log_per_phys),
hfsmp->hfs_physical_block_size, NOCRED, &bp);
if ((retval != 0 ) && (retval != ENXIO)) {
printf("hfs_sync_metadata: can't read volume header at %d! (retval 0x%x)\n",
(int)priIDSector, retval);
}
if (retval == 0 && ((buf_flags(bp) & (B_DELWRI | B_LOCKED)) == B_DELWRI)) {
buf_bwrite(bp);
} else if (bp) {
buf_brelse(bp);
}
if (hfsmp->hfs_partition_avh_sector) {
retval = (int)buf_meta_bread(hfsmp->hfs_devvp,
HFS_PHYSBLK_ROUNDDOWN(hfsmp->hfs_partition_avh_sector, hfsmp->hfs_log_per_phys),
hfsmp->hfs_physical_block_size, NOCRED, &bp);
if (retval == 0 && ((buf_flags(bp) & (B_DELWRI | B_LOCKED)) == B_DELWRI)) {
buf_bwrite(bp);
} else if (bp) {
buf_brelse(bp);
}
}
if ((hfsmp->hfs_fs_avh_sector) && (hfsmp->hfs_partition_avh_sector != hfsmp->hfs_fs_avh_sector)) {
retval = (int)buf_meta_bread(hfsmp->hfs_devvp,
HFS_PHYSBLK_ROUNDDOWN(hfsmp->hfs_fs_avh_sector, hfsmp->hfs_log_per_phys),
hfsmp->hfs_physical_block_size, NOCRED, &bp);
if (retval == 0 && ((buf_flags(bp) & (B_DELWRI | B_LOCKED)) == B_DELWRI)) {
buf_bwrite(bp);
} else if (bp) {
buf_brelse(bp);
}
}
}
struct hfs_sync_cargs {
kauth_cred_t cred;
struct proc *p;
int waitfor;
int error;
int atime_only_syncs;
time_t sync_start_time;
};
static int
hfs_sync_callback(struct vnode *vp, void *cargs)
{
struct cnode *cp = VTOC(vp);
struct hfs_sync_cargs *args;
int error;
args = (struct hfs_sync_cargs *)cargs;
if (hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT) != 0) {
return (VNODE_RETURNED);
}
hfs_dirty_t dirty_state = hfs_is_dirty(cp);
bool sync = dirty_state == HFS_DIRTY || vnode_hasdirtyblks(vp);
if (!sync && dirty_state == HFS_DIRTY_ATIME
&& args->atime_only_syncs < 256) {
if (args->sync_start_time - cp->c_attr.ca_atime > 60) {
sync = true;
++args->atime_only_syncs;
}
}
if (sync) {
error = hfs_fsync(vp, args->waitfor, 0, args->p);
if (error)
args->error = error;
} else if (cp->c_touch_acctime)
hfs_touchtimes(VTOHFS(vp), cp);
hfs_unlock(cp);
return (VNODE_RETURNED);
}
int
hfs_sync(struct mount *mp, int waitfor, vfs_context_t context)
{
struct proc *p = vfs_context_proc(context);
struct cnode *cp;
struct hfsmount *hfsmp;
ExtendedVCB *vcb;
struct vnode *meta_vp[4];
int i;
int error, allerror = 0;
struct hfs_sync_cargs args;
hfsmp = VFSTOHFS(mp);
hfs_lock_mount(hfsmp);
if ((hfsmp->hfs_flags & HFS_IN_CHANGEFS)
|| hfsmp->hfs_freeze_state != HFS_THAWED) {
hfs_unlock_mount(hfsmp);
return 0;
}
if (hfsmp->hfs_flags & HFS_READ_ONLY) {
hfs_unlock_mount(hfsmp);
return (EROFS);
}
++hfsmp->hfs_syncers;
hfs_unlock_mount(hfsmp);
args.cred = kauth_cred_get();
args.waitfor = waitfor;
args.p = p;
args.error = 0;
args.atime_only_syncs = 0;
struct timeval tv;
microtime(&tv);
args.sync_start_time = tv.tv_sec;
vnode_iterate(mp, 0, hfs_sync_callback, (void *)&args);
if (args.error)
allerror = args.error;
vcb = HFSTOVCB(hfsmp);
meta_vp[0] = vcb->extentsRefNum;
meta_vp[1] = vcb->catalogRefNum;
meta_vp[2] = vcb->allocationsRefNum;
meta_vp[3] = hfsmp->hfs_attribute_vp;
for (i = 0; i < 4; ++i) {
struct vnode *btvp;
btvp = meta_vp[i];;
if ((btvp==0) || (vnode_mount(btvp) != mp))
continue;
(void) hfs_lock(VTOC(btvp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
cp = VTOC(btvp);
if (!hfs_is_dirty(cp) && !vnode_hasdirtyblks(btvp)) {
hfs_unlock(VTOC(btvp));
continue;
}
error = vnode_get(btvp);
if (error) {
hfs_unlock(VTOC(btvp));
continue;
}
if ((error = hfs_fsync(btvp, waitfor, 0, p)))
allerror = error;
hfs_unlock(cp);
vnode_put(btvp);
};
#if CONFIG_HFS_STD
if (vcb->vcbSigWord == kHFSSigWord) {
if ((error = VNOP_FSYNC(hfsmp->hfs_devvp, waitfor, context))) {
allerror = error;
}
}
#endif
#if QUOTA
hfs_qsync(mp);
#endif
hfs_hotfilesync(hfsmp, vfs_context_kernel());
if (IsVCBDirty(vcb)) {
error = hfs_flushvolumeheader(hfsmp, waitfor == MNT_WAIT ? HFS_FVH_WAIT : 0);
if (error)
allerror = error;
}
if (hfsmp->jnl) {
hfs_flush(hfsmp, HFS_FLUSH_JOURNAL);
}
hfs_lock_mount(hfsmp);
boolean_t wake = (!--hfsmp->hfs_syncers
&& hfsmp->hfs_freeze_state == HFS_WANT_TO_FREEZE);
hfs_unlock_mount(hfsmp);
if (wake)
wakeup(&hfsmp->hfs_freeze_state);
return (allerror);
}
static int
hfs_fhtovp(struct mount *mp, int fhlen, unsigned char *fhp, struct vnode **vpp, __unused vfs_context_t context)
{
struct hfsfid *hfsfhp;
struct vnode *nvp;
int result;
*vpp = NULL;
hfsfhp = (struct hfsfid *)fhp;
if (fhlen < (int)sizeof(struct hfsfid))
return (EINVAL);
result = hfs_vget(VFSTOHFS(mp), ntohl(hfsfhp->hfsfid_cnid), &nvp, 0, 0);
if (result) {
if (result == ENOENT)
result = ESTALE;
return result;
}
*vpp = nvp;
hfs_unlock(VTOC(nvp));
return (0);
}
static int
hfs_vptofh(struct vnode *vp, int *fhlenp, unsigned char *fhp, __unused vfs_context_t context)
{
struct cnode *cp;
struct hfsfid *hfsfhp;
if (ISHFS(VTOVCB(vp)))
return (ENOTSUP);
if (*fhlenp < (int)sizeof(struct hfsfid))
return (EOVERFLOW);
cp = VTOC(vp);
hfsfhp = (struct hfsfid *)fhp;
hfsfhp->hfsfid_cnid = htonl(cp->c_fileid);
hfsfhp->hfsfid_gen = htonl(cp->c_fileid);
*fhlenp = sizeof(struct hfsfid);
return (0);
}
static int
hfs_init(__unused struct vfsconf *vfsp)
{
static int done = 0;
if (done)
return (0);
done = 1;
hfs_chashinit();
BTReserveSetup();
hfs_lock_attr = lck_attr_alloc_init();
hfs_group_attr = lck_grp_attr_alloc_init();
hfs_mutex_group = lck_grp_alloc_init("hfs-mutex", hfs_group_attr);
hfs_rwlock_group = lck_grp_alloc_init("hfs-rwlock", hfs_group_attr);
hfs_spinlock_group = lck_grp_alloc_init("hfs-spinlock", hfs_group_attr);
#if HFS_COMPRESSION
decmpfs_init();
#endif
journal_init();
return (0);
}
static void
hfs_locks_destroy(struct hfsmount *hfsmp)
{
lck_mtx_destroy(&hfsmp->hfs_mutex, hfs_mutex_group);
lck_mtx_destroy(&hfsmp->hfc_mutex, hfs_mutex_group);
lck_rw_destroy(&hfsmp->hfs_global_lock, hfs_rwlock_group);
lck_spin_destroy(&hfsmp->vcbFreeExtLock, hfs_spinlock_group);
return;
}
static int
hfs_getmountpoint(struct vnode *vp, struct hfsmount **hfsmpp)
{
struct hfsmount * hfsmp;
char fstypename[MFSNAMELEN];
if (vp == NULL)
return (EINVAL);
if (!vnode_isvroot(vp))
return (EINVAL);
vnode_vfsname(vp, fstypename);
if (strncmp(fstypename, "hfs", sizeof(fstypename)) != 0)
return (EINVAL);
hfsmp = VTOHFS(vp);
if (HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord)
return (EINVAL);
*hfsmpp = hfsmp;
return (0);
}
static errno_t ureplace(user_addr_t oldp, size_t *oldlenp,
user_addr_t newp, size_t newlen,
void *data, size_t len)
{
errno_t error;
if (!oldlenp)
return EFAULT;
if (oldp && *oldlenp < len)
return ENOMEM;
if (newp && newlen != len)
return EINVAL;
*oldlenp = len;
if (oldp) {
error = copyout(data, oldp, len);
if (error)
return error;
}
return newp ? copyin(newp, data, len) : 0;
}
#define UREPLACE(oldp, oldlenp, newp, newlenp, v) \
ureplace(oldp, oldlenp, newp, newlenp, &v, sizeof(v))
static hfsmount_t *hfs_mount_from_cwd(vfs_context_t ctx)
{
vnode_t vp = vfs_context_cwd(ctx);
if (!vp)
return NULL;
char fstypename[MFSNAMELEN];
vnode_vfsname(vp, fstypename);
if (strcmp(fstypename, "hfs"))
return NULL;
return VTOHFS(vp);
}
int
hfs_sysctl(int *name, u_int namelen, user_addr_t oldp, size_t *oldlenp,
user_addr_t newp, size_t newlen, vfs_context_t context)
{
#if !TARGET_OS_EMBEDDED
struct proc *p = vfs_context_proc(context);
#endif
int error;
struct hfsmount *hfsmp;
#if !TARGET_OS_EMBEDDED
if (name[0] == HFS_ENCODINGBIAS) {
int bias;
bias = hfs_getencodingbias();
error = UREPLACE(oldp, oldlenp, newp, newlen, bias);
if (error || !newp)
return error;
hfs_setencodingbias(bias);
return 0;
} else
#endif
if (name[0] == HFS_EXTEND_FS) {
u_int64_t newsize = 0;
vnode_t vp = vfs_context_cwd(context);
if (newp == USER_ADDR_NULL || vp == NULLVP
|| newlen != sizeof(quad_t) || !oldlenp)
return EINVAL;
if ((error = hfs_getmountpoint(vp, &hfsmp)))
return (error);
newsize = ((uint64_t)hfsmp->totalBlocks) * ((uint64_t)hfsmp->blockSize);
error = UREPLACE(oldp, oldlenp, newp, newlen, newsize);
if (error)
return error;
return hfs_extendfs(hfsmp, newsize, context);
} else if (name[0] == HFS_ENABLE_JOURNALING) {
vnode_t jvp;
ExtendedVCB *vcb;
struct cat_attr jnl_attr;
struct cat_attr jinfo_attr;
struct cat_fork jnl_fork;
struct cat_fork jinfo_fork;
buf_t jib_buf;
uint64_t jib_blkno;
uint32_t tmpblkno;
uint64_t journal_byte_offset;
uint64_t journal_size;
vnode_t jib_vp = NULLVP;
struct JournalInfoBlock local_jib;
int err = 0;
void *jnl = NULL;
int lockflags;
if (!kauth_cred_issuser(kauth_cred_get())) {
return (EPERM);
}
if (namelen != 4)
return EINVAL;
hfsmp = hfs_mount_from_cwd(context);
if (!hfsmp)
return EINVAL;
if (hfsmp->hfs_flags & HFS_READ_ONLY) {
return EROFS;
}
if (HFSTOVCB(hfsmp)->vcbSigWord == kHFSSigWord) {
printf("hfs: can't make a plain hfs volume journaled.\n");
return EINVAL;
}
if (hfsmp->jnl) {
printf("hfs: volume %s is already journaled!\n", hfsmp->vcbVN);
return EAGAIN;
}
vcb = HFSTOVCB(hfsmp);
tmpblkno = (uint32_t) name[1];
jib_blkno = (uint64_t) tmpblkno;
journal_byte_offset = (uint64_t) name[2];
journal_byte_offset *= hfsmp->blockSize;
journal_byte_offset += hfsmp->hfsPlusIOPosOffset;
journal_size = (uint64_t)((unsigned)name[3]);
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_EXTENTS, HFS_EXCLUSIVE_LOCK);
if (BTHasContiguousNodes(VTOF(vcb->catalogRefNum)) == 0 ||
BTHasContiguousNodes(VTOF(vcb->extentsRefNum)) == 0) {
printf("hfs: volume has a btree w/non-contiguous nodes. can not enable journaling.\n");
hfs_systemfile_unlock(hfsmp, lockflags);
return EINVAL;
}
hfs_systemfile_unlock(hfsmp, lockflags);
if ( GetFileInfo(vcb, kHFSRootFolderID, ".journal_info_block", &jinfo_attr, &jinfo_fork) == 0
|| GetFileInfo(vcb, kHFSRootFolderID, ".journal", &jnl_attr, &jnl_fork) == 0) {
return EINVAL;
}
if (jinfo_fork.cf_blocks > 1) {
return EINVAL;
}
if (jinfo_fork.cf_extents[0].startBlock != jib_blkno) {
return EINVAL;
}
if (hfs_vget (hfsmp, jinfo_attr.ca_fileid, &jib_vp, 1, 0)) {
return EINVAL;
}
vnode_recycle (jib_vp);
err = vnode_put (jib_vp);
if (err) {
return EINVAL;
}
memset (&local_jib, 'Z', sizeof(struct JournalInfoBlock));
local_jib.flags = SWAP_BE32(kJIJournalInFSMask);
local_jib.offset = SWAP_BE64(journal_byte_offset);
local_jib.size = SWAP_BE64(journal_size);
jib_buf = buf_getblk (hfsmp->hfs_devvp,
jib_blkno * (hfsmp->blockSize / hfsmp->hfs_logical_block_size),
hfsmp->blockSize, 0, 0, BLK_META);
char* buf_ptr = (char*) buf_dataptr (jib_buf);
memset (buf_ptr, 0, hfsmp->blockSize);
bcopy(&local_jib, buf_ptr, sizeof(local_jib));
if (buf_bwrite (jib_buf)) {
return EIO;
}
hfs_flush(hfsmp, HFS_FLUSH_CACHE);
hfs_sync(hfsmp->hfs_mp, MNT_WAIT, context);
printf("hfs: Initializing the journal (joffset 0x%llx sz 0x%llx)...\n",
(off_t)name[2], (off_t)name[3]);
jvp = hfsmp->hfs_devvp;
jnl = journal_create(jvp, journal_byte_offset, journal_size,
hfsmp->hfs_devvp,
hfsmp->hfs_logical_block_size,
0,
0,
hfs_sync_metadata, hfsmp->hfs_mp,
hfsmp->hfs_mp);
if (jnl)
journal_trim_set_callback(jnl, hfs_trim_callback, hfsmp);
if (jnl == NULL) {
printf("hfs: FAILED to create the journal!\n");
return EIO;
}
hfs_lock_global (hfsmp, HFS_EXCLUSIVE_LOCK);
buf_flushdirtyblks(hfsmp->hfs_devvp, TRUE, 0, "hfs_sysctl");
buf_flushdirtyblks(hfsmp->hfs_extents_vp, TRUE, 0, "hfs_sysctl");
buf_flushdirtyblks(hfsmp->hfs_catalog_vp, TRUE, 0, "hfs_sysctl");
buf_flushdirtyblks(hfsmp->hfs_allocation_vp, TRUE, 0, "hfs_sysctl");
if (hfsmp->hfs_attribute_vp)
buf_flushdirtyblks(hfsmp->hfs_attribute_vp, TRUE, 0, "hfs_sysctl");
HFSTOVCB(hfsmp)->vcbJinfoBlock = name[1];
HFSTOVCB(hfsmp)->vcbAtrb |= kHFSVolumeJournaledMask;
hfsmp->jvp = jvp;
hfsmp->jnl = jnl;
hfsmp->jnl_start = (u_int32_t)name[2];
hfsmp->jnl_size = (off_t)((unsigned)name[3]);
hfsmp->hfs_jnlinfoblkid = jinfo_attr.ca_fileid;
hfsmp->hfs_jnlfileid = jnl_attr.ca_fileid;
vfs_setflags(hfsmp->hfs_mp, (u_int64_t)((unsigned int)MNT_JOURNALED));
hfs_unlock_global (hfsmp);
hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT | HFS_FVH_WRITE_ALT);
{
fsid_t fsid;
fsid.val[0] = (int32_t)hfsmp->hfs_raw_dev;
fsid.val[1] = (int32_t)vfs_typenum(HFSTOVFS(hfsmp));
vfs_event_signal(&fsid, VQ_UPDATE, (intptr_t)NULL);
}
return 0;
} else if (name[0] == HFS_DISABLE_JOURNALING) {
if (!kauth_cred_issuser(kauth_cred_get())) {
return (EPERM);
}
hfsmp = hfs_mount_from_cwd(context);
if (!hfsmp)
return EINVAL;
if (hfsmp->hfs_private_attr[DIR_HARDLINKS].ca_entries != 0){
printf("hfs: cannot disable journaling on volumes with directory hardlinks\n");
return EPERM;
}
printf("hfs: disabling journaling for %s\n", hfsmp->vcbVN);
hfs_lock_global (hfsmp, HFS_EXCLUSIVE_LOCK);
journal_close(hfsmp->jnl);
hfsmp->jnl = NULL;
hfs_close_jvp(hfsmp);
vfs_clearflags(hfsmp->hfs_mp, (u_int64_t)((unsigned int)MNT_JOURNALED));
hfsmp->jnl_start = 0;
hfsmp->hfs_jnlinfoblkid = 0;
hfsmp->hfs_jnlfileid = 0;
HFSTOVCB(hfsmp)->vcbAtrb &= ~kHFSVolumeJournaledMask;
hfs_unlock_global (hfsmp);
hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT | HFS_FVH_WRITE_ALT);
{
fsid_t fsid;
fsid.val[0] = (int32_t)hfsmp->hfs_raw_dev;
fsid.val[1] = (int32_t)vfs_typenum(HFSTOVFS(hfsmp));
vfs_event_signal(&fsid, VQ_UPDATE, (intptr_t)NULL);
}
return 0;
} else if (name[0] == VFS_CTL_QUERY) {
#if TARGET_OS_EMBEDDED
return EPERM;
#else
struct sysctl_req *req;
union union_vfsidctl vc;
struct mount *mp;
struct vfsquery vq;
req = CAST_DOWN(struct sysctl_req *, oldp);
if (req == NULL) {
return EFAULT;
}
error = SYSCTL_IN(req, &vc, proc_is64bit(p)? sizeof(vc.vc64):sizeof(vc.vc32));
if (error) return (error);
mp = vfs_getvfs(&vc.vc32.vc_fsid);
if (mp == NULL) return (ENOENT);
hfsmp = VFSTOHFS(mp);
bzero(&vq, sizeof(vq));
vq.vq_flags = hfsmp->hfs_notification_conditions;
return SYSCTL_OUT(req, &vq, sizeof(vq));;
#endif
} else if (name[0] == HFS_REPLAY_JOURNAL) {
vnode_t devvp = NULL;
int device_fd;
if (namelen != 2) {
return (EINVAL);
}
device_fd = name[1];
error = file_vnode(device_fd, &devvp);
if (error) {
return error;
}
error = vnode_getwithref(devvp);
if (error) {
file_drop(device_fd);
return error;
}
error = hfs_journal_replay(devvp, context);
file_drop(device_fd);
vnode_put(devvp);
return error;
}
#if DEBUG || !TARGET_OS_EMBEDDED
else if (name[0] == HFS_ENABLE_RESIZE_DEBUG) {
if (!kauth_cred_issuser(kauth_cred_get())) {
return (EPERM);
}
int old = hfs_resize_debug;
int res = UREPLACE(oldp, oldlenp, newp, newlen, hfs_resize_debug);
if (old != hfs_resize_debug) {
printf("hfs: %s resize debug\n",
hfs_resize_debug ? "enabled" : "disabled");
}
return res;
}
#endif
return (ENOTSUP);
}
int
hfs_vfs_vget(struct mount *mp, ino64_t ino, struct vnode **vpp, __unused vfs_context_t context)
{
int error;
int lockflags;
struct hfsmount *hfsmp;
hfsmp = VFSTOHFS(mp);
error = hfs_vget(hfsmp, (cnid_t)ino, vpp, 1, 0);
if (error)
return error;
cnode_t *cp = VTOC(*vpp);
if (ISSET(cp->c_flag, C_HARDLINK) && ino == cp->c_fileid) {
hfs_lock_always(cp, HFS_SHARED_LOCK);
if (!hfs_haslinkorigin(cp)) {
if (!hfs_lock_upgrade(cp))
hfs_lock_always(cp, HFS_EXCLUSIVE_LOCK);
if (cp->c_cnid == cp->c_fileid) {
cnid_t link_id;
error = hfs_first_link(hfsmp, cp, &link_id);
if (!error) {
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
error = cat_findname(hfsmp, link_id, &cp->c_desc);
hfs_systemfile_unlock(hfsmp, lockflags);
}
} else {
error = 0;
}
if (!error)
hfs_savelinkorigin(cp, cp->c_parentcnid);
}
hfs_unlock(cp);
if (error) {
vnode_put(*vpp);
*vpp = NULL;
}
}
return error;
}
int
hfs_vget(struct hfsmount *hfsmp, cnid_t cnid, struct vnode **vpp, int skiplock, int allow_deleted)
{
struct vnode *vp = NULLVP;
struct cat_desc cndesc;
struct cat_attr cnattr;
struct cat_fork cnfork;
u_int32_t linkref = 0;
int error;
if ((cnid < kHFSFirstUserCatalogNodeID) &&
(cnid != kHFSRootFolderID && cnid != kHFSRootParentID)) {
return (ENOENT);
}
if (cnid == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid ||
cnid == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) {
return (ENOENT);
}
vp = hfs_chash_getvnode(hfsmp, cnid, 0, skiplock, allow_deleted);
if (vp) {
*vpp = vp;
return(0);
}
bzero(&cndesc, sizeof(cndesc));
bzero(&cnattr, sizeof(cnattr));
bzero(&cnfork, sizeof(cnfork));
if (cnid == kHFSRootParentID) {
static char hfs_rootname[] = "/";
cndesc.cd_nameptr = (const u_int8_t *)&hfs_rootname[0];
cndesc.cd_namelen = 1;
cndesc.cd_parentcnid = kHFSRootParentID;
cndesc.cd_cnid = kHFSRootFolderID;
cndesc.cd_flags = CD_ISDIR;
cnattr.ca_fileid = kHFSRootFolderID;
cnattr.ca_linkcount = 1;
cnattr.ca_entries = 1;
cnattr.ca_dircount = 1;
cnattr.ca_mode = (S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO);
} else {
int lockflags;
cnid_t pid;
const char *nameptr;
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_SHARED_LOCK);
error = cat_idlookup(hfsmp, cnid, 0, 0, &cndesc, &cnattr, &cnfork);
hfs_systemfile_unlock(hfsmp, lockflags);
if (error) {
*vpp = NULL;
return (error);
}
pid = cndesc.cd_parentcnid;
nameptr = (const char *)cndesc.cd_nameptr;
if ((pid == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid) &&
cndesc.cd_namelen > HFS_INODE_PREFIX_LEN &&
(bcmp(nameptr, HFS_INODE_PREFIX, HFS_INODE_PREFIX_LEN) == 0)) {
linkref = strtoul(&nameptr[HFS_INODE_PREFIX_LEN], NULL, 10);
} else if ((pid == hfsmp->hfs_private_desc[DIR_HARDLINKS].cd_cnid) &&
cndesc.cd_namelen > HFS_DIRINODE_PREFIX_LEN &&
(bcmp(nameptr, HFS_DIRINODE_PREFIX, HFS_DIRINODE_PREFIX_LEN) == 0)) {
linkref = strtoul(&nameptr[HFS_DIRINODE_PREFIX_LEN], NULL, 10);
} else if ((pid == hfsmp->hfs_private_desc[FILE_HARDLINKS].cd_cnid) &&
cndesc.cd_namelen > HFS_DELETE_PREFIX_LEN &&
(bcmp(nameptr, HFS_DELETE_PREFIX, HFS_DELETE_PREFIX_LEN) == 0)) {
*vpp = NULL;
cat_releasedesc(&cndesc);
return (ENOENT);
}
}
if (linkref) {
cnid_t lastid;
struct cat_desc linkdesc;
int linkerr = 0;
cnattr.ca_linkref = linkref;
bzero (&linkdesc, sizeof (linkdesc));
linkerr = hfs_lookup_lastlink (hfsmp, linkref, &lastid, &linkdesc);
if ((linkerr == 0) && (lastid != 0)) {
cat_releasedesc (&cndesc);
bcopy (&linkdesc, &cndesc, sizeof(linkdesc));
}
}
if (linkref) {
int newvnode_flags = 0;
error = hfs_getnewvnode(hfsmp, NULL, NULL, &cndesc, 0, &cnattr,
&cnfork, &vp, &newvnode_flags);
if (error == 0) {
VTOC(vp)->c_flag |= C_HARDLINK;
vnode_setmultipath(vp);
}
} else {
int newvnode_flags = 0;
void *buf = hfs_malloc(MAXPATHLEN);
struct componentname cn = {
.cn_nameiop = LOOKUP,
.cn_flags = ISLASTCN,
.cn_pnlen = MAXPATHLEN,
.cn_namelen = cndesc.cd_namelen,
.cn_pnbuf = buf,
.cn_nameptr = buf
};
bcopy(cndesc.cd_nameptr, cn.cn_nameptr, cndesc.cd_namelen + 1);
error = hfs_getnewvnode(hfsmp, NULLVP, &cn, &cndesc, 0, &cnattr,
&cnfork, &vp, &newvnode_flags);
if (error == 0 && (VTOC(vp)->c_flag & C_HARDLINK)) {
hfs_savelinkorigin(VTOC(vp), cndesc.cd_parentcnid);
}
hfs_free(buf, MAXPATHLEN);
}
cat_releasedesc(&cndesc);
*vpp = vp;
if (vp && skiplock) {
hfs_unlock(VTOC(vp));
}
return (error);
}
static int
#if QUOTA
hfs_flushfiles(struct mount *mp, int flags, struct proc *p)
#else
hfs_flushfiles(struct mount *mp, int flags, __unused struct proc *p)
#endif
{
struct hfsmount *hfsmp;
struct vnode *skipvp = NULLVP;
int error;
int accounted_root_usecounts;
#if QUOTA
int i;
#endif
hfsmp = VFSTOHFS(mp);
accounted_root_usecounts = 0;
#if QUOTA
if (((unsigned int)vfs_flags(mp)) & MNT_QUOTA) {
for (i = 0; i < MAXQUOTAS; i++) {
if (hfsmp->hfs_qfiles[i].qf_vp != NULLVP)
++accounted_root_usecounts;
}
}
#endif
if (accounted_root_usecounts > 0) {
skipvp = hfs_chash_getvnode(hfsmp, kHFSRootFolderID, 0, 0, 0);
}
error = vflush(mp, skipvp, SKIPSYSTEM | SKIPSWAP | flags);
if (error != 0)
return(error);
error = vflush(mp, skipvp, SKIPSYSTEM | flags);
if (skipvp) {
if ((error == 0) &&
(vnode_isinuse(skipvp, accounted_root_usecounts))) {
error = EBUSY;
}
hfs_unlock(VTOC(skipvp));
vnode_put(skipvp);
}
if (error && (flags & FORCECLOSE) == 0)
return (error);
#if QUOTA
if (((unsigned int)vfs_flags(mp)) & MNT_QUOTA) {
for (i = 0; i < MAXQUOTAS; i++) {
if (hfsmp->hfs_qfiles[i].qf_vp == NULLVP)
continue;
hfs_quotaoff(p, mp, i);
}
}
#endif
if (skipvp) {
error = vflush(mp, NULLVP, SKIPSYSTEM | flags);
}
return (error);
}
void
hfs_setencodingbits(struct hfsmount *hfsmp, u_int32_t encoding)
{
#define kIndexMacUkrainian 48
#define kIndexMacFarsi 49
u_int32_t index;
switch (encoding) {
case kTextEncodingMacUkrainian:
index = kIndexMacUkrainian;
break;
case kTextEncodingMacFarsi:
index = kIndexMacFarsi;
break;
default:
index = encoding;
break;
}
if (index < 64 && (hfsmp->encodingsBitmap & (u_int64_t)(1ULL << index)) == 0) {
hfs_lock_mount (hfsmp);
hfsmp->encodingsBitmap |= (u_int64_t)(1ULL << index);
MarkVCBDirty(hfsmp);
hfs_unlock_mount(hfsmp);
}
}
int
hfs_volupdate(struct hfsmount *hfsmp, enum volop op, int inroot)
{
struct timeval tv;
microtime(&tv);
hfs_lock_mount (hfsmp);
MarkVCBDirty(hfsmp);
hfsmp->hfs_mtime = tv.tv_sec;
switch (op) {
case VOL_UPDATE:
break;
case VOL_MKDIR:
if (hfsmp->hfs_dircount != 0xFFFFFFFF)
++hfsmp->hfs_dircount;
if (inroot && hfsmp->vcbNmRtDirs != 0xFFFF)
++hfsmp->vcbNmRtDirs;
break;
case VOL_RMDIR:
if (hfsmp->hfs_dircount != 0)
--hfsmp->hfs_dircount;
if (inroot && hfsmp->vcbNmRtDirs != 0xFFFF)
--hfsmp->vcbNmRtDirs;
break;
case VOL_MKFILE:
if (hfsmp->hfs_filecount != 0xFFFFFFFF)
++hfsmp->hfs_filecount;
if (inroot && hfsmp->vcbNmFls != 0xFFFF)
++hfsmp->vcbNmFls;
break;
case VOL_RMFILE:
if (hfsmp->hfs_filecount != 0)
--hfsmp->hfs_filecount;
if (inroot && hfsmp->vcbNmFls != 0xFFFF)
--hfsmp->vcbNmFls;
break;
}
hfs_unlock_mount (hfsmp);
if (hfsmp->jnl) {
hfs_flushvolumeheader(hfsmp, 0);
}
return (0);
}
#if CONFIG_HFS_STD
static int
hfs_flushMDB(struct hfsmount *hfsmp, int waitfor, int altflush)
{
ExtendedVCB *vcb = HFSTOVCB(hfsmp);
struct filefork *fp;
HFSMasterDirectoryBlock *mdb;
struct buf *bp = NULL;
int retval;
int sector_size;
ByteCount namelen;
sector_size = hfsmp->hfs_logical_block_size;
retval = (int)buf_bread(hfsmp->hfs_devvp, (daddr64_t)HFS_PRI_SECTOR(sector_size), sector_size, NOCRED, &bp);
if (retval) {
if (bp)
buf_brelse(bp);
return retval;
}
hfs_lock_mount (hfsmp);
mdb = (HFSMasterDirectoryBlock *)(buf_dataptr(bp) + HFS_PRI_OFFSET(sector_size));
mdb->drCrDate = SWAP_BE32 (UTCToLocal(to_hfs_time(vcb->hfs_itime)));
mdb->drLsMod = SWAP_BE32 (UTCToLocal(to_hfs_time(vcb->vcbLsMod)));
mdb->drAtrb = SWAP_BE16 (vcb->vcbAtrb);
mdb->drNmFls = SWAP_BE16 (vcb->vcbNmFls);
mdb->drAllocPtr = SWAP_BE16 (vcb->nextAllocation);
mdb->drClpSiz = SWAP_BE32 (vcb->vcbClpSiz);
mdb->drNxtCNID = SWAP_BE32 (vcb->vcbNxtCNID);
mdb->drFreeBks = SWAP_BE16 (vcb->freeBlocks);
namelen = strlen((char *)vcb->vcbVN);
retval = utf8_to_hfs(vcb, namelen, vcb->vcbVN, mdb->drVN);
if (retval)
retval = utf8_to_mac_roman(namelen, vcb->vcbVN, mdb->drVN);
mdb->drVolBkUp = SWAP_BE32 (UTCToLocal(to_hfs_time(vcb->vcbVolBkUp)));
mdb->drWrCnt = SWAP_BE32 (vcb->vcbWrCnt);
mdb->drNmRtDirs = SWAP_BE16 (vcb->vcbNmRtDirs);
mdb->drFilCnt = SWAP_BE32 (vcb->vcbFilCnt);
mdb->drDirCnt = SWAP_BE32 (vcb->vcbDirCnt);
bcopy(vcb->vcbFndrInfo, mdb->drFndrInfo, sizeof(mdb->drFndrInfo));
fp = VTOF(vcb->extentsRefNum);
mdb->drXTExtRec[0].startBlock = SWAP_BE16 (fp->ff_extents[0].startBlock);
mdb->drXTExtRec[0].blockCount = SWAP_BE16 (fp->ff_extents[0].blockCount);
mdb->drXTExtRec[1].startBlock = SWAP_BE16 (fp->ff_extents[1].startBlock);
mdb->drXTExtRec[1].blockCount = SWAP_BE16 (fp->ff_extents[1].blockCount);
mdb->drXTExtRec[2].startBlock = SWAP_BE16 (fp->ff_extents[2].startBlock);
mdb->drXTExtRec[2].blockCount = SWAP_BE16 (fp->ff_extents[2].blockCount);
mdb->drXTFlSize = SWAP_BE32 (fp->ff_blocks * vcb->blockSize);
mdb->drXTClpSiz = SWAP_BE32 (fp->ff_clumpsize);
FTOC(fp)->c_flag &= ~C_MODIFIED;
fp = VTOF(vcb->catalogRefNum);
mdb->drCTExtRec[0].startBlock = SWAP_BE16 (fp->ff_extents[0].startBlock);
mdb->drCTExtRec[0].blockCount = SWAP_BE16 (fp->ff_extents[0].blockCount);
mdb->drCTExtRec[1].startBlock = SWAP_BE16 (fp->ff_extents[1].startBlock);
mdb->drCTExtRec[1].blockCount = SWAP_BE16 (fp->ff_extents[1].blockCount);
mdb->drCTExtRec[2].startBlock = SWAP_BE16 (fp->ff_extents[2].startBlock);
mdb->drCTExtRec[2].blockCount = SWAP_BE16 (fp->ff_extents[2].blockCount);
mdb->drCTFlSize = SWAP_BE32 (fp->ff_blocks * vcb->blockSize);
mdb->drCTClpSiz = SWAP_BE32 (fp->ff_clumpsize);
FTOC(fp)->c_flag &= ~C_MODIFIED;
MarkVCBClean( vcb );
hfs_unlock_mount (hfsmp);
if (altflush) {
struct buf *alt_bp = NULL;
if (buf_meta_bread(hfsmp->hfs_devvp, hfsmp->hfs_partition_avh_sector, sector_size, NOCRED, &alt_bp) == 0) {
bcopy(mdb, (char *)buf_dataptr(alt_bp) + HFS_ALT_OFFSET(sector_size), kMDBSize);
(void) VNOP_BWRITE(alt_bp);
} else if (alt_bp)
buf_brelse(alt_bp);
}
if (waitfor != MNT_WAIT)
buf_bawrite(bp);
else
retval = VNOP_BWRITE(bp);
return (retval);
}
#endif
int
hfs_flushvolumeheader(struct hfsmount *hfsmp,
hfs_flush_volume_header_options_t options)
{
ExtendedVCB *vcb = HFSTOVCB(hfsmp);
struct filefork *fp;
HFSPlusVolumeHeader *volumeHeader, *altVH;
int retval;
struct buf *bp, *alt_bp;
int i;
daddr64_t priIDSector;
bool critical = false;
u_int16_t signature;
u_int16_t hfsversion;
daddr64_t avh_sector;
bool altflush = ISSET(options, HFS_FVH_WRITE_ALT);
if (ISSET(options, HFS_FVH_FLUSH_IF_DIRTY)
&& !hfs_header_needs_flushing(hfsmp)) {
return 0;
}
if (hfsmp->hfs_flags & HFS_READ_ONLY) {
return(0);
}
#if CONFIG_HFS_STD
if (hfsmp->hfs_flags & HFS_STANDARD) {
return hfs_flushMDB(hfsmp, ISSET(options, HFS_FVH_WAIT) ? MNT_WAIT : 0, altflush);
}
#endif
priIDSector = (daddr64_t)((vcb->hfsPlusIOPosOffset / hfsmp->hfs_logical_block_size) +
HFS_PRI_SECTOR(hfsmp->hfs_logical_block_size));
if (hfs_start_transaction(hfsmp) != 0) {
return EINVAL;
}
bp = NULL;
alt_bp = NULL;
retval = (int)buf_meta_bread(hfsmp->hfs_devvp,
HFS_PHYSBLK_ROUNDDOWN(priIDSector, hfsmp->hfs_log_per_phys),
hfsmp->hfs_physical_block_size, NOCRED, &bp);
if (retval) {
printf("hfs: err %d reading VH blk (vol=%s)\n", retval, vcb->vcbVN);
goto err_exit;
}
volumeHeader = (HFSPlusVolumeHeader *)((char *)buf_dataptr(bp) +
HFS_PRI_OFFSET(hfsmp->hfs_physical_block_size));
signature = SWAP_BE16 (volumeHeader->signature);
hfsversion = SWAP_BE16 (volumeHeader->version);
if ((signature != kHFSPlusSigWord && signature != kHFSXSigWord) ||
(hfsversion < kHFSPlusVersion) || (hfsversion > 100) ||
(SWAP_BE32 (volumeHeader->blockSize) != vcb->blockSize)) {
printf("hfs: corrupt VH on %s, sig 0x%04x, ver %d, blksize %d\n",
vcb->vcbVN, signature, hfsversion,
SWAP_BE32 (volumeHeader->blockSize));
hfs_mark_inconsistent(hfsmp, HFS_INCONSISTENCY_DETECTED);
avh_sector = hfsmp->hfs_partition_avh_sector;
if (hfsmp->hfs_partition_avh_sector != hfsmp->hfs_fs_avh_sector) {
uint64_t sector_count;
if ((retval = VNOP_IOCTL(hfsmp->hfs_devvp, DKIOCGETBLOCKCOUNT,
(caddr_t)§or_count, 0, vfs_context_current()))) {
printf("hfs_flushVH: err %d getting block count (%s) \n", retval, vcb->vcbVN);
retval = ENXIO;
goto err_exit;
}
if (sector_count != (uint64_t)hfsmp->hfs_logical_block_count) {
hfsmp->hfs_partition_avh_sector = (hfsmp->hfsPlusIOPosOffset / hfsmp->hfs_logical_block_size) +
HFS_ALT_SECTOR(hfsmp->hfs_logical_block_size, sector_count);
printf ("hfs_flushVH: partition size changed, partition_avh_sector=%qu, fs_avh_sector=%qu\n",
hfsmp->hfs_partition_avh_sector, hfsmp->hfs_fs_avh_sector);
avh_sector = hfsmp->hfs_fs_avh_sector;
}
}
printf ("hfs: trying alternate (for %s) avh_sector=%qu\n",
(avh_sector == hfsmp->hfs_fs_avh_sector) ? "file system" : "partition", avh_sector);
if (avh_sector) {
retval = buf_meta_bread(hfsmp->hfs_devvp,
HFS_PHYSBLK_ROUNDDOWN(avh_sector, hfsmp->hfs_log_per_phys),
hfsmp->hfs_physical_block_size, NOCRED, &alt_bp);
if (retval) {
printf("hfs: err %d reading alternate VH (%s)\n", retval, vcb->vcbVN);
goto err_exit;
}
altVH = (HFSPlusVolumeHeader *)((char *)buf_dataptr(alt_bp) +
HFS_ALT_OFFSET(hfsmp->hfs_physical_block_size));
signature = SWAP_BE16(altVH->signature);
hfsversion = SWAP_BE16(altVH->version);
if ((signature != kHFSPlusSigWord && signature != kHFSXSigWord) ||
(hfsversion < kHFSPlusVersion) || (kHFSPlusVersion > 100) ||
(SWAP_BE32(altVH->blockSize) != vcb->blockSize)) {
printf("hfs: corrupt alternate VH on %s, sig 0x%04x, ver %d, blksize %d\n",
vcb->vcbVN, signature, hfsversion,
SWAP_BE32(altVH->blockSize));
retval = EIO;
goto err_exit;
}
bcopy(altVH, volumeHeader, kMDBSize);
buf_brelse(alt_bp);
alt_bp = NULL;
} else {
retval = EIO;
goto err_exit;
}
}
if (hfsmp->jnl) {
journal_modify_block_start(hfsmp->jnl, bp);
}
if ((vcb->hfsPlusIOPosOffset != 0) &&
(SWAP_BE32 (volumeHeader->createDate) != vcb->localCreateDate)) {
struct buf *bp2;
HFSMasterDirectoryBlock *mdb;
retval = (int)buf_meta_bread(hfsmp->hfs_devvp,
HFS_PHYSBLK_ROUNDDOWN(HFS_PRI_SECTOR(hfsmp->hfs_logical_block_size), hfsmp->hfs_log_per_phys),
hfsmp->hfs_physical_block_size, NOCRED, &bp2);
if (retval) {
if (bp2)
buf_brelse(bp2);
retval = 0;
} else {
mdb = (HFSMasterDirectoryBlock *)(buf_dataptr(bp2) +
HFS_PRI_OFFSET(hfsmp->hfs_physical_block_size));
if ( SWAP_BE32 (mdb->drCrDate) != vcb->localCreateDate )
{
if (hfsmp->jnl) {
journal_modify_block_start(hfsmp->jnl, bp2);
}
mdb->drCrDate = SWAP_BE32 (vcb->localCreateDate);
if (hfsmp->jnl) {
journal_modify_block_end(hfsmp->jnl, bp2, NULL, NULL);
} else {
(void) VNOP_BWRITE(bp2);
}
}
else
{
buf_brelse(bp2);
}
}
}
hfs_lock_mount (hfsmp);
volumeHeader->attributes = SWAP_BE32 (vcb->vcbAtrb);
volumeHeader->journalInfoBlock = SWAP_BE32 (vcb->vcbJinfoBlock);
if (hfsmp->jnl) {
volumeHeader->lastMountedVersion = SWAP_BE32 (kHFSJMountVersion);
} else {
volumeHeader->lastMountedVersion = SWAP_BE32 (kHFSPlusMountVersion);
}
volumeHeader->createDate = SWAP_BE32 (vcb->localCreateDate);
volumeHeader->modifyDate = SWAP_BE32 (to_hfs_time(vcb->vcbLsMod));
volumeHeader->backupDate = SWAP_BE32 (to_hfs_time(vcb->vcbVolBkUp));
volumeHeader->fileCount = SWAP_BE32 (vcb->vcbFilCnt);
volumeHeader->folderCount = SWAP_BE32 (vcb->vcbDirCnt);
volumeHeader->totalBlocks = SWAP_BE32 (vcb->totalBlocks);
volumeHeader->freeBlocks = SWAP_BE32 (vcb->freeBlocks + vcb->reclaimBlocks);
volumeHeader->nextAllocation = SWAP_BE32 (vcb->nextAllocation);
volumeHeader->rsrcClumpSize = SWAP_BE32 (vcb->vcbClpSiz);
volumeHeader->dataClumpSize = SWAP_BE32 (vcb->vcbClpSiz);
volumeHeader->nextCatalogID = SWAP_BE32 (vcb->vcbNxtCNID);
volumeHeader->writeCount = SWAP_BE32 (vcb->vcbWrCnt);
volumeHeader->encodingsBitmap = SWAP_BE64 (vcb->encodingsBitmap);
if (bcmp(vcb->vcbFndrInfo, volumeHeader->finderInfo, sizeof(volumeHeader->finderInfo)) != 0) {
bcopy(vcb->vcbFndrInfo, volumeHeader->finderInfo, sizeof(volumeHeader->finderInfo));
critical = true;
}
if (!altflush && !ISSET(options, HFS_FVH_FLUSH_IF_DIRTY)) {
goto done;
}
fp = VTOF(vcb->extentsRefNum);
if (FTOC(fp)->c_flag & C_MODIFIED) {
for (i = 0; i < kHFSPlusExtentDensity; i++) {
volumeHeader->extentsFile.extents[i].startBlock =
SWAP_BE32 (fp->ff_extents[i].startBlock);
volumeHeader->extentsFile.extents[i].blockCount =
SWAP_BE32 (fp->ff_extents[i].blockCount);
}
volumeHeader->extentsFile.logicalSize = SWAP_BE64 (fp->ff_size);
volumeHeader->extentsFile.totalBlocks = SWAP_BE32 (fp->ff_blocks);
volumeHeader->extentsFile.clumpSize = SWAP_BE32 (fp->ff_clumpsize);
FTOC(fp)->c_flag &= ~C_MODIFIED;
altflush = true;
}
fp = VTOF(vcb->catalogRefNum);
if (FTOC(fp)->c_flag & C_MODIFIED) {
for (i = 0; i < kHFSPlusExtentDensity; i++) {
volumeHeader->catalogFile.extents[i].startBlock =
SWAP_BE32 (fp->ff_extents[i].startBlock);
volumeHeader->catalogFile.extents[i].blockCount =
SWAP_BE32 (fp->ff_extents[i].blockCount);
}
volumeHeader->catalogFile.logicalSize = SWAP_BE64 (fp->ff_size);
volumeHeader->catalogFile.totalBlocks = SWAP_BE32 (fp->ff_blocks);
volumeHeader->catalogFile.clumpSize = SWAP_BE32 (fp->ff_clumpsize);
FTOC(fp)->c_flag &= ~C_MODIFIED;
altflush = true;
}
fp = VTOF(vcb->allocationsRefNum);
if (FTOC(fp)->c_flag & C_MODIFIED) {
for (i = 0; i < kHFSPlusExtentDensity; i++) {
volumeHeader->allocationFile.extents[i].startBlock =
SWAP_BE32 (fp->ff_extents[i].startBlock);
volumeHeader->allocationFile.extents[i].blockCount =
SWAP_BE32 (fp->ff_extents[i].blockCount);
}
volumeHeader->allocationFile.logicalSize = SWAP_BE64 (fp->ff_size);
volumeHeader->allocationFile.totalBlocks = SWAP_BE32 (fp->ff_blocks);
volumeHeader->allocationFile.clumpSize = SWAP_BE32 (fp->ff_clumpsize);
FTOC(fp)->c_flag &= ~C_MODIFIED;
altflush = true;
}
if (hfsmp->hfs_attribute_vp) {
fp = VTOF(hfsmp->hfs_attribute_vp);
for (i = 0; i < kHFSPlusExtentDensity; i++) {
volumeHeader->attributesFile.extents[i].startBlock =
SWAP_BE32 (fp->ff_extents[i].startBlock);
volumeHeader->attributesFile.extents[i].blockCount =
SWAP_BE32 (fp->ff_extents[i].blockCount);
}
if (ISSET(FTOC(fp)->c_flag, C_MODIFIED)) {
FTOC(fp)->c_flag &= ~C_MODIFIED;
altflush = true;
}
volumeHeader->attributesFile.logicalSize = SWAP_BE64 (fp->ff_size);
volumeHeader->attributesFile.totalBlocks = SWAP_BE32 (fp->ff_blocks);
volumeHeader->attributesFile.clumpSize = SWAP_BE32 (fp->ff_clumpsize);
}
if (hfsmp->hfs_startup_vp) {
fp = VTOF(hfsmp->hfs_startup_vp);
if (FTOC(fp)->c_flag & C_MODIFIED) {
for (i = 0; i < kHFSPlusExtentDensity; i++) {
volumeHeader->startupFile.extents[i].startBlock =
SWAP_BE32 (fp->ff_extents[i].startBlock);
volumeHeader->startupFile.extents[i].blockCount =
SWAP_BE32 (fp->ff_extents[i].blockCount);
}
volumeHeader->startupFile.logicalSize = SWAP_BE64 (fp->ff_size);
volumeHeader->startupFile.totalBlocks = SWAP_BE32 (fp->ff_blocks);
volumeHeader->startupFile.clumpSize = SWAP_BE32 (fp->ff_clumpsize);
FTOC(fp)->c_flag &= ~C_MODIFIED;
altflush = true;
}
}
if (altflush)
critical = true;
done:
MarkVCBClean(hfsmp);
hfs_unlock_mount (hfsmp);
if (altflush) {
if (hfsmp->hfs_partition_avh_sector != hfsmp->hfs_fs_avh_sector) {
uint64_t sector_count;
if ((retval = VNOP_IOCTL(hfsmp->hfs_devvp, DKIOCGETBLOCKCOUNT,
(caddr_t)§or_count, 0, vfs_context_current()))) {
printf("hfs_flushVH: err %d getting block count (%s) \n", retval, vcb->vcbVN);
retval = ENXIO;
goto err_exit;
}
if (sector_count != (uint64_t)hfsmp->hfs_logical_block_count) {
hfsmp->hfs_partition_avh_sector = (hfsmp->hfsPlusIOPosOffset / hfsmp->hfs_logical_block_size) +
HFS_ALT_SECTOR(hfsmp->hfs_logical_block_size, sector_count);
printf ("hfs_flushVH: altflush: partition size changed, partition_avh_sector=%qu, fs_avh_sector=%qu\n",
hfsmp->hfs_partition_avh_sector, hfsmp->hfs_fs_avh_sector);
}
}
if ((hfsmp->hfs_fs_avh_sector) &&
(hfsmp->hfs_partition_avh_sector != hfsmp->hfs_fs_avh_sector)) {
if (buf_meta_bread(hfsmp->hfs_devvp,
HFS_PHYSBLK_ROUNDDOWN(hfsmp->hfs_fs_avh_sector, hfsmp->hfs_log_per_phys),
hfsmp->hfs_physical_block_size, NOCRED, &alt_bp) == 0) {
if (hfsmp->jnl) {
journal_modify_block_start(hfsmp->jnl, alt_bp);
}
bcopy(volumeHeader, (char *)buf_dataptr(alt_bp) +
HFS_ALT_OFFSET(hfsmp->hfs_physical_block_size),
kMDBSize);
if (hfsmp->jnl) {
journal_modify_block_end(hfsmp->jnl, alt_bp, NULL, NULL);
} else {
(void) VNOP_BWRITE(alt_bp);
}
} else if (alt_bp) {
buf_brelse(alt_bp);
}
}
if (hfsmp->hfs_partition_avh_sector) {
if (buf_meta_bread(hfsmp->hfs_devvp,
HFS_PHYSBLK_ROUNDDOWN(hfsmp->hfs_partition_avh_sector, hfsmp->hfs_log_per_phys),
hfsmp->hfs_physical_block_size, NOCRED, &alt_bp) == 0) {
if ((hfsmp->jnl) && (hfsmp->hfs_partition_avh_sector == hfsmp->hfs_fs_avh_sector)) {
journal_modify_block_start(hfsmp->jnl, alt_bp);
}
bcopy(volumeHeader, (char *)buf_dataptr(alt_bp) +
HFS_ALT_OFFSET(hfsmp->hfs_physical_block_size),
kMDBSize);
if ((hfsmp->jnl) && (hfsmp->hfs_partition_avh_sector == hfsmp->hfs_fs_avh_sector)) {
journal_modify_block_end (hfsmp->jnl, alt_bp, NULL, NULL);
} else {
(void) VNOP_BWRITE(alt_bp);
hfs_flush(hfsmp, HFS_FLUSH_CACHE);
}
} else if (alt_bp) {
buf_brelse(alt_bp);
}
}
}
if (hfsmp->jnl) {
journal_modify_block_end(hfsmp->jnl, bp, NULL, NULL);
} else {
if (!ISSET(options, HFS_FVH_WAIT)) {
buf_bawrite(bp);
} else {
retval = VNOP_BWRITE(bp);
if (critical && (retval == 0)) {
hfs_flush(hfsmp, HFS_FLUSH_CACHE);
}
}
}
hfs_end_transaction(hfsmp);
return (retval);
err_exit:
if (alt_bp)
buf_brelse(alt_bp);
if (bp)
buf_brelse(bp);
hfs_end_transaction(hfsmp);
return retval;
}
void
hfs_getvoluuid(struct hfsmount *hfsmp, uuid_t result_uuid)
{
if (uuid_is_null(hfsmp->hfs_full_uuid)) {
uuid_t result;
MD5_CTX md5c;
uint8_t rawUUID[8];
((uint32_t *)rawUUID)[0] = hfsmp->vcbFndrInfo[6];
((uint32_t *)rawUUID)[1] = hfsmp->vcbFndrInfo[7];
MD5Init( &md5c );
MD5Update( &md5c, HFS_UUID_NAMESPACE_ID, sizeof( uuid_t ) );
MD5Update( &md5c, rawUUID, sizeof (rawUUID) );
MD5Final( result, &md5c );
result[6] = 0x30 | ( result[6] & 0x0F );
result[8] = 0x80 | ( result[8] & 0x3F );
uuid_copy(hfsmp->hfs_full_uuid, result);
}
uuid_copy (result_uuid, hfsmp->hfs_full_uuid);
}
static int
hfs_vfs_getattr(struct mount *mp, struct vfs_attr *fsap, __unused vfs_context_t context)
{
#define HFS_ATTR_FILE_VALIDMASK (ATTR_FILE_VALIDMASK & ~(ATTR_FILE_FILETYPE | ATTR_FILE_FORKCOUNT | ATTR_FILE_FORKLIST | ATTR_FILE_CLUMPSIZE))
#define HFS_ATTR_CMN_VOL_VALIDMASK (ATTR_CMN_VALIDMASK & ~(ATTR_CMN_DATA_PROTECT_FLAGS))
ExtendedVCB *vcb = VFSTOVCB(mp);
struct hfsmount *hfsmp = VFSTOHFS(mp);
int searchfs_on = 0;
int exchangedata_on = 1;
#if CONFIG_SEARCHFS
searchfs_on = 1;
#endif
#if CONFIG_PROTECT
if (cp_fs_protected(mp)) {
exchangedata_on = 0;
}
#endif
VFSATTR_RETURN(fsap, f_objcount, (u_int64_t)hfsmp->vcbFilCnt + (u_int64_t)hfsmp->vcbDirCnt);
VFSATTR_RETURN(fsap, f_filecount, (u_int64_t)hfsmp->vcbFilCnt);
VFSATTR_RETURN(fsap, f_dircount, (u_int64_t)hfsmp->vcbDirCnt);
VFSATTR_RETURN(fsap, f_maxobjcount, (u_int64_t)0xFFFFFFFF);
VFSATTR_RETURN(fsap, f_iosize, (size_t)cluster_max_io_size(mp, 0));
VFSATTR_RETURN(fsap, f_blocks, (u_int64_t)hfsmp->totalBlocks);
VFSATTR_RETURN(fsap, f_bfree, (u_int64_t)hfs_freeblks(hfsmp, 0));
VFSATTR_RETURN(fsap, f_bavail, (u_int64_t)hfs_freeblks(hfsmp, 1));
VFSATTR_RETURN(fsap, f_bsize, (u_int32_t)vcb->blockSize);
VFSATTR_RETURN(fsap, f_bused, hfsmp->totalBlocks - hfs_freeblks(hfsmp, 1));
VFSATTR_RETURN(fsap, f_files, (u_int64_t)HFS_MAX_FILES);
VFSATTR_RETURN(fsap, f_ffree, (u_int64_t)hfs_free_cnids(hfsmp));
fsap->f_fsid.val[0] = hfsmp->hfs_raw_dev;
fsap->f_fsid.val[1] = vfs_typenum(mp);
VFSATTR_SET_SUPPORTED(fsap, f_fsid);
VFSATTR_RETURN(fsap, f_signature, vcb->vcbSigWord);
VFSATTR_RETURN(fsap, f_carbon_fsid, 0);
if (VFSATTR_IS_ACTIVE(fsap, f_capabilities)) {
vol_capabilities_attr_t *cap;
cap = &fsap->f_capabilities;
if ((hfsmp->hfs_flags & HFS_STANDARD) == 0) {
cap->capabilities[VOL_CAPABILITIES_FORMAT] =
VOL_CAP_FMT_PERSISTENTOBJECTIDS |
VOL_CAP_FMT_SYMBOLICLINKS |
VOL_CAP_FMT_HARDLINKS |
VOL_CAP_FMT_JOURNAL |
VOL_CAP_FMT_ZERO_RUNS |
(hfsmp->jnl ? VOL_CAP_FMT_JOURNAL_ACTIVE : 0) |
(hfsmp->hfs_flags & HFS_CASE_SENSITIVE ? VOL_CAP_FMT_CASE_SENSITIVE : 0) |
VOL_CAP_FMT_CASE_PRESERVING |
VOL_CAP_FMT_FAST_STATFS |
VOL_CAP_FMT_2TB_FILESIZE |
VOL_CAP_FMT_HIDDEN_FILES |
#if HFS_COMPRESSION
VOL_CAP_FMT_DECMPFS_COMPRESSION |
#endif
#if CONFIG_HFS_DIRLINK
VOL_CAP_FMT_DIR_HARDLINKS |
#endif
#ifdef VOL_CAP_FMT_DOCUMENT_ID
VOL_CAP_FMT_DOCUMENT_ID |
#endif
#ifdef VOL_CAP_FMT_WRITE_GENERATION_COUNT
VOL_CAP_FMT_WRITE_GENERATION_COUNT |
#endif
VOL_CAP_FMT_PATH_FROM_ID;
}
#if CONFIG_HFS_STD
else {
cap->capabilities[VOL_CAPABILITIES_FORMAT] =
VOL_CAP_FMT_PERSISTENTOBJECTIDS |
VOL_CAP_FMT_CASE_PRESERVING |
VOL_CAP_FMT_FAST_STATFS |
VOL_CAP_FMT_HIDDEN_FILES |
VOL_CAP_FMT_PATH_FROM_ID;
}
#endif
cap->capabilities[VOL_CAPABILITIES_INTERFACES] =
VOL_CAP_INT_ATTRLIST |
VOL_CAP_INT_NFSEXPORT |
VOL_CAP_INT_READDIRATTR |
VOL_CAP_INT_ALLOCATE |
VOL_CAP_INT_VOL_RENAME |
VOL_CAP_INT_ADVLOCK |
VOL_CAP_INT_FLOCK |
#if VOL_CAP_INT_RENAME_EXCL
VOL_CAP_INT_RENAME_EXCL |
#endif
#if NAMEDSTREAMS
VOL_CAP_INT_EXTENDED_ATTR |
VOL_CAP_INT_NAMEDSTREAMS;
#else
VOL_CAP_INT_EXTENDED_ATTR;
#endif
if (searchfs_on) {
cap->capabilities[VOL_CAPABILITIES_INTERFACES] |= VOL_CAP_INT_SEARCHFS;
}
if (exchangedata_on) {
cap->capabilities[VOL_CAPABILITIES_INTERFACES] |= VOL_CAP_INT_EXCHANGEDATA;
}
cap->capabilities[VOL_CAPABILITIES_RESERVED1] = 0;
cap->capabilities[VOL_CAPABILITIES_RESERVED2] = 0;
cap->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 |
VOL_CAP_FMT_PATH_FROM_ID |
VOL_CAP_FMT_DECMPFS_COMPRESSION |
#ifdef VOL_CAP_FMT_DOCUMENT_ID
VOL_CAP_FMT_DOCUMENT_ID |
#endif
#ifdef VOL_CAP_FMT_WRITE_GENERATION_COUNT
VOL_CAP_FMT_WRITE_GENERATION_COUNT |
#endif
VOL_CAP_FMT_DIR_HARDLINKS;
cap->valid[VOL_CAPABILITIES_INTERFACES] =
VOL_CAP_INT_ATTRLIST |
VOL_CAP_INT_NFSEXPORT |
VOL_CAP_INT_READDIRATTR |
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 |
#if VOL_CAP_INT_RENAME_EXCL
VOL_CAP_INT_RENAME_EXCL |
#endif
#if NAMEDSTREAMS
VOL_CAP_INT_EXTENDED_ATTR |
VOL_CAP_INT_NAMEDSTREAMS;
#else
VOL_CAP_INT_EXTENDED_ATTR;
#endif
cap->valid[VOL_CAPABILITIES_INTERFACES] |= (VOL_CAP_INT_SEARCHFS | VOL_CAP_INT_EXCHANGEDATA);
cap->valid[VOL_CAPABILITIES_RESERVED1] = 0;
cap->valid[VOL_CAPABILITIES_RESERVED2] = 0;
VFSATTR_SET_SUPPORTED(fsap, f_capabilities);
}
if (VFSATTR_IS_ACTIVE(fsap, f_attributes)) {
vol_attributes_attr_t *attrp = &fsap->f_attributes;
attrp->validattr.commonattr = HFS_ATTR_CMN_VOL_VALIDMASK;
#if CONFIG_PROTECT
attrp->validattr.commonattr |= ATTR_CMN_DATA_PROTECT_FLAGS;
#endif // CONFIG_PROTECT
attrp->validattr.volattr = ATTR_VOL_VALIDMASK & ~ATTR_VOL_INFO;
attrp->validattr.dirattr = ATTR_DIR_VALIDMASK;
attrp->validattr.fileattr = HFS_ATTR_FILE_VALIDMASK;
attrp->validattr.forkattr = 0;
attrp->nativeattr.commonattr = HFS_ATTR_CMN_VOL_VALIDMASK;
#if CONFIG_PROTECT
attrp->nativeattr.commonattr |= ATTR_CMN_DATA_PROTECT_FLAGS;
#endif // CONFIG_PROTECT
attrp->nativeattr.volattr = ATTR_VOL_VALIDMASK & ~ATTR_VOL_INFO;
attrp->nativeattr.dirattr = ATTR_DIR_VALIDMASK;
attrp->nativeattr.fileattr = HFS_ATTR_FILE_VALIDMASK;
attrp->nativeattr.forkattr = 0;
VFSATTR_SET_SUPPORTED(fsap, f_attributes);
}
fsap->f_create_time.tv_sec = hfsmp->hfs_itime;
fsap->f_create_time.tv_nsec = 0;
VFSATTR_SET_SUPPORTED(fsap, f_create_time);
fsap->f_modify_time.tv_sec = hfsmp->vcbLsMod;
fsap->f_modify_time.tv_nsec = 0;
VFSATTR_SET_SUPPORTED(fsap, f_modify_time);
if (VFSATTR_IS_ACTIVE(fsap, f_access_time)) {
struct timeval tv;
microtime(&tv);
fsap->f_access_time.tv_sec = tv.tv_sec;
fsap->f_access_time.tv_nsec = 0;
VFSATTR_SET_SUPPORTED(fsap, f_access_time);
}
fsap->f_backup_time.tv_sec = hfsmp->vcbVolBkUp;
fsap->f_backup_time.tv_nsec = 0;
VFSATTR_SET_SUPPORTED(fsap, f_backup_time);
if (VFSATTR_IS_ACTIVE(fsap, f_fssubtype)) {
u_int16_t subtype = 0;
if ((hfsmp->hfs_flags & HFS_STANDARD) == 0) {
if (hfsmp->jnl) {
subtype |= HFS_SUBTYPE_JOURNALED;
}
if (hfsmp->hfs_flags & HFS_CASE_SENSITIVE) {
subtype |= HFS_SUBTYPE_CASESENSITIVE;
}
}
#if CONFIG_HFS_STD
else {
subtype = HFS_SUBTYPE_STANDARDHFS;
}
#endif
fsap->f_fssubtype = subtype;
VFSATTR_SET_SUPPORTED(fsap, f_fssubtype);
}
if (VFSATTR_IS_ACTIVE(fsap, f_vol_name)) {
strlcpy(fsap->f_vol_name, (char *) hfsmp->vcbVN, MAXPATHLEN);
VFSATTR_SET_SUPPORTED(fsap, f_vol_name);
}
if (VFSATTR_IS_ACTIVE(fsap, f_uuid)) {
hfs_getvoluuid(hfsmp, fsap->f_uuid);
VFSATTR_SET_SUPPORTED(fsap, f_uuid);
}
return (0);
}
static int
hfs_rename_volume(struct vnode *vp, const char *name, proc_t p)
{
ExtendedVCB *vcb = VTOVCB(vp);
struct cnode *cp = VTOC(vp);
struct hfsmount *hfsmp = VTOHFS(vp);
struct cat_desc to_desc;
struct cat_desc todir_desc;
struct cat_desc new_desc;
cat_cookie_t cookie;
int lockflags;
int error = 0;
char converted_volname[256];
size_t volname_length = 0;
size_t conv_volname_length = 0;
if (name[0] == 0)
return(0);
bzero(&to_desc, sizeof(to_desc));
bzero(&todir_desc, sizeof(todir_desc));
bzero(&new_desc, sizeof(new_desc));
bzero(&cookie, sizeof(cookie));
todir_desc.cd_parentcnid = kHFSRootParentID;
todir_desc.cd_cnid = kHFSRootFolderID;
todir_desc.cd_flags = CD_ISDIR;
to_desc.cd_nameptr = (const u_int8_t *)name;
to_desc.cd_namelen = strlen(name);
to_desc.cd_parentcnid = kHFSRootParentID;
to_desc.cd_cnid = cp->c_cnid;
to_desc.cd_flags = CD_ISDIR;
if ((error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT)) == 0) {
if ((error = hfs_start_transaction(hfsmp)) == 0) {
if ((error = cat_preflight(hfsmp, CAT_RENAME, &cookie, p)) == 0) {
lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG, HFS_EXCLUSIVE_LOCK);
error = cat_rename(hfsmp, &cp->c_desc, &todir_desc, &to_desc, &new_desc);
if (error == 0) {
strlcpy((char *)vcb->vcbVN, name, sizeof(vcb->vcbVN));
volname_length = strlen ((const char*)vcb->vcbVN);
error = utf8_normalizestr(vcb->vcbVN, volname_length, (u_int8_t*)converted_volname, &conv_volname_length, 256, UTF_PRECOMPOSED);
if (error == 0) {
(void) VNOP_IOCTL (hfsmp->hfs_devvp, _DKIOCCSSETLVNAME, converted_volname, 0, vfs_context_current());
}
error = 0;
}
hfs_systemfile_unlock(hfsmp, lockflags);
cat_postflight(hfsmp, &cookie, p);
if (error)
MarkVCBDirty(vcb);
(void) hfs_flushvolumeheader(hfsmp, HFS_FVH_WAIT);
}
hfs_end_transaction(hfsmp);
}
if (!error) {
if (cp->c_desc.cd_flags & CD_HASBUF) {
const char *tmp_name = (const char *)cp->c_desc.cd_nameptr;
cp->c_desc.cd_nameptr = 0;
cp->c_desc.cd_namelen = 0;
cp->c_desc.cd_flags &= ~CD_HASBUF;
vfs_removename(tmp_name);
}
replace_desc(cp, &new_desc);
vcb->volumeNameEncodingHint = new_desc.cd_encoding;
cp->c_touch_chgtime = TRUE;
}
hfs_unlock(cp);
}
return(error);
}
static int
hfs_vfs_setattr(struct mount *mp, struct vfs_attr *fsap, vfs_context_t context)
{
kauth_cred_t cred = vfs_context_ucred(context);
int error = 0;
if (!kauth_cred_issuser(cred) && (kauth_cred_getuid(cred) != vfs_statfs(mp)->f_owner))
return(EACCES);
if (VFSATTR_IS_ACTIVE(fsap, f_vol_name)) {
vnode_t root_vp;
error = hfs_vfs_root(mp, &root_vp, context);
if (error)
goto out;
error = hfs_rename_volume(root_vp, fsap->f_vol_name, vfs_context_proc(context));
(void) vnode_put(root_vp);
if (error)
goto out;
VFSATTR_SET_SUPPORTED(fsap, f_vol_name);
}
out:
return error;
}
void hfs_mark_inconsistent(struct hfsmount *hfsmp,
hfs_inconsistency_reason_t reason)
{
hfs_lock_mount (hfsmp);
if ((hfsmp->vcbAtrb & kHFSVolumeInconsistentMask) == 0) {
hfsmp->vcbAtrb |= kHFSVolumeInconsistentMask;
MarkVCBDirty(hfsmp);
}
if ((hfsmp->hfs_flags & HFS_READ_ONLY)==0) {
switch (reason) {
case HFS_INCONSISTENCY_DETECTED:
printf("hfs_mark_inconsistent: Runtime corruption detected on %s, fsck will be forced on next mount.\n",
hfsmp->vcbVN);
break;
case HFS_ROLLBACK_FAILED:
printf("hfs_mark_inconsistent: Failed to roll back; volume `%s' might be inconsistent; fsck will be forced on next mount.\n",
hfsmp->vcbVN);
break;
case HFS_OP_INCOMPLETE:
printf("hfs_mark_inconsistent: Failed to complete operation; volume `%s' might be inconsistent; fsck will be forced on next mount.\n",
hfsmp->vcbVN);
break;
case HFS_FSCK_FORCED:
printf("hfs_mark_inconsistent: fsck requested for `%s'; fsck will be forced on next mount.\n",
hfsmp->vcbVN);
break;
}
}
hfs_unlock_mount (hfsmp);
}
static int hfs_journal_replay(vnode_t devvp, vfs_context_t context)
{
int retval = 0;
int error = 0;
if (!vnode_ischr(devvp) && !vnode_isblk(devvp))
return EINVAL;
retval = hfs_mountfs(devvp, NULL, NULL, 1, context);
buf_flushdirtyblks(devvp, TRUE, 0, "hfs_journal_replay");
error = VNOP_FSYNC(devvp, MNT_WAIT, context);
if (error) {
retval = error;
}
return retval;
}
static void
hfs_syncer_free(struct hfsmount *hfsmp)
{
if (hfsmp && ISSET(hfsmp->hfs_flags, HFS_RUN_SYNCER)) {
hfs_syncer_lock(hfsmp);
CLR(hfsmp->hfs_flags, HFS_RUN_SYNCER);
hfs_syncer_unlock(hfsmp);
if (hfsmp->hfs_syncer_thread) {
hfs_syncer_wakeup(hfsmp);
hfs_syncer_lock(hfsmp);
while (hfsmp->hfs_syncer_thread)
hfs_syncer_wait(hfsmp, NULL);
hfs_syncer_unlock(hfsmp);
}
}
}
static int hfs_vfs_ioctl(struct mount *mp, u_long command, caddr_t data,
__unused int flags, __unused vfs_context_t context)
{
switch (command) {
#if CONFIG_PROTECT
case FIODEVICELOCKED:
cp_device_locked_callback(mp, (cp_lock_state_t)data);
return 0;
#endif
}
return ENOTTY;
}
struct vfsops hfs_vfsops = {
.vfs_mount = hfs_mount,
.vfs_start = hfs_start,
.vfs_unmount = hfs_unmount,
.vfs_root = hfs_vfs_root,
.vfs_quotactl = hfs_quotactl,
.vfs_getattr = hfs_vfs_getattr,
.vfs_sync = hfs_sync,
.vfs_vget = hfs_vfs_vget,
.vfs_fhtovp = hfs_fhtovp,
.vfs_vptofh = hfs_vptofh,
.vfs_init = hfs_init,
.vfs_sysctl = hfs_sysctl,
.vfs_setattr = hfs_vfs_setattr,
.vfs_ioctl = hfs_vfs_ioctl,
};