lf_hfs_readwrite_ops.c [plain text]
#include "lf_hfs_readwrite_ops.h"
#include "lf_hfs_rangelist.h"
#include "lf_hfs_vfsutils.h"
#include "lf_hfs_file_extent_mapping.h"
#include "lf_hfs_vfsops.h"
#include "lf_hfs_cnode.h"
#include "lf_hfs_file_mgr_internal.h"
#include "lf_hfs_utils.h"
#include "lf_hfs_vnops.h"
#include "lf_hfs_raw_read_write.h"
#include <assert.h>
static int do_hfs_truncate(struct vnode *vp, off_t length, int flags, int skip);
static int
do_hfs_truncate(struct vnode *vp, off_t length, int flags, int truncateflags)
{
register struct cnode *cp = VTOC(vp);
struct filefork *fp = VTOF(vp);
int retval;
off_t bytesToAdd;
off_t actualBytesAdded;
off_t filebytes;
u_int32_t fileblocks;
int blksize;
struct hfsmount *hfsmp;
int lockflags;
int suppress_times = (truncateflags & HFS_TRUNCATE_SKIPTIMES);
blksize = VTOVCB(vp)->blockSize;
fileblocks = fp->ff_blocks;
filebytes = (off_t)fileblocks * (off_t)blksize;
if (length < 0)
return (EINVAL);
if ((off_t)fp->ff_size < 0)
return (EINVAL);
hfsmp = VTOHFS(vp);
retval = E_NONE;
if (length > (off_t)fp->ff_size) {
if (length > filebytes) {
int eflags = kEFReserveMask;
u_int32_t blockHint = 0;
eflags |= kEFAllMask | kEFNoClumpMask;
if (hfs_start_transaction(hfsmp) != 0) {
retval = EINVAL;
goto Err_Exit;
}
lockflags = SFL_BITMAP;
if (overflow_extents(fp))
lockflags |= SFL_EXTENTS;
lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
while ((length > filebytes) && (retval == E_NONE)) {
bytesToAdd = length - filebytes;
retval = MacToVFSError(ExtendFileC(VTOVCB(vp),
(FCB*)fp,
bytesToAdd,
blockHint,
eflags,
&actualBytesAdded));
filebytes = (off_t)fp->ff_blocks * (off_t)blksize;
if (actualBytesAdded == 0 && retval == E_NONE) {
if (length > filebytes)
length = filebytes;
break;
}
}
hfs_systemfile_unlock(hfsmp, lockflags);
if (hfsmp->jnl) {
hfs_update(vp, 0);
hfs_volupdate(hfsmp, VOL_UPDATE, 0);
}
hfs_end_transaction(hfsmp);
if (retval)
goto Err_Exit;
}
if (ISSET(flags, IO_NOZEROFILL))
{
}
else
{
if (!vnode_issystem(vp) && retval == E_NONE) {
if (length > (off_t)fp->ff_size) {
struct timeval tv;
retval = raw_readwrite_zero_fill_last_block_suffix(vp);
if (retval) goto Err_Exit;
microuptime(&tv);
cp->c_zftimeout = (uint32_t)tv.tv_sec + ZFTIMELIMIT;
}
}else{
LFHFS_LOG(LEVEL_ERROR, "hfs_truncate: invoked on non-UBC object?!");
hfs_assert(0);
}
}
if (suppress_times == 0) {
cp->c_touch_modtime = TRUE;
}
fp->ff_size = length;
} else {
if ((off_t)fp->ff_size > length) {
rl_remove(length, fp->ff_size - 1, &fp->ff_invalidranges);
}
if (fp->ff_unallocblocks > 0) {
u_int32_t finalblks;
u_int32_t loanedBlocks;
hfs_lock_mount(hfsmp);
loanedBlocks = fp->ff_unallocblocks;
cp->c_blocks -= loanedBlocks;
fp->ff_blocks -= loanedBlocks;
fp->ff_unallocblocks = 0;
hfsmp->loanedBlocks -= loanedBlocks;
finalblks = (uint32_t)((length + blksize - 1) / blksize);
if (finalblks > fp->ff_blocks) {
loanedBlocks = finalblks - fp->ff_blocks;
hfsmp->loanedBlocks += loanedBlocks;
fp->ff_unallocblocks = loanedBlocks;
cp->c_blocks += loanedBlocks;
fp->ff_blocks += loanedBlocks;
}
hfs_unlock_mount (hfsmp);
}
if (hfs_start_transaction(hfsmp) != 0) {
retval = EINVAL;
goto Err_Exit;
}
if (fp->ff_unallocblocks == 0) {
lockflags = SFL_BITMAP;
if (overflow_extents(fp))
lockflags |= SFL_EXTENTS;
lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
retval = MacToVFSError(TruncateFileC(VTOVCB(vp), (FCB*)fp, length, 0,
FORK_IS_RSRC (fp), FTOC(fp)->c_fileid, false));
hfs_systemfile_unlock(hfsmp, lockflags);
}
if (hfsmp->jnl) {
if (retval == 0) {
fp->ff_size = length;
}
hfs_update(vp, 0);
hfs_volupdate(hfsmp, VOL_UPDATE, 0);
}
hfs_end_transaction(hfsmp);
if (retval) goto Err_Exit;
if (((off_t)fp->ff_size != length) && (suppress_times == 0)) {
cp->c_touch_modtime = TRUE;
}
fp->ff_size = length;
}
cp->c_flag |= C_MODIFIED;
cp->c_touch_chgtime = TRUE;
if (suppress_times == 0) {
cp->c_touch_modtime = TRUE;
if (S_ISREG(cp->c_attr.ca_mode) || S_ISLNK (cp->c_attr.ca_mode)) {
hfs_incr_gencount(cp);
}
}
retval = hfs_update(vp, 0);
Err_Exit:
return (retval);
}
int
hfs_vnop_blockmap(struct vnop_blockmap_args *ap)
{
struct vnode *vp = ap->a_vp;
struct cnode *cp;
struct filefork *fp;
struct hfsmount *hfsmp;
size_t bytesContAvail = ap->a_size;
int retval = E_NONE;
int syslocks = 0;
int lockflags = 0;
struct rl_entry *invalid_range;
enum rl_overlaptype overlaptype;
int started_tr = 0;
int tooklock = 0;
if (vnode_isdir(vp)) {
return (ENOTSUP);
}
if (ap->a_bpn == NULL)
return (0);
hfsmp = VTOHFS(vp);
cp = VTOC(vp);
fp = VTOF(vp);
if ( !vnode_issystem(vp) && !vnode_islnk(vp) ) {
if (cp->c_lockowner != pthread_self()) {
hfs_lock(VTOC(vp), HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS);
tooklock = 1;
}
if (ISSET(ap->a_flags, VNODE_READ)) {
if (ap->a_foffset >= fp->ff_size) {
retval = ERANGE;
goto exit;
}
overlaptype = rl_scan(&fp->ff_invalidranges, ap->a_foffset,
ap->a_foffset + (off_t)bytesContAvail - 1,
&invalid_range);
switch(overlaptype) {
case RL_MATCHINGOVERLAP:
case RL_OVERLAPCONTAINSRANGE:
case RL_OVERLAPSTARTSBEFORE:
*ap->a_bpn = (daddr64_t)-1;
if (((off_t)fp->ff_size > (invalid_range->rl_end + 1)) &&
((size_t)(invalid_range->rl_end + 1 - ap->a_foffset) < bytesContAvail)) {
bytesContAvail = invalid_range->rl_end + 1 - ap->a_foffset;
}
retval = 0;
goto exit;
case RL_OVERLAPISCONTAINED:
case RL_OVERLAPENDSAFTER:
if (invalid_range->rl_start == ap->a_foffset) {
*ap->a_bpn = (daddr64_t)-1;
if (((off_t)fp->ff_size > (invalid_range->rl_end + 1)) &&
((size_t)(invalid_range->rl_end + 1 - ap->a_foffset) < bytesContAvail)) {
bytesContAvail = invalid_range->rl_end + 1 - ap->a_foffset;
}
retval = 0;
goto exit;
} else {
do {
off_t rounded_start = (((uint64_t)(invalid_range->rl_start) + (off_t)PAGE_MASK) & ~((off_t)PAGE_MASK));
if ((off_t)bytesContAvail < rounded_start - ap->a_foffset)
break;
if (rounded_start < invalid_range->rl_end + 1) {
bytesContAvail = rounded_start - ap->a_foffset;
break;
}
} while ((invalid_range = TAILQ_NEXT(invalid_range,
rl_link)));
}
break;
case RL_NOOVERLAP:
break;
} }
}
retry:
if ((ap->a_flags & VNODE_WRITE) && (fp->ff_unallocblocks != 0)) {
if (hfs_start_transaction(hfsmp) != 0) {
retval = EINVAL;
goto exit;
} else {
started_tr = 1;
}
syslocks = SFL_EXTENTS | SFL_BITMAP;
} else if (overflow_extents(fp)) {
syslocks = SFL_EXTENTS;
}
if (syslocks)
lockflags = hfs_systemfile_lock(hfsmp, syslocks, HFS_EXCLUSIVE_LOCK);
if ((ap->a_flags & VNODE_WRITE) && (fp->ff_unallocblocks != 0)) {
int64_t actbytes;
u_int32_t loanedBlocks;
if (started_tr == 0) {
if (syslocks) {
hfs_systemfile_unlock(hfsmp, lockflags);
syslocks = 0;
}
goto retry;
}
loanedBlocks = fp->ff_unallocblocks;
retval = ExtendFileC(hfsmp, (FCB*)fp, 0, 0,
kEFAllMask | kEFNoClumpMask, &actbytes);
if (retval) {
fp->ff_unallocblocks = loanedBlocks;
cp->c_blocks += loanedBlocks;
fp->ff_blocks += loanedBlocks;
hfs_lock_mount (hfsmp);
hfsmp->loanedBlocks += loanedBlocks;
hfs_unlock_mount (hfsmp);
hfs_systemfile_unlock(hfsmp, lockflags);
cp->c_flag |= C_MODIFIED;
if (started_tr) {
(void) hfs_update(vp, 0);
(void) hfs_volupdate(hfsmp, VOL_UPDATE, 0);
hfs_end_transaction(hfsmp);
started_tr = 0;
}
goto exit;
}
}
retval = MapFileBlockC(hfsmp, (FCB *)fp, bytesContAvail, ap->a_foffset,
ap->a_bpn, &bytesContAvail);
if (syslocks) {
hfs_systemfile_unlock(hfsmp, lockflags);
}
if (retval) {
if ((MacToVFSError(retval) != ERANGE) ||
(ap->a_flags & VNODE_WRITE) ||
((ap->a_flags & VNODE_READ) && (fp->ff_unallocblocks == 0))) {
goto exit;
}
if (ap->a_foffset >= fp->ff_size) {
goto exit;
}
if (fp->ff_size - ap->a_foffset < (off_t)bytesContAvail)
bytesContAvail = fp->ff_size - ap->a_foffset;
*ap->a_bpn = (daddr64_t) -1;
retval = 0;
goto exit;
}
exit:
if (retval == 0) {
if (ISSET(ap->a_flags, VNODE_WRITE)) {
struct rl_entry *r = TAILQ_FIRST(&fp->ff_invalidranges);
if (r && (ap->a_foffset + (off_t)bytesContAvail) > r->rl_start) {
if (ap->a_foffset <= r->rl_start)
SET(cp->c_flag, C_MODIFIED);
rl_remove(ap->a_foffset, ap->a_foffset + bytesContAvail - 1,
&fp->ff_invalidranges);
if (!TAILQ_FIRST(&fp->ff_invalidranges)) {
cp->c_flag &= ~C_ZFWANTSYNC;
cp->c_zftimeout = 0;
}
}
}
if (ap->a_run)
*ap->a_run = bytesContAvail;
if (ap->a_poff)
*(int *)ap->a_poff = 0;
}
if (started_tr) {
hfs_update(vp, TRUE);
hfs_volupdate(hfsmp, VOL_UPDATE, 0);
hfs_end_transaction(hfsmp);
}
if (tooklock)
hfs_unlock(cp);
return (MacToVFSError(retval));
}
int
hfs_prepare_release_storage (struct hfsmount *hfsmp, struct vnode *vp) {
struct filefork *fp = VTOF(vp);
struct cnode *cp = VTOC(vp);
if (IS_DIR(vp))
{
return (EISDIR);
}
if ((off_t)fp->ff_size < 0)
return (EINVAL);
rl_remove(0, fp->ff_size - 1, &fp->ff_invalidranges);
if (fp->ff_unallocblocks > 0)
{
u_int32_t loanedBlocks;
hfs_lock_mount (hfsmp);
loanedBlocks = fp->ff_unallocblocks;
cp->c_blocks -= loanedBlocks;
fp->ff_blocks -= loanedBlocks;
fp->ff_unallocblocks = 0;
hfsmp->loanedBlocks -= loanedBlocks;
hfs_unlock_mount (hfsmp);
}
return 0;
}
int
hfs_release_storage (struct hfsmount *hfsmp, struct filefork *datafork, struct filefork *rsrcfork, u_int32_t fileid)
{
int error = 0;
int blksize = hfsmp->blockSize;
if (datafork)
{
datafork->ff_size = 0;
u_int32_t fileblocks = datafork->ff_blocks;
off_t filebytes = (off_t)fileblocks * (off_t)blksize;
while (filebytes > 0) {
if (filebytes > HFS_BIGFILE_SIZE) {
filebytes -= HFS_BIGFILE_SIZE;
} else {
filebytes = 0;
}
if (hfs_start_transaction(hfsmp) != 0) {
error = EINVAL;
break;
}
if (datafork->ff_unallocblocks == 0)
{
int lockflags = SFL_BITMAP;
if (overflow_extents(datafork))
lockflags |= SFL_EXTENTS;
lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
error = MacToVFSError(TruncateFileC(HFSTOVCB(hfsmp), datafork, filebytes, 1, 0, fileid, false));
hfs_systemfile_unlock(hfsmp, lockflags);
}
(void) hfs_volupdate(hfsmp, VOL_UPDATE, 0);
hfs_end_transaction(hfsmp);
if (error) {
break;
}
}
}
if (error == 0 && rsrcfork)
{
rsrcfork->ff_size = 0;
u_int32_t fileblocks = rsrcfork->ff_blocks;
off_t filebytes = (off_t)fileblocks * (off_t)blksize;
while (filebytes > 0)
{
if (filebytes > HFS_BIGFILE_SIZE)
{
filebytes -= HFS_BIGFILE_SIZE;
}
else
{
filebytes = 0;
}
if (hfs_start_transaction(hfsmp) != 0)
{
error = EINVAL;
break;
}
if (rsrcfork->ff_unallocblocks == 0)
{
int lockflags = SFL_BITMAP;
if (overflow_extents(rsrcfork))
lockflags |= SFL_EXTENTS;
lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
error = MacToVFSError(TruncateFileC(HFSTOVCB(hfsmp), rsrcfork, filebytes, 1, 1, fileid, false));
hfs_systemfile_unlock(hfsmp, lockflags);
}
(void) hfs_volupdate(hfsmp, VOL_UPDATE, 0);
hfs_end_transaction(hfsmp);
if (error)
{
break;
}
}
}
return error;
}
int
hfs_truncate(struct vnode *vp, off_t length, int flags, int truncateflags)
{
struct filefork *fp = VTOF(vp);
off_t filebytes;
u_int32_t fileblocks;
int blksize;
errno_t error = 0;
struct cnode *cp = VTOC(vp);
hfsmount_t *hfsmp = VTOHFS(vp);
if (vnode_isdir(vp)) {
return (EISDIR);
}
blksize = hfsmp->blockSize;
fileblocks = fp->ff_blocks;
filebytes = (off_t)fileblocks * (off_t)blksize;
bool caller_has_cnode_lock = (cp->c_lockowner == pthread_self());
if (!caller_has_cnode_lock) {
error = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
if (error)
return error;
}
if (vnode_islnk(vp) && cp->c_datafork->ff_symlinkptr) {
hfs_free(cp->c_datafork->ff_symlinkptr);
cp->c_datafork->ff_symlinkptr = NULL;
}
if (length < filebytes) {
while (filebytes > length) {
if ((filebytes - length) > HFS_BIGFILE_SIZE) {
filebytes -= HFS_BIGFILE_SIZE;
} else {
filebytes = length;
}
error = do_hfs_truncate(vp, filebytes, flags, truncateflags);
if (error)
break;
}
} else if (length > filebytes) {
const bool keep_reserve = false;
if (hfs_freeblks(hfsmp, keep_reserve) < howmany(length - filebytes, blksize))
{
error = ENOSPC;
}
else
{
while (filebytes < length) {
if ((length - filebytes) > HFS_BIGFILE_SIZE) {
filebytes += HFS_BIGFILE_SIZE;
} else {
filebytes = length;
}
error = do_hfs_truncate(vp, filebytes, flags, truncateflags);
if (error)
break;
}
}
} else {
error = do_hfs_truncate(vp, length, flags, truncateflags);
}
if (!caller_has_cnode_lock)
hfs_unlock(cp);
return error;
}
int
hfs_vnop_preallocate(struct vnode * vp, LIFilePreallocateArgs_t* psPreAllocReq, LIFilePreallocateArgs_t* psPreAllocRes)
{
struct cnode *cp = VTOC(vp);
struct filefork *fp = VTOF(vp);
struct hfsmount *hfsmp = VTOHFS(vp);
ExtendedVCB *vcb = VTOVCB(vp);
int retval = E_NONE , retval2 = E_NONE;
off_t length = psPreAllocReq->length;
psPreAllocRes->bytesallocated = 0;
if (vnode_isdir(vp) || vnode_islnk(vp)) {
LFHFS_LOG(LEVEL_ERROR, "hfs_vnop_preallocate: Cannot change size of a directory or symlink!");
return EPERM;
}
if (length == 0)
return (0);
if (psPreAllocReq->flags & LI_PREALLOCATE_ALLOCATEFROMVOL){
LFHFS_LOG(LEVEL_ERROR, "hfs_vnop_preallocate: Not supporting LI_PREALLOCATE_ALLOCATEFROMVOL mode\n");
return ENOTSUP;
}
hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT);
if ((retval = hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_DEFAULT))) {
hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
return (retval);
}
off_t filebytes = (off_t)fp->ff_blocks * (off_t)vcb->blockSize;
off_t startingPEOF = filebytes;
if (filebytes == length)
goto exit;
u_int32_t extendFlags = kEFNoClumpMask;
if (psPreAllocReq->flags & LI_PREALLOCATE_ALLOCATECONTIG)
extendFlags |= kEFContigMask;
if (psPreAllocReq->flags & LI_PREALLOCATE_ALLOCATEALL)
extendFlags |= kEFAllMask;
if (length > filebytes)
{
off_t total_bytes_added = 0, orig_request_size, moreBytesRequested, actualBytesAdded;
orig_request_size = moreBytesRequested = length - filebytes;
while ((length > filebytes) && (retval == E_NONE))
{
off_t bytesRequested;
if (hfs_start_transaction(hfsmp) != 0)
{
retval = EINVAL;
goto err_exit;
}
int lockflags = SFL_BITMAP;
if (overflow_extents(fp))
lockflags |= SFL_EXTENTS;
lockflags = hfs_systemfile_lock(hfsmp, lockflags, HFS_EXCLUSIVE_LOCK);
if (moreBytesRequested >= HFS_BIGFILE_SIZE) {
bytesRequested = HFS_BIGFILE_SIZE;
} else {
bytesRequested = moreBytesRequested;
}
retval = MacToVFSError(ExtendFileC(vcb,
(FCB*)fp,
bytesRequested,
0,
extendFlags,
&actualBytesAdded));
if (retval == E_NONE)
{
psPreAllocRes->bytesallocated += actualBytesAdded;
total_bytes_added += actualBytesAdded;
moreBytesRequested -= actualBytesAdded;
}
filebytes = (off_t)fp->ff_blocks * (off_t)vcb->blockSize;
hfs_systemfile_unlock(hfsmp, lockflags);
if (hfsmp->jnl) {
(void) hfs_update(vp, 0);
(void) hfs_volupdate(hfsmp, VOL_UPDATE, 0);
}
hfs_end_transaction(hfsmp);
}
if (retval && (startingPEOF == filebytes))
goto err_exit;
if (total_bytes_added != 0 && orig_request_size < total_bytes_added)
psPreAllocRes->bytesallocated = roundup(orig_request_size, (off_t)vcb->blockSize);
} else {
goto err_exit;
}
exit:
cp->c_flag |= C_MODIFIED;
cp->c_touch_chgtime = TRUE;
cp->c_touch_modtime = TRUE;
retval2 = hfs_update(vp, 0);
if (retval == 0)
retval = retval2;
err_exit:
hfs_unlock_truncate(cp, HFS_LOCK_DEFAULT);
hfs_unlock(cp);
return (retval);
}