#include <sys/buf.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include <sys/ucred.h>
#include <sys/vnode.h>
#include <string.h>
#include <libkern/OSMalloc.h>
#include <kern/debug.h>
#include <kern/locks.h>
#include "ntfs.h"
#include "ntfs_attr.h"
#include "ntfs_bitmap.h"
#include "ntfs_debug.h"
#include "ntfs_inode.h"
#include "ntfs_lcnalloc.h"
#include "ntfs_page.h"
#include "ntfs_runlist.h"
#include "ntfs_types.h"
#include "ntfs_volume.h"
static errno_t ntfs_cluster_free_from_rl_nolock(ntfs_volume *vol,
ntfs_rl_element *rl, const VCN start_vcn, s64 count,
s64 *nr_freed);
errno_t ntfs_cluster_alloc(ntfs_volume *vol, const VCN start_vcn,
const s64 count, const LCN start_lcn,
const NTFS_CLUSTER_ALLOCATION_ZONES zone,
const BOOL is_extension, ntfs_runlist *runlist)
{
LCN zone_start, zone_end, bmp_pos, bmp_initial_pos, last_read_pos, lcn;
LCN prev_lcn = 0, prev_run_len = 0, mft_zone_size;
s64 clusters, data_size;
ntfs_inode *lcnbmp_ni;
ntfs_rl_element *rl = NULL;
upl_t upl = NULL;
upl_page_info_array_t pl;
u8 *b, *byte;
int rlpos, rlsize, bsize;
errno_t err;
u8 pass, done_zones, search_zone, bit;
BOOL need_writeback = FALSE;
ntfs_debug("Entering for start_vcn 0x%llx, count 0x%llx, start_lcn "
"0x%llx, zone %s_ZONE.", (unsigned long long)start_vcn,
(unsigned long long)count,
(unsigned long long)start_lcn,
zone == MFT_ZONE ? "MFT" : "DATA");
if (!vol)
panic("%s(): !vol\n", __FUNCTION__);
lcnbmp_ni = vol->lcnbmp_ni;
if (!lcnbmp_ni)
panic("%s(): !lcnbmp_ni\n", __FUNCTION__);
if (start_vcn < 0)
panic("%s(): start_vcn < 0\n", __FUNCTION__);
if (count < 0)
panic("%s(): count < 0\n", __FUNCTION__);
if (start_lcn < -1)
panic("%s(): start_lcn < -1\n", __FUNCTION__);
if (zone < FIRST_ZONE)
panic("%s(): zone < FIRST_ZONE\n", __FUNCTION__);
if (zone > LAST_ZONE)
panic("%s(): zone > LAST_ZONE\n", __FUNCTION__);
if (!count) {
if (runlist->alloc)
OSFree(runlist->rl, runlist->alloc, ntfs_malloc_tag);
runlist->rl = NULL;
runlist->elements = 0;
runlist->alloc = 0;
return 0;
}
lck_rw_lock_exclusive(&vol->lcnbmp_lock);
err = vnode_getwithref(lcnbmp_ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to get vnode for $Bitmap.");
lck_rw_unlock_exclusive(&vol->lcnbmp_lock);
return err;
}
lck_rw_lock_shared(&lcnbmp_ni->lock);
done_zones = 0;
pass = 1;
zone_start = start_lcn;
if (zone_start < 0) {
if (zone == DATA_ZONE)
zone_start = vol->data1_zone_pos;
else
zone_start = vol->mft_zone_pos;
if (!zone_start) {
pass = 2;
}
} else if (zone == DATA_ZONE && zone_start >= vol->mft_zone_start &&
zone_start < vol->mft_zone_end) {
zone_start = vol->mft_zone_end;
pass = 2;
} else if (zone == MFT_ZONE && (zone_start < vol->mft_zone_start ||
zone_start >= vol->mft_zone_end)) {
zone_start = vol->mft_lcn;
if (!vol->mft_zone_end)
zone_start = 0;
pass = 2;
}
if (zone == MFT_ZONE) {
zone_end = vol->mft_zone_end;
search_zone = 1;
} else {
done_zones |= 1;
if (zone_start >= vol->mft_zone_end) {
zone_end = vol->nr_clusters;
search_zone = 2;
} else {
zone_end = vol->mft_zone_start;
search_zone = 4;
}
}
bmp_pos = bmp_initial_pos = zone_start;
clusters = count;
rlpos = rlsize = 0;
lck_spin_lock(&lcnbmp_ni->size_lock);
data_size = ubc_getsize(lcnbmp_ni->vn);
if (data_size != lcnbmp_ni->data_size)
panic("%s(): data_size != lcnbmp_ni->data_size\n",
__FUNCTION__);
lck_spin_unlock(&lcnbmp_ni->size_lock);
while (1) {
ntfs_debug("Start of outer while loop: done_zones 0x%x, "
"search_zone %d, pass %d, zone_start 0x%llx, "
"zone_end 0x%llx, bmp_initial_pos 0x%llx, "
"bmp_pos 0x%llx, rlpos %d, rlsize %d.",
done_zones, search_zone, pass,
(unsigned long long)zone_start,
(unsigned long long)zone_end,
(unsigned long long)bmp_initial_pos,
(unsigned long long)bmp_pos, rlpos, rlsize);
last_read_pos = bmp_pos >> 3;
ntfs_debug("last_read_pos 0x%llx.",
(unsigned long long)last_read_pos);
if (last_read_pos > data_size) {
ntfs_debug("End of attribute reached. "
"Skipping to zone_pass_done.");
goto zone_pass_done;
}
if (upl) {
ntfs_page_unmap(lcnbmp_ni, upl, pl, need_writeback);
if (need_writeback) {
ntfs_debug("Marking page dirty.");
need_writeback = FALSE;
}
}
err = ntfs_page_map(lcnbmp_ni, last_read_pos & ~PAGE_MASK_64,
&upl, &pl, &b, TRUE);
if (err) {
ntfs_error(vol->mp, "Failed to map page.");
upl = NULL;
goto out;
}
bsize = last_read_pos & PAGE_MASK;
b += bsize;
bsize = PAGE_SIZE - bsize;
if (last_read_pos + bsize > data_size)
bsize = data_size - last_read_pos;
bsize <<= 3;
lcn = bmp_pos & 7;
bmp_pos &= ~(LCN)7;
ntfs_debug("Before inner while loop: bsize %d, lcn 0x%llx, "
"bmp_pos 0x%llx, need_writeback is %s.", bsize,
(unsigned long long)lcn,
(unsigned long long)bmp_pos,
need_writeback ? "true" : "false");
while (lcn < bsize && lcn + bmp_pos < zone_end) {
byte = b + (lcn >> 3);
ntfs_debug("In inner while loop: bsize %d, lcn "
"0x%llx, bmp_pos 0x%llx, "
"need_writeback is %s, byte ofs 0x%x, "
"*byte 0x%x.", bsize,
(unsigned long long)lcn,
(unsigned long long)bmp_pos,
need_writeback ? "true" : "false",
(unsigned)(lcn >> 3), (unsigned)*byte);
if (*byte == 0xff) {
lcn = (lcn + 8) & ~(LCN)7;
ntfs_debug("Continuing while loop 1.");
continue;
}
bit = 1 << (lcn & 7);
ntfs_debug("bit 0x%x.", bit);
if (*byte & bit) {
lcn++;
ntfs_debug("Continuing while loop 2.");
continue;
}
if ((rlpos + 2) * (int)sizeof(*rl) > rlsize) {
ntfs_rl_element *rl2;
ntfs_debug("Reallocating memory.");
rl2 = OSMalloc(rlsize + NTFS_ALLOC_BLOCK,
ntfs_malloc_tag);
if (!rl2) {
err = ENOMEM;
ntfs_error(vol->mp, "Failed to "
"allocate memory.");
goto out;
}
if (!rl)
ntfs_debug("First free bit is at LCN "
"0x%llx.",
(unsigned long long)
(lcn + bmp_pos));
else {
memcpy(rl2, rl, rlsize);
OSFree(rl, rlsize, ntfs_malloc_tag);
}
rl = rl2;
rlsize += NTFS_ALLOC_BLOCK;
ntfs_debug("Reallocated memory, rlsize 0x%x.",
rlsize);
}
*byte |= bit;
vol->nr_free_clusters--;
if (vol->nr_free_clusters < 0)
vol->nr_free_clusters = 0;
need_writeback = TRUE;
ntfs_debug("*byte 0x%x, need_writeback is set.",
(unsigned)*byte);
ntfs_debug("Adding run (lcn 0x%llx, len 0x%llx), "
"prev_lcn 0x%llx, lcn 0x%llx, "
"bmp_pos 0x%llx, prev_run_len 0x%llx, "
"rlpos %d.",
(unsigned long long)(lcn + bmp_pos),
1ULL, (unsigned long long)prev_lcn,
(unsigned long long)lcn,
(unsigned long long)bmp_pos,
(unsigned long long)prev_run_len,
rlpos);
if (prev_lcn == lcn + bmp_pos - prev_run_len && rlpos) {
ntfs_debug("Coalescing to run (lcn 0x%llx, "
"len 0x%llx).",
(unsigned long long)
rl[rlpos - 1].lcn,
(unsigned long long)
rl[rlpos - 1].length);
rl[rlpos - 1].length = ++prev_run_len;
ntfs_debug("Run now (lcn 0x%llx, len 0x%llx), "
"prev_run_len 0x%llx.",
(unsigned long long)
rl[rlpos - 1].lcn,
(unsigned long long)
rl[rlpos - 1].length,
(unsigned long long)
prev_run_len);
} else {
if (rlpos) {
ntfs_debug("Adding new run, (previous "
"run lcn 0x%llx, "
"len 0x%llx).",
(unsigned long long)
rl[rlpos - 1].lcn,
(unsigned long long)
rl[rlpos - 1].length);
rl[rlpos].vcn = rl[rlpos - 1].vcn +
prev_run_len;
} else {
ntfs_debug("Adding new run, is first "
"run.");
rl[rlpos].vcn = start_vcn;
}
rl[rlpos].lcn = prev_lcn = lcn + bmp_pos;
rl[rlpos].length = prev_run_len = 1;
rlpos++;
}
if (!--clusters) {
LCN tc;
tc = lcn + bmp_pos + 1;
ntfs_debug("Done. Updating current zone "
"position, tc 0x%llx, "
"search_zone %d.",
(unsigned long long)tc,
search_zone);
switch (search_zone) {
case 1:
ntfs_debug("Before checks, "
"vol->mft_zone_pos "
"0x%llx.",
(unsigned long long)
vol->mft_zone_pos);
if (tc >= vol->mft_zone_end) {
vol->mft_zone_pos =
vol->mft_lcn;
if (!vol->mft_zone_end)
vol->mft_zone_pos = 0;
} else if ((bmp_initial_pos >=
vol->mft_zone_pos ||
tc > vol->mft_zone_pos)
&& tc >= vol->mft_lcn)
vol->mft_zone_pos = tc;
ntfs_debug("After checks, "
"vol->mft_zone_pos "
"0x%llx.",
(unsigned long long)
vol->mft_zone_pos);
break;
case 2:
ntfs_debug("Before checks, "
"vol->data1_zone_pos "
"0x%llx.",
(unsigned long long)
vol->data1_zone_pos);
if (tc >= vol->nr_clusters)
vol->data1_zone_pos =
vol->mft_zone_end;
else if ((bmp_initial_pos >=
vol->data1_zone_pos ||
tc > vol->data1_zone_pos)
&& tc >= vol->mft_zone_end)
vol->data1_zone_pos = tc;
ntfs_debug("After checks, "
"vol->data1_zone_pos "
"0x%llx.",
(unsigned long long)
vol->data1_zone_pos);
break;
case 4:
ntfs_debug("Before checks, "
"vol->data2_zone_pos "
"0x%llx.",
(unsigned long long)
vol->data2_zone_pos);
if (tc >= vol->mft_zone_start)
vol->data2_zone_pos = 0;
else if (bmp_initial_pos >=
vol->data2_zone_pos ||
tc > vol->data2_zone_pos)
vol->data2_zone_pos = tc;
ntfs_debug("After checks, "
"vol->data2_zone_pos "
"0x%llx.",
(unsigned long long)
vol->data2_zone_pos);
break;
default:
panic("%s(): Reached default case in "
"switch!\n",
__FUNCTION__);
}
ntfs_debug("Finished. Going to out.");
goto out;
}
lcn++;
}
bmp_pos += bsize;
ntfs_debug("After inner while loop: bsize 0x%x, lcn 0x%llx, "
"bmp_pos 0x%llx, need_writeback is %s.", bsize,
(unsigned long long)lcn,
(unsigned long long)bmp_pos,
need_writeback ? "true" : "false");
if (bmp_pos < zone_end) {
ntfs_debug("Continuing outer while loop, "
"bmp_pos 0x%llx, zone_end 0x%llx.",
(unsigned long long)bmp_pos,
(unsigned long long)zone_end);
continue;
}
zone_pass_done:
ntfs_debug("At zone_pass_done, pass %d.", pass);
if (pass == 1) {
pass = 2;
zone_end = zone_start;
switch (search_zone) {
case 1:
zone_start = vol->mft_zone_start;
break;
case 2:
zone_start = vol->mft_zone_end;
break;
case 4:
zone_start = 0;
break;
default:
panic("%s(): Reached default case in switch "
"(2)!\n", __FUNCTION__);
}
if (zone_end < zone_start)
zone_end = zone_start;
bmp_pos = zone_start;
ntfs_debug("Continuing outer while loop, pass 2, "
"zone_start 0x%llx, zone_end 0x%llx, "
"bmp_pos 0x%llx.",
(unsigned long long)zone_start,
(unsigned long long)zone_end,
(unsigned long long)bmp_pos);
continue;
}
done_zones_check:
ntfs_debug("At done_zones_check, search_zone %d, done_zones "
"before 0x%x, done_zones after 0x%x.",
search_zone, done_zones,
done_zones | search_zone);
done_zones |= search_zone;
if (done_zones < 7) {
ntfs_debug("Switching zone.");
pass = 1;
switch (search_zone) {
case 1:
ntfs_debug("Switching from mft zone to data1 "
"zone.");
if (rlpos) {
LCN tc;
ntfs_debug("Before checks, "
"vol->mft_zone_pos "
"0x%llx.",
(unsigned long long)
vol->mft_zone_pos);
tc = rl[rlpos - 1].lcn +
rl[rlpos - 1].length;
if (tc >= vol->mft_zone_end) {
vol->mft_zone_pos =
vol->mft_lcn;
if (!vol->mft_zone_end)
vol->mft_zone_pos = 0;
} else if ((bmp_initial_pos >=
vol->mft_zone_pos ||
tc > vol->mft_zone_pos)
&& tc >= vol->mft_lcn)
vol->mft_zone_pos = tc;
ntfs_debug("After checks, "
"vol->mft_zone_pos "
"0x%llx.",
(unsigned long long)
vol->mft_zone_pos);
}
switch_to_data1_zone: search_zone = 2;
zone_start = bmp_initial_pos =
vol->data1_zone_pos;
zone_end = vol->nr_clusters;
if (zone_start == vol->mft_zone_end)
pass = 2;
if (zone_start >= zone_end) {
vol->data1_zone_pos = zone_start =
vol->mft_zone_end;
pass = 2;
}
break;
case 2:
ntfs_debug("Switching from data1 zone to "
"data2 zone.");
if (rlpos) {
LCN tc;
ntfs_debug("Before checks, "
"vol->data1_zone_pos "
"0x%llx.",
(unsigned long long)
vol->data1_zone_pos);
tc = rl[rlpos - 1].lcn +
rl[rlpos - 1].length;
if (tc >= vol->nr_clusters)
vol->data1_zone_pos =
vol->mft_zone_end;
else if ((bmp_initial_pos >=
vol->data1_zone_pos ||
tc > vol->data1_zone_pos)
&& tc >= vol->mft_zone_end)
vol->data1_zone_pos = tc;
ntfs_debug("After checks, "
"vol->data1_zone_pos "
"0x%llx.",
(unsigned long long)
vol->data1_zone_pos);
}
search_zone = 4;
zone_start = bmp_initial_pos =
vol->data2_zone_pos;
zone_end = vol->mft_zone_start;
if (!zone_start)
pass = 2;
if (zone_start >= zone_end) {
vol->data2_zone_pos = zone_start =
bmp_initial_pos = 0;
pass = 2;
}
break;
case 4:
ntfs_debug("Switching from data2 zone to "
"data1 zone.");
if (rlpos) {
LCN tc;
ntfs_debug("Before checks, "
"vol->data2_zone_pos "
"0x%llx.",
(unsigned long long)
vol->data2_zone_pos);
tc = rl[rlpos - 1].lcn +
rl[rlpos - 1].length;
if (tc >= vol->mft_zone_start)
vol->data2_zone_pos = 0;
else if (bmp_initial_pos >=
vol->data2_zone_pos ||
tc > vol->data2_zone_pos)
vol->data2_zone_pos = tc;
ntfs_debug("After checks, "
"vol->data2_zone_pos "
"0x%llx.",
(unsigned long long)
vol->data2_zone_pos);
}
goto switch_to_data1_zone;
default:
panic("%s(): Reached default case in switch "
"(3)!\n", __FUNCTION__);
}
ntfs_debug("After zone switch, search_zone %d, "
"pass %d, bmp_initial_pos 0x%llx, "
"zone_start 0x%llx, zone_end 0x%llx.",
search_zone, pass,
(unsigned long long)bmp_initial_pos,
(unsigned long long)zone_start,
(unsigned long long)zone_end);
bmp_pos = zone_start;
if (zone_start == zone_end) {
ntfs_debug("Empty zone, going to "
"done_zones_check.");
goto done_zones_check;
}
ntfs_debug("Continuing outer while loop.");
continue;
}
ntfs_debug("All zones are finished.");
mft_zone_size = vol->mft_zone_end - vol->mft_zone_start;
ntfs_debug("vol->mft_zone_start 0x%llx, vol->mft_zone_end "
"0x%llx, mft_zone_size 0x%llx.",
(unsigned long long)vol->mft_zone_start,
(unsigned long long)vol->mft_zone_end,
(unsigned long long)mft_zone_size);
if (zone == MFT_ZONE || mft_zone_size <= 0) {
ntfs_debug("No free clusters left, going to out.");
err = ENOSPC;
goto out;
}
ntfs_debug("Shrinking mft zone.");
zone_end = vol->mft_zone_end;
mft_zone_size >>= 1;
if (mft_zone_size > 0)
vol->mft_zone_end = vol->mft_zone_start + mft_zone_size;
else
vol->data2_zone_pos = vol->mft_zone_start =
vol->mft_zone_end = 0;
if (vol->mft_zone_pos >= vol->mft_zone_end) {
vol->mft_zone_pos = vol->mft_lcn;
if (!vol->mft_zone_end)
vol->mft_zone_pos = 0;
}
bmp_pos = zone_start = bmp_initial_pos =
vol->data1_zone_pos = vol->mft_zone_end;
search_zone = 2;
pass = 2;
done_zones &= ~2;
ntfs_debug("After shrinking mft zone, mft_zone_size 0x%llx, "
"vol->mft_zone_start 0x%llx, "
"vol->mft_zone_end 0x%llx, "
"vol->mft_zone_pos 0x%llx, search_zone 2, "
"pass 2, dones_zones 0x%x, zone_start 0x%llx, "
"zone_end 0x%llx, vol->data1_zone_pos 0x%llx, "
"continuing outer while loop.",
(unsigned long long)mft_zone_size,
(unsigned long long)vol->mft_zone_start,
(unsigned long long)vol->mft_zone_end,
(unsigned long long)vol->mft_zone_pos,
done_zones, (unsigned long long)zone_start,
(unsigned long long)zone_end,
(unsigned long long)vol->data1_zone_pos);
}
ntfs_debug("After outer while loop.");
out:
ntfs_debug("At out.");
if (rl) {
rl[rlpos].vcn = rl[rlpos - 1].vcn + rl[rlpos - 1].length;
rl[rlpos].lcn = is_extension ? LCN_ENOENT : LCN_RL_NOT_MAPPED;
rl[rlpos].length = 0;
}
if (upl) {
ntfs_page_unmap(lcnbmp_ni, upl, pl, need_writeback);
if (need_writeback) {
ntfs_debug("Marking page dirty.");
need_writeback = FALSE;
}
}
if (!err) {
if (rl) {
ntfs_rl_element *trl;
vnode_t dev_vn = vol->dev_vn;
const u8 cluster_to_block_shift =
vol->cluster_size_shift -
vol->sector_size_shift;
for (trl = rl; trl->length; trl++) {
daddr64_t block, end_block;
if (trl->lcn < 0)
continue;
block = trl->lcn << cluster_to_block_shift;
end_block = block + (trl->length <<
cluster_to_block_shift);
for (; block < end_block; block++) {
do {
err = buf_invalblkno(dev_vn,
block,
BUF_WAIT);
} while (err);
}
}
}
lck_rw_unlock_shared(&lcnbmp_ni->lock);
(void)vnode_put(lcnbmp_ni->vn);
lck_rw_unlock_exclusive(&vol->lcnbmp_lock);
if (runlist->alloc)
OSFree(runlist->rl, runlist->alloc, ntfs_malloc_tag);
runlist->rl = rl;
runlist->elements = rlpos + 1;
runlist->alloc = rlsize;
ntfs_debug("Done.");
return 0;
}
ntfs_error(vol->mp, "Failed to allocate clusters, aborting (error "
"%d).", err);
if (rl) {
errno_t err2;
if (err == ENOSPC)
ntfs_debug("Not enough space to complete allocation, "
"err ENOSPC, first free lcn 0x%llx, "
"could allocate up to 0x%llx "
"clusters.",
(unsigned long long)rl[0].lcn,
(unsigned long long)(count - clusters));
ntfs_debug("Attempting rollback...");
err2 = ntfs_cluster_free_from_rl_nolock(vol, rl, 0, -1, NULL);
if (err2) {
ntfs_error(vol->mp, "Failed to rollback (error %d). "
"Leaving inconsistent metadata! "
"Unmount and run chkdsk.", err2);
NVolSetErrors(vol);
}
OSFree(rl, rlsize, ntfs_malloc_tag);
} else if (err == ENOSPC)
ntfs_debug("No space left at all, err ENOSPC, first free lcn "
"0x%llx.", (long long)vol->data1_zone_pos);
lck_rw_unlock_shared(&lcnbmp_ni->lock);
(void)vnode_put(lcnbmp_ni->vn);
lck_rw_unlock_exclusive(&vol->lcnbmp_lock);
return err;
}
static errno_t ntfs_cluster_free_from_rl_nolock(ntfs_volume *vol,
ntfs_rl_element *rl, const VCN start_vcn, s64 count,
s64 *nr_freed)
{
s64 delta, to_free, real_freed;
ntfs_inode *lcnbmp_ni = vol->lcnbmp_ni;
errno_t err;
ntfs_debug("Entering for start_vcn 0x%llx, count 0x%llx.",
(unsigned long long)start_vcn,
(unsigned long long)count);
if (!lcnbmp_ni)
panic("%s(): !lcnbmp_ni\n", __FUNCTION__);
if (start_vcn < 0)
panic("%s(): start_vcn < 0\n", __FUNCTION__);
if (count < -1)
panic("%s(): count < -1\n", __FUNCTION__);
real_freed = 0;
if (nr_freed)
*nr_freed = 0;
rl = ntfs_rl_find_vcn_nolock(rl, start_vcn);
if (!rl)
return 0;
if (rl->lcn < LCN_HOLE) {
ntfs_error(vol->mp, "First runlist element has invalid lcn, "
"aborting.");
return EIO;
}
delta = start_vcn - rl->vcn;
to_free = rl->length - delta;
if (count >= 0 && to_free > count)
to_free = count;
if (rl->lcn >= 0) {
err = ntfs_bitmap_clear_run(lcnbmp_ni, rl->lcn + delta,
to_free);
if (err) {
ntfs_error(vol->mp, "Failed to clear first run "
"(error %d), aborting.", err);
return err;
}
real_freed = to_free;
vol->nr_free_clusters += to_free;
if (vol->nr_free_clusters > vol->nr_clusters)
vol->nr_free_clusters = vol->nr_clusters;
}
++rl;
if (count >= 0)
count -= to_free;
for (; rl->length && count; ++rl) {
if (rl->lcn < LCN_HOLE) {
ntfs_error(vol->mp, "Runlist element has invalid lcn "
"%lld at vcn 0x%llx, not freeing. "
"Run chkdsk to recover the lost "
"space.", (long long)rl->lcn,
(unsigned long long)rl->vcn);
NVolSetErrors(vol);
}
to_free = rl->length;
if (count >= 0 && to_free > count)
to_free = count;
if (rl->lcn >= 0) {
err = ntfs_bitmap_clear_run(lcnbmp_ni, rl->lcn,
to_free);
if (err) {
ntfs_warning(vol->mp, "Failed to free "
"clusters in subsequent run. "
"Run chkdsk to recover the "
"lost space.");
NVolSetErrors(vol);
} else {
vol->nr_free_clusters += to_free;
if (vol->nr_free_clusters > vol->nr_clusters)
vol->nr_free_clusters =
vol->nr_clusters;
}
real_freed += to_free;
}
if (count >= 0)
count -= to_free;
}
if (count > 0)
panic("%s(): count > 0\n", __FUNCTION__);
ntfs_debug("Done.");
if (nr_freed)
*nr_freed = real_freed;
return 0;
}
errno_t ntfs_cluster_free_from_rl(ntfs_volume *vol, ntfs_rl_element *rl,
const VCN start_vcn, s64 count, s64 *nr_freed)
{
ntfs_inode *lcnbmp_ni;
vnode_t lcnbmp_vn;
errno_t err;
lcnbmp_ni = vol->lcnbmp_ni;
lcnbmp_vn = lcnbmp_ni->vn;
lck_rw_lock_exclusive(&vol->lcnbmp_lock);
err = vnode_getwithref(lcnbmp_vn);
if (!err) {
lck_rw_lock_shared(&lcnbmp_ni->lock);
err = ntfs_cluster_free_from_rl_nolock(vol, rl, start_vcn,
count, nr_freed);
lck_rw_unlock_shared(&lcnbmp_ni->lock);
(void)vnode_put(lcnbmp_vn);
lck_rw_unlock_exclusive(&vol->lcnbmp_lock);
return err;
}
lck_rw_unlock_exclusive(&vol->lcnbmp_lock);
ntfs_error(vol->mp, "Failed to get vnode for $Bitmap.");
return err;
}
static errno_t ntfs_cluster_free_nolock(ntfs_inode *ni, const VCN start_vcn,
s64 count, ntfs_attr_search_ctx *ctx, s64 *nr_freed,
const BOOL is_rollback)
{
s64 delta, to_free, total_freed, real_freed;
ntfs_volume *vol;
ntfs_inode *lcnbmp_ni;
ntfs_rl_element *rl;
errno_t err, err2;
vol = ni->vol;
lcnbmp_ni = vol->lcnbmp_ni;
if (!lcnbmp_ni)
panic("%s(): !lcnbmp_ni\n", __FUNCTION__);
if (start_vcn < 0)
panic("%s(): start_vcn < 0\n", __FUNCTION__);
if (count < -1)
panic("%s(): count < -1\n", __FUNCTION__);
total_freed = real_freed = 0;
if (nr_freed)
*nr_freed = 0;
err = ntfs_attr_find_vcn_nolock(ni, start_vcn, &rl, ctx);
if (err) {
if (!is_rollback)
ntfs_error(vol->mp, "Failed to find first runlist "
"element (error %d), aborting.", err);
goto err;
}
if (rl->lcn < LCN_HOLE) {
if (!is_rollback)
ntfs_error(vol->mp, "First runlist element has "
"invalid lcn, aborting.");
err = EIO;
goto err;
}
delta = start_vcn - rl->vcn;
to_free = rl->length - delta;
if (count >= 0 && to_free > count)
to_free = count;
if (rl->lcn >= 0) {
err = ntfs_bitmap_set_bits_in_run(lcnbmp_ni, rl->lcn + delta,
to_free, !is_rollback ? 0 : 1);
if (err) {
if (!is_rollback)
ntfs_error(vol->mp, "Failed to clear first run "
"(error %d), aborting.", err);
goto err;
}
real_freed = to_free;
if (is_rollback) {
vol->nr_free_clusters -= to_free;
if (vol->nr_free_clusters < 0)
vol->nr_free_clusters = 0;
} else {
vol->nr_free_clusters += to_free;
if (vol->nr_free_clusters > vol->nr_clusters)
vol->nr_free_clusters = vol->nr_clusters;
}
}
++rl;
if (count >= 0)
count -= to_free;
total_freed = to_free;
for (; count; ++rl) {
if (rl->lcn < LCN_HOLE) {
VCN vcn;
if (rl->lcn == LCN_ENOENT)
break;
vcn = rl->vcn;
err = ntfs_attr_find_vcn_nolock(ni, vcn, &rl, ctx);
if (err) {
if (err == ENOENT)
break;
if (!is_rollback)
ntfs_error(vol->mp, "Failed to map "
"runlist fragment or "
"failed to find "
"subsequent runlist "
"element.");
goto err;
}
if (rl->lcn < LCN_HOLE) {
if (!is_rollback)
ntfs_error(vol->mp, "Runlist element "
"has invalid lcn "
"(0x%llx).",
(unsigned long long)
rl->lcn);
err = EIO;
goto err;
}
}
to_free = rl->length;
if (count >= 0 && to_free > count)
to_free = count;
if (rl->lcn >= 0) {
err = ntfs_bitmap_set_bits_in_run(lcnbmp_ni, rl->lcn,
to_free, !is_rollback ? 0 : 1);
if (err) {
if (!is_rollback)
ntfs_error(vol->mp, "Failed to clear "
"subsequent run.");
goto err;
}
real_freed += to_free;
if (is_rollback) {
vol->nr_free_clusters -= to_free;
if (vol->nr_free_clusters < 0)
vol->nr_free_clusters = 0;
} else {
vol->nr_free_clusters += to_free;
if (vol->nr_free_clusters > vol->nr_clusters)
vol->nr_free_clusters =
vol->nr_clusters;
}
}
if (count >= 0)
count -= to_free;
total_freed += to_free;
}
if (count > 0)
panic("%s(): count > 0\n", __FUNCTION__);
ntfs_debug("Done.");
if (nr_freed)
*nr_freed = real_freed;
return 0;
err:
if (is_rollback)
return err;
if (!real_freed)
return err;
err2 = ntfs_cluster_free_nolock(ni, start_vcn, total_freed, ctx, NULL,
TRUE);
if (err2) {
ntfs_error(vol->mp, "Failed to rollback (error %d). Leaving "
"inconsistent metadata! Unmount and run "
"chkdsk.", err2);
NVolSetErrors(vol);
}
ntfs_error(vol->mp, "Aborting (error %d).", err);
return err;
}
errno_t ntfs_cluster_free(ntfs_inode *ni, const VCN start_vcn, s64 count,
ntfs_attr_search_ctx *ctx, s64 *nr_freed)
{
ntfs_volume *vol;
ntfs_inode *lcnbmp_ni;
vnode_t lcnbmp_vn;
errno_t err;
if (!ni)
panic("%s(): !ni\n", __FUNCTION__);
ntfs_debug("Entering for mft_no 0x%llx, start_vcn 0x%llx, count "
"0x%llx.", (unsigned long long)ni->mft_no,
(unsigned long long)start_vcn,
(unsigned long long)count);
vol = ni->vol;
lcnbmp_ni = vol->lcnbmp_ni;
lcnbmp_vn = lcnbmp_ni->vn;
lck_rw_lock_exclusive(&vol->lcnbmp_lock);
err = vnode_getwithref(lcnbmp_vn);
if (!err) {
lck_rw_lock_shared(&lcnbmp_ni->lock);
err = ntfs_cluster_free_nolock(ni, start_vcn, count, ctx,
nr_freed, FALSE);
lck_rw_unlock_shared(&lcnbmp_ni->lock);
(void)vnode_put(lcnbmp_vn);
lck_rw_unlock_exclusive(&vol->lcnbmp_lock);
return err;
}
lck_rw_unlock_exclusive(&vol->lcnbmp_lock);
ntfs_error(vol->mp, "Failed to get vnode for $Bitmap.");
return err;
}