From ec99dd3007e7873c4585ff2dde91ee4d9b7e9da9 Mon Sep 17 00:00:00 2001 From: Apple Date: Tue, 26 Sep 2017 16:55:08 +0000 Subject: [PATCH 1/1] hfs-407.1.3.tar.gz --- core/VolumeAllocation.c | 4 +- core/hfs.h | 21 +- core/hfs_attrlist.c | 31 +- core/hfs_btreeio.c | 20 +- core/hfs_fsctl.h | 46 +-- core/hfs_journal.c | 21 ++ core/hfs_notification.c | 86 +++++- core/hfs_readwrite.c | 96 +++--- core/hfs_vfsops.c | 73 +++-- core/hfs_vnops.c | 371 +++++++++++++++++++++++- core/hfs_xattr.c | 60 +++- fs/Info.plist | 4 + fsck_hfs/dfalib/SVerify1.c | 7 +- fsck_hfs/fsck_hfs.8 | 4 +- hfs.xcodeproj/project.pbxproj | 114 ++++++-- tests/cases/test-access.c | 1 + tests/cases/test-dateadded.c | 1 + tests/cases/test-doc-tombstone.c | 3 +- tests/cases/test-fsinfo.c | 6 +- tests/cases/test-fsync.c | 1 + tests/cases/test-invalid-ranges.m | 12 +- tests/cases/test-mmap-mod-time.c | 1 + tests/cases/test-mod-time.c | 2 + tests/cases/test-move-data-extents.c | 15 +- tests/cases/test-msync-16k.c | 2 + tests/cases/test-renamex.c | 3 + tests/cases/test-set-create-time.c | 1 + tests/cases/test-set-protection-class.c | 4 + tests/cases/test-sparse-dev.c | 3 + tests/cases/test-throttled-io.c | 6 +- tests/cases/test-transcode.m | 3 + tests/cases/test-uncached-io.c | 4 +- tests/cases/test-unicode-file-names.c | 3 +- tests/cases/test_disklevel.c | 68 +++++ tests/disk-image.h | 3 + tests/disk-image.m | 53 +++- tests/gen-dmg.sh | 2 +- tests/hfs-tests.mm | 1 + 38 files changed, 970 insertions(+), 186 deletions(-) create mode 100644 tests/cases/test_disklevel.c diff --git a/core/VolumeAllocation.c b/core/VolumeAllocation.c index 3ecccbe..f26811c 100644 --- a/core/VolumeAllocation.c +++ b/core/VolumeAllocation.c @@ -794,13 +794,13 @@ CheckUnmappedBytes (struct hfsmount *hfsmp, uint64_t blockno, uint64_t numblocks ; Additionally build up the summary table as needed. ; ; This function reads the bitmap in large block size - ; (up to 1MB) unlink the runtime which reads the bitmap + ; (up to 1MB) unlike the runtime which reads the bitmap ; in 4K block size. So if this function is being called ; after the volume is mounted and actively modified, the ; caller needs to invalidate all of the existing buffers ; associated with the bitmap vnode before calling this ; function. If the buffers are not invalidated, it can - ; cause but_t collision and potential data corruption. + ; cause buf_t collision and potential data corruption. ; ; Input Arguments: ; hfsmp - The volume containing the allocation blocks. diff --git a/core/hfs.h b/core/hfs.h index f726cd3..5dce5da 100644 --- a/core/hfs.h +++ b/core/hfs.h @@ -143,15 +143,19 @@ extern struct timezone gTimeZone; #define HFS_ROOTVERYLOWDISKTRIGGERLEVEL ((u_int64_t)(512*1024*1024)) #define HFS_ROOTLOWDISKTRIGGERFRACTION 10 #define HFS_ROOTLOWDISKTRIGGERLEVEL ((u_int64_t)(1024*1024*1024)) +#define HFS_ROOTNEARLOWDISKTRIGGERFRACTION 10.5 +#define HFS_ROOTNEARLOWDISKTRIGGERLEVEL ((u_int64_t)(1024*1024*1024 + 100*1024*1024)) #define HFS_ROOTLOWDISKSHUTOFFFRACTION 11 #define HFS_ROOTLOWDISKSHUTOFFLEVEL ((u_int64_t)(1024*1024*1024 + 250*1024*1024)) #define HFS_VERYLOWDISKTRIGGERFRACTION 1 -#define HFS_VERYLOWDISKTRIGGERLEVEL ((u_int64_t)(100*1024*1024)) +#define HFS_VERYLOWDISKTRIGGERLEVEL ((u_int64_t)(150*1024*1024)) #define HFS_LOWDISKTRIGGERFRACTION 2 -#define HFS_LOWDISKTRIGGERLEVEL ((u_int64_t)(150*1024*1024)) -#define HFS_LOWDISKSHUTOFFFRACTION 3 -#define HFS_LOWDISKSHUTOFFLEVEL ((u_int64_t)(200*1024*1024)) +#define HFS_LOWDISKTRIGGERLEVEL ((u_int64_t)(500*1024*1024)) +#define HFS_NEARLOWDISKTRIGGERFRACTION 10 +#define HFS_NEARLOWDISKTRIGGERLEVEL ((uint64_t)(1024*1024*1024)) +#define HFS_LOWDISKSHUTOFFFRACTION 12 +#define HFS_LOWDISKSHUTOFFLEVEL ((u_int64_t)(1024*1024*1024 + 200*1024*1024)) /* Internal Data structures*/ @@ -305,6 +309,7 @@ typedef struct hfsmount { u_int32_t hfs_notification_conditions; u_int32_t hfs_freespace_notify_dangerlimit; u_int32_t hfs_freespace_notify_warninglimit; + u_int32_t hfs_freespace_notify_nearwarninglimit; u_int32_t hfs_freespace_notify_desiredlevel; /* time mounted and last mounted mod time "snapshot" */ @@ -320,7 +325,7 @@ typedef struct hfsmount { u_int32_t hfs_freed_block_count; u_int64_t hfs_cs_hotfile_size; // in bytes int hfs_hotfile_freeblks; - int hfs_hotfile_blk_adjust; + int hfs_hotfile_blk_adjust; // since we pass this to OSAddAtomic, this needs to be 4-byte aligned int hfs_hotfile_maxblks; int hfs_overflow_maxblks; int hfs_catalog_maxblks; @@ -418,7 +423,8 @@ typedef struct hfsmount { queing more syncs. */ thread_t hfs_syncer_thread; - // Not currently used except for debugging purposes + // Not currently used except for debugging purposes + // Since we pass this to OSAddAtomic, this needs to be 4-byte aligned. uint32_t hfs_active_threads; enum { @@ -1091,6 +1097,9 @@ int hfs_setxattr_internal(struct cnode *, const void *, size_t, struct vnop_setxattr_args *, struct hfsmount *, u_int32_t); extern int hfs_removeallattr(struct hfsmount *hfsmp, u_int32_t fileid, bool *open_transaction); + +int hfs_removexattr_by_id (struct hfsmount *hfsmp, uint32_t fileid, const char *xattr_name ); + extern int hfs_set_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype, int state); diff --git a/core/hfs_attrlist.c b/core/hfs_attrlist.c index 40dced0..1fa4268 100644 --- a/core/hfs_attrlist.c +++ b/core/hfs_attrlist.c @@ -211,7 +211,18 @@ hfs_readdirattr_internal(struct vnode *dvp, struct attrlist *alist, int lockflags; u_int32_t dirchg = 0; int reachedeof = 0; - + int internal_actualcount; + int internal_eofflag; + + /* Lets makse sure we have something assign to actualcount always, min change required */ + if (actualcount == NULL) { + actualcount = &internal_actualcount; + } + /* Lets makse sure we have something assign to eofflag always, min change required */ + if (eofflag == NULL) { + eofflag = &internal_eofflag; + } + *(actualcount) = 0; *(eofflag) = 0; @@ -1128,9 +1139,10 @@ hfs_attrblksize(struct attrlist *attrlist) hfs_assert((attrlist->fileattr & ~ATTR_FILE_VALIDMASK) == 0); - // disable this for build machines - // it's not useful and will break every time we add new _CMNEXT_ bits - // hfs_assert((attrlist->forkattr & ~ATTR_FORK_VALIDMASK) == 0); + // disable this because it will break the simulator/build machines each + // time a new _CMNEXT_ bit is added + // hfs_assert(((attrlist->forkattr & ~ATTR_FORK_VALIDMASK) == 0) || + // ((attrlist->forkattr & ~ATTR_CMNEXT_VALIDMASK) == 0)); size = 0; @@ -1272,9 +1284,10 @@ get_vattr_data_for_attrs(struct attrlist *alp, struct vnode_attr *vap, struct cat_attr *atrp, struct cat_fork *datafork, struct cat_fork *rsrcfork, vfs_context_t ctx) { - if (alp->commonattr) + if (alp->commonattr || alp->forkattr) { vattr_data_for_common_attrs(alp, vap, hfsmp, vp, descp, atrp, - ctx); + ctx); + } if (alp->dirattr && S_ISDIR(atrp->ca_mode)) vattr_data_for_dir_attrs(alp, vap, hfsmp, vp, descp, atrp); @@ -1386,9 +1399,11 @@ vattr_data_for_common_attrs( struct attrlist *alp, struct vnode_attr *vap, * The stat call (getattr) will always return the c_fileid * and Carbon APIs, which are hardlink-ignorant, will always * receive the c_cnid (from getattrlist). + * + * Forkattrs are now repurposed for Common Extended Attributes. */ - if ((ATTR_CMN_OBJID & attr) || - (ATTR_CMN_OBJPERMANENTID & attr)) { + if ((ATTR_CMN_OBJID & attr) || (ATTR_CMN_OBJPERMANENTID & attr) || + alp->forkattr & ATTR_CMNEXT_LINKID) { vap->va_linkid = cdp->cd_cnid; VATTR_SET_SUPPORTED(vap, va_linkid); } diff --git a/core/hfs_btreeio.c b/core/hfs_btreeio.c index 75fda11..ec2072e 100644 --- a/core/hfs_btreeio.c +++ b/core/hfs_btreeio.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2015 Apple Inc. All rights reserved. + * Copyright (c) 2000-2017 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -722,6 +722,12 @@ again: btcb->releaseBlockProc = ReleaseBTreeBlock; btcb->setEndOfForkProc = ExtendBTreeFile; btcb->keyCompareProc = (KeyCompareProcPtr)hfs_attrkeycompare; + + /* + * NOTE: We must make sure to zero out this pointer if we error out in this function! + * If we don't, then unmount will treat it as a valid pointer which can lead to a + * use-after-free + */ VTOF(vp)->fcbBTCBPtr = btcb; /* @@ -903,6 +909,18 @@ again: } exit: + + if (vp && result) { + /* + * If we're about to error out, then make sure to zero out the B-Tree control block pointer + * from the filefork of the EA B-Tree cnode/vnode. Failing to do this will lead to a use + * after free at unmount or BTFlushPath. Since we're about to error out anyway, this memory + * will be freed. + */ + VTOF(vp)->fcbBTCBPtr = NULL; + } + + if (vp) { hfs_unlock(VTOC(vp)); } diff --git a/core/hfs_fsctl.h b/core/hfs_fsctl.h index a1ba597..6fa854c 100644 --- a/core/hfs_fsctl.h +++ b/core/hfs_fsctl.h @@ -252,26 +252,20 @@ enum { /* HFS FS CONTROL COMMANDS */ #define HFSIOC_RESIZE_PROGRESS _IOR('h', 1, u_int32_t) -#define HFS_RESIZE_PROGRESS IOCBASECMD(HFSIOC_RESIZE_PROGRESS) #define HFSIOC_RESIZE_VOLUME _IOW('h', 2, u_int64_t) -#define HFS_RESIZE_VOLUME IOCBASECMD(HFSIOC_RESIZE_VOLUME) #define HFSIOC_CHANGE_NEXT_ALLOCATION _IOWR('h', 3, u_int32_t) -#define HFS_CHANGE_NEXT_ALLOCATION IOCBASECMD(HFSIOC_CHANGE_NEXT_ALLOCATION) /* Magic value for next allocation to use with fcntl to set next allocation * to zero and never update it again on new block allocation. */ #define HFS_NO_UPDATE_NEXT_ALLOCATION 0xffffFFFF #define HFSIOC_GETCREATETIME _IOR('h', 4, time_t) -#define HFS_GETCREATETIME IOCBASECMD(HFSIOC_GETCREATETIME) #define HFSIOC_SETBACKINGSTOREINFO _IOW('h', 7, struct hfs_backingstoreinfo) -#define HFS_SETBACKINGSTOREINFO IOCBASECMD(HFSIOC_SETBACKINGSTOREINFO) #define HFSIOC_CLRBACKINGSTOREINFO _IO('h', 8) -#define HFS_CLRBACKINGSTOREINFO IOCBASECMD(HFSIOC_CLRBACKINGSTOREINFO) // 'h', 9 used to be HFSIOC_BULKACCESS which is now deprecated @@ -279,13 +273,10 @@ enum { #define HFSIOC_UNSUPPORTED _IOW('h', 10, int32_t) #define HFSIOC_PREV_LINK _IOWR('h', 11, u_int32_t) -#define HFS_PREV_LINK IOCBASECMD(HFSIOC_PREV_LINK) #define HFSIOC_NEXT_LINK _IOWR('h', 12, u_int32_t) -#define HFS_NEXT_LINK IOCBASECMD(HFSIOC_NEXT_LINK) #define HFSIOC_GETPATH _IOWR('h', 13, pathname_t) -#define HFS_GETPATH IOCBASECMD(HFSIOC_GETPATH) /* By default, the path returned by HFS_GETPATH is an absolute path, * i.e. it also contains the mount point of the volume on which the * fileID exists. If the following bit is set, the path returned is @@ -295,49 +286,44 @@ enum { /* Enable/disable extent-based extended attributes */ #define HFSIOC_SET_XATTREXTENTS_STATE _IOW('h', 14, u_int32_t) -#define HFS_SET_XATTREXTENTS_STATE IOCBASECMD(HFSIOC_SET_XATTREXTENTS_STATE) -#define HFSIOC_EXT_BULKACCESS _IOW('h', 15, struct user32_ext_access_t) -#define HFS_EXT_BULKACCESS_FSCTL IOCBASECMD(HFSIOC_EXT_BULKACCESS) +#if defined(KERNEL) +#define HFSIOC_EXT_BULKACCESS32 _IOW('h', 15, struct user32_ext_access_t) +#define HFSIOC_EXT_BULKACCESS64 _IOW('h', 15, struct user64_ext_access_t) +#else +#define HFSIOC_EXT_BULKACCESS _IOW('h', 15, struct ext_access_t) +#endif /* KERNEL */ #define HFSIOC_MARK_BOOT_CORRUPT _IO('h', 16) -#define HFS_MARK_BOOT_CORRUPT IOCBASECMD(HFSIOC_MARK_BOOT_CORRUPT) #define HFSIOC_GET_JOURNAL_INFO _IOR('h', 17, struct hfs_journal_info) -#define HFS_FSCTL_GET_JOURNAL_INFO IOCBASECMD(HFSIOC_GET_JOURNAL_INFO) #define HFSIOC_SET_VERY_LOW_DISK _IOW('h', 20, u_int32_t) -#define HFS_FSCTL_SET_VERY_LOW_DISK IOCBASECMD(HFSIOC_SET_VERY_LOW_DISK) #define HFSIOC_SET_LOW_DISK _IOW('h', 21, u_int32_t) -#define HFS_FSCTL_SET_LOW_DISK IOCBASECMD(HFSIOC_SET_LOW_DISK) #define HFSIOC_SET_DESIRED_DISK _IOW('h', 22, u_int32_t) -#define HFS_FSCTL_SET_DESIRED_DISK IOCBASECMD(HFSIOC_SET_DESIRED_DISK) #define HFSIOC_SET_ALWAYS_ZEROFILL _IOW('h', 23, int32_t) + /* XXXJRT Keep until 31866920 is resolved. */ #define HFS_SET_ALWAYS_ZEROFILL IOCBASECMD(HFSIOC_SET_ALWAYS_ZEROFILL) #define HFSIOC_VOLUME_STATUS _IOR('h', 24, u_int32_t) -#define HFS_VOLUME_STATUS IOCBASECMD(HFSIOC_VOLUME_STATUS) /* Disable metadata zone for given volume */ #define HFSIOC_DISABLE_METAZONE _IO('h', 25) -#define HFS_DISABLE_METAZONE IOCBASECMD(HFSIOC_DISABLE_METAZONE) /* Change the next CNID value */ #define HFSIOC_CHANGE_NEXTCNID _IOWR('h', 26, u_int32_t) + /* XXXJRT Keep until 31866920 is resolved. */ #define HFS_CHANGE_NEXTCNID IOCBASECMD(HFSIOC_CHANGE_NEXTCNID) /* Get the low disk space values */ #define HFSIOC_GET_VERY_LOW_DISK _IOR('h', 27, u_int32_t) -#define HFS_FSCTL_GET_VERY_LOW_DISK IOCBASECMD(HFSIOC_GET_VERY_LOW_DISK) #define HFSIOC_GET_LOW_DISK _IOR('h', 28, u_int32_t) -#define HFS_FSCTL_GET_LOW_DISK IOCBASECMD(HFSIOC_GET_LOW_DISK) #define HFSIOC_GET_DESIRED_DISK _IOR('h', 29, u_int32_t) -#define HFS_FSCTL_GET_DESIRED_DISK IOCBASECMD(HFSIOC_GET_DESIRED_DISK) /* 30 was HFSIOC_GET_WRITE_GEN_COUNTER and is now deprecated */ @@ -345,7 +331,6 @@ enum { /* revisiond only uses this when something transforms in a way the kernel can't track such as "foo.rtf" -> "foo.rtfd" */ #define HFSIOC_TRANSFER_DOCUMENT_ID _IOW('h', 32, u_int32_t) -#define HFS_TRANSFER_DOCUMENT_ID IOCBASECMD(HFSIOC_TRANSFER_DOCUMENT_ID) /* @@ -357,27 +342,22 @@ enum { * f_bsize by statfs(2). */ #define HFSIOC_FSINFO_METADATA_BLOCKS _IOWR('h', 38, struct hfsinfo_metadata) -#define HFS_FSINFO_METADATA_BLOCKS IOCBASECMD(HFSIOC_FSINFO_METADATA_BLOCKS) /* Send TRIMs for all free blocks to the underlying device */ #define HFSIOC_CS_FREESPACE_TRIM _IOWR('h', 39, u_int32_t) -#define HFS_CS_FREESPACE_TRIM IOCBASECMD(HFSIOC_CS_FREESPACE_TRIM) /* Get file system information for the given volume */ #define HFSIOC_GET_FSINFO _IOWR('h', 45, hfs_fsinfo) -#define HFS_GET_FSINFO IOCBASECMD(HFSIOC_GET_FSINFO) /* Re-pin hotfile data; argument controls what state gets repinned */ #define HFSIOC_REPIN_HOTFILE_STATE _IOWR('h', 46, u_int32_t) -#define HFS_REPIN_HOTFILE_STATE IOCBASECMD(HFSIOC_REPIN_HOTFILE_STATE) #define HFS_REPIN_METADATA 0x0001 #define HFS_REPIN_USERDATA 0x0002 /* Mark a directory or file as worth caching on any underlying "fast" device */ #define HFSIOC_SET_HOTFILE_STATE _IOWR('h', 47, u_int32_t) -#define HFS_SET_HOTFILE_STATE IOCBASECMD(HFSIOC_SET_HOTFILE_STATE) /* flags to pass to SET_HOTFILE_STATE */ #define HFS_MARK_FASTDEVCANDIDATE 0x0001 @@ -385,11 +365,17 @@ enum { #define HFS_NEVER_FASTDEVCANDIDATE 0x0004 #define HFSIOC_SET_MAX_DEFRAG_SIZE _IOWR('h', 48, u_int32_t) -#define HFS_SET_MAX_DEFRAG_SIZE IOCBASECMD(HFSIOC_SET_MAX_DEFRAG_SIZE) #define HFSIOC_FORCE_ENABLE_DEFRAG _IOWR('h', 49, u_int32_t) -#define HFS_FORCE_ENABLE_DEFRAG IOCBASECMD(HFSIOC_FORCE_ENABLE_DEFRAG) +/* These fsctls are ported from apfs. */ +#ifndef APFSIOC_SET_NEAR_LOW_DISK +#define APFSIOC_SET_NEAR_LOW_DISK _IOW('J', 17, u_int32_t) +#endif /* APFSIOC_SET_NEAR_LOW_DISK */ + +#ifndef APFSIOC_GET_NEAR_LOW_DISK +#define APFSIOC_GET_NEAR_LOW_DISK _IOR('J', 18, u_int32_t) +#endif /* APFSIOC_GET_NEAR_LOW_DISK */ #endif /* __APPLE_API_UNSTABLE */ diff --git a/core/hfs_journal.c b/core/hfs_journal.c index bfcc640..8556c1c 100644 --- a/core/hfs_journal.c +++ b/core/hfs_journal.c @@ -1923,6 +1923,15 @@ journal_open(struct vnode *jvp, goto bad_journal; } + /* + * Check for a bad jhdr size after reading in the journal header. + * The journal header length cannot be zero + */ + if (jnl->jhdr->jhdr_size == 0) { + printf("jnl: %s: open: bad jhdr size (%d) \n", jdev_name, jnl->jhdr->jhdr_size); + goto bad_journal; + } + orig_checksum = jnl->jhdr->checksum; jnl->jhdr->checksum = 0; @@ -2016,6 +2025,13 @@ journal_open(struct vnode *jvp, goto bad_journal; } + if (jnl->jhdr->blhdr_size < 0) { + //throw out invalid sizes + printf("jnl %s: open: blhdr size looks bogus! (%d) \n", + jdev_name, jnl->jhdr->blhdr_size); + goto bad_journal; + } + // take care of replaying the journal if necessary if (flags & JOURNAL_RESET) { printf("jnl: %s: journal start/end pointers reset! (s 0x%llx e 0x%llx)\n", @@ -4771,6 +4787,11 @@ int journal_relocate(journal *jnl, off_t offset, off_t journal_size, int32_t tbu /* * Sanity check inputs, and adjust the size of the transaction buffer. */ + if (jnl->jhdr->jhdr_size == 0) { + printf("jnl: %s: relocate: bad jhdr size (%d)\n", jnl->jdev_name, jnl->jhdr->jhdr_size); + return EINVAL; + } + if ((offset % jnl->jhdr->jhdr_size) != 0) { printf("jnl: %s: relocate: offset 0x%llx is not an even multiple of block size 0x%x\n", jnl->jdev_name, offset, jnl->jhdr->jhdr_size); diff --git a/core/hfs_notification.c b/core/hfs_notification.c index a2c361e..614c32d 100644 --- a/core/hfs_notification.c +++ b/core/hfs_notification.c @@ -67,16 +67,49 @@ void hfs_generate_volume_notifications(struct hfsmount *hfsmp) freeblks = hfs_freeblks(hfsmp, 1); + /* + * Find the theshold the number of free blocks fits into. + * We fire upon reaching a level below desired only once, + * except for when we reach the low disk or near low disk levels + * from below, in which case we do not fire unless we have also + * reached the desired disk level (hysteresis). + * This is illustrated in the following diagram: + * + * fire ^ + * --------- desired level + * | + * + * + * | + * --------- near low disk level + * fire v + * + * + * | + * --------- low disk level + * fire v + * + * + * | ^ fire + * --------- very low disk level + * fire v | + * + */ if (freeblks < hfsmp->hfs_freespace_notify_dangerlimit) { - state = 2; + state = 4; } else if (freeblks < hfsmp->hfs_freespace_notify_warninglimit) { + state = 3; + } else if (freeblks < hfsmp->hfs_freespace_notify_nearwarninglimit) { + state = 2; + } else if (freeblks < hfsmp->hfs_freespace_notify_desiredlevel) { + /* We are between the near low disk and desired levels */ state = 1; } else if (freeblks >= hfsmp->hfs_freespace_notify_desiredlevel) { state = 0; } /* Free blocks are less than dangerlimit for the first time */ - if (state == 2 && !(hfsmp->hfs_notification_conditions & VQ_VERYLOWDISK)) { + if (state == 4 && !(hfsmp->hfs_notification_conditions & VQ_VERYLOWDISK)) { /* Dump some logging to track down intermittent issues */ printf("hfs: set VeryLowDisk: vol:%s, freeblks:%d, dangerlimit:%d\n", hfsmp->vcbVN, freeblks, hfsmp->hfs_freespace_notify_dangerlimit); @@ -96,13 +129,13 @@ void hfs_generate_volume_notifications(struct hfsmount *hfsmp) } #endif - hfsmp->hfs_notification_conditions |= (VQ_VERYLOWDISK|VQ_LOWDISK); + hfsmp->hfs_notification_conditions |= (VQ_VERYLOWDISK|VQ_LOWDISK|VQ_NEARLOWDISK); vfs_event_signal(&fsid, hfsmp->hfs_notification_conditions, (intptr_t)NULL); - } else if (state == 1) { + } else if (state == 3) { /* Free blocks are less than warning limit for the first time */ if (!(hfsmp->hfs_notification_conditions & VQ_LOWDISK)) { printf("hfs: set LowDisk: vol:%s, freeblks:%d, warninglimit:%d\n", hfsmp->vcbVN, freeblks, hfsmp->hfs_freespace_notify_warninglimit); - hfsmp->hfs_notification_conditions |= VQ_LOWDISK; + hfsmp->hfs_notification_conditions |= (VQ_LOWDISK|VQ_NEARLOWDISK); vfs_event_signal(&fsid, hfsmp->hfs_notification_conditions, (intptr_t)NULL); } else if (hfsmp->hfs_notification_conditions & VQ_VERYLOWDISK) { /* Free blocks count has increased from danger limit to warning limit, so just clear VERYLOWDISK warning */ @@ -110,18 +143,53 @@ void hfs_generate_volume_notifications(struct hfsmount *hfsmp) hfsmp->hfs_notification_conditions &= ~VQ_VERYLOWDISK; vfs_event_signal(&fsid, hfsmp->hfs_notification_conditions, (intptr_t)NULL); } + } else if (state == 2) { + /* Free blocks are less than the near warning limit for the first time */ + if (!(hfsmp->hfs_notification_conditions & VQ_NEARLOWDISK)) { + printf("hfs: set NearLowDisk: vol:%s, freeblks:%d, nearwarninglimit:%d\n", hfsmp->vcbVN, freeblks, + hfsmp->hfs_freespace_notify_nearwarninglimit); + + hfsmp->hfs_notification_conditions |= VQ_NEARLOWDISK; + vfs_event_signal(&fsid, hfsmp->hfs_notification_conditions, (intptr_t)NULL); + } else { + /* Free blocks count has increased from warning/danger limit to near warning limit, + * so clear VERYLOWDISK / LOWDISK warnings, and signal if we clear VERYLOWDISK */ + hfsmp->hfs_notification_conditions &= ~VQ_LOWDISK; + if (hfsmp->hfs_notification_conditions & VQ_VERYLOWDISK) { + printf("hfs: clear VeryLowDisk: vol:%s, freeblks:%d, dangerlimit:%d\n", hfsmp->vcbVN, freeblks, + hfsmp->hfs_freespace_notify_dangerlimit); + + hfsmp->hfs_notification_conditions &= ~VQ_VERYLOWDISK; + vfs_event_signal(&fsid, hfsmp->hfs_notification_conditions, (intptr_t)NULL); + } + } + } else if (state == 1) { + /* Free blocks are less than the desireable level, but more than the near warning level + * In this case, we may have to notify if we were previously underneath the danger limit */ + if (hfsmp->hfs_notification_conditions & VQ_VERYLOWDISK) { + printf("hfs: clear VeryLowDisk: vol:%s, freeblks:%d, dangerlimit:%d\n", hfsmp->vcbVN, freeblks, + hfsmp->hfs_freespace_notify_dangerlimit); + + hfsmp->hfs_notification_conditions &= ~VQ_VERYLOWDISK; + vfs_event_signal(&fsid, hfsmp->hfs_notification_conditions, (intptr_t)NULL); + } } else if (state == 0) { /* Free blocks count has increased to desirable level, so clear all conditions */ - if (hfsmp->hfs_notification_conditions & (VQ_LOWDISK|VQ_VERYLOWDISK)) { + if (hfsmp->hfs_notification_conditions & (VQ_NEARLOWDISK|VQ_LOWDISK|VQ_VERYLOWDISK)) { + if (hfsmp->hfs_notification_conditions & VQ_NEARLOWDISK) { + printf("hfs: clear NearLowDisk: vol:%s, freeblks:%d, nearwarninglimit:%d, desiredlevel:%d\n", hfsmp->vcbVN, + freeblks, hfsmp->hfs_freespace_notify_nearwarninglimit, hfsmp->hfs_freespace_notify_desiredlevel); + } if (hfsmp->hfs_notification_conditions & VQ_LOWDISK) { - printf("hfs: clear LowDisk: vol:%s, freeblks:%d, warninglimit:%d, desiredlevel:%d\n", hfsmp->vcbVN, freeblks, hfsmp->hfs_freespace_notify_warninglimit, hfsmp->hfs_freespace_notify_desiredlevel); + printf("hfs: clear LowDisk: vol:%s, freeblks:%d, warninglimit:%d, desiredlevel:%d\n", hfsmp->vcbVN, freeblks, + hfsmp->hfs_freespace_notify_warninglimit, hfsmp->hfs_freespace_notify_desiredlevel); } if (hfsmp->hfs_notification_conditions & VQ_VERYLOWDISK) { printf("hfs: clear VeryLowDisk: vol:%s, freeblks:%d, dangerlimit:%d\n", hfsmp->vcbVN, freeblks, hfsmp->hfs_freespace_notify_warninglimit); } - hfsmp->hfs_notification_conditions &= ~(VQ_VERYLOWDISK|VQ_LOWDISK); + hfsmp->hfs_notification_conditions &= ~(VQ_VERYLOWDISK|VQ_LOWDISK|VQ_NEARLOWDISK); if (hfsmp->hfs_notification_conditions == 0) { - vfs_event_signal(&fsid, VQ_UPDATE, (intptr_t)NULL); + vfs_event_signal(&fsid, VQ_UPDATE|VQ_DESIRED_DISK, (intptr_t)NULL); } else { vfs_event_signal(&fsid, hfsmp->hfs_notification_conditions, (intptr_t)NULL); } diff --git a/core/hfs_readwrite.c b/core/hfs_readwrite.c index 879fe0a..9b3f586 100644 --- a/core/hfs_readwrite.c +++ b/core/hfs_readwrite.c @@ -1539,8 +1539,8 @@ hfs_vnop_ioctl( struct vnop_ioctl_args /* { #if CONFIG_PROTECT #if HFS_CONFIG_KEY_ROLL - // The HFS_KEY_ROLL fsctl does its own access checks - if (ap->a_command != HFS_KEY_ROLL) + // The HFSIOC_KEY_ROLL fsctl does its own access checks + if (ap->a_command != HFSIOC_KEY_ROLL) #endif { int error = 0; @@ -1552,7 +1552,7 @@ hfs_vnop_ioctl( struct vnop_ioctl_args /* { switch (ap->a_command) { - case HFS_GETPATH: + case HFSIOC_GETPATH: { struct vnode *file_vp; cnid_t cnid; @@ -1592,7 +1592,7 @@ hfs_vnop_ioctl( struct vnop_ioctl_args /* { return (error); } - case HFS_SET_MAX_DEFRAG_SIZE: + case HFSIOC_SET_MAX_DEFRAG_SIZE: { int error = 0; /* Assume success */ u_int32_t maxsize = 0; @@ -1619,7 +1619,7 @@ hfs_vnop_ioctl( struct vnop_ioctl_args /* { return (error); } - case HFS_FORCE_ENABLE_DEFRAG: + case HFSIOC_FORCE_ENABLE_DEFRAG: { int error = 0; /* Assume success */ u_int32_t do_enable = 0; @@ -1648,7 +1648,7 @@ hfs_vnop_ioctl( struct vnop_ioctl_args /* { } - case HFS_TRANSFER_DOCUMENT_ID: + case HFSIOC_TRANSFER_DOCUMENT_ID: { struct cnode *cp = NULL; int error; @@ -1770,8 +1770,8 @@ hfs_vnop_ioctl( struct vnop_ioctl_args /* { - case HFS_PREV_LINK: - case HFS_NEXT_LINK: + case HFSIOC_PREV_LINK: + case HFSIOC_NEXT_LINK: { cnid_t linkfileid; cnid_t nextlinkid; @@ -1795,7 +1795,7 @@ hfs_vnop_ioctl( struct vnop_ioctl_args /* { if ((error = hfs_lookup_siblinglinks(hfsmp, linkfileid, &prevlinkid, &nextlinkid))) { return (error); } - if (ap->a_command == HFS_NEXT_LINK) { + if (ap->a_command == HFSIOC_NEXT_LINK) { *(cnid_t *)ap->a_data = nextlinkid; } else { *(cnid_t *)ap->a_data = prevlinkid; @@ -1803,7 +1803,7 @@ hfs_vnop_ioctl( struct vnop_ioctl_args /* { return (0); } - case HFS_RESIZE_PROGRESS: { + case HFSIOC_RESIZE_PROGRESS: { vfsp = vfs_statfs(HFSTOVFS(hfsmp)); if (suser(cred, NULL) && @@ -1821,7 +1821,7 @@ hfs_vnop_ioctl( struct vnop_ioctl_args /* { return hfs_resize_progress(hfsmp, (u_int32_t *)ap->a_data); } - case HFS_RESIZE_VOLUME: { + case HFSIOC_RESIZE_VOLUME: { u_int64_t newsize; u_int64_t cursize; int ret; @@ -1854,7 +1854,7 @@ hfs_vnop_ioctl( struct vnop_ioctl_args /* { IOBSDMountChange(hfsmp->hfs_mp, kIOMountChangeDidResize); return (ret); } - case HFS_CHANGE_NEXT_ALLOCATION: { + case HFSIOC_CHANGE_NEXT_ALLOCATION: { int error = 0; /* Assume success */ u_int32_t location; @@ -1898,7 +1898,7 @@ fail_change_next_allocation: } #if HFS_SPARSE_DEV - case HFS_SETBACKINGSTOREINFO: { + case HFSIOC_SETBACKINGSTOREINFO: { struct vnode * di_vp; struct hfs_backingstoreinfo *bsdata; int error = 0; @@ -1975,7 +1975,8 @@ fail_change_next_allocation: file_drop(bsdata->backingfd); return (0); } - case HFS_CLRBACKINGSTOREINFO: { + + case HFSIOC_CLRBACKINGSTOREINFO: { struct vnode * tmpvp; vfsp = vfs_statfs(HFSTOVFS(hfsmp)); @@ -2004,7 +2005,7 @@ fail_change_next_allocation: #endif /* HFS_SPARSE_DEV */ /* Change the next CNID stored in the VH */ - case HFS_CHANGE_NEXTCNID: { + case HFSIOC_CHANGE_NEXTCNID: { int error = 0; /* Assume success */ u_int32_t fileid; int wraparound = 0; @@ -2074,7 +2075,8 @@ fail_change_next_allocation: return hfs_thaw(hfsmp, current_proc()); } - case HFS_EXT_BULKACCESS_FSCTL: { + case HFSIOC_EXT_BULKACCESS32: + case HFSIOC_EXT_BULKACCESS64: { int size; #if CONFIG_HFS_STD if (hfsmp->hfs_flags & HFS_STANDARD) { @@ -2091,7 +2093,7 @@ fail_change_next_allocation: return do_bulk_access_check(hfsmp, vp, ap, size, context); } - case HFS_SET_XATTREXTENTS_STATE: { + case HFSIOC_SET_XATTREXTENTS_STATE: { int state; if (ap->a_data == NULL) { @@ -2114,7 +2116,7 @@ fail_change_next_allocation: return (EPERM); } if (state == 0 || state == 1) - return hfs_set_volxattr(hfsmp, HFS_SET_XATTREXTENTS_STATE, state); + return hfs_set_volxattr(hfsmp, HFSIOC_SET_XATTREXTENTS_STATE, state); else return (EINVAL); } @@ -2471,19 +2473,19 @@ fail_change_next_allocation: return 0; } - case SPOTLIGHT_FSCTL_GET_MOUNT_TIME: + case SPOTLIGHT_IOC_GET_MOUNT_TIME: *(uint32_t *)ap->a_data = hfsmp->hfs_mount_time; break; - case SPOTLIGHT_FSCTL_GET_LAST_MTIME: + case SPOTLIGHT_IOC_GET_LAST_MTIME: *(uint32_t *)ap->a_data = hfsmp->hfs_last_mounted_mtime; break; - case HFS_FSCTL_GET_VERY_LOW_DISK: + case HFSIOC_GET_VERY_LOW_DISK: *(uint32_t*)ap->a_data = hfsmp->hfs_freespace_notify_dangerlimit; break; - case HFS_FSCTL_SET_VERY_LOW_DISK: + case HFSIOC_SET_VERY_LOW_DISK: if (*(uint32_t *)ap->a_data >= hfsmp->hfs_freespace_notify_warninglimit) { return EINVAL; } @@ -2491,11 +2493,11 @@ fail_change_next_allocation: hfsmp->hfs_freespace_notify_dangerlimit = *(uint32_t *)ap->a_data; break; - case HFS_FSCTL_GET_LOW_DISK: + case HFSIOC_GET_LOW_DISK: *(uint32_t*)ap->a_data = hfsmp->hfs_freespace_notify_warninglimit; break; - case HFS_FSCTL_SET_LOW_DISK: + case HFSIOC_SET_LOW_DISK: if ( *(uint32_t *)ap->a_data >= hfsmp->hfs_freespace_notify_desiredlevel || *(uint32_t *)ap->a_data <= hfsmp->hfs_freespace_notify_dangerlimit) { @@ -2505,11 +2507,25 @@ fail_change_next_allocation: hfsmp->hfs_freespace_notify_warninglimit = *(uint32_t *)ap->a_data; break; - case HFS_FSCTL_GET_DESIRED_DISK: + /* The following two fsctls were ported from apfs. */ + case APFSIOC_GET_NEAR_LOW_DISK: + *(uint32_t*)ap->a_data = hfsmp->hfs_freespace_notify_nearwarninglimit; + break; + + case APFSIOC_SET_NEAR_LOW_DISK: + if ( *(uint32_t *)ap->a_data >= hfsmp->hfs_freespace_notify_desiredlevel + || *(uint32_t *)ap->a_data <= hfsmp->hfs_freespace_notify_warninglimit) { + return EINVAL; + } + + hfsmp->hfs_freespace_notify_nearwarninglimit = *(uint32_t *)ap->a_data; + break; + + case HFSIOC_GET_DESIRED_DISK: *(uint32_t*)ap->a_data = hfsmp->hfs_freespace_notify_desiredlevel; break; - case HFS_FSCTL_SET_DESIRED_DISK: + case HFSIOC_SET_DESIRED_DISK: if (*(uint32_t *)ap->a_data <= hfsmp->hfs_freespace_notify_warninglimit) { return EINVAL; } @@ -2517,7 +2533,7 @@ fail_change_next_allocation: hfsmp->hfs_freespace_notify_desiredlevel = *(uint32_t *)ap->a_data; break; - case HFS_VOLUME_STATUS: + case HFSIOC_VOLUME_STATUS: *(uint32_t *)ap->a_data = hfsmp->hfs_notification_conditions; break; @@ -2545,7 +2561,8 @@ fail_change_next_allocation: hfs_unlock_mount(hfsmp); break; - case HFS_MARK_BOOT_CORRUPT: + /* case HFS_MARK_BOOT_CORRUPT: _IO are the same */ + case HFSIOC_MARK_BOOT_CORRUPT: /* Mark the boot volume corrupt by setting * kHFSVolumeInconsistentBit in the volume header. This will * force fsck_hfs on next mount. @@ -2566,7 +2583,7 @@ fail_change_next_allocation: hfs_mark_inconsistent(hfsmp, HFS_FSCK_FORCED); break; - case HFS_FSCTL_GET_JOURNAL_INFO: + case HFSIOC_GET_JOURNAL_INFO: jip = (struct hfs_journal_info*)ap->a_data; if (vp == NULLVP) @@ -2584,7 +2601,7 @@ fail_change_next_allocation: jip->jsize = jnl_size; break; - case HFS_SET_ALWAYS_ZEROFILL: { + case HFSIOC_SET_ALWAYS_ZEROFILL: { struct cnode *cp = VTOC(vp); if (*(int *)ap->a_data) { @@ -2595,7 +2612,8 @@ fail_change_next_allocation: break; } - case HFS_DISABLE_METAZONE: { + /* case HFS_DISABLE_METAZONE: _IO are the same */ + case HFSIOC_DISABLE_METAZONE: { /* Only root can disable metadata zone */ if (!kauth_cred_issuser(kauth_cred_get())) { return EACCES; @@ -2611,7 +2629,7 @@ fail_change_next_allocation: } - case HFS_FSINFO_METADATA_BLOCKS: { + case HFSIOC_FSINFO_METADATA_BLOCKS: { int error; struct hfsinfo_metadata *hinfo; @@ -2626,7 +2644,7 @@ fail_change_next_allocation: break; } - case HFS_GET_FSINFO: { + case HFSIOC_GET_FSINFO: { hfs_fsinfo *fsinfo = (hfs_fsinfo *)ap->a_data; /* Only root is allowed to get fsinfo */ @@ -2657,7 +2675,7 @@ fail_change_next_allocation: return hfs_get_fsinfo(hfsmp, ap->a_data); } - case HFS_CS_FREESPACE_TRIM: { + case HFSIOC_CS_FREESPACE_TRIM: { int error = 0; int lockflags = 0; @@ -2747,7 +2765,7 @@ fail_change_next_allocation: break; } - case HFS_SET_HOTFILE_STATE: { + case HFSIOC_SET_HOTFILE_STATE: { int error; struct cnode *cp = VTOC(vp); uint32_t hf_state = *((uint32_t*)ap->a_data); @@ -2793,7 +2811,7 @@ fail_change_next_allocation: return error; } - case HFS_REPIN_HOTFILE_STATE: { + case HFSIOC_REPIN_HOTFILE_STATE: { int error=0; uint32_t repin_what = *((uint32_t*)ap->a_data); @@ -2831,7 +2849,7 @@ fail_change_next_allocation: #if HFS_CONFIG_KEY_ROLL - case HFS_KEY_ROLL: { + case HFSIOC_KEY_ROLL: { if (!kauth_cred_issuser(kauth_cred_get())) return EACCES; @@ -2840,7 +2858,7 @@ fail_change_next_allocation: return hfs_key_roll_op(ap->a_context, ap->a_vp, args); } - case HFS_GET_KEY_AUTO_ROLL: { + case HFSIOC_GET_KEY_AUTO_ROLL: { if (!kauth_cred_issuser(kauth_cred_get())) return EACCES; @@ -2854,7 +2872,7 @@ fail_change_next_allocation: break; } - case HFS_SET_KEY_AUTO_ROLL: { + case HFSIOC_SET_KEY_AUTO_ROLL: { if (!kauth_cred_issuser(kauth_cred_get())) return EACCES; diff --git a/core/hfs_vfsops.c b/core/hfs_vfsops.c index 10e5f53..6d07488 100644 --- a/core/hfs_vfsops.c +++ b/core/hfs_vfsops.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999-2015 Apple Inc. All rights reserved. + * Copyright (c) 1999-2017 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -1133,11 +1133,6 @@ void hfs_scan_blocks (struct hfsmount *hfsmp) { } -static int hfs_root_unmounted_cleanly = 0; - -SYSCTL_DECL(_vfs_generic); -HFS_SYSCTL(INT, _vfs_generic, OID_AUTO, root_unmounted_cleanly, CTLFLAG_RD, &hfs_root_unmounted_cleanly, 0, "Root filesystem was unmounted cleanly"); - /* * Common code for mount and mountroot */ @@ -1632,8 +1627,8 @@ hfs_mountfs(struct vnode *devvp, struct mount *mp, struct hfs_mount_args *args, goto error_exit; } - if (isroot) { - hfs_root_unmounted_cleanly = ((SWAP_BE32(vhp->attributes) & kHFSVolumeUnmountedMask) != 0); + if (isroot && ((SWAP_BE32(vhp->attributes) & kHFSVolumeUnmountedMask) != 0)) { + vfs_set_root_unmounted_cleanly(); } /* @@ -1890,10 +1885,11 @@ hfs_mountfs(struct vnode *devvp, struct mount *mp, struct hfs_mount_args *args, /* * Set the free space warning levels for a non-root volume: * - * Set the "danger" limit to 1% of the volume size or 100MB, whichever - * is less. Set the "warning" limit to 2% of the volume size or 150MB, - * whichever is less. And last, set the "desired" freespace level to - * to 3% of the volume size or 200MB, whichever is less. + * Set the "danger" limit to 1% of the volume size or 150MB, whichever is less. + * Set the "warning" limit to 2% of the volume size or 500MB, whichever is less. + * Set the "near warning" limit to 10% of the volume size or 1GB, whichever is less. + * And last, set the "desired" freespace level to to 12% of the volume size or 1.2GB, + * whichever is less. */ hfsmp->hfs_freespace_notify_dangerlimit = MIN(HFS_VERYLOWDISKTRIGGERLEVEL / HFSTOVCB(hfsmp)->blockSize, @@ -1901,6 +1897,9 @@ hfs_mountfs(struct vnode *devvp, struct mount *mp, struct hfs_mount_args *args, 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); @@ -1908,10 +1907,11 @@ hfs_mountfs(struct vnode *devvp, struct mount *mp, struct hfs_mount_args *args, /* * Set the free space warning levels for the root volume: * - * Set the "danger" limit to 5% of the volume size or 512MB, whichever - * is less. Set the "warning" limit to 10% of the volume size or 1GB, - * whichever is less. And last, set the "desired" freespace level to - * to 11% of the volume size or 1.25GB, whichever is less. + * Set the "danger" limit to 5% of the volume size or 512MB, whichever is less. + * Set the "warning" limit to 10% of the volume size or 1GB, whichever is less. + * Set the "near warning" limit to 10.5% of the volume size or 1.1GB, whichever is less. + * And last, set the "desired" freespace level to to 11% of the volume size or 1.25GB, + * whichever is less. * * NOTE: While those are the default limits, KernelEventAgent (as of 3/2016) * will unilaterally override these to the following on OSX only: @@ -1925,6 +1925,9 @@ hfs_mountfs(struct vnode *devvp, struct mount *mp, struct hfs_mount_args *args, 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); @@ -4227,8 +4230,8 @@ hfs_getvoluuid(struct hfsmount *hfsmp, uuid_t result_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)) -#define HFS_ATTR_CMN_VOL_VALIDMASK (ATTR_CMN_VALIDMASK & ~(ATTR_CMN_ACCTIME)) +#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); @@ -4292,6 +4295,12 @@ hfs_vfs_getattr(struct mount *mp, struct vfs_attr *fsap, __unused vfs_context_t #if CONFIG_HFS_DIRLINK VOL_CAP_FMT_DIR_HARDLINKS | #endif +#ifdef VOL_CAP_FMT_DOCUMENT_ID + VOL_CAP_FMT_DOCUMENT_ID | +#endif /* VOL_CAP_FMT_DOCUMENT_ID */ +#ifdef VOL_CAP_FMT_WRITE_GENERATION_COUNT + VOL_CAP_FMT_WRITE_GENERATION_COUNT | +#endif /* VOL_CAP_FMT_WRITE_GENERATION_COUNT */ VOL_CAP_FMT_PATH_FROM_ID; } #if CONFIG_HFS_STD @@ -4358,6 +4367,12 @@ hfs_vfs_getattr(struct mount *mp, struct vfs_attr *fsap, __unused vfs_context_t 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 /* VOL_CAP_FMT_DOCUMENT_ID */ +#ifdef VOL_CAP_FMT_WRITE_GENERATION_COUNT + VOL_CAP_FMT_WRITE_GENERATION_COUNT | +#endif /* VOL_CAP_FMT_WRITE_GENERATION_COUNT */ VOL_CAP_FMT_DIR_HARDLINKS; /* @@ -4398,13 +4413,21 @@ hfs_vfs_getattr(struct mount *mp, struct vfs_attr *fsap, __unused vfs_context_t 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; - attrp->nativeattr.volattr = ATTR_VOL_VALIDMASK & ~ATTR_VOL_INFO; + 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; @@ -4416,10 +4439,20 @@ hfs_vfs_getattr(struct mount *mp, struct vfs_attr *fsap, __unused vfs_context_t fsap->f_modify_time.tv_sec = hfsmp->vcbLsMod; fsap->f_modify_time.tv_nsec = 0; VFSATTR_SET_SUPPORTED(fsap, f_modify_time); + // We really don't have volume access time, they should check the root node, fake it up + 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; diff --git a/core/hfs_vnops.c b/core/hfs_vnops.c index 316a657..16d1d1d 100644 --- a/core/hfs_vnops.c +++ b/core/hfs_vnops.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2016 Apple Inc. All rights reserved. + * Copyright (c) 2000-2017 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -55,7 +55,6 @@ #include #include #include -#include #include #include @@ -114,6 +113,22 @@ static int hfs_move_data(cnode_t *from_cp, cnode_t *to_cp, static int hfs_move_fork(filefork_t *srcfork, cnode_t *src, filefork_t *dstfork, cnode_t *dst); + +static int hfs_exchangedata_getxattr (struct vnode *vp, uint32_t name_selector, void **buffer, size_t *xattr_size); +static int hfs_exchangedata_setxattr (struct hfsmount *hfsmp, uint32_t fileid, + uint32_t name_selector, void *buffer, size_t xattr_size); + +enum XATTR_NAME_ENTRIES { + quarantine = 0, + MAX_NUM_XATTR_NAMES //must be last +}; + + +/* These are special EAs that follow the content in exchangedata(2). */ +const char *XATTR_NAMES [MAX_NUM_XATTR_NAMES] = { "com.apple.quarantine" }; + +#define MAX_EXCHANGE_EA_SIZE 4096 + #if HFS_COMPRESSION static int hfs_move_compressed(cnode_t *from_vp, cnode_t *to_vp); #endif @@ -1876,6 +1891,156 @@ static int hfs_flush_rsrc(vnode_t vp, vfs_context_t ctx) } #endif // HFS_COMPRESSION + +/* Helper Functions for exchangedata(2) */ + +/* + * hfs_exchangedata_getxattr + * arguments: + * vp: vnode to extract the EA for + * name_selector: the index into the array of EA name entries. + * buffer: address for output buffer to store the output EA + * NOTE: This function will allocate the buffer, it is the caller's responsibility to free it. + * xattr_size: output argument; will return the size of the EA, to correspond with the buffer. + * + * Return: 0 on success. + * errno on error. If we return any error, the buffer is guaranteed to be NULL. + * + * Assumes CNODE lock held on cnode for 'vp' + */ +static +int hfs_exchangedata_getxattr (struct vnode *vp, uint32_t name_selector, void **buffer, size_t *xattr_size) { + void *xattr_rawdata = NULL; + void *extracted_xattr = NULL; + uio_t uio; + size_t memsize = MAX_EXCHANGE_EA_SIZE; + size_t attrsize; + int error = 0; + struct hfsmount *hfsmp = NULL; + + /* Sanity check inputs */ + if (name_selector > MAX_NUM_XATTR_NAMES) { + return EINVAL; + } + + if (buffer == NULL || xattr_size == NULL) { + return EINVAL; + } + + hfsmp = VTOHFS(vp); + + //allocate 4k memory to hold the EA. We don't use this for "large" EAs, and the default + //EA B-tree size should produce inline attributes of size < 4K + xattr_rawdata = hfs_malloc (MAX_EXCHANGE_EA_SIZE); + if (!xattr_rawdata) { + return ENOMEM; + } + + //now create the UIO + uio = uio_create (1, 0, UIO_SYSSPACE, UIO_READ); + if (!uio) { + hfs_free (xattr_rawdata, memsize); + return ENOMEM; + } + uio_addiov(uio, CAST_USER_ADDR_T(xattr_rawdata), memsize); + attrsize = memsize; + + struct vnop_getxattr_args vga = { + .a_uio = uio, + .a_name = XATTR_NAMES[name_selector], + .a_size = &attrsize + }; + + // this takes care of grabbing the systemfile locks for us. + error = hfs_getxattr_internal (VTOC(vp), &vga, hfsmp, 0); + + if (error) { + /* + * We could have gotten a variety of errors back from the XATTR tree: + * is it too big? (bigger than 4k?) == ERANGE + * was the EA not found? == ENOATTR + */ + uio_free(uio); + hfs_free (xattr_rawdata, memsize); + return error; + } + + //free the UIO + uio_free(uio); + + //upon success, a_size/attrsize now contains the actua/exported EA size + extracted_xattr = hfs_malloc (attrsize); + memcpy (extracted_xattr, xattr_rawdata, attrsize); + hfs_free (xattr_rawdata, memsize); + + *xattr_size = attrsize; + *buffer = extracted_xattr; + + return error; +} + + +/* + * hfs_exchangedata_setxattr + * + * Note: This function takes fileIDs in as inputs, because exchangedata does + * swizzly things with the two cnodes (See big block comment in hfs_vnop_exchange) + * so we operate with FileIDs more or less directly on the XATTR b-tree. + * + * arguments: + * hfsmp: the mount we're working on + * fileid: the fileID of the EA to store into the tree. + * name_selector: selector into the EA name array. + * buffer: pointer to the memory of the EA to write. + * xattr_size: size of the EA to write. + * + * Returns 0 on success + * errno on failure + * + * Assumes that a transaction has already begun when this is called + */ + +static +int hfs_exchangedata_setxattr (struct hfsmount *hfsmp, uint32_t fileid, + uint32_t name_selector, void *buffer, size_t xattr_size) { + + int error = 0; + + + /* Sanity check arguments */ + if (name_selector > MAX_NUM_XATTR_NAMES) { + return EINVAL; + } + + if (buffer == NULL || xattr_size == 0 || fileid < kHFSFirstUserCatalogNodeID ) { + return EINVAL; + } + + // is the size too big? + if (xattr_size > hfsmp->hfs_max_inline_attrsize) { + return EINVAL; + } + + /* setup the arguments to setxattr*/ + struct vnop_setxattr_args vsa = { + .a_desc = NULL, + .a_vp = NULL, + .a_name = XATTR_NAMES[name_selector], + .a_uio = NULL, // we use the data_ptr argument to setxattr_internal instead + .a_options = 0, + .a_context = NULL // no context needed, only done from within exchangedata + }; + + /* + * Since we must be in a transaction to guard the exchangedata operation, this will start + * a nested transaction within the exchangedata one. + */ + error = hfs_setxattr_internal (NULL, (caddr_t) buffer, xattr_size, &vsa, hfsmp, fileid); + + return error; + +} + /* * hfs_vnop_exchange: * @@ -1912,6 +2077,10 @@ hfs_vnop_exchange(struct vnop_exchange_args *ap) char to_iname[32]; uint32_t to_flag_special; uint32_t from_flag_special; + + uint16_t to_recflags_special; + uint16_t from_recflags_special; + cnid_t from_parid; cnid_t to_parid; int lockflags; @@ -1919,6 +2088,13 @@ hfs_vnop_exchange(struct vnop_exchange_args *ap) cat_cookie_t cookie; time_t orig_from_ctime, orig_to_ctime; bool have_cnode_locks = false, have_from_trunc_lock = false, have_to_trunc_lock = false; + + /* For the quarantine EA */ + void *from_xattr = NULL; + void *to_xattr = NULL; + size_t from_attrsize = 0; + size_t to_attrsize = 0; + /* * VFS does the following checks: @@ -2053,8 +2229,7 @@ hfs_vnop_exchange(struct vnop_exchange_args *ap) } /* - * Ok, now that all of the pre-flighting is done, call the underlying - * function if needed. + * If doing a data move, then call the underlying function. */ if (ISSET(ap->a_options, FSOPT_EXCHANGE_DATA_ONLY)) { #if HFS_COMPRESSION @@ -2068,6 +2243,38 @@ hfs_vnop_exchange(struct vnop_exchange_args *ap) goto exit; } + /* + * If we're doing a normal exchangedata, then get the source/dst quarantine + * EAs as needed. We do it here before we start the transaction. + */ + + //get the EA for the 'from' vnode if it exists. + error = hfs_exchangedata_getxattr (from_vp, quarantine, &from_xattr, &from_attrsize); + if (error) { + if (error == ENOATTR) { + //it's OK for the quarantine EA to not exist + error = 0; + } + else { + goto exit; + } + } + + + //get the EA from the 'to' vnode if it exists + error = hfs_exchangedata_getxattr (to_vp, quarantine, &to_xattr, &to_attrsize); + if (error) { + if (error == ENOATTR) { + //it's OK for the quarantine EA to not exist + error = 0; + } + else { + goto exit; + } + } + + + /* Start a transaction; we have to do all of this atomically */ if ((error = hfs_start_transaction(hfsmp)) != 0) { goto exit; } @@ -2138,18 +2345,89 @@ hfs_vnop_exchange(struct vnop_exchange_args *ap) */ error = ExchangeFileIDs(hfsmp, from_nameptr, to_nameptr, from_parid, to_parid, from_cp->c_hint, to_cp->c_hint); - hfs_systemfile_unlock(hfsmp, lockflags); - - /* - * Note that we don't need to exchange any extended attributes - * since the attributes are keyed by file ID. - */ + hfs_systemfile_unlock(hfsmp, lockflags); if (error != E_NONE) { error = MacToVFSError(error); goto exit; } + /* + * Now, we have to swap the quarantine EA. + * + * Ordinarily, we would not have to swap/exchange any extended attributes, + * since they are keyed by the file ID, and this function is supposed + * to manipulate the main data stream/fork only. + * + * However, we want the quarantine EA to follow the file content. + */ + + int from_xattr_status = 0; + if (from_xattr) { + /* + * Caution! + * We've crossed a point of no return here, because if we + * have successfully swapped the file content above, we need to continue here + * to swap the rest of the cnode content, which is not subject to failure. + * Failing the whole function because the xattr swap will result in perceived + * data loss to the caller, so we swallow the error case here. + */ + from_xattr_status = hfs_removexattr_by_id (hfsmp, from_cp->c_fileid, XATTR_NAMES[quarantine]); + if (from_xattr_status == 0) { + int xattr_lockflags; + int remaining_eas; + /* + * Check to see if we need to remove the xattr bit from the catalog record flags while + * 'from_cp' still tracks with its original file ID. Once the cnodes' contents are swapped + * and they are ready to be re-hashed, we will OR in the bit if we know that we moved the + * EA to the counterpart. + */ + xattr_lockflags = hfs_systemfile_lock (hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); + remaining_eas = file_attribute_exist (hfsmp, from_cp->c_fileid); + if (remaining_eas == 0) { + from_cp->c_attr.ca_recflags &= ~kHFSHasAttributesMask; + //the cnode will be pushed out to disk LATER on. + } + hfs_systemfile_unlock (hfsmp, xattr_lockflags); + + } + } + + //and the same for to_xattr + if (to_xattr) { + int xattr_status = hfs_removexattr_by_id (hfsmp, to_cp->c_fileid, XATTR_NAMES[quarantine]); + + if (xattr_status == 0) { + int xattr_lockflags; + int remaining_eas; + /* + * Check to see if we need to remove the xattr bit from the catalog record flags while + * 'to_cp' still tracks with its original file ID. Once the cnodes' contents are swapped + * and they are ready to be re-hashed, we will OR in the bit if we know that we moved the + * EA to the counterpart. + */ + xattr_lockflags = hfs_systemfile_lock (hfsmp, SFL_ATTRIBUTE, HFS_SHARED_LOCK); + remaining_eas = file_attribute_exist (hfsmp, from_cp->c_fileid); + if (remaining_eas == 0) { + to_cp->c_attr.ca_recflags &= ~kHFSHasAttributesMask; + //the cnode will be pushed out to disk LATER on. + } + hfs_systemfile_unlock (hfsmp, xattr_lockflags); + + /* Now move the EA to the counterparty fileID. We piggyback on the larger transaction here */ + hfs_exchangedata_setxattr (hfsmp, from_cp->c_fileid, quarantine, to_xattr, to_attrsize); + } + } + + if (from_xattr && from_xattr_status == 0) { + /* + * if the from EA got removed properly, then attach it to the 'to' file. We do it at this point + * to ensure that it got removed properly above before re-setting it again. + */ + hfs_exchangedata_setxattr (hfsmp, to_cp->c_fileid, quarantine, from_xattr, from_attrsize); + } + + /* Purge the vnodes from the name cache */ if (from_vp) cache_purge(from_vp); @@ -2168,11 +2446,16 @@ hfs_vnop_exchange(struct vnop_exchange_args *ap) /* Save whether or not each cnode is a hardlink or has EAs */ from_flag_special = from_cp->c_flag & (C_HARDLINK | C_HASXATTRS); + from_recflags_special = (from_cp->c_attr.ca_recflags & kHFSHasAttributesMask); + to_flag_special = to_cp->c_flag & (C_HARDLINK | C_HASXATTRS); + to_recflags_special = (to_cp->c_attr.ca_recflags & kHFSHasAttributesMask); /* Drop the special bits from each cnode */ from_cp->c_flag &= ~(C_HARDLINK | C_HASXATTRS); to_cp->c_flag &= ~(C_HARDLINK | C_HASXATTRS); + from_cp->c_attr.ca_recflags &= ~(kHFSHasAttributesMask); + to_cp->c_attr.ca_recflags &= ~(kHFSHasAttributesMask); /* * Now complete the in-memory portion of the copy. @@ -2240,6 +2523,9 @@ hfs_vnop_exchange(struct vnop_exchange_args *ap) * The flags that are special are: * C_HARDLINK, C_HASXATTRS * + * and the c_attr recflag: + * kHFSHasAttributesMask + * * These flags move with the item and file ID in the namespace since their * state is tied to that of the file ID. * @@ -2250,6 +2536,20 @@ hfs_vnop_exchange(struct vnop_exchange_args *ap) */ from_cp->c_flag |= to_flag_special | C_MODIFIED; from_cp->c_attr.ca_recflags = to_cp->c_attr.ca_recflags; + from_cp->c_attr.ca_recflags |= to_recflags_special; + if (from_xattr) { + /* + * NOTE: + * This is counter-intuitive and part of the complexity of exchangedata. + * if 'from_cp' originally had a quarantine EA, then ensure that the cnode + * pointed to by 'from_cp' CONTINUES to keep the "has EAs" bit. This is because + * the cnode is about to be re-hashed with a new ID, but the file CONTENT + * (i.e. the file fork) stayed put. And we want the quarantine EA to follow + * the content. The check above is correct. + */ + from_cp->c_attr.ca_recflags |= kHFSHasAttributesMask; + } + bcopy(to_cp->c_finderinfo, from_cp->c_finderinfo, 32); @@ -2278,8 +2578,22 @@ hfs_vnop_exchange(struct vnop_exchange_args *ap) * Leave the rest of the flags alone. */ to_cp->c_flag |= from_flag_special | C_MODIFIED; - to_cp->c_attr.ca_recflags = tempattr.ca_recflags; + to_cp->c_attr.ca_recflags |= from_recflags_special; + + if (to_xattr) { + /* + * NOTE: + * This is counter-intuitive and part of the complexity of exchangedata. + * if 'to_cp' originally had a quarantine EA, then ensure that the cnode + * pointed to by 'to_cp' CONTINUES to keep the "has EAs" bit. This is because + * the cnode is about to be re-hashed with a new ID, but the file CONTENT + * (i.e. the file fork) stayed put. And we want the quarantine EA to follow + * the content. The check above is correct. + */ + to_cp->c_attr.ca_recflags |= kHFSHasAttributesMask; + } + bcopy(tempattr.ca_finderinfo, to_cp->c_finderinfo, 32); @@ -2318,6 +2632,17 @@ exit: if (have_to_trunc_lock) hfs_unlock_truncate(to_cp, 0); + /* Free the memory used by the EAs */ + if (from_xattr) { + hfs_free (from_xattr, from_attrsize); + from_xattr = NULL; + } + + if (to_xattr) { + hfs_free (to_xattr, to_attrsize); + to_xattr = NULL; + } + return (error); } @@ -5355,6 +5680,7 @@ hfs_vnop_readdir(struct vnop_readdir_args *ap) int eofflag = 0; user_addr_t user_start = 0; user_size_t user_len = 0; + user_size_t user_original_resid = 0; int index; unsigned int tag; int items; @@ -5409,10 +5735,15 @@ hfs_vnop_readdir(struct vnop_readdir_args *ap) if (user_len > (256 * 1024)) { /* only allow the user to wire down at most 256k */ user_len = (256 * 1024); + user_original_resid = uio_resid(uio); uio_setresid (uio, (user_ssize_t)(256 * 1024)); } if ((error = vslock(user_start, user_len)) != 0) { + if (user_original_resid > 0) { + uio_setresid(uio, user_original_resid); + user_original_resid = 0; + } return error; } } @@ -5420,6 +5751,10 @@ hfs_vnop_readdir(struct vnop_readdir_args *ap) /* Note that the dirhint calls require an exclusive lock. */ if ((error = hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) { if (user_start) { + if (user_original_resid > 0) { + uio_setresid(uio, user_original_resid); + user_original_resid = 0; + } vsunlock(user_start, user_len, TRUE); } return error; @@ -5569,6 +5904,10 @@ hfs_vnop_readdir(struct vnop_readdir_args *ap) error = 0; eofflag = 1; uio_setoffset(uio, startoffset); + if (user_original_resid > 0) { + uio_setresid(uio, user_original_resid); + user_original_resid = 0; + } hfs_systemfile_unlock (hfsmp, lockflags); goto seekoffcalc; @@ -5578,6 +5917,12 @@ hfs_vnop_readdir(struct vnop_readdir_args *ap) /* Pack the buffer with dirent entries. */ error = cat_getdirentries(hfsmp, cp->c_entries, dirhint, uio, ap->a_flags, &items, &eofflag); + if (user_original_resid > 0) { + user_original_resid = user_original_resid - ((user_ssize_t)256*1024 - uio_resid(uio)); + uio_setresid(uio, user_original_resid); + user_original_resid = 0; + } + if (index == 0 && error == 0) { cp->c_dirthreadhint = dirhint->dh_threadhint; } @@ -5637,6 +5982,10 @@ seekoffcalc: out: if (user_start) { + if (user_original_resid > 0) { + uio_setresid(uio, user_original_resid); + user_original_resid = 0; + } vsunlock(user_start, user_len, TRUE); } /* If we didn't do anything then go ahead and dump the hint. */ diff --git a/core/hfs_xattr.c b/core/hfs_xattr.c index c379f9f..0d3dcd3 100644 --- a/core/hfs_xattr.c +++ b/core/hfs_xattr.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2015 Apple Inc. All rights reserved. + * Copyright (c) 2004-2017 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -1507,6 +1507,62 @@ exit_nolock: return MacToVFSError(result); } +/* + * Removes a non rsrc-fork, non-finderinfo EA from the specified file ID. + * Note that this results in a bit of code duplication for the xattr removal + * path. This is done because it's a bit messy to deal with things without the + * cnode. This function is used by exchangedata to port XATTRS to alternate + * fileIDs while everything is locked, and the cnodes are in a transitional state. + * + * Assumes that the cnode backing the fileid specified is LOCKED. + */ + +int +hfs_removexattr_by_id (struct hfsmount *hfsmp, uint32_t fileid, const char *xattr_name ) { + struct BTreeIterator iter; // allocated on the stack to avoid heap allocation mid-txn + int ret = 0; + int started_txn = 0; + int lockflags; + + memset (&iter, 0, sizeof(iter)); + + //position the B-Tree iter key before grabbing locks and starting a txn + ret = hfs_buildattrkey (fileid, xattr_name, (HFSPlusAttrKey*)&iter.key); + if (ret) { + goto xattr_out; + } + + //note: this is likely a nested transaction since there is a global transaction cover + if (hfs_start_transaction (hfsmp) != 0) { + ret = EINVAL; + goto xattr_out; + } + started_txn = 1; + + + lockflags = hfs_systemfile_lock(hfsmp, SFL_ATTRIBUTE | SFL_BITMAP, HFS_EXCLUSIVE_LOCK); + + //actually remove the EA from the tree + ret = remove_attribute_records(hfsmp, &iter); + + hfs_systemfile_unlock(hfsmp, lockflags); + + /* + * NOTE: Responsibility of the caller to remove the "has XATTRs" bit in the catalog record + * if this was the last EA. + */ + + +xattr_out: + if (started_txn) { + hfs_end_transaction(hfsmp); + } + + return MacToVFSError(ret); + +} + + /* Check if any attribute record exist for given fileID. This function * is called by hfs_vnop_removexattr to determine if it should clear the * attribute bit in the catalog record or not. @@ -2016,7 +2072,7 @@ hfs_set_volxattr(struct hfsmount *hfsmp, unsigned int xattrtype, int state) return (ENOTSUP); } #endif - if (xattrtype != HFS_SET_XATTREXTENTS_STATE) { + if (xattrtype != HFSIOC_SET_XATTREXTENTS_STATE) { return EINVAL; } diff --git a/fs/Info.plist b/fs/Info.plist index c8a77b1..c3dfd93 100644 --- a/fs/Info.plist +++ b/fs/Info.plist @@ -237,6 +237,8 @@ fsck_hfs FSLiveVerificationArguments -l + FSEncryptionName + Mac OS Extended (Journaled, Encrypted) FSCoreStorageEncryptionName Mac OS Extended (Journaled, Encrypted) FSfileObjectsAreCasePreserving @@ -315,6 +317,8 @@ fsck_hfs FSLiveVerificationArguments -l + FSEncryptionName + Mac OS Extended (Case-sensitive, Journaled, Encrypted) FSCoreStorageEncryptionName Mac OS Extended (Case-sensitive, Journaled, Encrypted) FSfileObjectsAreCasePreserving diff --git a/fsck_hfs/dfalib/SVerify1.c b/fsck_hfs/dfalib/SVerify1.c index adcfeae..7ccbafd 100644 --- a/fsck_hfs/dfalib/SVerify1.c +++ b/fsck_hfs/dfalib/SVerify1.c @@ -2446,6 +2446,7 @@ CheckAttributeRecord(SGlobPtr GPtr, const HFSPlusAttrKey *key, const HFSPlusAttr * currently. Also check extents only in verify stage to avoid * false overlap extents error. */ + if (dfaStage == kVerifyStage) { /* Start block in the key should be zero */ if (key->startBlock != 0) { @@ -2454,9 +2455,11 @@ CheckAttributeRecord(SGlobPtr GPtr, const HFSPlusAttrKey *key, const HFSPlusAttr goto err_out; } + HFSPlusForkData forkData; + memcpy((void*)(&forkData), (void*)(&rec->forkData.theFork), sizeof(HFSPlusForkData)); /* Check the extent information and record overlapping extents, if any */ - result = CheckFileExtents (GPtr, fileID, kEAData, attrname, - rec->forkData.theFork.extents, &blocks); + result = CheckFileExtents (GPtr, fileID, kEAData, attrname, + &forkData.extents, &blocks); if (result) { goto update_out; } diff --git a/fsck_hfs/fsck_hfs.8 b/fsck_hfs/fsck_hfs.8 index 159a532..df3f11f 100644 --- a/fsck_hfs/fsck_hfs.8 +++ b/fsck_hfs/fsck_hfs.8 @@ -229,7 +229,9 @@ No errors found, or successfully repaired. .It 3 A quick-check (the .Fl n -option) found a dirty filesystem; no repairs were made. +option) found a dirty filesystem; no repairs were made. There is a potential +corruption in the filesystem, and either the journal could not be read, or a +runtime corruption was present so the HFS Volume Inconsistent bit was set. .It 4 During boot, the root filesystem was found to be dirty; repairs were made, and the filesystem was remounted. The system should be rebooted. diff --git a/hfs.xcodeproj/project.pbxproj b/hfs.xcodeproj/project.pbxproj index ef8da41..9ec964f 100644 --- a/hfs.xcodeproj/project.pbxproj +++ b/hfs.xcodeproj/project.pbxproj @@ -7,6 +7,17 @@ objects = { /* Begin PBXAggregateTarget section */ + 07828B591E3FDD25009D2106 /* hfs_libraries */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 07828B5D1E3FDD25009D2106 /* Build configuration list for PBXAggregateTarget "hfs_libraries" */; + buildPhases = ( + ); + dependencies = ( + 07828B5F1E3FDD2E009D2106 /* PBXTargetDependency */, + ); + name = hfs_libraries; + productName = hfs_libraries; + }; 4DBD523B1548A488007AA736 /* Common */ = { isa = PBXAggregateTarget; buildConfigurationList = 4DBD523C1548A488007AA736 /* Build configuration list for PBXAggregateTarget "Common" */; @@ -15,7 +26,6 @@ dependencies = ( FBA540011B7BF2DF00899E5B /* PBXTargetDependency */, FBC234C41B4EC6AE0002D849 /* PBXTargetDependency */, - 8657285C18319A93007F580F /* PBXTargetDependency */, 4DBD523F1548A499007AA736 /* PBXTargetDependency */, 4DBD52411548A49A007AA736 /* PBXTargetDependency */, 4DBD52431548A49D007AA736 /* PBXTargetDependency */, @@ -23,7 +33,6 @@ 4DBD52471548A4A2007AA736 /* PBXTargetDependency */, 4DBD52491548A4A4007AA736 /* PBXTargetDependency */, 4DBD524B1548A4A7007AA736 /* PBXTargetDependency */, - 4DBD524D1548A4AA007AA736 /* PBXTargetDependency */, ); name = Common; productName = Common; @@ -34,6 +43,7 @@ buildPhases = ( ); dependencies = ( + 07CA47231DA859CA00138D78 /* PBXTargetDependency */, FBE3A5A51BBEE34400CB9A33 /* PBXTargetDependency */, FB48E4BC1BB30CC400523121 /* PBXTargetDependency */, FBC234C61B4EC6B90002D849 /* PBXTargetDependency */, @@ -49,6 +59,7 @@ buildPhases = ( ); dependencies = ( + 07828B611E3FDD3B009D2106 /* PBXTargetDependency */, FBE3A5A31BBEE33D00CB9A33 /* PBXTargetDependency */, 4DBD524F1548A4C8007AA736 /* PBXTargetDependency */, ); @@ -135,6 +146,7 @@ /* Begin PBXBuildFile section */ 0703A0541CD826160035BCFD /* test-defrag.c in Sources */ = {isa = PBXBuildFile; fileRef = 0703A0531CD826160035BCFD /* test-defrag.c */; }; 07C2BF891CB43F5E00D8327D /* test-renamex.c in Sources */ = {isa = PBXBuildFile; fileRef = 07C2BF881CB43F5E00D8327D /* test-renamex.c */; }; + 09D6B7D71E317ED2003C20DC /* test_disklevel.c in Sources */ = {isa = PBXBuildFile; fileRef = 09D6B7D61E317ED2003C20DC /* test_disklevel.c */; }; 2A386A3B1C22209C007FEDAC /* test-list-ids.c in Sources */ = {isa = PBXBuildFile; fileRef = 2A386A3A1C221E67007FEDAC /* test-list-ids.c */; }; 2A84DBD41D9E15F2007964B8 /* test-raw-dev-unaligned.c in Sources */ = {isa = PBXBuildFile; fileRef = 2A84DBD31D9E1179007964B8 /* test-raw-dev-unaligned.c */; }; 2A9399951BDFEB5200FB075B /* test-access.c in Sources */ = {isa = PBXBuildFile; fileRef = 2A9399941BDFEA6E00FB075B /* test-access.c */; }; @@ -354,6 +366,27 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 07828B5E1E3FDD2E009D2106 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 86CBF37E183186C300A64A93; + remoteInfo = hfs_metadata; + }; + 07828B601E3FDD3B009D2106 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 07828B591E3FDD25009D2106; + remoteInfo = hfs_libraries; + }; + 07CA47221DA859CA00138D78 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4D07DCB71538EF3A002B57CB; + remoteInfo = fstyp_hfs; + }; 4DBD523E1548A499007AA736 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; @@ -403,13 +436,6 @@ remoteGlobalIDString = 4DFD93F31535FF510039B6BA; remoteInfo = fsck_hfs; }; - 4DBD524C1548A4AA007AA736 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4D07DCB71538EF3A002B57CB; - remoteInfo = fstyp_hfs; - }; 4DBD524E1548A4C8007AA736 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; @@ -431,13 +457,6 @@ remoteGlobalIDString = 4DFD94BC15373C2C0039B6BA; remoteInfo = fsck_makestrings; }; - 8657285B18319A93007F580F /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 86CBF37E183186C300A64A93; - remoteInfo = hfs_metadata; - }; FB48E4BB1BB30CC400523121 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 08FB7793FE84155DC02AAC07 /* Project object */; @@ -718,6 +737,7 @@ /* Begin PBXFileReference section */ 0703A0531CD826160035BCFD /* test-defrag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "test-defrag.c"; sourceTree = ""; }; 07C2BF881CB43F5E00D8327D /* test-renamex.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "test-renamex.c"; sourceTree = ""; }; + 09D6B7D61E317ED2003C20DC /* test_disklevel.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = test_disklevel.c; sourceTree = ""; }; 2A386A3A1C221E67007FEDAC /* test-list-ids.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "test-list-ids.c"; sourceTree = ""; }; 2A84DBD31D9E1179007964B8 /* test-raw-dev-unaligned.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "test-raw-dev-unaligned.c"; sourceTree = ""; }; 2A9399941BDFEA6E00FB075B /* test-access.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "test-access.c"; sourceTree = ""; }; @@ -1427,6 +1447,7 @@ FB285C281B7E81180099B2ED /* test-sparse-dev.c */, FB2B5C551B87656900ACEDD9 /* test-transcode.m */, FBD69AF81B91309C0022ECAD /* test-dateadded.c */, + 09D6B7D61E317ED2003C20DC /* test_disklevel.c */, ); path = cases; sourceTree = ""; @@ -1963,6 +1984,10 @@ attributes = { LastUpgradeCheck = 0710; TargetAttributes = { + 07828B591E3FDD25009D2106 = { + CreatedOnToolsVersion = 8.3; + ProvisioningStyle = Automatic; + }; FB20E0DF1AE950C200CEBE7B = { CreatedOnToolsVersion = 6.3; }; @@ -2043,6 +2068,7 @@ FBCC52FD1B852758008B752C /* hfs-alloc-trace */, FB48E5031BB3798500523121 /* Sim_Headers */, FB7C140C1C2368E6004F8B2C /* kext-version */, + 07828B591E3FDD25009D2106 /* hfs_libraries */, ); }; /* End PBXProject section */ @@ -2467,6 +2493,7 @@ 2A9399D31BE2C06800FB075B /* test-uncached-io.c in Sources */, 2A9399D11BE2BFFD00FB075B /* test-throttled-io.c in Sources */, 2A9399CF1BE2BCEA00FB075B /* test-mod-time.c in Sources */, + 09D6B7D71E317ED2003C20DC /* test_disklevel.c in Sources */, 2A9399CD1BE2BC6900FB075B /* test-mmap-mod-time.c in Sources */, 2A9399CA1BE18A5000FB075B /* test-invalid-ranges.m in Sources */, 2A9399C91BE1747900FB075B /* test-map-private.m in Sources */, @@ -2557,6 +2584,21 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 07828B5F1E3FDD2E009D2106 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 86CBF37E183186C300A64A93 /* hfs_metadata */; + targetProxy = 07828B5E1E3FDD2E009D2106 /* PBXContainerItemProxy */; + }; + 07828B611E3FDD3B009D2106 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 07828B591E3FDD25009D2106 /* hfs_libraries */; + targetProxy = 07828B601E3FDD3B009D2106 /* PBXContainerItemProxy */; + }; + 07CA47231DA859CA00138D78 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4D07DCB71538EF3A002B57CB /* fstyp_hfs */; + targetProxy = 07CA47221DA859CA00138D78 /* PBXContainerItemProxy */; + }; 4DBD523F1548A499007AA736 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4DFD95111537402A0039B6BA /* hfs.fs */; @@ -2592,11 +2634,6 @@ target = 4DFD93F31535FF510039B6BA /* fsck_hfs */; targetProxy = 4DBD524A1548A4A7007AA736 /* PBXContainerItemProxy */; }; - 4DBD524D1548A4AA007AA736 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4D07DCB71538EF3A002B57CB /* fstyp_hfs */; - targetProxy = 4DBD524C1548A4AA007AA736 /* PBXContainerItemProxy */; - }; 4DBD524F1548A4C8007AA736 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 4DBD523B1548A488007AA736 /* Common */; @@ -2612,11 +2649,6 @@ target = 4DFD94BC15373C2C0039B6BA /* fsck_makestrings */; targetProxy = 4DBD52521548A4D4007AA736 /* PBXContainerItemProxy */; }; - 8657285C18319A93007F580F /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 86CBF37E183186C300A64A93 /* hfs_metadata */; - targetProxy = 8657285B18319A93007F580F /* PBXContainerItemProxy */; - }; FB48E4BC1BB30CC400523121 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = FB48E49B1BB3070400523121 /* OSX Kernel Framework Headers */; @@ -2736,6 +2768,27 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 07828B5A1E3FDD25009D2106 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 07828B5B1E3FDD25009D2106 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 07828B5C1E3FDD25009D2106 /* Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Coverage; + }; 1DEB928708733DD80010E9CD /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -4654,6 +4707,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 07828B5D1E3FDD25009D2106 /* Build configuration list for PBXAggregateTarget "hfs_libraries" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 07828B5A1E3FDD25009D2106 /* Release */, + 07828B5B1E3FDD25009D2106 /* Debug */, + 07828B5C1E3FDD25009D2106 /* Coverage */, + ); + defaultConfigurationIsVisible = 0; + }; 1DEB928508733DD80010E9CD /* Build configuration list for PBXNativeTarget "hfs.util" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/tests/cases/test-access.c b/tests/cases/test-access.c index de1b2b7..f5e28c6 100644 --- a/tests/cases/test-access.c +++ b/tests/cases/test-access.c @@ -59,6 +59,7 @@ int run_access(__unused test_ctx_t *ctx) assert_fail("access_vector[0] != ENOENT (== %u)!", access_vector[0]); free(path); + assert_no_err (close(fd)); return 0; } diff --git a/tests/cases/test-dateadded.c b/tests/cases/test-dateadded.c index 1e58d1a..6693c49 100644 --- a/tests/cases/test-dateadded.c +++ b/tests/cases/test-dateadded.c @@ -85,6 +85,7 @@ int run_dateadded(__unused test_ctx_t *ctx) assert_no_err(unlink(file3)); assert_no_err(rmdir(dir)); + assert_no_err(close(fd)); return 0; } diff --git a/tests/cases/test-doc-tombstone.c b/tests/cases/test-doc-tombstone.c index 6b1cc6b..acb42a6 100644 --- a/tests/cases/test-doc-tombstone.c +++ b/tests/cases/test-doc-tombstone.c @@ -280,7 +280,6 @@ int run_doc_tombstone(__unused test_ctx_t *ctx) assert_equal(orig_doc_id, attrs.doc_id, "%u"); assert_no_err(rmdir(file)); - + return 0; - } diff --git a/tests/cases/test-fsinfo.c b/tests/cases/test-fsinfo.c index 7d1c7db..56d413d 100644 --- a/tests/cases/test-fsinfo.c +++ b/tests/cases/test-fsinfo.c @@ -249,7 +249,7 @@ static void setup_testvolume() { char *path; int fd; - void *buf = malloc(1 KB); + void *buf; // Create a test folder with MAX_FILES files assert_no_err(systemx("/bin/rm", "-rf", srcdir, NULL)); @@ -261,10 +261,12 @@ static void setup_testvolume() assert_with_errno((fd = open(path, O_RDWR | O_TRUNC | O_CREAT, 0666)) >= 0); free(path); - unsigned buf_size = (1 KB) * i; + unsigned buf_size = (1 KB) * (i + 1); buf = malloc(buf_size); memset(buf, 0x25, buf_size); check_io(write(fd, buf, buf_size), buf_size); + free(buf); + close(fd); } } diff --git a/tests/cases/test-fsync.c b/tests/cases/test-fsync.c index 144b7f1..b8e9440 100644 --- a/tests/cases/test-fsync.c +++ b/tests/cases/test-fsync.c @@ -109,6 +109,7 @@ int run_fsync(__unused test_ctx_t *ctx) free(file); free(sock); free(fifo); + free(buf); return 0; } diff --git a/tests/cases/test-invalid-ranges.m b/tests/cases/test-invalid-ranges.m index 409a961..8e85f5b 100644 --- a/tests/cases/test-invalid-ranges.m +++ b/tests/cases/test-invalid-ranges.m @@ -133,6 +133,8 @@ int run_invalid_ranges(__unused test_ctx_t *ctx) pwrite(fd, "hello", 5, i * ps * 2); } + assert_no_err(munmap(p, o + 2 * ps)); + // OK, that should have created 1024 invalid ranges. Sync the data. p = mmap(NULL, 1024 * ps * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); @@ -147,6 +149,8 @@ int run_invalid_ranges(__unused test_ctx_t *ctx) assert_no_err(unlink(file)); + assert_no_err(munmap(p, 1024 * ps * 2)); + #if !TARGET_OS_EMBEDDED disk_image_t *di2 = disk_image_create(DISK_IMAGE, &(disk_image_opts_t){ .size = 100 * 1024 * 1024 @@ -295,6 +299,7 @@ int run_invalid_ranges(__unused test_ctx_t *ctx) char *fsync_path; asprintf(&fsync_path, "%s/fsync", di->mount_point); + assert_no_err(close(fd)); fd = open(fsync_path, O_CREAT | O_RDWR | O_TRUNC, 0666); assert_with_errno(fd >= 0); @@ -315,13 +320,14 @@ int run_invalid_ranges(__unused test_ctx_t *ctx) // Attach to the disk image again assert(!systemx("/usr/bin/hdiutil", SYSTEMX_QUIET, "attach", DISK_IMAGE, NULL)); + assert_no_err(close(fd)); assert_with_errno((fd = open(path, O_RDWR)) >= 0); // Either the file should be short, or there should be zeroes ssize_t amount = pread(fd, buf, block_size, block_size); assert_with_errno(amount >= 0); - assert(!memcmp(buf, zero, amount)); + assert(!memcmp(buf, zero, (amount > 1000) ? 1000 : amount)); assert_no_err(close(fd)); @@ -346,6 +352,10 @@ int run_invalid_ranges(__unused test_ctx_t *ctx) check_io(pread(fd, buf3, 0x100000, 0x100000), 0x100000); assert(!memcmp(buf2, buf3, 0x100000)); + free(buf3); + free(buf2); + free(buf); + free(zero); assert_no_err(close(fd)); assert_no_err(unlink(file)); diff --git a/tests/cases/test-mmap-mod-time.c b/tests/cases/test-mmap-mod-time.c index bc940a0..9859780 100644 --- a/tests/cases/test-mmap-mod-time.c +++ b/tests/cases/test-mmap-mod-time.c @@ -77,6 +77,7 @@ int run_mmap_mod_time(__unused test_ctx_t *ctx) assert_no_err(unlink(file)); + assert_no_err(close(fd)); free(file); return 0; diff --git a/tests/cases/test-mod-time.c b/tests/cases/test-mod-time.c index 2f55662..d306c70 100644 --- a/tests/cases/test-mod-time.c +++ b/tests/cases/test-mod-time.c @@ -58,7 +58,9 @@ static void run_test(void) assert_no_err(close(fd)); assert_no_err(close(fd2)); + assert_no_err(unlink(file)); + assert_no_err(munmap(p, NBPG)); } int run_mod_time(__unused test_ctx_t *ctx) diff --git a/tests/cases/test-move-data-extents.c b/tests/cases/test-move-data-extents.c index 003bcba..8e4750f 100644 --- a/tests/cases/test-move-data-extents.c +++ b/tests/cases/test-move-data-extents.c @@ -60,7 +60,6 @@ static int make_frag_file(int blocks_per_extent) .l2p_contigbytes = 1024 * 1024, .l2p_devoffset = len - 1, }; - if (fcntl(fd, F_LOG2PHYS_EXT, &l2p)) { if (errno == ERANGE) break; @@ -87,12 +86,13 @@ int run_move_data_extents(__unused test_ctx_t *ctx) { di = disk_image_get(); - int fd = make_frag_file(1); + int frag_fd = make_frag_file(1); + int fd; + int fd2; struct stat sb; - fstat(fd, &sb); + fstat(frag_fd, &sb); - int fd2; asprintf(&file2, "%s/move_data_extents.2", di->mount_point); assert_with_errno((fd2 = open(file2, @@ -100,7 +100,7 @@ int run_move_data_extents(__unused test_ctx_t *ctx) #define F_MOVEDATAEXTENTS 69 - assert_no_err(fcntl(fd, F_MOVEDATAEXTENTS, fd2)); + assert_no_err(fcntl(frag_fd, F_MOVEDATAEXTENTS, fd2)); char buf[4096]; check_io(pread(fd2, buf, 4096, sb.st_size - 4096), 4096); @@ -130,6 +130,7 @@ int run_move_data_extents(__unused test_ctx_t *ctx) pthread_join(thread, NULL); close(fd); + close(frag_fd); /* * Make sure that the extents from move_data_extents.1 got deleted @@ -194,6 +195,10 @@ int run_move_data_extents(__unused test_ctx_t *ctx) assert_no_err(unlink(file2)); + assert_no_err (close(fd)); + assert_no_err (close(fd2)); + assert_no_err (close(fd3)); + free(file1); free(file2); free(file3); diff --git a/tests/cases/test-msync-16k.c b/tests/cases/test-msync-16k.c index 974fd52..555746e 100644 --- a/tests/cases/test-msync-16k.c +++ b/tests/cases/test-msync-16k.c @@ -145,6 +145,8 @@ int run_msync_16k(__unused test_ctx_t *ctx) assert_no_err(unlink(file)); + free(cmp_buf); + free(buf); free(file); return 0; diff --git a/tests/cases/test-renamex.c b/tests/cases/test-renamex.c index 1af045e..4858a7d 100644 --- a/tests/cases/test-renamex.c +++ b/tests/cases/test-renamex.c @@ -56,6 +56,9 @@ int run_renamex_test (__unused test_ctx_t *ctx) { error = unlink(dst_file); assert (error == 0); + assert_no_err(close(src_file_fd)); + assert_no_err(close(dst_file_fd)); + return 0; } diff --git a/tests/cases/test-set-create-time.c b/tests/cases/test-set-create-time.c index 4498cd3..041b5ae 100644 --- a/tests/cases/test-set-create-time.c +++ b/tests/cases/test-set-create-time.c @@ -41,6 +41,7 @@ int run_set_create_time(__unused test_ctx_t *ctx) assert(attrs.cr_time.tv_sec == 2000 && attrs.mod_time.tv_sec == 1000); + assert_no_err (close(fd)); unlink(file); free(file); diff --git a/tests/cases/test-set-protection-class.c b/tests/cases/test-set-protection-class.c index 164de50..8898493 100644 --- a/tests/cases/test-set-protection-class.c +++ b/tests/cases/test-set-protection-class.c @@ -158,9 +158,13 @@ int run_set_protection_class(__unused test_ctx_t *ctx) assert_no_err(close(fd)); if (fd2 != -1) assert_no_err(close(fd2)); + + assert_no_err(munmap(p, size)); } unlink(path); + free(buf); + free(buf2); return 0; } diff --git a/tests/cases/test-sparse-dev.c b/tests/cases/test-sparse-dev.c index 4168330..c33a53f 100644 --- a/tests/cases/test-sparse-dev.c +++ b/tests/cases/test-sparse-dev.c @@ -49,6 +49,9 @@ int run_sparse_dev(__unused test_ctx_t *ctx) assert(sfs.f_bfree * sfs.f_bsize < 64 * 1024 * 1024); + + assert_no_err (close(fd)); + return 0; } diff --git a/tests/cases/test-throttled-io.c b/tests/cases/test-throttled-io.c index 2d38181..2bf0c14 100644 --- a/tests/cases/test-throttled-io.c +++ b/tests/cases/test-throttled-io.c @@ -135,7 +135,6 @@ static void test2_thread(void) } while (!done); assert_no_err (close(fd)); - } static int run_test2(void) @@ -194,6 +193,7 @@ static bool clean_up(void) int run_throttled_io(__unused test_ctx_t *ctx) { + gDI = disk_image_get(); asprintf(&gFile1, "%s/throttled_io.1", gDI->mount_point); @@ -203,11 +203,11 @@ int run_throttled_io(__unused test_ctx_t *ctx) test_cleanup(^ bool { return clean_up(); }); - + int res = run_test1(); if (res) return res; - + res = run_test2(); if (res) return res; diff --git a/tests/cases/test-transcode.m b/tests/cases/test-transcode.m index 5dba386..70ca87a 100644 --- a/tests/cases/test-transcode.m +++ b/tests/cases/test-transcode.m @@ -60,6 +60,9 @@ int run_transcode(__unused test_ctx_t *ctx) assert_no_err(MKBKeyBagRegisterOTABackup(NULL, NULL)); assert_no_err(MKBKeyBagRelease(handle)); + close(fd); + free(key); + return 0; } diff --git a/tests/cases/test-uncached-io.c b/tests/cases/test-uncached-io.c index 5193693..6279227 100644 --- a/tests/cases/test-uncached-io.c +++ b/tests/cases/test-uncached-io.c @@ -186,7 +186,9 @@ int run_uncached_io(__unused test_ctx_t *ctx) } // for (pass...) assert_no_err(unlink(file1)); - + + assert_no_err (close(fd)); + assert_no_err (close(fd2)); free(file1); free(file2); diff --git a/tests/cases/test-unicode-file-names.c b/tests/cases/test-unicode-file-names.c index 9d3e6e3..016fb9f 100644 --- a/tests/cases/test-unicode-file-names.c +++ b/tests/cases/test-unicode-file-names.c @@ -68,7 +68,8 @@ int run_unicode_file_names(__unused test_ctx_t *ctx) assert((fd = open(path3, O_RDONLY)) >= 0); assert_no_err(unlink(path3)); - + assert_no_err (close(fd)); + free(dir); free(path); free(path2); diff --git a/tests/cases/test_disklevel.c b/tests/cases/test_disklevel.c new file mode 100644 index 0000000..eaf7f6d --- /dev/null +++ b/tests/cases/test_disklevel.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include "hfs-tests.h" +#include "test-utils.h" +#include "../core/hfs_fsctl.h" +#include "disk-image.h" + +TEST(disklevel) + +int run_disklevel(__unused test_ctx_t *ctx) +{ + disk_image_t *di = disk_image_get(); + const char *test_hfs_volume = di->mount_point; + uint32_t very_low_disk = 0, low_disk = 0, near_low_disk = 0, desired_disk = 0; + + // Make sure initial values are sane. + assert_no_err(fsctl(test_hfs_volume, HFSIOC_GET_VERY_LOW_DISK, &very_low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, HFSIOC_GET_LOW_DISK, &low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, APFSIOC_GET_NEAR_LOW_DISK, &near_low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, HFSIOC_GET_DESIRED_DISK, &desired_disk, 0)); + assert(very_low_disk > 0); + assert(very_low_disk < low_disk); + assert(low_disk < near_low_disk); + assert(near_low_disk < desired_disk); + + very_low_disk = 1; + low_disk = 2; + near_low_disk = 3; + desired_disk = 4; + // Re-assign the values to new legal values and make sure they are preserved. + assert_no_err(fsctl(test_hfs_volume, HFSIOC_SET_VERY_LOW_DISK, &very_low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, HFSIOC_SET_LOW_DISK, &low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, APFSIOC_SET_NEAR_LOW_DISK, &near_low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, HFSIOC_SET_DESIRED_DISK, &desired_disk, 0)); + + assert_no_err(fsctl(test_hfs_volume, HFSIOC_GET_VERY_LOW_DISK, &very_low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, HFSIOC_GET_LOW_DISK, &low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, APFSIOC_GET_NEAR_LOW_DISK, &near_low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, HFSIOC_GET_DESIRED_DISK, &desired_disk, 0)); + assert_equal(very_low_disk, 1, "%d"); + assert_equal(low_disk, 2, "%d"); + assert_equal(near_low_disk, 3, "%d"); + assert_equal(desired_disk, 4, "%d"); + + // Now, attempt to reassign the levels to illegal values and make sure they don't lose their previous value. + very_low_disk = 4; + low_disk = 1; + near_low_disk = 2; + desired_disk = 0; + assert(fsctl(test_hfs_volume, HFSIOC_SET_VERY_LOW_DISK, &very_low_disk, 0) < 0); + assert(fsctl(test_hfs_volume, HFSIOC_SET_LOW_DISK, &low_disk, 0) < 0); + assert(fsctl(test_hfs_volume, APFSIOC_SET_NEAR_LOW_DISK, &near_low_disk, 0) < 0); + assert(fsctl(test_hfs_volume, HFSIOC_SET_DESIRED_DISK, &desired_disk, 0) < 0); + + assert_no_err(fsctl(test_hfs_volume, HFSIOC_GET_VERY_LOW_DISK, &very_low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, HFSIOC_GET_LOW_DISK, &low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, APFSIOC_GET_NEAR_LOW_DISK, &near_low_disk, 0)); + assert_no_err(fsctl(test_hfs_volume, HFSIOC_GET_DESIRED_DISK, &desired_disk, 0)); + assert_equal(very_low_disk, 1, "%d"); + assert_equal(low_disk, 2, "%d"); + assert_equal(near_low_disk, 3, "%d"); + assert_equal(desired_disk, 4, "%d"); + + return 0; +} diff --git a/tests/disk-image.h b/tests/disk-image.h index cd9d9ae..43a4868 100644 --- a/tests/disk-image.h +++ b/tests/disk-image.h @@ -16,6 +16,9 @@ * One 'shared' disk image is created for any test to use, if it wants. * To use this disk image, call disk_image_get(). To create a 'not-shared' * disk image for use just within your test, call disk_image_create(). + * + * Callers of disk_image_create() and disk_image_get() should not free the pointer they receive, + * as it is freed automatically. */ __BEGIN_DECLS diff --git a/tests/disk-image.m b/tests/disk-image.m index e7bb56c..4940be4 100644 --- a/tests/disk-image.m +++ b/tests/disk-image.m @@ -30,6 +30,7 @@ bool disk_image_cleanup(disk_image_t *di) { pid_t pid; + bool result = false; // We need to be root assert(seteuid(0) == 0); @@ -61,10 +62,15 @@ bool disk_image_cleanup(disk_image_t *di) if (WIFEXITED(status) && !WEXITSTATUS(status) && stat(di->disk, &sb) == -1 && errno == ENOENT) { unlink(di->path); - return true; + result = true; + // We are the last user of di, so free it. + free(di->mount_point); + free(di->disk); + free(di->path); + free(di); } - - return false; + + return result; } void *zalloc(__unused void *opaque, uInt items, uInt size) @@ -196,12 +202,6 @@ disk_image_t *disk_image_create(const char *path, disk_image_opts_t *opts) di->mount_point = strdup(opts->mount_point); - char *chown_args[5] = { "chown", "-R", "mobile:mobile", (char *)di->mount_point, NULL }; - assert_no_err(posix_spawn(&pid, "/usr/sbin/chown", NULL, NULL, chown_args, NULL)); - - assert_with_errno(ignore_eintr(waitpid(pid, &status, 0), -1) == pid); - assert(WIFEXITED(status) && !WEXITSTATUS(status)); - if (strcmp(path, SHARED_PATH)) { // Don't register a cleanup for the shared image test_cleanup(^ bool { return disk_image_cleanup(di); @@ -223,11 +223,21 @@ disk_image_t *disk_image_get(void) di->mount_point = SHARED_MOUNT; di->disk = strdup(sfs.f_mntfromname); di->path = SHARED_PATH; + + // Make sure the di struct is freed when tests are complete. + test_cleanup(^ bool { + free(di->disk); + free(di); + return true; + }); } else { disk_image_opts_t opts = { .mount_point = SHARED_MOUNT }; di = disk_image_create(SHARED_PATH, &opts); + // Per the contract of disk_image_create(), + // di will be freed when disk_image_cleanup() is called, + // so don't free it here. } return di; @@ -241,6 +251,7 @@ bool disk_image_cleanup(disk_image_t *di) = { "hdiutil", "detach", (char *)di->disk, "-force", NULL }; pid_t pid; + bool result = false; posix_spawn_file_actions_t facts; posix_spawn_file_actions_init(&facts); @@ -260,10 +271,16 @@ bool disk_image_cleanup(disk_image_t *di) && stat(di->disk, &sb) == -1 && errno == ENOENT) { if (unlink(di->path) && errno == EACCES && !seteuid(0)) unlink(di->path); - return true; + result = true; + + // We are the last user of di, so free it. + free(di->mount_point); + free(di->disk); + free(di->path); + free(di); } - - return false; + + return result; } disk_image_t *disk_image_create(const char *path, disk_image_opts_t *opts) @@ -454,15 +471,25 @@ disk_image_t *disk_image_get(void) di->mount_point = SHARED_MOUNT; di->disk = strdup(sfs.f_mntfromname); di->path = SHARED_PATH; + + // Make sure the di struct is freed when tests are complete. + test_cleanup(^ bool { + free(di->disk); + free(di); + return true; + }); } else { disk_image_opts_t opts = { .size = 4 GB, .mount_point = SHARED_MOUNT }; di = disk_image_create(SHARED_PATH, &opts); + // Per the contract of disk_image_create(), + // di will be freed when disk_image_cleanup() is called, + // so don't free it here. } return di; } -#endif // TARGET_OS_EMBEDDED \ No newline at end of file +#endif // TARGET_OS_EMBEDDED diff --git a/tests/gen-dmg.sh b/tests/gen-dmg.sh index 0c5c1c9..92d1122 100755 --- a/tests/gen-dmg.sh +++ b/tests/gen-dmg.sh @@ -11,6 +11,6 @@ set -e mkdir -p "$DERIVED_FILE_DIR" env -i xcrun clang "$SRCROOT"/tests/img-to-c.c -lz -o "$DERIVED_FILE_DIR"/img-to-c -"$DERIVED_FILE_DIR"/img-to-c -size 2g -type SPARSE -fs JHFS+ >"$1" +"$DERIVED_FILE_DIR"/img-to-c -size 2g -type SPARSE -fs JHFS+ -uid 501 -gid 501 >"$1" echo "Created $1" diff --git a/tests/hfs-tests.mm b/tests/hfs-tests.mm index 7ece91a..eeb2f82 100644 --- a/tests/hfs-tests.mm +++ b/tests/hfs-tests.mm @@ -228,6 +228,7 @@ int main(int argc, char *argv[]) } if (di) { + // disk_image_cleanup(di) will free di. disk_image_cleanup(di); systemx("/bin/rm", SYSTEMX_QUIET, "-rf", "/tmp/mnt", NULL); } -- 2.45.2