2 // lf_hfs_readwrite_ops.c
5 // Created by Yakov Ben Zaken on 22/03/2018.
8 #include "lf_hfs_readwrite_ops.h"
9 #include "lf_hfs_rangelist.h"
10 #include "lf_hfs_vfsutils.h"
11 #include "lf_hfs_file_extent_mapping.h"
12 #include "lf_hfs_vfsops.h"
13 #include "lf_hfs_cnode.h"
14 #include "lf_hfs_file_mgr_internal.h"
15 #include "lf_hfs_utils.h"
16 #include "lf_hfs_vnops.h"
17 #include "lf_hfs_raw_read_write.h"
21 static int do_hfs_truncate(struct vnode
*vp
, off_t length
, int flags
, int skip
);
24 do_hfs_truncate(struct vnode
*vp
, off_t length
, int flags
, int truncateflags
)
26 register struct cnode
*cp
= VTOC(vp
);
27 struct filefork
*fp
= VTOF(vp
);
30 off_t actualBytesAdded
;
34 struct hfsmount
*hfsmp
;
36 int suppress_times
= (truncateflags
& HFS_TRUNCATE_SKIPTIMES
);
38 blksize
= VTOVCB(vp
)->blockSize
;
39 fileblocks
= fp
->ff_blocks
;
40 filebytes
= (off_t
)fileblocks
* (off_t
)blksize
;
45 /* This should only happen with a corrupt filesystem */
46 if ((off_t
)fp
->ff_size
< 0)
53 * Lengthen the size of the file. We must ensure that the
54 * last byte of the file is allocated. Since the smallest
55 * value of ff_size is 0, length will be at least 1.
57 if (length
> (off_t
)fp
->ff_size
) {
59 * If we don't have enough physical space then
60 * we need to extend the physical size.
62 if (length
> filebytes
) {
63 int eflags
= kEFReserveMask
;
64 u_int32_t blockHint
= 0;
66 /* All or nothing and don't round up to clumpsize. */
67 eflags
|= kEFAllMask
| kEFNoClumpMask
;
69 if (hfs_start_transaction(hfsmp
) != 0) {
74 /* Protect extents b-tree and allocation bitmap */
75 lockflags
= SFL_BITMAP
;
76 if (overflow_extents(fp
))
77 lockflags
|= SFL_EXTENTS
;
78 lockflags
= hfs_systemfile_lock(hfsmp
, lockflags
, HFS_EXCLUSIVE_LOCK
);
81 * Keep growing the file as long as the current EOF is
82 * less than the desired value.
84 while ((length
> filebytes
) && (retval
== E_NONE
)) {
85 bytesToAdd
= length
- filebytes
;
86 retval
= MacToVFSError(ExtendFileC(VTOVCB(vp
),
93 filebytes
= (off_t
)fp
->ff_blocks
* (off_t
)blksize
;
94 if (actualBytesAdded
== 0 && retval
== E_NONE
) {
95 if (length
> filebytes
)
101 hfs_systemfile_unlock(hfsmp
, lockflags
);
105 hfs_volupdate(hfsmp
, VOL_UPDATE
, 0);
108 hfs_end_transaction(hfsmp
);
114 if (ISSET(flags
, IO_NOZEROFILL
))
120 if (!vnode_issystem(vp
) && retval
== E_NONE
) {
121 if (length
> (off_t
)fp
->ff_size
) {
124 /* Extending the file: time to fill out the current last page w. zeroes? */
125 retval
= raw_readwrite_zero_fill_last_block_suffix(vp
);
126 if (retval
) goto Err_Exit
;
129 // Currently disabling the rl_add, sice the
130 // data is being filled with 0's and that a valid content for us
131 // rl_add(fp->ff_size, length - 1, &fp->ff_invalidranges);
132 cp
->c_zftimeout
= (uint32_t)tv
.tv_sec
+ ZFTIMELIMIT
;
135 LFHFS_LOG(LEVEL_ERROR
, "hfs_truncate: invoked on non-UBC object?!");
139 if (suppress_times
== 0) {
140 cp
->c_touch_modtime
= TRUE
;
142 fp
->ff_size
= length
;
144 } else { /* Shorten the size of the file */
146 if ((off_t
)fp
->ff_size
> length
) {
147 /* Any space previously marked as invalid is now irrelevant: */
148 rl_remove(length
, fp
->ff_size
- 1, &fp
->ff_invalidranges
);
152 * Account for any unmapped blocks. Note that the new
153 * file length can still end up with unmapped blocks.
155 if (fp
->ff_unallocblocks
> 0) {
157 u_int32_t loanedBlocks
;
159 hfs_lock_mount(hfsmp
);
160 loanedBlocks
= fp
->ff_unallocblocks
;
161 cp
->c_blocks
-= loanedBlocks
;
162 fp
->ff_blocks
-= loanedBlocks
;
163 fp
->ff_unallocblocks
= 0;
165 hfsmp
->loanedBlocks
-= loanedBlocks
;
167 finalblks
= (uint32_t)((length
+ blksize
- 1) / blksize
);
168 if (finalblks
> fp
->ff_blocks
) {
169 /* calculate required unmapped blocks */
170 loanedBlocks
= finalblks
- fp
->ff_blocks
;
171 hfsmp
->loanedBlocks
+= loanedBlocks
;
173 fp
->ff_unallocblocks
= loanedBlocks
;
174 cp
->c_blocks
+= loanedBlocks
;
175 fp
->ff_blocks
+= loanedBlocks
;
177 hfs_unlock_mount (hfsmp
);
179 if (hfs_start_transaction(hfsmp
) != 0) {
184 if (fp
->ff_unallocblocks
== 0) {
185 /* Protect extents b-tree and allocation bitmap */
186 lockflags
= SFL_BITMAP
;
187 if (overflow_extents(fp
))
188 lockflags
|= SFL_EXTENTS
;
189 lockflags
= hfs_systemfile_lock(hfsmp
, lockflags
, HFS_EXCLUSIVE_LOCK
);
191 retval
= MacToVFSError(TruncateFileC(VTOVCB(vp
), (FCB
*)fp
, length
, 0,
192 FORK_IS_RSRC (fp
), FTOC(fp
)->c_fileid
, false));
194 hfs_systemfile_unlock(hfsmp
, lockflags
);
198 fp
->ff_size
= length
;
201 hfs_volupdate(hfsmp
, VOL_UPDATE
, 0);
204 hfs_end_transaction(hfsmp
);
206 if (retval
) goto Err_Exit
;
209 * Only set update flag if the logical length changes & we aren't
210 * suppressing modtime updates.
212 if (((off_t
)fp
->ff_size
!= length
) && (suppress_times
== 0)) {
213 cp
->c_touch_modtime
= TRUE
;
215 fp
->ff_size
= length
;
218 cp
->c_flag
|= C_MODIFIED
;
219 cp
->c_touch_chgtime
= TRUE
; /* status changed */
220 if (suppress_times
== 0) {
221 cp
->c_touch_modtime
= TRUE
; /* file data was modified */
224 * If we are not suppressing the modtime update, then
225 * update the gen count as well.
227 if (S_ISREG(cp
->c_attr
.ca_mode
) || S_ISLNK (cp
->c_attr
.ca_mode
)) {
228 hfs_incr_gencount(cp
);
232 retval
= hfs_update(vp
, 0);
240 hfs_vnop_blockmap(struct vnop_blockmap_args
*ap
)
242 struct vnode
*vp
= ap
->a_vp
;
245 struct hfsmount
*hfsmp
;
246 size_t bytesContAvail
= ap
->a_size
;
250 struct rl_entry
*invalid_range
;
251 enum rl_overlaptype overlaptype
;
255 /* Do not allow blockmap operation on a directory */
256 if (vnode_isdir(vp
)) {
261 * Check for underlying vnode requests and ensure that logical
262 * to physical mapping is requested.
264 if (ap
->a_bpn
== NULL
)
271 if ( !vnode_issystem(vp
) && !vnode_islnk(vp
) ) {
272 if (cp
->c_lockowner
!= pthread_self()) {
273 hfs_lock(VTOC(vp
), HFS_EXCLUSIVE_LOCK
, HFS_LOCK_ALLOW_NOEXISTS
);
277 // For reads, check the invalid ranges
278 if (ISSET(ap
->a_flags
, VNODE_READ
)) {
279 if (ap
->a_foffset
>= fp
->ff_size
) {
284 overlaptype
= rl_scan(&fp
->ff_invalidranges
, ap
->a_foffset
,
285 ap
->a_foffset
+ (off_t
)bytesContAvail
- 1,
287 switch(overlaptype
) {
288 case RL_MATCHINGOVERLAP
:
289 case RL_OVERLAPCONTAINSRANGE
:
290 case RL_OVERLAPSTARTSBEFORE
:
291 /* There's no valid block for this byte offset */
292 *ap
->a_bpn
= (daddr64_t
)-1;
293 /* There's no point limiting the amount to be returned
294 * if the invalid range that was hit extends all the way
295 * to the EOF (i.e. there's no valid bytes between the
296 * end of this range and the file's EOF):
298 if (((off_t
)fp
->ff_size
> (invalid_range
->rl_end
+ 1)) &&
299 ((size_t)(invalid_range
->rl_end
+ 1 - ap
->a_foffset
) < bytesContAvail
)) {
300 bytesContAvail
= invalid_range
->rl_end
+ 1 - ap
->a_foffset
;
306 case RL_OVERLAPISCONTAINED
:
307 case RL_OVERLAPENDSAFTER
:
308 /* The range of interest hits an invalid block before the end: */
309 if (invalid_range
->rl_start
== ap
->a_foffset
) {
310 /* There's actually no valid information to be had starting here: */
311 *ap
->a_bpn
= (daddr64_t
)-1;
312 if (((off_t
)fp
->ff_size
> (invalid_range
->rl_end
+ 1)) &&
313 ((size_t)(invalid_range
->rl_end
+ 1 - ap
->a_foffset
) < bytesContAvail
)) {
314 bytesContAvail
= invalid_range
->rl_end
+ 1 - ap
->a_foffset
;
321 * Sadly, the lower layers don't like us to
322 * return unaligned ranges, so we skip over
323 * any invalid ranges here that are less than
324 * a page: zeroing of those bits is not our
325 * responsibility (it's dealt with elsewhere).
328 off_t rounded_start
= (((uint64_t)(invalid_range
->rl_start
) + (off_t
)PAGE_MASK
) & ~((off_t
)PAGE_MASK
));
329 if ((off_t
)bytesContAvail
< rounded_start
- ap
->a_foffset
)
331 if (rounded_start
< invalid_range
->rl_end
+ 1) {
332 bytesContAvail
= rounded_start
- ap
->a_foffset
;
335 } while ((invalid_range
= TAILQ_NEXT(invalid_range
,
348 /* Check virtual blocks only when performing write operation */
349 if ((ap
->a_flags
& VNODE_WRITE
) && (fp
->ff_unallocblocks
!= 0)) {
350 if (hfs_start_transaction(hfsmp
) != 0) {
356 syslocks
= SFL_EXTENTS
| SFL_BITMAP
;
358 } else if (overflow_extents(fp
)) {
359 syslocks
= SFL_EXTENTS
;
363 lockflags
= hfs_systemfile_lock(hfsmp
, syslocks
, HFS_EXCLUSIVE_LOCK
);
366 * Check for any delayed allocations.
368 if ((ap
->a_flags
& VNODE_WRITE
) && (fp
->ff_unallocblocks
!= 0)) {
370 u_int32_t loanedBlocks
;
373 // Make sure we have a transaction. It's possible
374 // that we came in and fp->ff_unallocblocks was zero
375 // but during the time we blocked acquiring the extents
376 // btree, ff_unallocblocks became non-zero and so we
377 // will need to start a transaction.
379 if (started_tr
== 0) {
381 hfs_systemfile_unlock(hfsmp
, lockflags
);
388 * Note: ExtendFileC will Release any blocks on loan and
389 * aquire real blocks. So we ask to extend by zero bytes
390 * since ExtendFileC will account for the virtual blocks.
393 loanedBlocks
= fp
->ff_unallocblocks
;
394 retval
= ExtendFileC(hfsmp
, (FCB
*)fp
, 0, 0,
395 kEFAllMask
| kEFNoClumpMask
, &actbytes
);
398 fp
->ff_unallocblocks
= loanedBlocks
;
399 cp
->c_blocks
+= loanedBlocks
;
400 fp
->ff_blocks
+= loanedBlocks
;
402 hfs_lock_mount (hfsmp
);
403 hfsmp
->loanedBlocks
+= loanedBlocks
;
404 hfs_unlock_mount (hfsmp
);
406 hfs_systemfile_unlock(hfsmp
, lockflags
);
407 cp
->c_flag
|= C_MODIFIED
;
409 (void) hfs_update(vp
, 0);
410 (void) hfs_volupdate(hfsmp
, VOL_UPDATE
, 0);
412 hfs_end_transaction(hfsmp
);
419 retval
= MapFileBlockC(hfsmp
, (FCB
*)fp
, bytesContAvail
, ap
->a_foffset
,
420 ap
->a_bpn
, &bytesContAvail
);
422 hfs_systemfile_unlock(hfsmp
, lockflags
);
426 /* On write, always return error because virtual blocks, if any,
427 * should have been allocated in ExtendFileC(). We do not
428 * allocate virtual blocks on read, therefore return error
429 * only if no virtual blocks are allocated. Otherwise we search
430 * rangelist for zero-fills
432 if ((MacToVFSError(retval
) != ERANGE
) ||
433 (ap
->a_flags
& VNODE_WRITE
) ||
434 ((ap
->a_flags
& VNODE_READ
) && (fp
->ff_unallocblocks
== 0))) {
438 /* Validate if the start offset is within logical file size */
439 if (ap
->a_foffset
>= fp
->ff_size
) {
444 * At this point, we have encountered a failure during
445 * MapFileBlockC that resulted in ERANGE, and we are not
446 * servicing a write, and there are borrowed blocks.
448 * However, the cluster layer will not call blockmap for
449 * blocks that are borrowed and in-cache. We have to assume
450 * that because we observed ERANGE being emitted from
451 * MapFileBlockC, this extent range is not valid on-disk. So
452 * we treat this as a mapping that needs to be zero-filled
456 if (fp
->ff_size
- ap
->a_foffset
< (off_t
)bytesContAvail
)
457 bytesContAvail
= fp
->ff_size
- ap
->a_foffset
;
459 *ap
->a_bpn
= (daddr64_t
) -1;
467 if (ISSET(ap
->a_flags
, VNODE_WRITE
)) {
468 struct rl_entry
*r
= TAILQ_FIRST(&fp
->ff_invalidranges
);
470 // See if we might be overlapping invalid ranges...
471 if (r
&& (ap
->a_foffset
+ (off_t
)bytesContAvail
) > r
->rl_start
) {
473 * Mark the file as needing an update if we think the
474 * on-disk EOF has changed.
476 if (ap
->a_foffset
<= r
->rl_start
)
477 SET(cp
->c_flag
, C_MODIFIED
);
480 * This isn't the ideal place to put this. Ideally, we
481 * should do something *after* we have successfully
482 * written to the range, but that's difficult to do
483 * because we cannot take locks in the callback. At
484 * present, the cluster code will call us with VNODE_WRITE
485 * set just before it's about to write the data so we know
486 * that data is about to be written. If we get an I/O
487 * error at this point then chances are the metadata
488 * update to follow will also have an I/O error so the
489 * risk here is small.
491 rl_remove(ap
->a_foffset
, ap
->a_foffset
+ bytesContAvail
- 1,
492 &fp
->ff_invalidranges
);
494 if (!TAILQ_FIRST(&fp
->ff_invalidranges
)) {
495 cp
->c_flag
&= ~C_ZFWANTSYNC
;
502 *ap
->a_run
= bytesContAvail
;
505 *(int *)ap
->a_poff
= 0;
509 hfs_update(vp
, TRUE
);
510 hfs_volupdate(hfsmp
, VOL_UPDATE
, 0);
511 hfs_end_transaction(hfsmp
);
517 return (MacToVFSError(retval
));
521 hfs_prepare_release_storage (struct hfsmount
*hfsmp
, struct vnode
*vp
) {
523 struct filefork
*fp
= VTOF(vp
);
524 struct cnode
*cp
= VTOC(vp
);
526 /* Cannot truncate an HFS directory! */
532 /* This should only happen with a corrupt filesystem */
533 if ((off_t
)fp
->ff_size
< 0)
537 * We cannot just check if fp->ff_size == length (as an optimization)
538 * since there may be extra physical blocks that also need truncation.
541 /* Wipe out any invalid ranges which have yet to be backed by disk */
542 rl_remove(0, fp
->ff_size
- 1, &fp
->ff_invalidranges
);
545 * Account for any unmapped blocks. Since we're deleting the
546 * entire file, we don't have to worry about just shrinking
547 * to a smaller number of borrowed blocks.
549 if (fp
->ff_unallocblocks
> 0)
551 u_int32_t loanedBlocks
;
553 hfs_lock_mount (hfsmp
);
554 loanedBlocks
= fp
->ff_unallocblocks
;
555 cp
->c_blocks
-= loanedBlocks
;
556 fp
->ff_blocks
-= loanedBlocks
;
557 fp
->ff_unallocblocks
= 0;
559 hfsmp
->loanedBlocks
-= loanedBlocks
;
561 hfs_unlock_mount (hfsmp
);
568 hfs_release_storage (struct hfsmount
*hfsmp
, struct filefork
*datafork
, struct filefork
*rsrcfork
, u_int32_t fileid
)
571 int blksize
= hfsmp
->blockSize
;
576 datafork
->ff_size
= 0;
578 u_int32_t fileblocks
= datafork
->ff_blocks
;
579 off_t filebytes
= (off_t
)fileblocks
* (off_t
)blksize
;
581 /* We killed invalid ranges and loaned blocks before we removed the catalog entry */
583 while (filebytes
> 0) {
584 if (filebytes
> HFS_BIGFILE_SIZE
) {
585 filebytes
-= HFS_BIGFILE_SIZE
;
590 /* Start a transaction, and wipe out as many blocks as we can in this iteration */
591 if (hfs_start_transaction(hfsmp
) != 0) {
596 if (datafork
->ff_unallocblocks
== 0)
598 /* Protect extents b-tree and allocation bitmap */
599 int lockflags
= SFL_BITMAP
;
600 if (overflow_extents(datafork
))
601 lockflags
|= SFL_EXTENTS
;
602 lockflags
= hfs_systemfile_lock(hfsmp
, lockflags
, HFS_EXCLUSIVE_LOCK
);
604 error
= MacToVFSError(TruncateFileC(HFSTOVCB(hfsmp
), datafork
, filebytes
, 1, 0, fileid
, false));
606 hfs_systemfile_unlock(hfsmp
, lockflags
);
608 (void) hfs_volupdate(hfsmp
, VOL_UPDATE
, 0);
610 /* Finish the transaction and start over if necessary */
611 hfs_end_transaction(hfsmp
);
620 if (error
== 0 && rsrcfork
)
622 rsrcfork
->ff_size
= 0;
624 u_int32_t fileblocks
= rsrcfork
->ff_blocks
;
625 off_t filebytes
= (off_t
)fileblocks
* (off_t
)blksize
;
627 /* We killed invalid ranges and loaned blocks before we removed the catalog entry */
629 while (filebytes
> 0)
631 if (filebytes
> HFS_BIGFILE_SIZE
)
633 filebytes
-= HFS_BIGFILE_SIZE
;
640 /* Start a transaction, and wipe out as many blocks as we can in this iteration */
641 if (hfs_start_transaction(hfsmp
) != 0)
647 if (rsrcfork
->ff_unallocblocks
== 0)
649 /* Protect extents b-tree and allocation bitmap */
650 int lockflags
= SFL_BITMAP
;
651 if (overflow_extents(rsrcfork
))
652 lockflags
|= SFL_EXTENTS
;
653 lockflags
= hfs_systemfile_lock(hfsmp
, lockflags
, HFS_EXCLUSIVE_LOCK
);
655 error
= MacToVFSError(TruncateFileC(HFSTOVCB(hfsmp
), rsrcfork
, filebytes
, 1, 1, fileid
, false));
657 hfs_systemfile_unlock(hfsmp
, lockflags
);
659 (void) hfs_volupdate(hfsmp
, VOL_UPDATE
, 0);
661 /* Finish the transaction and start over if necessary */
662 hfs_end_transaction(hfsmp
);
675 * Truncate a cnode to at most length size, freeing (or adding) the
679 hfs_truncate(struct vnode
*vp
, off_t length
, int flags
, int truncateflags
)
681 struct filefork
*fp
= VTOF(vp
);
683 u_int32_t fileblocks
;
686 struct cnode
*cp
= VTOC(vp
);
687 hfsmount_t
*hfsmp
= VTOHFS(vp
);
689 /* Cannot truncate an HFS directory! */
690 if (vnode_isdir(vp
)) {
694 blksize
= hfsmp
->blockSize
;
695 fileblocks
= fp
->ff_blocks
;
696 filebytes
= (off_t
)fileblocks
* (off_t
)blksize
;
698 bool caller_has_cnode_lock
= (cp
->c_lockowner
== pthread_self());
700 if (!caller_has_cnode_lock
) {
701 error
= hfs_lock(cp
, HFS_EXCLUSIVE_LOCK
, HFS_LOCK_DEFAULT
);
706 if (vnode_islnk(vp
) && cp
->c_datafork
->ff_symlinkptr
) {
707 hfs_free(cp
->c_datafork
->ff_symlinkptr
);
708 cp
->c_datafork
->ff_symlinkptr
= NULL
;
711 // have to loop truncating or growing files that are
712 // really big because otherwise transactions can get
713 // enormous and consume too many kernel resources.
715 if (length
< filebytes
) {
716 while (filebytes
> length
) {
717 if ((filebytes
- length
) > HFS_BIGFILE_SIZE
) {
718 filebytes
-= HFS_BIGFILE_SIZE
;
722 error
= do_hfs_truncate(vp
, filebytes
, flags
, truncateflags
);
726 } else if (length
> filebytes
) {
727 const bool keep_reserve
= false; //cred && suser(cred, NULL) != 0;
729 if (hfs_freeblks(hfsmp
, keep_reserve
) < howmany(length
- filebytes
, blksize
))
735 while (filebytes
< length
) {
736 if ((length
- filebytes
) > HFS_BIGFILE_SIZE
) {
737 filebytes
+= HFS_BIGFILE_SIZE
;
741 error
= do_hfs_truncate(vp
, filebytes
, flags
, truncateflags
);
746 } else /* Same logical size */ {
748 error
= do_hfs_truncate(vp
, length
, flags
, truncateflags
);
751 if (!caller_has_cnode_lock
)