1 /* Copyright © 2017-2018 Apple Inc. All rights reserved.
3 * lf_hfs_fileops_handler.c
6 * Created by Yakov Ben Zaken on 31/12/2017.
9 #include "lf_hfs_fileops_handler.h"
10 #include "lf_hfs_dirops_handler.h"
12 #include "lf_hfs_utils.h"
13 #include "lf_hfs_vnode.h"
14 #include "lf_hfs_raw_read_write.h"
15 #include "lf_hfs_vnops.h"
16 #include "lf_hfs_xattr.h"
17 #include "lf_hfs_cnode.h"
18 #include "lf_hfs_logger.h"
19 #include "lf_hfs_vfsutils.h"
20 #include "lf_hfs_vfsops.h"
21 #include "lf_hfs_file_extent_mapping.h"
22 #include "lf_hfs_readwrite_ops.h"
23 #include "lf_hfs_file_mgr_internal.h"
26 int LFHFS_Read ( UVFSFileNode psNode
, uint64_t uOffset
, size_t iLength
, void *pvBuf
, size_t *iActuallyRead
)
28 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_Read (psNode %p, uOffset %llu, iLength %lu)\n", psNode
, uOffset
, iLength
);
29 VERIFY_NODE_IS_VALID(psNode
);
31 struct vnode
*vp
= (vnode_t
)psNode
;
36 int took_truncate_lock
= 0;
39 /* Preflight checks */
40 if (!vnode_isreg(vp
)) {
41 /* can only read regular files */
42 return ( vnode_isdir(vp
) ? EISDIR
: EPERM
);
48 /* Protect against a size change. */
49 hfs_lock_truncate(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
50 took_truncate_lock
= 1;
52 filesize
= fp
->ff_size
;
54 * Check the file size. Note that per POSIX spec, we return 0 at
55 * file EOF, so attempting a read at an offset that is too big
56 * should just return 0 on HFS+. Since the return value was initialized
57 * to 0 above, we just jump to exit. HFS Standard has its own behavior.
59 if (uOffset
> filesize
)
61 LFHFS_LOG( LEVEL_ERROR
, "LFHFS_Read: wanted offset is greater then file size\n" );
65 // If we asked to read above the file size, adjust the read size;
66 if ( uOffset
+ iLength
> filesize
)
68 iLength
= filesize
- uOffset
;
71 uint64_t uReadStartCluster
;
72 retval
= raw_readwrite_read( vp
, uOffset
, pvBuf
, iLength
, iActuallyRead
, &uReadStartCluster
);
74 cp
->c_touch_acctime
= TRUE
;
77 if (took_truncate_lock
)
79 hfs_unlock_truncate(cp
, HFS_LOCK_DEFAULT
);
85 int LFHFS_Write ( UVFSFileNode psNode
, uint64_t uOffset
, size_t iLength
, const void *pvBuf
, size_t *iActuallyWrite
)
87 #pragma unused (psNode, uOffset, iLength, pvBuf, iActuallyWrite)
89 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_Write (psNode %p, uOffset %llu, iLength %lu)\n", psNode
, uOffset
, iLength
);
90 VERIFY_NODE_IS_VALID(psNode
);
93 struct vnode
*vp
= (vnode_t
)psNode
;
96 struct hfsmount
*hfsmp
;
100 off_t actualBytesAdded
;
102 int eflags
= kEFReserveMask
;
105 int cnode_locked
= 0;
107 int took_truncate_lock
= 0;
108 size_t iActualLengthToWrite
= iLength
;
110 if (!vnode_isreg(vp
))
112 return ( vnode_isdir(vp
) ? EISDIR
: EPERM
); /* Can only write regular files */
120 * Protect against a size change.
122 hfs_lock_truncate(cp
, HFS_SHARED_LOCK
, HFS_LOCK_DEFAULT
);
123 took_truncate_lock
= 1;
125 origFileSize
= fp
->ff_size
;
126 writelimit
= uOffset
+ iLength
;
129 * We may need an exclusive truncate lock for several reasons, all
130 * of which are because we may be writing to a (portion of a) block
131 * for the first time, and we need to make sure no readers see the
132 * prior, uninitialized contents of the block. The cases are:
134 * 1. We have unallocated (delayed allocation) blocks. We may be
135 * allocating new blocks to the file and writing to them.
136 * (A more precise check would be whether the range we're writing
137 * to contains delayed allocation blocks.)
138 * 2. We need to extend the file. The bytes between the old EOF
139 * and the new EOF are not yet initialized. This is important
140 * even if we're not allocating new blocks to the file. If the
141 * old EOF and new EOF are in the same block, we still need to
142 * protect that range of bytes until they are written for the
145 * If we had a shared lock with the above cases, we need to try to upgrade
146 * to an exclusive lock. If the upgrade fails, we will lose the shared
147 * lock, and will need to take the truncate lock again; the took_truncate_lock
148 * flag will still be set, causing us to try for an exclusive lock next time.
150 if ((cp
->c_truncatelockowner
== HFS_SHARED_OWNER
) &&
151 ((fp
->ff_unallocblocks
!= 0) ||
152 (writelimit
> origFileSize
)))
154 lf_lck_rw_lock_shared_to_exclusive(&cp
->c_truncatelock
);
155 /* Store the owner in the c_truncatelockowner field if we successfully upgrade */
156 cp
->c_truncatelockowner
= pthread_self();
159 if ( (retval
= hfs_lock(VTOC(vp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
))) {
164 filebytes
= blk_to_bytes(fp
->ff_blocks
, hfsmp
->blockSize
);
166 if ((off_t
)uOffset
> filebytes
167 && (blk_to_bytes(hfs_freeblks(hfsmp
, ISSET(eflags
, kEFReserveMask
)) , hfsmp
->blockSize
) < (off_t
)uOffset
- filebytes
))
173 /* Check if we do not need to extend the file */
174 if (writelimit
<= filebytes
) {
178 bytesToAdd
= writelimit
- filebytes
;
179 if (hfs_start_transaction(hfsmp
) != 0) {
184 while (writelimit
> filebytes
)
186 bytesToAdd
= writelimit
- filebytes
;
188 /* Protect extents b-tree and allocation bitmap */
189 lockflags
= SFL_BITMAP
;
190 if (overflow_extents(fp
))
191 lockflags
|= SFL_EXTENTS
;
192 lockflags
= hfs_systemfile_lock(hfsmp
, lockflags
, HFS_EXCLUSIVE_LOCK
);
194 retval
= MacToVFSError(ExtendFileC (hfsmp
, (FCB
*)fp
, bytesToAdd
,
195 0, eflags
, &actualBytesAdded
));
197 hfs_systemfile_unlock(hfsmp
, lockflags
);
199 if ((actualBytesAdded
== 0) && (retval
== E_NONE
))
201 if (retval
!= E_NONE
)
203 filebytes
= (off_t
)fp
->ff_blocks
* (off_t
)hfsmp
->blockSize
;
206 (void) hfs_update(vp
, 0);
207 (void) hfs_volupdate(hfsmp
, VOL_UPDATE
, 0);
208 (void) hfs_end_transaction(hfsmp
);
211 * If we didn't grow the file enough try a partial write.
212 * POSIX expects this behavior.
214 if ((retval
== ENOSPC
) && (filebytes
> (off_t
)uOffset
)) {
216 iActualLengthToWrite
-= bytesToAdd
;
217 writelimit
= filebytes
;
220 if (retval
== E_NONE
) {
223 if (writelimit
> fp
->ff_size
) {
224 filesize
= writelimit
;
226 rl_add(fp
->ff_size
, writelimit
- 1 , &fp
->ff_invalidranges
);
228 cp
->c_zftimeout
= (uint32_t)(tv
.tv_sec
+ ZFTIMELIMIT
);
230 filesize
= fp
->ff_size
;
233 // Fill last cluster with zeros.
234 if ( origFileSize
< (off_t
)uOffset
)
236 raw_readwrite_zero_fill_last_block_suffix(vp
);
239 if (filesize
> fp
->ff_size
) {
240 fp
->ff_new_size
= filesize
;
243 uint64_t uActuallyWritten
;
244 retval
= raw_readwrite_write(vp
, uOffset
, (void*)pvBuf
, iActualLengthToWrite
, &uActuallyWritten
);
245 *iActuallyWrite
= uActuallyWritten
;
247 fp
->ff_new_size
= 0; /* no longer extending; use ff_size */
251 if (filesize
> origFileSize
) {
252 fp
->ff_size
= filesize
;
254 fp
->ff_new_size
= 0; /* ff_size now has the correct size */
257 hfs_flush(hfsmp
, HFS_FLUSH_CACHE
);
262 hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_ALLOW_NOEXISTS
);
266 if (*iActuallyWrite
> 0)
268 cp
->c_flag
|= C_MODIFIED
;
269 cp
->c_touch_chgtime
= TRUE
;
270 cp
->c_touch_modtime
= TRUE
;
271 hfs_incr_gencount(cp
);
275 (void)hfs_truncate(vp
, origFileSize
, IO_SYNC
, 0);
277 else if (*iActuallyWrite
> 0)
279 retval
= hfs_update(vp
, 0);
282 /* Updating vcbWrCnt doesn't need to be atomic. */
286 if (retval
&& took_truncate_lock
287 && cp
->c_truncatelockowner
== pthread_self()) {
289 rl_remove(fp
->ff_size
, RL_INFINITY
, &fp
->ff_invalidranges
);
296 if (took_truncate_lock
) {
297 hfs_unlock_truncate(cp
, HFS_LOCK_DEFAULT
);
303 int LFHFS_Create ( UVFSFileNode psNode
, const char *pcName
, const UVFSFileAttributes
*psAttr
, UVFSFileNode
*ppsOutNode
)
305 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_Create\n");
306 VERIFY_NODE_IS_VALID(psNode
);
309 vnode_t psParentVnode
= (vnode_t
)psNode
;
311 if (!vnode_isdir(psParentVnode
))
317 //@param cnp Name information for new directory.
318 struct componentname sNewFileComponentName
= {0};
319 sNewFileComponentName
.cn_nameptr
= (char*) pcName
;
320 sNewFileComponentName
.cn_namelen
= (int) strlen(pcName
);
322 iError
= hfs_vnop_create(psParentVnode
, (vnode_t
*)ppsOutNode
, &sNewFileComponentName
, (UVFSFileAttributes
*) psAttr
);
326 //Since hfs_vnop_create doesn’t allocate clusters for new files.
327 //In case of non-zero given size, we need to call setAttr, after successfully creating the file.
328 if ((psAttr
->fa_validmask
& UVFS_FA_VALID_SIZE
) != 0 && psAttr
->fa_size
!= 0)
330 iError
= hfs_vnop_setattr( (vnode_t
) *ppsOutNode
, psAttr
);
331 //In case of a failure in setAttr, need to remove the created file
334 DIROPS_RemoveInternal(psParentVnode
, pcName
);
335 LFHFS_Reclaim((vnode_t
) *ppsOutNode
);
343 int LFHFS_GetAttr ( UVFSFileNode psNode
, UVFSFileAttributes
*psOutAttr
)
345 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_GetAttr\n");
346 VERIFY_NODE_IS_VALID(psNode
);
349 vnode_t vp
= (vnode_t
)psNode
;
351 hfs_lock(VTOC(vp
),0,0);
352 vnode_GetAttrInternal(vp
, psOutAttr
);
353 hfs_unlock(VTOC(vp
));
358 int LFHFS_SetAttr ( UVFSFileNode psNode
, const UVFSFileAttributes
*psSetAttr
, UVFSFileAttributes
*psOutAttr
)
360 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_SetAttr\n");
361 VERIFY_NODE_IS_VALID(psNode
);
363 vnode_t psVnode
= (vnode_t
)psNode
;
365 int iErr
= hfs_vnop_setattr( psVnode
, psSetAttr
);
371 iErr
= LFHFS_GetAttr( psNode
, psOutAttr
);
377 int LFHFS_Reclaim ( UVFSFileNode psNode
)
379 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_Reclaim\n");
382 vnode_t vp
= (vnode_t
)psNode
;
384 if ( psNode
!= NULL
)
386 VERIFY_NODE_IS_VALID_FOR_RECLAIM(psNode
);
388 iErr
= hfs_vnop_reclaim(vp
);
395 int LFHFS_ReadLink ( UVFSFileNode psNode
, void *pvOutBuf
, size_t iBufSize
, size_t *iActuallyRead
, UVFSFileAttributes
*psOutAttr
)
397 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_ReadLink\n");
398 VERIFY_NODE_IS_VALID(psNode
);
402 vnode_t vp
= (vnode_t
)psNode
;
404 iErr
= hfs_vnop_readlink(vp
, pvOutBuf
, iBufSize
, iActuallyRead
);
410 iErr
= LFHFS_GetAttr( psNode
, psOutAttr
);
420 int LFHFS_SymLink ( UVFSFileNode psNode
, const char *pcName
, const char *psContent
, const UVFSFileAttributes
*psAttr
, UVFSFileNode
*ppsOutNode
)
422 VERIFY_NODE_IS_VALID(psNode
);
423 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_SymLink\n");
426 vnode_t psParentVnode
= (vnode_t
)psNode
;
428 if (!vnode_isdir(psParentVnode
))
434 vnode_t psSymLinkVnode
= {0};
435 struct componentname sCompName
= {0};
436 sCompName
.cn_nameiop
= CREATE
;
437 sCompName
.cn_flags
= ISLASTCN
;
438 sCompName
.cn_pnbuf
= (char *)pcName
;
439 sCompName
.cn_pnlen
= (int)strlen(pcName
);
440 sCompName
.cn_nameptr
= (char *)pcName
;
441 sCompName
.cn_namelen
= (int)strlen(pcName
);
442 sCompName
.cn_hash
= 0;
443 sCompName
.cn_consume
= (int)strlen(pcName
);
445 iErr
= hfs_vnop_symlink( psParentVnode
, &psSymLinkVnode
, &sCompName
, (char*)psContent
, (UVFSFileAttributes
*)psAttr
);
447 *ppsOutNode
= (UVFSFileNode
)psSymLinkVnode
;
453 int LFHFS_Rename (UVFSFileNode psFromDirNode
, UVFSFileNode psFromNode
, const char *pcFromName
, UVFSFileNode psToDirNode
, UVFSFileNode psToNode
, const char *pcToName
, uint32_t flags __unused
)
455 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_Rename\n");
457 VERIFY_NODE_IS_VALID(psFromDirNode
);
458 VERIFY_NODE_IS_VALID(psToDirNode
);
459 if ( psFromNode
!= NULL
)
461 VERIFY_NODE_IS_VALID(psFromNode
);
463 if ( psToNode
!= NULL
)
465 VERIFY_NODE_IS_VALID(psToNode
);
469 vnode_t psFromParentVnode
= (vnode_t
)psFromDirNode
;
470 vnode_t psToParentVnode
= (vnode_t
)psToDirNode
;
472 if (!vnode_isdir(psFromParentVnode
) || !vnode_isdir(psToParentVnode
))
478 UVFSFileNode psFromFileNode
= {0};
479 UVFSFileNode psToFileNode
= {0};
480 bool bGotFromNode
= (psFromNode
!= NULL
);
481 bool bGotToNode
= (psToNode
!= NULL
);
483 vnode_t psFromVnode
= (vnode_t
) psFromNode
;
487 iErr
= DIROPS_LookupInternal( psFromDirNode
, pcFromName
, &psFromFileNode
);
492 psFromVnode
= (vnode_t
)psFromFileNode
;
495 vnode_t psToVnode
= psToNode
;
498 iErr
= DIROPS_LookupInternal( psToDirNode
, pcToName
, &psToFileNode
);
501 psToVnode
= (vnode_t
)psToFileNode
;
503 else if (iErr
!= ENOENT
)
509 // If only one of the vnodes is of type directory,
510 // we can't allow the rename
513 if (vnode_isdir(psFromVnode
) && !vnode_isdir(psToVnode
))
519 if (!vnode_isdir(psFromVnode
) && vnode_isdir(psToVnode
))
525 struct componentname sFromCompName
= {0};
526 sFromCompName
.cn_nameiop
= RENAME
;
527 sFromCompName
.cn_flags
= ISLASTCN
;
528 sFromCompName
.cn_pnbuf
= (char *)pcFromName
;
529 sFromCompName
.cn_pnlen
= (int)strlen(pcFromName
);
530 sFromCompName
.cn_nameptr
= (char *)pcFromName
;
531 sFromCompName
.cn_namelen
= (int)strlen(pcFromName
);
532 sFromCompName
.cn_hash
= 0;
533 sFromCompName
.cn_consume
= (int)strlen(pcFromName
);
535 struct componentname sToCompName
= {0};
536 sToCompName
.cn_nameiop
= RENAME
;
537 sToCompName
.cn_flags
= ISLASTCN
;
538 sToCompName
.cn_pnbuf
= (char *)pcToName
;
539 sToCompName
.cn_pnlen
= (int)strlen(pcToName
);
540 sToCompName
.cn_nameptr
= (char *)pcToName
;
541 sToCompName
.cn_namelen
= (int)strlen(pcToName
);
542 sToCompName
.cn_hash
= 0;
543 sToCompName
.cn_consume
= (int)strlen(pcToName
);
545 iErr
= hfs_vnop_renamex(psFromParentVnode
, psFromVnode
, &sFromCompName
, psToParentVnode
, psToVnode
, &sToCompName
);
548 LFHFS_Reclaim(psFromVnode
);
549 if (!bGotToNode
&& psToVnode
)
550 LFHFS_Reclaim(psToVnode
);
556 int LFHFS_Link ( UVFSFileNode psFromNode
, UVFSFileNode psToDirNode
, const char *pcToName
, UVFSFileAttributes
* psOutFileAttrs
, UVFSFileAttributes
* psOutDirAttrs
)
558 VERIFY_NODE_IS_VALID(psFromNode
);
559 VERIFY_NODE_IS_VALID(psToDirNode
);
561 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_Link\n");
564 vnode_t psFromVnode
= (vnode_t
)psFromNode
;
565 vnode_t psToDirVnode
= (vnode_t
)psToDirNode
;
567 if (!vnode_isdir(psToDirVnode
))
572 /* Preflight checks */
573 if (!vnode_isreg(psFromVnode
)) {
574 /* can only create hardlinks for regular files */
575 return ( vnode_isdir(psFromVnode
) ? EISDIR
: EPERM
);
578 struct componentname sToCompName
= {0};
579 sToCompName
.cn_nameiop
= CREATE
;
580 sToCompName
.cn_flags
= ISLASTCN
;
581 sToCompName
.cn_pnbuf
= (char *)pcToName
;
582 sToCompName
.cn_pnlen
= (int)strlen(pcToName
);
583 sToCompName
.cn_nameptr
= (char *)pcToName
;
584 sToCompName
.cn_namelen
= (int)strlen(pcToName
);
585 sToCompName
.cn_hash
= 0;
586 sToCompName
.cn_consume
= (int)strlen(pcToName
);
588 iErr
= hfs_vnop_link(psFromVnode
, psToDirVnode
, &sToCompName
);
594 iErr
= LFHFS_GetAttr( psFromNode
, psOutFileAttrs
);
597 LFHFS_LOG(LEVEL_ERROR
, "LFHFS_Link: Failed in getting FromNode Attr\n");
601 iErr
= LFHFS_GetAttr( psToDirNode
, psOutDirAttrs
);
604 LFHFS_LOG(LEVEL_ERROR
, "LFHFS_Link: Failed in getting ToDir Attr\n");
612 int LFHFS_GetXAttr ( UVFSFileNode psNode
, const char *pcAttr
, void *pvOutBuf
, size_t iBufSize
, size_t *iActualSize
)
616 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_GetXAttr\n");
618 VERIFY_NODE_IS_VALID(psNode
);
620 iErr
= hfs_vnop_getxattr((vnode_t
)psNode
, pcAttr
, pvOutBuf
, iBufSize
, iActualSize
);
625 int LFHFS_SetXAttr ( UVFSFileNode psNode
, const char *pcAttr
, const void *pvInBuf
, size_t iBufSize
, UVFSXattrHow How
)
629 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_SetXAttr\n");
631 VERIFY_NODE_IS_VALID(psNode
);
633 if (How
== UVFSXattrHowRemove
)
635 iErr
= hfs_vnop_removexattr((vnode_t
)psNode
, pcAttr
);
639 iErr
= hfs_vnop_setxattr((vnode_t
)psNode
, pcAttr
, pvInBuf
, iBufSize
, How
);
645 int LFHFS_ListXAttr ( UVFSFileNode psNode
, void *pvOutBuf
, size_t iBufSize
, size_t *iActualSize
)
649 LFHFS_LOG(LEVEL_DEBUG
, "LFHFS_ListXAttr\n");
651 VERIFY_NODE_IS_VALID(psNode
);
653 iErr
= hfs_vnop_listxattr((vnode_t
)psNode
, pvOutBuf
, iBufSize
, iActualSize
);