#include <sys/cdefs.h>
#include <sys/attr.h>
#include <sys/buf.h>
#include <sys/disk.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/kauth.h>
#include <sys/kernel_types.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ubc.h>
#include <sys/ucred.h>
#include <sys/vnode.h>
#include <mach/kern_return.h>
#include <mach/kmod.h>
#include <mach/machine/vm_param.h>
#include <string.h>
#include <libkern/libkern.h>
#include <libkern/OSMalloc.h>
#include <libkern/OSKextLib.h>
#include <kern/debug.h>
#include <kern/locks.h>
#include <miscfs/specfs/specdev.h>
#include "ntfs.h"
#include "ntfs_attr.h"
#include "ntfs_attr_list.h"
#include "ntfs_debug.h"
#include "ntfs_dir.h"
#include "ntfs_hash.h"
#include "ntfs_inode.h"
#include "ntfs_layout.h"
#include "ntfs_logfile.h"
#include "ntfs_mft.h"
#include "ntfs_mst.h"
#include "ntfs_page.h"
#include "ntfs_quota.h"
#include "ntfs_secure.h"
#include "ntfs_time.h"
#include "ntfs_unistr.h"
#include "ntfs_usnjrnl.h"
#include "ntfs_version.h"
#include "ntfs_vnops.h"
#include "ntfs_volume.h"
const char ntfs_dev_email[] = "linux-ntfs-dev@lists.sourceforge.net";
const char ntfs_please_email[] = "Please email "
"linux-ntfs-dev@lists.sourceforge.net and say that you saw "
"this message. Thank you.";
static lck_mtx_t ntfs_lock;
static unsigned long ntfs_compression_users;
static u8 *ntfs_compression_buffer;
#define ntfs_compression_buffer_size (16 * 4096)
static unsigned long ntfs_default_upcase_users;
static ntfschar *ntfs_default_upcase;
#define ntfs_default_upcase_size (64 * 1024 * sizeof(ntfschar))
static errno_t ntfs_blocksize_set(mount_t mp, vnode_t dev_vn, u32 blocksize,
vfs_context_t context)
{
errno_t err;
struct vfsioattr ia;
err = VNOP_IOCTL(dev_vn, DKIOCSETBLOCKSIZE, (caddr_t)&blocksize,
FWRITE, context);
if (err)
return ENXIO;
ntfs_debug("Updating io attributes with new block size.");
vfs_ioattr(mp, &ia);
ia.io_devblocksize = blocksize;
vfs_setioattr(mp, &ia);
ntfs_debug("Updating device vnode with new block size.");
set_fsblocksize(dev_vn);
return 0;
}
static BOOL ntfs_boot_sector_is_valid(const mount_t mp,
const NTFS_BOOT_SECTOR *b)
{
ntfs_debug("Entering.");
if ((void*)b < (void*)&b->checksum && b->checksum) {
le32 *u;
u32 i;
for (i = 0, u = (le32*)b; u < (le32*)(&b->checksum); ++u)
i += le32_to_cpup(u);
if (le32_to_cpu(b->checksum) != i)
ntfs_warning(mp, "Invalid boot sector checksum.");
}
if (b->oem_id != magicNTFS)
goto not_ntfs;
if (le16_to_cpu(b->bpb.bytes_per_sector) < 0x100 ||
le16_to_cpu(b->bpb.bytes_per_sector) >
NTFS_MAX_SECTOR_SIZE)
goto not_ntfs;
switch (b->bpb.sectors_per_cluster) {
case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128:
break;
default:
goto not_ntfs;
}
if ((u32)le16_to_cpu(b->bpb.bytes_per_sector) *
b->bpb.sectors_per_cluster > NTFS_MAX_CLUSTER_SIZE)
goto not_ntfs;
if (le16_to_cpu(b->bpb.reserved_sectors) ||
le16_to_cpu(b->bpb.root_entries) ||
le16_to_cpu(b->bpb.sectors) ||
le16_to_cpu(b->bpb.sectors_per_fat) ||
le32_to_cpu(b->bpb.large_sectors) || b->bpb.fats)
goto not_ntfs;
if ((u8)b->clusters_per_mft_record < 0xe1 ||
(u8)b->clusters_per_mft_record > 0xf7)
switch (b->clusters_per_mft_record) {
case 1: case 2: case 4: case 8: case 16: case 32: case 64:
break;
default:
goto not_ntfs;
}
if ((u8)b->clusters_per_index_block < 0xe1 ||
(u8)b->clusters_per_index_block > 0xf7)
switch (b->clusters_per_index_block) {
case 1: case 2: case 4: case 8: case 16: case 32: case 64:
break;
default:
goto not_ntfs;
}
if (b->end_of_sector_marker != const_cpu_to_le16(0xaa55))
ntfs_warning(mp, "Invalid end of sector marker.");
ntfs_debug("Done.");
return TRUE;
not_ntfs:
ntfs_debug("Not an NTFS boot sector.");
return FALSE;
}
static errno_t ntfs_boot_sector_read(ntfs_volume *vol, kauth_cred_t cred,
buf_t *buf, NTFS_BOOT_SECTOR **bs)
{
daddr64_t nr_blocks = vol->nr_blocks;
static const char read_err_str[] =
"Unable to read %s boot sector (error %d).";
mount_t mp = vol->mp;
vnode_t dev_vn = vol->dev_vn;
buf_t primary, backup;
NTFS_BOOT_SECTOR *bs1, *bs2;
errno_t err, err2;
u32 blocksize = vfs_devblocksize(mp);
ntfs_debug("Entering.");
err = buf_meta_bread(dev_vn, 0, blocksize, cred, &primary);
buf_setflags(primary, B_NOCACHE);
if (!err) {
err = buf_map(primary, (caddr_t*)&bs1);
if (err) {
ntfs_error(mp, "Failed to map buffer of primary boot "
"sector (error %d).", err);
bs1 = NULL;
} else {
if (ntfs_boot_sector_is_valid(mp, bs1)) {
*buf = primary;
*bs = bs1;
ntfs_debug("Done.");
return 0;
}
ntfs_error(mp, "Primary boot sector is invalid.");
err = EIO;
}
} else {
ntfs_error(mp, read_err_str, "primary", err);
bs1 = NULL;
}
if (!(vol->on_errors & ON_ERRORS_RECOVER)) {
ntfs_error(mp, "Mount option errors=recover not used. "
"Aborting without trying to recover.");
if (bs1) {
err2 = buf_unmap(primary);
if (err2)
ntfs_error(mp, "Failed to unmap buffer of "
"primary boot sector (error "
"%d).", err2);
}
buf_brelse(primary);
return err;
}
err = buf_meta_bread(dev_vn, nr_blocks - 1, blocksize, cred, &backup);
buf_setflags(backup, B_NOCACHE);
if (!err) {
err = buf_map(backup, (caddr_t*)&bs2);
if (err)
ntfs_error(mp, "Failed to map buffer of backup boot "
"sector (error %d).", err);
else {
if (ntfs_boot_sector_is_valid(mp, bs2))
goto hotfix_primary_boot_sector;
err = buf_unmap(backup);
if (err)
ntfs_error(mp, "Failed to unmap buffer of "
"backup boot sector (error "
"%d).", err);
}
} else
ntfs_error(mp, read_err_str, "backup", err);
buf_brelse(backup);
err = buf_meta_bread(dev_vn, nr_blocks >> 1, blocksize, cred, &backup);
buf_setflags(backup, B_NOCACHE);
if (!err) {
err = buf_map(backup, (caddr_t*)&bs2);
if (err)
ntfs_error(mp, "Failed to map buffer of old backup "
"boot sector (error %d).", err);
else {
if (ntfs_boot_sector_is_valid(mp, bs2))
goto hotfix_primary_boot_sector;
err = buf_unmap(backup);
if (err)
ntfs_error(mp, "Failed to unmap buffer of old "
"backup boot sector (error "
"%d).", err);
err = EIO;
}
ntfs_error(mp, "Could not find a valid backup boot sector.");
} else
ntfs_error(mp, read_err_str, "backup", err);
buf_brelse(backup);
if (bs1) {
err2 = buf_unmap(primary);
if (err2)
ntfs_error(mp, "Failed to unmap buffer of primary "
"boot sector (error %d).", err2);
}
buf_brelse(primary);
return err;
hotfix_primary_boot_sector:
ntfs_warning(mp, "Using backup boot sector.");
if (bs1 && !NVolReadOnly(vol)) {
ntfs_warning(mp, "Hot-fix: Recovering invalid primary boot "
"sector from backup copy.");
memcpy(bs1, bs2, blocksize);
err = buf_bwrite(primary);
if (err)
ntfs_error(mp, "Hot-fix: Device write error while "
"recovering primary boot sector "
"(error %d).", err);
} else {
if (bs1) {
ntfs_warning(mp, "Hot-fix: Recovery of primary boot "
"sector failed: Read-only mount.");
err = buf_unmap(primary);
if (err)
ntfs_error(mp, "Failed to unmap buffer of "
"primary boot sector (error "
"%d).", err);
} else
ntfs_warning(mp, "Hot-fix: Recovery of primary boot "
"sector failed as it could not be "
"mapped.");
buf_brelse(primary);
}
*buf = backup;
*bs = bs2;
return 0;
}
static errno_t ntfs_boot_sector_parse(ntfs_volume *vol,
const NTFS_BOOT_SECTOR *b)
{
s64 ll;
mount_t mp = vol->mp;
unsigned sectors_per_cluster_shift, nr_hidden_sects;
int clusters_per_mft_record, clusters_per_index_block;
ntfs_debug("Entering.");
vol->sector_size = le16_to_cpu(b->bpb.bytes_per_sector);
vol->sector_size_mask = vol->sector_size - 1;
vol->sector_size_shift = ffs(vol->sector_size) - 1;
ntfs_debug("vol->sector_size = %u (0x%x)", vol->sector_size,
vol->sector_size);
ntfs_debug("vol->sector_size_shift = %u", vol->sector_size_shift);
if (vol->sector_size < (u32)vfs_devblocksize(mp)) {
ntfs_error(mp, "Sector size (%u) is smaller than the device "
"block size (%d). This is not supported. "
"Sorry.", vol->sector_size,
vfs_devblocksize(mp));
return ENOTSUP;
}
ntfs_debug("sectors_per_cluster = %u", b->bpb.sectors_per_cluster);
sectors_per_cluster_shift = ffs(b->bpb.sectors_per_cluster) - 1;
ntfs_debug("sectors_per_cluster_shift = %u", sectors_per_cluster_shift);
nr_hidden_sects = le32_to_cpu(b->bpb.hidden_sectors);
ntfs_debug("number of hidden sectors = 0x%x", nr_hidden_sects);
vol->cluster_size = vol->sector_size << sectors_per_cluster_shift;
vol->cluster_size_mask = vol->cluster_size - 1;
vol->cluster_size_shift = ffs(vol->cluster_size) - 1;
ntfs_debug("vol->cluster_size = %u (0x%x)", vol->cluster_size,
vol->cluster_size);
ntfs_debug("vol->cluster_size_mask = 0x%x", vol->cluster_size_mask);
ntfs_debug("vol->cluster_size_shift = %u", vol->cluster_size_shift);
if (vol->cluster_size < vol->sector_size) {
ntfs_error(mp, "Cluster size (%u) is smaller than the sector "
"size (%u). This is not supported. Sorry.",
vol->cluster_size, vol->sector_size);
return ENOTSUP;
}
clusters_per_mft_record = b->clusters_per_mft_record;
ntfs_debug("clusters_per_mft_record = %u (0x%x)",
clusters_per_mft_record, clusters_per_mft_record);
if (clusters_per_mft_record > 0)
vol->mft_record_size = vol->cluster_size *
clusters_per_mft_record;
else
vol->mft_record_size = 1 << -clusters_per_mft_record;
vol->mft_record_size_mask = vol->mft_record_size - 1;
vol->mft_record_size_shift = ffs(vol->mft_record_size) - 1;
ntfs_debug("vol->mft_record_size = %u (0x%x)", vol->mft_record_size,
vol->mft_record_size);
ntfs_debug("vol->mft_record_size_mask = 0x%x",
vol->mft_record_size_mask);
ntfs_debug("vol->mft_record_size_shift = %u)",
vol->mft_record_size_shift);
if (vol->mft_record_size > PAGE_SIZE) {
ntfs_error(mp, "Mft record size (%u) exceeds the PAGE_SIZE on "
"your system (%u). This is not supported. "
"Sorry.", vol->mft_record_size, PAGE_SIZE);
return ENOTSUP;
}
if (vol->mft_record_size < vol->sector_size) {
ntfs_error(mp, "Mft record size (%u) is smaller than the "
"sector size (%u). This is not supported. "
"Sorry.", vol->mft_record_size,
vol->sector_size);
return ENOTSUP;
}
clusters_per_index_block = b->clusters_per_index_block;
ntfs_debug("clusters_per_index_block = %d (0x%x)",
clusters_per_index_block, clusters_per_index_block);
if (clusters_per_index_block > 0) {
vol->index_block_size = vol->cluster_size *
clusters_per_index_block;
vol->blocks_per_index_block = clusters_per_index_block;
} else {
vol->index_block_size = 1 << -clusters_per_index_block;
vol->blocks_per_index_block = vol->index_block_size /
vol->sector_size;
}
vol->index_block_size_mask = vol->index_block_size - 1;
vol->index_block_size_shift = ffs(vol->index_block_size) - 1;
ntfs_debug("vol->index_block_size = %u (0x%x)",
vol->index_block_size, vol->index_block_size);
ntfs_debug("vol->index_block_size_mask = 0x%x",
vol->index_block_size_mask);
ntfs_debug("vol->index_block_size_shift = %u",
vol->index_block_size_shift);
ntfs_debug("vol->blocks_per_index_block = %u",
vol->blocks_per_index_block);
if (vol->index_block_size < vol->sector_size) {
ntfs_error(mp, "Index block size (%u) is smaller than the "
"sector size (%u). This is not supported. "
"Sorry.", vol->index_block_size,
vol->sector_size);
return ENOTSUP;
}
ll = sle64_to_cpu(b->number_of_sectors) >> sectors_per_cluster_shift;
if ((u64)ll >= (u64)1 << 32) {
ntfs_error(mp, "Volume specifies 64-bit clusters but only "
"32-bit clusters are allowed by Microsoft "
"Windows. Weird.");
return EINVAL;
}
vol->nr_clusters = ll;
ntfs_debug("vol->nr_clusters = 0x%llx",
(unsigned long long)vol->nr_clusters);
ll = sle64_to_cpu(b->mft_lcn);
if (ll >= vol->nr_clusters) {
ntfs_error(mp, "MFT LCN (%lld, 0x%llx) is beyond end of "
"volume. Weird.", (unsigned long long)ll,
(unsigned long long)ll);
return EINVAL;
}
vol->mft_lcn = ll;
ntfs_debug("vol->mft_lcn = 0x%llx", (unsigned long long)vol->mft_lcn);
ll = sle64_to_cpu(b->mftmirr_lcn);
if (ll >= vol->nr_clusters) {
ntfs_error(mp, "MFTMirr LCN (%lld, 0x%llx) is beyond end of "
"volume. Weird.", (unsigned long long)ll,
(unsigned long long)ll);
return EINVAL;
}
vol->mftmirr_lcn = ll;
ntfs_debug("vol->mftmirr_lcn = 0x%llx",
(unsigned long long)vol->mftmirr_lcn);
#if 0
if (vol->cluster_size <= ((u32)4 << vol->mft_record_size_shift))
vol->mftmirr_size = 4;
else
vol->mftmirr_size = vol->cluster_size >>
vol->mft_record_size_shift;
#else
vol->mftmirr_size = 4;
#endif
ntfs_debug("vol->mftmirr_size = 0x%x", vol->mftmirr_size);
vol->serial_no = le64_to_cpu(b->volume_serial_number);
ntfs_debug("vol->serial_no = 0x%llx",
(unsigned long long)vol->serial_no);
ntfs_debug("Done.");
return 0;
}
static void ntfs_setup_allocators(ntfs_volume *vol)
{
LCN mft_zone_size, mft_lcn;
ntfs_debug("Entering.");
ntfs_debug("vol->mft_zone_multiplier = 0x%x",
vol->mft_zone_multiplier);
mft_zone_size = vol->nr_clusters;
switch (vol->mft_zone_multiplier) {
case 4:
mft_zone_size >>= 1;
break;
case 3:
mft_zone_size = (mft_zone_size +
(mft_zone_size >> 1)) >> 2;
break;
case 2:
mft_zone_size >>= 2;
break;
default:
mft_zone_size >>= 3;
break;
}
vol->mft_zone_start = vol->mft_zone_pos = vol->mft_lcn;
ntfs_debug("vol->mft_zone_pos = 0x%llx",
(unsigned long long)vol->mft_zone_pos);
mft_lcn = (8192 + 2 * vol->cluster_size - 1) / vol->cluster_size;
if (mft_lcn * vol->cluster_size < 16 * 1024)
mft_lcn = (16 * 1024 + vol->cluster_size - 1) /
vol->cluster_size;
if (vol->mft_zone_start <= mft_lcn)
vol->mft_zone_start = 0;
ntfs_debug("vol->mft_zone_start = 0x%llx",
(unsigned long long)vol->mft_zone_start);
vol->mft_zone_end = vol->mft_lcn + mft_zone_size;
while (vol->mft_zone_end >= vol->nr_clusters) {
mft_zone_size >>= 1;
vol->mft_zone_end = vol->mft_lcn + mft_zone_size;
}
ntfs_debug("vol->mft_zone_end = 0x%llx",
(unsigned long long)vol->mft_zone_end);
vol->data1_zone_pos = vol->mft_zone_end;
ntfs_debug("vol->data1_zone_pos = 0x%llx",
(unsigned long long)vol->data1_zone_pos);
vol->data2_zone_pos = 0;
ntfs_debug("vol->data2_zone_pos = 0x%llx",
(unsigned long long)vol->data2_zone_pos);
vol->mft_data_pos = 24;
ntfs_debug("vol->mft_data_pos = 0x%llx",
(unsigned long long)vol->mft_data_pos);
ntfs_debug("Done.");
}
static errno_t ntfs_mft_inode_get(ntfs_volume *vol)
{
daddr64_t block;
VCN next_vcn, last_vcn, highest_vcn;
ntfs_inode *ni;
MFT_RECORD *m = NULL;
vnode_t dev_vn = vol->dev_vn;
buf_t buf;
ntfs_attr_search_ctx *ctx = NULL;
ATTR_RECORD *a;
STANDARD_INFORMATION *si;
errno_t err;
const int block_size = vol->sector_size;
unsigned nr_blocks, u;
ntfs_attr na;
char *es = " $MFT is corrupt. Run chkdsk.";
const u8 block_size_shift = vol->sector_size_shift;
ntfs_debug("Entering.");
na = (ntfs_attr) {
.mft_no = FILE_MFT,
.type = AT_UNUSED,
.raw = FALSE,
};
ni = ntfs_inode_hash_get(vol, &na);
if (!ni) {
ntfs_error(vol->mp, "Failed to allocate new inode.");
return ENOMEM;
}
if (!NInoAlloc(ni)) {
ntfs_error(vol->mp, "Failed (found stale inode in cache).");
err = ESTALE;
goto err;
}
NInoSetNonResident(ni);
NInoSetMstProtected(ni);
NInoSetSparseDisabled(ni);
ni->type = AT_DATA;
ni->block_size = vol->mft_record_size;
ni->block_size_shift = vol->mft_record_size_shift;
ni->uid = 0;
ni->gid = 0;
ni->mode = S_IFREG;
m = OSMalloc(vol->mft_record_size, ntfs_malloc_tag);
if (!m) {
ntfs_error(vol->mp, "Failed to allocate buffer for $MFT "
"record 0.");
err = ENOMEM;
goto err;
}
block = vol->mft_lcn << (vol->cluster_size_shift - block_size_shift);
nr_blocks = vol->mft_record_size >> block_size_shift;
if (!nr_blocks)
nr_blocks = 1;
for (u = 0; u < nr_blocks; u++, block++) {
u8 *src;
err = buf_meta_bread(dev_vn, block, block_size, NOCRED, &buf);
buf_setflags(buf, B_NOCACHE);
if (err) {
ntfs_error(vol->mp, "Failed to read $MFT record 0 "
"(block %u, physical block 0x%llx, "
"physical block size %d).", u,
(unsigned long long)block, block_size);
buf_brelse(buf);
goto err;
}
err = buf_map(buf, (caddr_t*)&src);
if (err) {
ntfs_error(vol->mp, "Failed to map buffer of mft "
"record 0 (block %u, physical block "
"0x%llx, physical block size %d).", u,
(unsigned long long)block, block_size);
buf_brelse(buf);
goto err;
}
memcpy((u8*)m + (u << block_size_shift), src, block_size);
err = buf_unmap(buf);
if (err)
ntfs_error(vol->mp, "Failed to unmap buffer of mft "
"record 0 (error %d).", err);
buf_brelse(buf);
}
err = ntfs_mst_fixup_post_read((NTFS_RECORD*)m, vol->mft_record_size);
if (err) {
ntfs_error(vol->mp, "MST fixup failed.%s", es);
goto io_err;
}
ni->seq_no = le16_to_cpu(m->sequence_number);
ni->link_count = le16_to_cpu(m->link_count);
ctx = ntfs_attr_search_ctx_get(ni, m);
if (!ctx) {
err = ENOMEM;
goto err;
}
err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, AT_UNNAMED, 0, 0, NULL,
0, ctx);
a = ctx->a;
if (err || a->non_resident || a->flags) {
if (err) {
if (err == ENOENT) {
ntfs_error(vol->mp, "Standard information "
"attribute is missing.");
} else
ntfs_error(vol->mp, "Failed to lookup "
"standard information "
"attribute.");
} else {
info_err:
ntfs_error(vol->mp, "Standard information attribute "
"is corrupt.");
err = EIO;
}
goto err;
}
si = (STANDARD_INFORMATION*)((u8*)a +
le16_to_cpu(a->value_offset));
if ((u8*)si < (u8*)a || (u8*)si + le32_to_cpu(a->value_length) >
(u8*)a + le32_to_cpu(a->length) ||
(u8*)a + le32_to_cpu(a->length) > (u8*)ctx->m +
vol->mft_record_size)
goto info_err;
ni->creation_time = ntfs2utc(si->creation_time);
ni->last_data_change_time = ntfs2utc(si->last_data_change_time);
ni->last_mft_change_time = ntfs2utc(si->last_mft_change_time);
ni->last_access_time = ntfs2utc(si->last_access_time);
ntfs_attr_search_ctx_reinit(ctx);
err = ntfs_attr_lookup(AT_ATTRIBUTE_LIST, AT_UNNAMED, 0, 0, NULL, 0,
ctx);
if (err) {
if (err != ENOENT) {
ntfs_error(vol->mp, "Failed to lookup attribute list "
"attribute.%s", es);
goto err;
}
ntfs_debug("$MFT does not have an attribute list attribute.");
} else {
ATTR_LIST_ENTRY *al_entry, *next_al_entry;
u8 *al_end;
ntfs_debug("Attribute list attribute found in $MFT.");
NInoSetAttrList(ni);
a = ctx->a;
if (a->flags & ATTR_COMPRESSION_MASK) {
ntfs_error(vol->mp, "Attribute list attribute is "
"compressed. Not allowed.%s", es);
goto io_err;
}
if (a->flags & (ATTR_IS_ENCRYPTED | ATTR_IS_SPARSE)) {
if (a->non_resident) {
ntfs_error(vol->mp, "Non-resident attribute "
"list attribute is encrypted/"
"sparse. Not allowed.%s", es);
goto io_err;
}
ntfs_warning(vol->mp, "Resident attribute list "
"attribute is marked encrypted/sparse "
"which is not true. However, Windows "
"allows this and chkdsk does not "
"detect or correct it so we will just "
"ignore the invalid flags and pretend "
"they are not set.");
}
ni->attr_list_size = (u32)ntfs_attr_size(a);
ni->attr_list_alloc = (ni->attr_list_size + NTFS_ALLOC_BLOCK -
1) & ~(NTFS_ALLOC_BLOCK - 1);
ni->attr_list = OSMalloc(ni->attr_list_alloc, ntfs_malloc_tag);
if (!ni->attr_list) {
ni->attr_list_alloc = 0;
ntfs_error(vol->mp, "Not enough memory to allocate "
"buffer for attribute list.");
err = ENOMEM;
goto err;
}
if (a->non_resident) {
NInoSetAttrListNonResident(ni);
if (a->lowest_vcn) {
ntfs_error(vol->mp, "Attribute list has non-"
"zero lowest_vcn.%s", es);
goto io_err;
}
err = ntfs_mapping_pairs_decompress(vol, a,
&ni->attr_list_rl);
if (err) {
ntfs_error(vol->mp, "Mapping pairs "
"decompression failed with "
"error code %d.%s", err, es);
goto err;
}
err = ntfs_rl_read(vol, &ni->attr_list_rl,
ni->attr_list, (s64)ni->attr_list_size,
sle64_to_cpu(a->initialized_size));
if (err) {
ntfs_error(vol->mp, "Failed to load attribute "
"list attribute with error "
"code %d.", err);
goto err;
}
} else {
u8 *al = (u8*)a + le16_to_cpu(a->value_offset);
u8 *a_end = (u8*)a + le32_to_cpu(a->length);
if (al < (u8*)a || al + le32_to_cpu(a->value_length) >
a_end || (u8*)a_end > (u8*)ctx->m +
vol->mft_record_size) {
ntfs_error(vol->mp, "Corrupt attribute list "
"attribute.%s", es);
goto io_err;
}
memcpy(ni->attr_list, (u8*)a +
le16_to_cpu(a->value_offset),
ni->attr_list_size);
}
al_entry = (ATTR_LIST_ENTRY*)ni->attr_list;
al_end = (u8*)al_entry + ni->attr_list_size;
for (;; al_entry = next_al_entry) {
if ((u8*)al_entry < ni->attr_list ||
(u8*)al_entry > al_end)
goto em_err;
if ((u8*)al_entry == al_end)
goto em_err;
if (!al_entry->length)
goto em_err;
if ((u8*)al_entry + 6 > al_end || (u8*)al_entry +
le16_to_cpu(al_entry->length) > al_end)
goto em_err;
next_al_entry = (ATTR_LIST_ENTRY*)((u8*)al_entry +
le16_to_cpu(al_entry->length));
if (le32_to_cpu(al_entry->type) >
const_le32_to_cpu(AT_DATA))
goto em_err;
if (al_entry->type != AT_DATA)
continue;
if (al_entry->name_length)
goto em_err;
if (al_entry->lowest_vcn)
goto em_err;
if (MREF_LE(al_entry->mft_reference) != ni->mft_no) {
ntfs_error(vol->mp, "BUG: The first $DATA "
"extent of $MFT is not in the "
"base mft record. Please "
"report you saw this message "
"to %s.", ntfs_dev_email);
goto io_err;
}
if (MSEQNO_LE(al_entry->mft_reference) != ni->seq_no)
goto em_err;
break;
}
}
ntfs_attr_search_ctx_reinit(ctx);
a = NULL;
next_vcn = last_vcn = highest_vcn = 0;
while (!(err = ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, next_vcn, NULL,
0, ctx))) {
a = ctx->a;
if (!a->non_resident) {
ntfs_error(vol->mp, "$MFT must be non-resident but a "
"resident extent was found.%s", es);
goto io_err;
}
if (a->flags & ATTR_COMPRESSION_MASK ||
a->flags & ATTR_IS_ENCRYPTED ||
a->flags & ATTR_IS_SPARSE) {
ntfs_error(vol->mp, "$MFT must be uncompressed, "
"non-sparse, and unencrypted but a "
"compressed/sparse/encrypted extent "
"was found.%s", es);
goto io_err;
}
err = ntfs_mapping_pairs_decompress(vol, a, &ni->rl);
if (err) {
ntfs_error(vol->mp, "Mapping pairs decompression "
"failed with error code %d.%s", err,
es);
goto err;
}
highest_vcn = sle64_to_cpu(a->highest_vcn);
if (!next_vcn) {
if (a->lowest_vcn) {
ntfs_error(vol->mp, "First extent of $DATA "
"attribute has non zero "
"lowest_vcn.%s", es);
goto io_err;
}
last_vcn = sle64_to_cpu(a->allocated_size)
>> vol->cluster_size_shift;
ni->allocated_size = sle64_to_cpu(a->allocated_size);
ni->data_size = sle64_to_cpu(a->data_size);
ni->initialized_size = sle64_to_cpu(
a->initialized_size);
if (ni->allocated_size & vol->cluster_size_mask ||
ni->data_size &
vol->mft_record_size_mask ||
ni->initialized_size &
vol->mft_record_size_mask) {
ntfs_error(vol->mp, "$DATA attribute contains "
"invalid size.%s", es);
goto io_err;
}
if (ni->data_size >> vol->mft_record_size_shift >=
1LL << 32) {
ntfs_error(vol->mp, "$MFT is too big. "
"Aborting.");
goto io_err;
}
err = ntfs_inode_add_vnode(ni, TRUE, NULL, NULL);
if (err) {
ntfs_error(vol->mp, "Failed to create a "
"system vnode for $MFT (error "
"%d).", err);
goto err;
}
err = vnode_ref(ni->vn);
if (err)
ntfs_error(vol->mp, "vnode_ref() failed!");
OSIncrementAtomic(&ni->nr_refs);
vol->mft_ni = ni;
ntfs_inode_unlock_alloc(ni);
(void)vnode_put(ni->vn);
if (highest_vcn == last_vcn - 1)
break;
}
next_vcn = highest_vcn + 1;
if (next_vcn <= 0) {
ntfs_error(vol->mp, "Invalid highest vcn in attribute "
"extent.%s", es);
goto io_err;
}
if (next_vcn < sle64_to_cpu(a->lowest_vcn)) {
ntfs_error(vol->mp, "Corrupt attribute extent would "
"cause endless loop, aborting.%s", es);
goto io_err;
}
}
if (err && err != ENOENT) {
ntfs_error(vol->mp, "Failed to lookup $MFT/$DATA attribute "
"extent.%s", es);
goto err;
}
if (!a) {
ntfs_error(vol->mp, "$MFT/$DATA attribute not found.%s", es);
err = ENOENT;
goto err;
}
if (highest_vcn != last_vcn - 1) {
ntfs_error(vol->mp, "Failed to load the complete runlist for "
"$MFT/$DATA. Driver bug or corrupt $MFT. "
"Run chkdsk.");
ntfs_debug("highest_vcn = 0x%llx, last_vcn - 1 = 0x%llx",
(unsigned long long)highest_vcn,
(unsigned long long)(last_vcn - 1));
goto io_err;
}
ntfs_attr_search_ctx_put(ctx);
OSFree(m, vol->mft_record_size, ntfs_malloc_tag);
ntfs_debug("Done.");
return 0;
em_err:
ntfs_error(vol->mp, "Could not find first extent of $DATA attribute "
"in attribute list.%s", es);
io_err:
err = EIO;
err:
if (ctx)
ntfs_attr_search_ctx_put(ctx);
if (m)
OSFree(m, vol->mft_record_size, ntfs_malloc_tag);
if (!vol->mft_ni)
ntfs_inode_reclaim(ni);
return err;
}
static errno_t ntfs_inode_attach(ntfs_volume *vol, const ino64_t mft_no,
ntfs_inode **ni, vnode_t parent_vn)
{
vnode_t vn;
errno_t err;
ntfs_debug("Entering.");
err = ntfs_inode_get(vol, mft_no, TRUE, LCK_RW_TYPE_SHARED, ni,
parent_vn, NULL);
if (err) {
ntfs_error(vol->mp, "Failed to load inode 0x%llx.",
(unsigned long long)mft_no);
*ni = NULL;
return err;
}
if (parent_vn)
OSIncrementAtomic(&NTFS_I(parent_vn)->nr_refs);
vn = (*ni)->vn;
err = vnode_ref(vn);
if (err)
ntfs_error(vol->mp, "vnode_ref() failed!");
OSIncrementAtomic(&(*ni)->nr_refs);
lck_rw_unlock_shared(&(*ni)->lock);
(void)vnode_put(vn);
ntfs_debug("Done.");
return 0;
}
static errno_t ntfs_attr_inode_attach(ntfs_inode *base_ni,
const ATTR_TYPE type, ntfschar *name, const u32 name_len,
ntfs_inode **ni)
{
vnode_t vn;
errno_t err;
ntfs_debug("Entering.");
err = ntfs_attr_inode_get(base_ni, type, name, name_len, TRUE,
LCK_RW_TYPE_SHARED, ni);
if (err) {
ntfs_error(base_ni->vol->mp, "Failed to load attribute inode "
"0x%llx, attribute type 0x%x, name length "
"0x%x.", (unsigned long long)base_ni->mft_no,
(unsigned)le32_to_cpu(type),
(unsigned)name_len);
*ni = NULL;
return err;
}
OSIncrementAtomic(&base_ni->nr_refs);
vn = (*ni)->vn;
err = vnode_ref(vn);
if (err)
ntfs_error(base_ni->vol->mp, "vnode_ref() failed!");
OSIncrementAtomic(&(*ni)->nr_refs);
lck_rw_unlock_shared(&(*ni)->lock);
(void)vnode_put(vn);
ntfs_debug("Done.");
return 0;
}
static errno_t ntfs_index_inode_attach(ntfs_inode *base_ni, ntfschar *name,
const u32 name_len, ntfs_inode **ni)
{
vnode_t vn;
errno_t err;
ntfs_debug("Entering.");
err = ntfs_index_inode_get(base_ni, name, name_len, TRUE, ni);
if (err) {
ntfs_error(base_ni->vol->mp, "Failed to load index inode "
"0x%llx, name length 0x%x.",
(unsigned long long)base_ni->mft_no,
(unsigned)name_len);
*ni = NULL;
return err;
}
OSIncrementAtomic(&base_ni->nr_refs);
vn = (*ni)->vn;
err = vnode_ref(vn);
if (err)
ntfs_error(base_ni->vol->mp, "vnode_ref() failed!");
OSIncrementAtomic(&(*ni)->nr_refs);
(void)vnode_put(vn);
ntfs_debug("Done.");
return 0;
}
static errno_t ntfs_mft_mirror_load(ntfs_volume *vol)
{
ntfs_inode *ni;
vnode_t vn;
errno_t err;
ntfs_debug("Entering.");
err = ntfs_inode_get(vol, FILE_MFTMirr, TRUE, LCK_RW_TYPE_SHARED, &ni,
vol->root_ni->vn, NULL);
if (err) {
ntfs_error(vol->mp, "Failed to load inode 0x%llx.",
(unsigned long long)FILE_MFTMirr);
return err;
}
vn = ni->vn;
ni->uid = 0;
ni->gid = 0;
ni->mode = S_IFREG;
NInoSetSparseDisabled(ni);
ni->block_size = vol->mft_record_size;
ni->block_size_shift = vol->mft_record_size_shift;
if (ni->allocated_size & vol->cluster_size_mask ||
ni->data_size & vol->mft_record_size_mask ||
ni->initialized_size & vol->mft_record_size_mask) {
ntfs_error(vol->mp, "$DATA attribute contains invalid size. "
"$MFTMirr is corrupt. Run chkdsk.");
(void)vnode_put(vn);
return EIO;
}
OSIncrementAtomic(&vol->root_ni->nr_refs);
err = vnode_ref(vn);
if (err)
ntfs_error(vol->mp, "vnode_ref() failed!");
OSIncrementAtomic(&ni->nr_refs);
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(vn);
vol->mftmirr_ni = ni;
ntfs_debug("Done.");
return 0;
}
static errno_t ntfs_mft_mirror_check(ntfs_volume *vol)
{
ntfs_inode *ni;
buf_t buf;
u8 *mirr_start;
MFT_RECORD *mirr, *m;
unsigned nr_mirr_recs, alloc_size, rec_size, i;
errno_t err, err2;
ntfs_debug("Entering.");
if (!vol->mftmirr_size)
panic("%s(): !vol->mftmirr_size\n", __FUNCTION__);
nr_mirr_recs = vol->mftmirr_size;
if (!nr_mirr_recs)
panic("%s(): !nr_mirr_recs\n", __FUNCTION__);
rec_size = vol->mft_record_size;
alloc_size = nr_mirr_recs << vol->mft_record_size_shift;
mirr_start = OSMalloc(alloc_size, ntfs_malloc_tag);
if (!mirr_start) {
ntfs_error(vol->mp, "Failed to allocate temporary mft mirror "
"buffer.");
return ENOMEM;
}
mirr = (MFT_RECORD*)mirr_start;
ni = vol->mftmirr_ni;
err = vnode_getwithref(ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to get vnode for $MFTMirr.");
goto err;
}
lck_rw_lock_shared(&ni->lock);
for (i = 0; i < nr_mirr_recs; i++) {
err = buf_meta_bread(ni->vn, i, rec_size, NOCRED, &buf);
if (err) {
ntfs_error(vol->mp, "Failed to read $MFTMirr record "
"%d (error %d).", i, err);
goto brelse;
}
err = buf_map(buf, (caddr_t*)&m);
if (err) {
ntfs_error(vol->mp, "Failed to map buffer of $MFTMirr "
"record %d (error %d).", i, err);
goto brelse;
}
memcpy(mirr, m, rec_size);
err = buf_unmap(buf);
if (err) {
ntfs_error(vol->mp, "Failed to unmap buffer of "
"$MFTMirr record %d (error %d).", i,
err);
goto brelse;
}
buf_brelse(buf);
err = ntfs_mst_fixup_post_read((NTFS_RECORD*)mirr, rec_size);
if (mirr->flags & MFT_RECORD_IN_USE) {
if (err || ntfs_is_baad_record(mirr->magic)) {
ntfs_error(vol->mp, "Incomplete multi sector "
"transfer detected in mft "
"mirror record %d.", i);
if (!err)
err = EIO;
goto unlock;
}
}
mirr = (MFT_RECORD*)((u8*)mirr + rec_size);
}
lck_rw_lock_shared(&ni->rl.lock);
if (ni->rl.rl->lcn != vol->mftmirr_lcn ||
ni->rl.rl->length < (((s64)vol->mftmirr_size <<
vol->mft_record_size_shift) +
vol->cluster_size_mask) >> vol->cluster_size_shift) {
ntfs_error(vol->mp, "$MFTMirr location mismatch. Run "
"chkdsk.");
err = EIO;
} else
ntfs_debug("Done.");
lck_rw_unlock_shared(&ni->rl.lock);
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(ni->vn);
ni = vol->mft_ni;
err = vnode_getwithref(ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to get vnode for $MFT.");
goto err;
}
lck_rw_lock_shared(&ni->lock);
mirr = (MFT_RECORD*)mirr_start;
for (i = 0; i < nr_mirr_recs; i++) {
unsigned bytes;
err = buf_meta_bread(ni->vn, i, rec_size, NOCRED, &buf);
if (err) {
ntfs_error(vol->mp, "Failed to read $MFT record %d "
"(error %d).", i, err);
goto brelse;
}
err = buf_map(buf, (caddr_t*)&m);
if (err) {
ntfs_error(vol->mp, "Failed to map buffer of $MFT "
"record %d (error %d).", i, err);
goto brelse;
}
if (m->flags & MFT_RECORD_IN_USE) {
if (ntfs_is_baad_record(m->magic)) {
ntfs_error(vol->mp, "Incomplete multi sector "
"transfer detected in mft "
"record %d.", i);
err = EIO;
goto unmap;
}
}
bytes = le32_to_cpu(m->bytes_in_use);
if (bytes < sizeof(MFT_RECORD_OLD) || bytes > rec_size ||
ntfs_is_baad_record(m->magic)) {
bytes = le32_to_cpu(mirr->bytes_in_use);
if (bytes < sizeof(MFT_RECORD_OLD) ||
bytes > rec_size ||
ntfs_is_baad_record(mirr->magic))
bytes = rec_size;
}
if (bcmp(m, mirr, bytes)) {
ntfs_error(vol->mp, "$MFT and $MFTMirr (record %d) do "
"not match. Run chkdsk.", i);
err = EIO;
goto unmap;
}
mirr = (MFT_RECORD*)((u8*)mirr + rec_size);
err = buf_unmap(buf);
if (err) {
ntfs_error(vol->mp, "Failed to unmap buffer of $MFT "
"record %d (error %d).", i, err);
goto brelse;
}
buf_brelse(buf);
}
unlock:
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(ni->vn);
err:
OSFree(mirr_start, alloc_size, ntfs_malloc_tag);
return err;
unmap:
err2 = buf_unmap(buf);
if (err2)
ntfs_error(vol->mp, "Failed to unmap buffer of mft record %d "
"in error code path (error %d).", i, err2);
brelse:
buf_brelse(buf);
goto unlock;
}
static errno_t ntfs_upcase_load(ntfs_volume *vol)
{
s64 ofs, data_size = 0;
ntfs_inode *ni;
upl_t upl;
upl_page_info_array_t pl;
u8 *kaddr;
errno_t err;
unsigned u;
ntfs_debug("Entering.");
err = ntfs_inode_get(vol, FILE_UpCase, TRUE, LCK_RW_TYPE_SHARED, &ni,
vol->root_ni->vn, NULL);
if (err) {
ni = NULL;
goto err;
}
lck_spin_lock(&ni->size_lock);
data_size = ni->data_size;
lck_spin_unlock(&ni->size_lock);
if (data_size <= 0 || data_size & (sizeof(ntfschar) - 1) ||
data_size > (s64)(64 * 1024 * sizeof(ntfschar))) {
err = EINVAL;
goto err;
}
vol->upcase = OSMalloc(data_size, ntfs_malloc_tag);
if (!vol->upcase) {
err = ENOMEM;
goto err;
}
u = PAGE_SIZE;
for (ofs = 0; ofs < data_size; ofs += PAGE_SIZE) {
err = ntfs_page_map(ni, ofs, &upl, &pl, &kaddr, FALSE);
if (err)
goto err;
if (ofs + u > data_size)
u = data_size - ofs;
memcpy((u8*)vol->upcase + ofs, kaddr, u);
ntfs_page_unmap(ni, upl, pl, FALSE);
}
lck_rw_unlock_shared(&ni->lock);
(void)vnode_recycle(ni->vn);
(void)vnode_put(ni->vn);
vol->upcase_len = data_size >> NTFSCHAR_SIZE_SHIFT;
ntfs_debug("Read %lld bytes from $UpCase (expected %lu bytes).",
(long long)data_size, 64LU * 1024 * sizeof(ntfschar));
lck_mtx_lock(&ntfs_lock);
if (!ntfs_default_upcase) {
ntfs_debug("Using volume specified $UpCase since default is "
"not present.");
} else {
unsigned max_size;
max_size = ntfs_default_upcase_size >> NTFSCHAR_SIZE_SHIFT;
if (max_size > vol->upcase_len)
max_size = vol->upcase_len;
for (u = 0; u < max_size; u++)
if (vol->upcase[u] != ntfs_default_upcase[u])
break;
if (u == max_size) {
OSFree(vol->upcase, data_size, ntfs_malloc_tag);
vol->upcase = ntfs_default_upcase;
vol->upcase_len = ntfs_default_upcase_size >>
NTFSCHAR_SIZE_SHIFT;
ntfs_default_upcase_users++;
ntfs_debug("Volume specified $UpCase matches "
"default. Using default.");
} else
ntfs_debug("Using volume specified $UpCase since it "
"does not match the default.");
}
lck_mtx_unlock(&ntfs_lock);
ntfs_debug("Done.");
return 0;
err:
if (vol->upcase) {
OSFree(vol->upcase, data_size, ntfs_malloc_tag);
vol->upcase = NULL;
vol->upcase_len = 0;
}
if (ni) {
lck_rw_unlock_shared(&ni->lock);
(void)vnode_recycle(ni->vn);
(void)vnode_put(ni->vn);
}
lck_mtx_lock(&ntfs_lock);
if (ntfs_default_upcase) {
vol->upcase = ntfs_default_upcase;
vol->upcase_len = ntfs_default_upcase_size >>
NTFSCHAR_SIZE_SHIFT;
ntfs_default_upcase_users++;
ntfs_error(vol->mp, "Failed to load $UpCase from the volume "
"(error %d). Using NTFS driver default "
"upcase table instead.", err);
err = 0;
} else
ntfs_error(vol->mp, "Failed to initialize upcase table.");
lck_mtx_unlock(&ntfs_lock);
return err;
}
static errno_t ntfs_attrdef_load(ntfs_volume *vol)
{
s64 ofs, data_size = 0;
ntfs_inode *ni;
upl_t upl;
upl_page_info_array_t pl;
u8 *kaddr;
errno_t err;
unsigned u;
ntfs_debug("Entering.");
err = ntfs_inode_get(vol, FILE_AttrDef, TRUE, LCK_RW_TYPE_SHARED, &ni,
vol->root_ni->vn, NULL);
if (err) {
ni = NULL;
goto err;
}
lck_spin_lock(&ni->size_lock);
data_size = ni->data_size;
lck_spin_unlock(&ni->size_lock);
if (data_size <= 0 || data_size > 0x7fffffff) {
err = EINVAL;
goto err;
}
vol->attrdef = OSMalloc(data_size, ntfs_malloc_tag);
if (!vol->attrdef) {
err = ENOMEM;
goto err;
}
u = PAGE_SIZE;
for (ofs = 0; ofs < data_size; ofs += PAGE_SIZE) {
err = ntfs_page_map(ni, ofs, &upl, &pl, &kaddr, FALSE);
if (err)
goto err;
if (ofs + u > data_size)
u = data_size - ofs;
memcpy((u8*)vol->attrdef + ofs, kaddr, u);
ntfs_page_unmap(ni, upl, pl, FALSE);
}
lck_rw_unlock_shared(&ni->lock);
(void)vnode_recycle(ni->vn);
(void)vnode_put(ni->vn);
vol->attrdef_size = data_size;
ntfs_debug("Done. Read %lld bytes from $AttrDef.",
(long long)data_size);
return 0;
err:
if (vol->attrdef) {
OSFree(vol->attrdef, data_size, ntfs_malloc_tag);
vol->attrdef = NULL;
}
if (ni) {
lck_rw_unlock_shared(&ni->lock);
(void)vnode_recycle(ni->vn);
(void)vnode_put(ni->vn);
}
ntfs_error(vol->mp, "Failed to initialize attribute definitions "
"table.");
return err;
}
static errno_t ntfs_volume_load(ntfs_volume *vol)
{
ntfs_inode *ni;
MFT_RECORD *m;
ntfs_attr_search_ctx *ctx;
ATTR_RECORD *a;
VOLUME_INFORMATION *vi;
errno_t err;
ntfs_debug("Entering.");
err = ntfs_inode_attach(vol, FILE_Volume, &ni, vol->root_ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to load $Volume.");
return err;
}
vol->vol_ni = ni;
err = vnode_getwithref(ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to get vnode for $Volume.");
return err;
}
err = ntfs_mft_record_map(ni, &m);
if (err) {
ntfs_error(vol->mp, "Failed to map mft record for $Volume.");
goto err;
}
ctx = ntfs_attr_search_ctx_get(ni, m);
if (!ctx) {
ntfs_error(vol->mp, "Failed to get attribute search context "
"for $Volume.");
err = ENOMEM;
goto unm_err;
}
err = ntfs_attr_lookup(AT_VOLUME_INFORMATION, AT_UNNAMED, 0, 0, NULL,
0, ctx);
a = ctx->a;
if (err || a->non_resident || a->flags) {
if (err)
ntfs_error(vol->mp, "Failed to lookup volume "
"information attribute in $Volume.");
else {
info_err:
ntfs_error(vol->mp, "Volume information attribute in "
"$Volume is corrupt. Run chkdsk.");
}
goto put_err;
}
vi = (VOLUME_INFORMATION*)((u8*)a + le16_to_cpu(a->value_offset));
if ((u8*)vi < (u8*)a || (u8*)vi + le32_to_cpu(a->value_length) >
(u8*)a + le32_to_cpu(a->length) ||
(u8*)a + le32_to_cpu(a->length) > (u8*)ctx->m +
vol->mft_record_size)
goto info_err;
vol->vol_flags = vi->flags;
vol->major_ver = vi->major_ver;
vol->minor_ver = vi->minor_ver;
ntfs_attr_search_ctx_reinit(ctx);
err = ntfs_attr_lookup(AT_VOLUME_NAME, AT_UNNAMED, 0, 0, NULL, 0, ctx);
if (err == ENOENT) {
ntfs_debug("Volume has no name, using empty string.");
no_name:
vol->name = OSMalloc(sizeof(char), ntfs_malloc_tag);
if (!vol->name) {
ntfs_error(vol->mp, "Failed to allocate memory for "
"volume name.");
err = ENOMEM;
goto put_err;
}
vol->name[0] = '\0';
} else {
ntfschar *ntfs_name;
u8 *utf8_name;
size_t ntfs_size, utf8_size;
signed res_size;
a = ctx->a;
if (err || a->non_resident || a->flags) {
if (err)
ntfs_error(vol->mp, "Failed to lookup volume "
"name attribute in $Volume.");
else {
name_err:
ntfs_error(vol->mp, "Volume name attribute in "
"$Volume is corrupt. Run "
"chkdsk.");
}
put_err:
ntfs_attr_search_ctx_put(ctx);
if (!err)
err = EIO;
goto unm_err;
}
ntfs_name = (ntfschar*)((u8*)a + le16_to_cpu(a->value_offset));
ntfs_size = le32_to_cpu(a->value_length);
if (!ntfs_size) {
ntfs_debug("Volume has empty name, using empty "
"string.");
goto no_name;
}
if ((u8*)ntfs_name < (u8*)a || (u8*)ntfs_name + ntfs_size >
(u8*)a + le32_to_cpu(a->length) ||
(u8*)a + le32_to_cpu(a->length) > (u8*)ctx->m +
vol->mft_record_size)
goto name_err;
utf8_name = NULL;
res_size = ntfs_to_utf8(vol, ntfs_name, ntfs_size, &utf8_name,
&utf8_size);
if (res_size < 0) {
err = -res_size;
ntfs_error(vol->mp, "Failed to convert volume name to "
"decomposed UTF-8 (error %d).",
(int)err);
goto put_err;
}
vol->name = (char*)utf8_name;
vol->name_size = utf8_size;
}
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(ni);
(void)vnode_put(ni->vn);
ntfs_debug("Done.");
return 0;
unm_err:
ntfs_mft_record_unmap(ni);
err:
(void)vnode_put(ni->vn);
return err;
}
#define NTFS_HIBERFIL_HEADER_SIZE 4096
static errno_t ntfs_windows_hibernation_status_check(ntfs_volume *vol,
BOOL *is_hibernated)
{
s64 data_size;
MFT_REF mref;
ntfs_dir_lookup_name *name = NULL;
ntfs_inode *ni;
upl_t upl = NULL;
upl_page_info_array_t pl;
le32 *kaddr, *kend;
errno_t err;
static const ntfschar hiberfil[13] = { const_cpu_to_le16('h'),
const_cpu_to_le16('i'), const_cpu_to_le16('b'),
const_cpu_to_le16('e'), const_cpu_to_le16('r'),
const_cpu_to_le16('f'), const_cpu_to_le16('i'),
const_cpu_to_le16('l'), const_cpu_to_le16('.'),
const_cpu_to_le16('s'), const_cpu_to_le16('y'),
const_cpu_to_le16('s'), 0 };
ntfs_debug("Entering.");
*is_hibernated = FALSE;
lck_rw_lock_shared(&vol->root_ni->lock);
err = ntfs_lookup_inode_by_name(vol->root_ni, hiberfil, 12, &mref,
&name);
lck_rw_unlock_shared(&vol->root_ni->lock);
if (err) {
if (err == ENOENT) {
ntfs_debug("hiberfil.sys not present. Windows is not "
"hibernated on the volume.");
return 0;
}
ntfs_error(vol->mp, "Failed to find inode number for "
"hiberfil.sys.");
return err;
}
if (name)
OSFree(name, sizeof(*name), ntfs_malloc_tag);
err = ntfs_inode_get(vol, MREF(mref), FALSE, LCK_RW_TYPE_SHARED, &ni,
vol->root_ni->vn, NULL);
if (err) {
ntfs_error(vol->mp, "Failed to load hiberfil.sys.");
return err;
}
lck_spin_lock(&ni->size_lock);
data_size = ni->data_size;
lck_spin_unlock(&ni->size_lock);
if (data_size < NTFS_HIBERFIL_HEADER_SIZE) {
ntfs_debug("Hiberfil.sys is present and smaller than the "
"hibernation header size. Windows is "
"hibernated on the volume. This is not the "
"system volume.");
*is_hibernated = TRUE;
goto put;
}
err = ntfs_page_map(ni, 0, &upl, &pl, (u8**)&kaddr, FALSE);
if (err) {
ntfs_error(vol->mp, "Failed to read from hiberfil.sys.");
goto put;
}
if (*kaddr == const_cpu_to_le32(0x72626968)) {
ntfs_debug("Magic \"hibr\" found in hiberfil.sys. Windows is "
"hibernated on the volume. This is the "
"system volume.");
*is_hibernated = TRUE;
goto unm;
}
kend = kaddr + NTFS_HIBERFIL_HEADER_SIZE/sizeof(*kaddr);
do {
if (*kaddr) {
ntfs_debug("hiberfil.sys is larger than 4kiB "
"(0x%llx), does not contain the "
"\"hibr\" magic, and does not have a "
"zero header. Windows is hibernated "
"on the volume. This is not the "
"system volume.", data_size);
*is_hibernated = TRUE;
goto unm;
}
} while (++kaddr < kend);
ntfs_debug("hiberfil.sys contains a zero header. Windows is not "
"hibernated on the volume. This is the system "
"volume.");
unm:
ntfs_page_unmap(ni, upl, pl, FALSE);
put:
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(ni->vn);
return err;
}
static errno_t ntfs_volume_flags_write(ntfs_volume *vol,
const VOLUME_FLAGS flags)
{
ntfs_inode *ni;
MFT_RECORD *m;
VOLUME_INFORMATION *vi;
ntfs_attr_search_ctx *ctx;
errno_t err;
ntfs_debug("Entering, old flags = 0x%x, new flags = 0x%x.",
le16_to_cpu(vol->vol_flags), le16_to_cpu(flags));
if (vol->vol_flags == flags)
goto done;
ni = vol->vol_ni;
if (!ni)
panic("%s(): Volume inode is not loaded.\n", __FUNCTION__);
err = ntfs_mft_record_map(ni, &m);
if (err)
goto err;
ctx = ntfs_attr_search_ctx_get(ni, m);
if (!ctx) {
err = ENOMEM;
goto put;
}
err = ntfs_attr_lookup(AT_VOLUME_INFORMATION, AT_UNNAMED, 0, 0, NULL,
0, ctx);
if (err)
goto put;
vi = (VOLUME_INFORMATION*)((u8*)ctx->a +
le16_to_cpu(ctx->a->value_offset));
vol->vol_flags = vi->flags = flags;
NInoSetMrecNeedsDirtying(ctx->ni);
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(ni);
done:
ntfs_debug("Done.");
return 0;
put:
if (ctx)
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(ni);
err:
ntfs_error(vol->mp, "Failed with error code %d.", err);
return err;
}
static inline errno_t ntfs_volume_flags_set(ntfs_volume *vol,
VOLUME_FLAGS flags)
{
flags &= VOLUME_FLAGS_MASK;
return ntfs_volume_flags_write(vol, vol->vol_flags | flags);
}
static inline errno_t ntfs_volume_flags_clear(ntfs_volume *vol,
VOLUME_FLAGS flags)
{
flags &= VOLUME_FLAGS_MASK;
return ntfs_volume_flags_write(vol, vol->vol_flags & ~flags);
}
static errno_t ntfs_secure_load(ntfs_volume *vol)
{
ntfs_inode *ni;
MFT_RECORD *m;
ntfs_attr_search_ctx *ctx;
FILENAME_ATTR *fn;
errno_t err;
static const ntfschar Secure[8] = { const_cpu_to_le16('$'),
const_cpu_to_le16('S'), const_cpu_to_le16('e'),
const_cpu_to_le16('c'), const_cpu_to_le16('u'),
const_cpu_to_le16('r'), const_cpu_to_le16('e'), 0 };
static ntfschar SDS[5] = { const_cpu_to_le16('$'),
const_cpu_to_le16('S'), const_cpu_to_le16('D'),
const_cpu_to_le16('S'), 0 };
static ntfschar SDH[5] = { const_cpu_to_le16('$'),
const_cpu_to_le16('S'), const_cpu_to_le16('D'),
const_cpu_to_le16('H'), 0 };
static ntfschar SII[5] = { const_cpu_to_le16('$'),
const_cpu_to_le16('S'), const_cpu_to_le16('I'),
const_cpu_to_le16('I'), 0 };
ntfs_debug("Entering.");
err = ntfs_inode_attach(vol, FILE_Secure, &ni, vol->root_ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to load $Secure.");
return err;
}
vol->secure_ni = ni;
err = ntfs_mft_record_map(ni, &m);
if (err) {
ntfs_error(vol->mp, "Failed to map mft record for $Secure.");
return err;
}
if (!(m->flags & MFT_RECORD_IN_USE)) {
not_in_use:
ntfs_debug("Done ($Secure is not in use).");
ntfs_mft_record_unmap(ni);
NVolSetUseSDAttr(vol);
return 0;
}
ctx = ntfs_attr_search_ctx_get(ni, m);
if (!ctx) {
ntfs_error(vol->mp, "Failed to allocate search context for "
"$Secure.");
ntfs_mft_record_unmap(ni);
return ENOMEM;
}
err = ntfs_attr_lookup(AT_FILENAME, AT_UNNAMED, 0, 0, NULL, 0, ctx);
if (err) {
ntfs_error(vol->mp, "Failed to look up filename attribute in "
"$Secure (error %d).", err);
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(ni);
return err;
}
fn = (FILENAME_ATTR*)((u8*)ctx->a + le16_to_cpu(ctx->a->value_offset));
if (!ntfs_are_names_equal(fn->filename, fn->filename_length,
Secure, 7, NVolCaseSensitive(vol), NULL, 0)) {
ntfs_attr_search_ctx_put(ctx);
goto not_in_use;
}
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(ni);
ntfs_debug("Verified identity of $Secure system file.");
err = ntfs_attr_inode_attach(vol->secure_ni, AT_DATA, SDS, 4,
&vol->secure_sds_ni);
if (err) {
ntfs_error(vol->mp, "Failed to load $Secure/$SDS data "
"attribute (error %d).", err);
return err;
}
err = ntfs_index_inode_attach(vol->secure_ni, SDH, 4,
&vol->secure_sdh_ni);
if (err) {
ntfs_error(vol->mp, "Failed to load $Secure/$SDH index "
"(error %d).", err);
return err;
}
err = ntfs_index_inode_attach(vol->secure_ni, SII, 4,
&vol->secure_sii_ni);
if (err) {
ntfs_error(vol->mp, "Failed to load $Secure/$SII index "
"(error %d).", err);
return err;
}
err = ntfs_next_security_id_init(vol, &vol->next_security_id);
if (err) {
ntfs_error(vol->mp, "Failed to determine next security_id "
"(error %d).", err);
return err;
}
NVolSetUseSDAttr(vol);
ntfs_debug("Done.");
return 0;
}
static errno_t ntfs_objid_load(ntfs_volume *vol)
{
MFT_REF mref;
ntfs_inode *ni;
ntfs_dir_lookup_name *name = NULL;
int err;
static const ntfschar ObjId[7] = { const_cpu_to_le16('$'),
const_cpu_to_le16('O'), const_cpu_to_le16('b'),
const_cpu_to_le16('j'), const_cpu_to_le16('I'),
const_cpu_to_le16('d'), 0 };
static ntfschar O[3] = { const_cpu_to_le16('$'),
const_cpu_to_le16('O'), 0 };
ntfs_debug("Entering.");
lck_rw_lock_shared(&vol->extend_ni->lock);
err = ntfs_lookup_inode_by_name(vol->extend_ni, ObjId, 6, &mref,
&name);
lck_rw_unlock_shared(&vol->extend_ni->lock);
if (err) {
if (err == ENOENT) {
ntfs_debug("$ObjId not present. Volume does not have "
"any object ids present.");
return 0;
}
ntfs_error(vol->mp, "Failed to find inode number for $ObjId.");
return err;
}
if (name)
OSFree(name, sizeof(*name), ntfs_malloc_tag);
err = ntfs_inode_attach(vol, MREF(mref), &ni, vol->extend_ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to load $ObjId.");
return err;
}
vol->objid_ni = ni;
err = ntfs_index_inode_attach(vol->objid_ni, O, 2, &vol->objid_o_ni);
if (err) {
ntfs_error(vol->mp, "Failed to load $ObjId/$O index (error "
"%d).", err);
return err;
}
ntfs_debug("Done.");
return 0;
}
static errno_t ntfs_quota_load(ntfs_volume *vol)
{
MFT_REF mref;
ntfs_dir_lookup_name *name = NULL;
int err;
static const ntfschar Quota[7] = { const_cpu_to_le16('$'),
const_cpu_to_le16('Q'), const_cpu_to_le16('u'),
const_cpu_to_le16('o'), const_cpu_to_le16('t'),
const_cpu_to_le16('a'), 0 };
static ntfschar Q[3] = { const_cpu_to_le16('$'),
const_cpu_to_le16('Q'), 0 };
ntfs_debug("Entering.");
lck_rw_lock_shared(&vol->extend_ni->lock);
err = ntfs_lookup_inode_by_name(vol->extend_ni, Quota, 6, &mref,
&name);
lck_rw_unlock_shared(&vol->extend_ni->lock);
if (err) {
if (err == ENOENT) {
ntfs_debug("$Quota not present. Volume does not have "
"quotas enabled.");
NVolSetQuotaOutOfDate(vol);
return 0;
}
ntfs_error(vol->mp, "Failed to find inode number for $Quota.");
return err;
}
if (name)
OSFree(name, sizeof(*name), ntfs_malloc_tag);
err = ntfs_inode_attach(vol, MREF(mref), &vol->quota_ni,
vol->extend_ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to load $Quota.");
return err;
}
err = ntfs_index_inode_attach(vol->quota_ni, Q, 2, &vol->quota_q_ni);
if (err) {
ntfs_error(vol->mp, "Failed to load $Quota/$Q index (error "
"%d).", err);
return err;
}
ntfs_debug("Done.");
return 0;
}
static errno_t ntfs_usnjrnl_load(ntfs_volume *vol)
{
s64 data_size;
MFT_REF mref;
ntfs_inode *ni, *max_ni;
ntfs_dir_lookup_name *name = NULL;
upl_t upl;
upl_page_info_array_t pl;
USN_HEADER *uh;
errno_t err;
static const ntfschar UsnJrnl[9] = { const_cpu_to_le16('$'),
const_cpu_to_le16('U'), const_cpu_to_le16('s'),
const_cpu_to_le16('n'), const_cpu_to_le16('J'),
const_cpu_to_le16('r'), const_cpu_to_le16('n'),
const_cpu_to_le16('l'), 0 };
static ntfschar Max[5] = { const_cpu_to_le16('$'),
const_cpu_to_le16('M'), const_cpu_to_le16('a'),
const_cpu_to_le16('x'), 0 };
static ntfschar J[3] = { const_cpu_to_le16('$'),
const_cpu_to_le16('J'), 0 };
ntfs_debug("Entering.");
lck_rw_lock_shared(&vol->extend_ni->lock);
err = ntfs_lookup_inode_by_name(vol->extend_ni, UsnJrnl, 8, &mref,
&name);
lck_rw_unlock_shared(&vol->extend_ni->lock);
if (err) {
if (err == ENOENT) {
ntfs_debug("$UsnJrnl not present. Volume does not "
"have transaction logging enabled.");
not_enabled:
NVolSetUsnJrnlStamped(vol);
return 0;
}
ntfs_error(vol->mp, "Failed to find inode number for "
"$UsnJrnl.");
return err;
}
if (name)
OSFree(name, sizeof(*name), ntfs_malloc_tag);
err = ntfs_inode_attach(vol, MREF(mref), &ni, vol->extend_ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to load $UsnJrnl.");
return err;
}
vol->usnjrnl_ni = ni;
if (vol->vol_flags & VOLUME_DELETE_USN_UNDERWAY) {
ntfs_debug("$UsnJrnl in the process of being disabled. "
"Volume does not have transaction logging "
"enabled.");
goto not_enabled;
}
err = ntfs_attr_inode_attach(vol->usnjrnl_ni, AT_DATA, Max, 4, &max_ni);
if (err) {
ntfs_error(vol->mp, "Failed to load $UsnJrnl/$DATA/$Max "
"attribute.");
return err;
}
vol->usnjrnl_max_ni = max_ni;
lck_spin_lock(&max_ni->size_lock);
data_size = max_ni->data_size;
lck_spin_unlock(&max_ni->size_lock);
if (data_size < (s64)sizeof(USN_HEADER)) {
ntfs_error(vol->mp, "Found corrupt $UsnJrnl/$DATA/$Max "
"attribute (size is 0x%llx but should be at "
"least 0x%x bytes).",
(unsigned long long)data_size,
(unsigned)sizeof(USN_HEADER));
return EIO;
}
err = vnode_getwithref(max_ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to get vnode for "
"$UsnJrnl/$DATA/$Max.");
return err;
}
lck_rw_lock_shared(&max_ni->lock);
err = ntfs_page_map(max_ni, 0, &upl, &pl, (u8**)&uh, FALSE);
if (err) {
ntfs_error(vol->mp, "Failed to read from $UsnJrnl/$DATA/$Max "
"attribute.");
goto put_err;
}
if (sle64_to_cpu(uh->allocation_delta) >
sle64_to_cpu(uh->maximum_size)) {
ntfs_error(vol->mp, "Allocation delta (0x%llx) exceeds "
"maximum size (0x%llx). $UsnJrnl is corrupt.",
(unsigned long long)
sle64_to_cpu(uh->allocation_delta),
(unsigned long long)
sle64_to_cpu(uh->maximum_size));
err = EIO;
goto unm_err;
}
err = ntfs_attr_inode_attach(vol->usnjrnl_ni, AT_DATA, J, 2, &ni);
if (err) {
ntfs_error(vol->mp, "Failed to load $UsnJrnl/$DATA/$J "
"attribute.");
goto unm_err;
}
vol->usnjrnl_j_ni = ni;
if (!NInoNonResident(ni) || !NInoSparse(ni)) {
ntfs_error(vol->mp, "$UsnJrnl/$DATA/$J attribute is resident "
"and/or not sparse.");
err = EIO;
goto unm_err;
}
lck_spin_lock(&ni->size_lock);
data_size = ni->data_size;
lck_spin_unlock(&ni->size_lock);
if (sle64_to_cpu(uh->lowest_valid_usn) >= data_size) {
if (sle64_to_cpu(uh->lowest_valid_usn) == data_size) {
ntfs_page_unmap(max_ni, upl, pl, FALSE);
lck_rw_unlock_shared(&max_ni->lock);
(void)vnode_put(max_ni->vn);
ntfs_debug("$UsnJrnl is enabled but nothing has been "
"logged since it was last stamped. "
"Treating this as if the volume does "
"not have transaction logging "
"enabled.");
goto not_enabled;
}
ntfs_error(vol->mp, "$UsnJrnl has lowest valid usn (0x%llx) "
"which is out of bounds (0x%llx). $UsnJrnl "
"is corrupt.", (unsigned long long)
sle64_to_cpu(uh->lowest_valid_usn),
(unsigned long long)data_size);
err = EIO;
goto unm_err;
}
ntfs_debug("Done.");
unm_err:
ntfs_page_unmap(max_ni, upl, pl, FALSE);
put_err:
lck_rw_unlock_shared(&max_ni->lock);
(void)vnode_put(max_ni->vn);
return err;
}
static errno_t ntfs_system_inodes_get(ntfs_volume *vol)
{
s64 size;
ntfs_inode *root_ni, *ni;
vnode_t root_vn;
errno_t err;
BOOL is_hibernated;
ntfs_debug("Entering.");
err = ntfs_inode_attach(vol, FILE_root, &root_ni, NULL);
if (err) {
ntfs_error(vol->mp, "Failed to load root directory.");
goto err;
}
vol->root_ni = root_ni;
root_vn = root_ni->vn;
vnode_update_identity(vol->mft_ni->vn, root_vn, NULL, 0, 0,
VNODE_UPDATE_PARENT);
OSIncrementAtomic(&root_ni->nr_refs);
err = ntfs_mft_mirror_load(vol);
if (!err)
err = ntfs_mft_mirror_check(vol);
if (err) {
static const char es1a[] = "Failed to load $MFTMirr";
static const char es1b[] = "$MFTMirr does not match $MFT";
static const char es2[] = ". Run ntfsfix and/or chkdsk.";
const char *es1;
es1 = !vol->mftmirr_ni ? es1a : es1b;
if (!NVolReadOnly(vol)) {
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors="
"remount-ro was specified%s",
es1, es2);
err = EIO;
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1,
es2);
} else
ntfs_warning(vol->mp, "%s. Will not be able to "
"remount read-write%s", es1, es2);
NVolSetErrors(vol);
}
err = ntfs_attr_inode_attach(vol->mft_ni, AT_BITMAP, NULL, 0,
&vol->mftbmp_ni);
if (err) {
ntfs_error(vol->mp, "Failed to load $MFT/$BITMAP attribute.");
goto err;
}
NInoSetSparseDisabled(vol->mftbmp_ni);
err = ntfs_attr_map_runlist(vol->mftbmp_ni);
if (err) {
ntfs_error(vol->mp, "Failed to map runlist of $MFT/$BITMAP "
"attribute.");
goto err;
}
err = ntfs_upcase_load(vol);
if (err)
goto err;
err = ntfs_attrdef_load(vol);
if (err)
goto err;
err = ntfs_inode_attach(vol, FILE_Bitmap, &ni, root_vn);
if (err) {
ntfs_error(vol->mp, "Failed to load $Bitmap.");
goto err;
}
NInoSetSparseDisabled(ni);
vol->lcnbmp_ni = ni;
lck_spin_lock(&ni->size_lock);
size = ni->data_size;
lck_spin_unlock(&ni->size_lock);
if ((vol->nr_clusters + 7) >> 3 > size) {
ntfs_error(vol->mp, "$Bitmap (%lld) is shorter than required "
"length of volume (%lld) as specified in the "
"boot sector. Run chkdsk.", (long long)size,
(long long)(vol->nr_clusters + 7) >> 3);
err = EIO;
goto err;
}
err = ntfs_attr_map_runlist(ni);
if (err) {
ntfs_error(vol->mp, "Failed to map runlist of $Bitmap/$DATA "
"attribute.");
goto err;
}
err = ntfs_volume_load(vol);
if (err)
goto err;
printf("NTFS volume name %s, version %u.%u.\n", vol->name,
(unsigned)vol->major_ver, (unsigned)vol->minor_ver);
if (vol->major_ver < 3 && NVolSparseEnabled(vol)) {
ntfs_warning(vol->mp, "Disabling sparse support due to NTFS "
"volume version %u.%u (need at least "
"version 3.0).", (unsigned)vol->major_ver,
(unsigned)vol->minor_ver);
NVolClearSparseEnabled(vol);
}
if (vol->vol_flags & VOLUME_IS_DIRTY) {
ntfs_warning(vol->mp, "NTFS volume is dirty. You should "
"unmount it and run chkdsk.");
NVolSetErrors(vol);
}
if (vol->vol_flags & VOLUME_MUST_MOUNT_RO_MASK) {
static const char es1[] = "Volume has unsupported flags set";
static const char es2[] = ". To fix this problem boot into "
"Windows, run chkdsk c: /f /v /x from the "
"command prompt (replace c: with the drive "
"letter of this volume), then reboot into Mac "
"OS X and mount the volume again.";
ntfs_warning(vol->mp, "Unsupported volume flags 0x%x "
"encountered.",
(unsigned)le16_to_cpu(vol->vol_flags));
if (!NVolReadOnly(vol)) {
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors="
"remount-ro was specified%s",
es1, es2);
err = EINVAL;
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1,
es2);
} else
ntfs_warning(vol->mp, "%s. Will not be able to "
"remount read-write%s", es1, es2);
}
err = ntfs_inode_attach(vol, FILE_LogFile, &ni, root_vn);
if (!err) {
RESTART_PAGE_HEADER *rp;
NInoSetSparseDisabled(ni);
vol->logfile_ni = ni;
err = ntfs_logfile_check(ni, &rp);
if (!err) {
if (!ntfs_logfile_is_clean(ni, rp))
err = EINVAL;
if (rp)
OSFree(rp, le32_to_cpu(rp->system_page_size),
ntfs_malloc_tag);
}
}
if (err) {
static const char es1a[] = "Failed to load $LogFile";
static const char es1b[] = "$LogFile is not clean";
static const char es2[] = ". Mount in Windows.";
const char *es1;
es1 = !vol->logfile_ni ? es1a : es1b;
if (!NVolReadOnly(vol)) {
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors="
"remount-ro was specified%s",
es1, es2);
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1,
es2);
} else
ntfs_warning(vol->mp, "%s. Will not be able to "
"remount read-write%s", es1, es2);
NVolSetErrors(vol);
}
err = ntfs_windows_hibernation_status_check(vol, &is_hibernated);
if (err || is_hibernated) {
static const char es1a[] = "Failed to determine if Windows is "
"hibernated";
static const char es1b[] = "Windows is hibernated";
static const char es2[] = ". Run chkdsk.";
const char *es1;
es1 = err ? es1a : es1b;
if (!NVolReadOnly(vol)) {
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors="
"remount-ro was specified%s",
es1, es2);
if (!err)
err = EINVAL;
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1,
es2);
} else
ntfs_warning(vol->mp, "%s. Will not be able to "
"remount read-write%s", es1, es2);
NVolSetErrors(vol);
}
if (!NVolReadOnly(vol) &&
(err = ntfs_volume_flags_set(vol, VOLUME_IS_DIRTY))) {
static const char es1[] = "Failed to set dirty bit in volume "
"information flags";
static const char es2[] = ". Run chkdsk.";
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors=remount-ro "
"was specified%s", es1, es2);
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1, es2);
}
if (!NVolReadOnly(vol) &&
(err = ntfs_logfile_empty(vol->logfile_ni))) {
static const char es1[] = "Failed to empty journal $LogFile";
static const char es2[] = ". Mount in Windows.";
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors=remount-ro "
"was specified%s", es1, es2);
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1, es2);
NVolSetErrors(vol);
}
if (vol->major_ver < 3) {
NVolSetUseSDAttr(vol);
ntfs_debug("Done (NTFS version < 3.0).");
return 0;
}
err = ntfs_secure_load(vol);
if (err)
goto err;
err = ntfs_inode_attach(vol, FILE_Extend, &vol->extend_ni, root_vn);
if (err) {
ntfs_error(vol->mp, "Failed to load $Extend directory.");
goto err;
}
err = ntfs_objid_load(vol);
if (err) {
static const char es1[] = "Failed to load $ObjId";
static const char es2[] = ". Run chkdsk.";
if (!NVolReadOnly(vol)) {
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors="
"remount-ro was specified%s",
es1, es2);
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1,
es2);
} else
ntfs_warning(vol->mp, "%s. Will not be able to "
"remount read-write%s", es1, es2);
NVolSetErrors(vol);
}
err = ntfs_quota_load(vol);
if (err) {
static const char es1[] = "Failed to load $Quota";
static const char es2[] = ". Run chkdsk.";
if (!NVolReadOnly(vol)) {
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors="
"remount-ro was specified%s",
es1, es2);
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1,
es2);
} else
ntfs_warning(vol->mp, "%s. Will not be able to "
"remount read-write%s", es1, es2);
NVolSetErrors(vol);
}
if (!NVolReadOnly(vol) && (err = ntfs_quotas_mark_out_of_date(vol))) {
static const char es1[] = "Failed to mark quotas out of date";
static const char es2[] = ". Run chkdsk.";
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors=remount-ro "
"was specified%s", es1, es2);
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1, es2);
NVolSetErrors(vol);
}
err = ntfs_usnjrnl_load(vol);
if (err) {
static const char es1[] = "Failed to load $UsnJrnl";
static const char es2[] = ". Run chkdsk.";
if (!NVolReadOnly(vol)) {
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors="
"remount-ro was specified%s",
es1, es2);
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1,
es2);
} else
ntfs_warning(vol->mp, "%s. Will not be able to "
"remount read-write%s", es1, es2);
NVolSetErrors(vol);
}
if (!NVolReadOnly(vol) && (err = ntfs_usnjrnl_stamp(vol))) {
static const char es1[] = "Failed to stamp transaction log "
"($UsnJrnl)";
static const char es2[] = ". Run chkdsk.";
if (!(vol->on_errors & (ON_ERRORS_REMOUNT_RO |
ON_ERRORS_CONTINUE))) {
ntfs_error(vol->mp, "%s and neither on_errors="
"continue nor on_errors=remount-ro "
"was specified%s", es1, es2);
goto err;
}
vfs_setflags(vol->mp, MNT_RDONLY);
NVolSetReadOnly(vol);
ntfs_error(vol->mp, "%s. Mounting read-only%s", es1, es2);
NVolSetErrors(vol);
}
ntfs_debug("Done (NTFS version >= 3.0).");
return 0;
err:
return err;
}
static inline u32 ntfs_popcount32(u32 v)
{
const u32 w = v - ((v >> 1) & 0x55555555);
const u32 x = (w & 0x33333333) + ((w >> 2) & 0x33333333);
return (((x + (x >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
static errno_t ntfs_get_nr_set_bits(vnode_t vn, const s64 nr_bits, s64 *res)
{
s64 max_ofs, ofs, nr_set;
ntfs_inode *ni = NTFS_I(vn);
errno_t err;
ntfs_debug("Entering.");
err = vnode_getwithref(vn);
if (err)
return err;
lck_rw_lock_shared(&ni->lock);
max_ofs = (nr_bits + 7) >> 3;
ntfs_debug("Reading bitmap, max_ofs %lld.", (long long)max_ofs);
for (nr_set = ofs = 0; ofs < max_ofs; ofs += PAGE_SIZE) {
upl_t upl;
upl_page_info_array_t pl;
u32 *p;
int i;
err = ntfs_page_map(ni, ofs, &upl, &pl, (u8**)&p, FALSE);
if (err) {
ntfs_debug("Failed to map page from bitmap (offset "
"%lld, size %d, error %d). Skipping "
"page.", (long long)ofs, PAGE_SIZE,
(int)err);
nr_set += PAGE_SIZE * 8;
continue;
}
for (i = 0; i < (PAGE_SIZE / 4); i++)
nr_set += ntfs_popcount32(p[i]);
ntfs_page_unmap(ni, upl, pl, FALSE);
}
lck_rw_unlock_shared(&ni->lock);
(void)vnode_put(vn);
ntfs_debug("Done (nr_bits %lld, nr_set %lld).", (long long)nr_bits,
(long long)nr_set);
*res = nr_set;
return 0;
}
static errno_t ntfs_set_nr_free_clusters(ntfs_volume *vol)
{
s64 nr_free;
errno_t err;
ntfs_debug("Entering.");
lck_rw_lock_exclusive(&vol->lcnbmp_lock);
err = ntfs_get_nr_set_bits(vol->lcnbmp_ni->vn, vol->nr_clusters,
&nr_free);
if (err) {
ntfs_error(vol->mp, "Failed to get vnode for $Bitmap.");
lck_rw_unlock_exclusive(&vol->lcnbmp_lock);
return err;
}
nr_free = vol->nr_clusters - nr_free;
if (vol->nr_clusters & 63)
nr_free += 64 - (vol->nr_clusters & 63);
if (nr_free < 0)
nr_free = 0;
vol->nr_free_clusters = nr_free;
ntfs_debug("Done (nr_clusters %lld, nr_free_clusters %lld).",
(long long)vol->nr_clusters, (long long)nr_free);
lck_rw_unlock_exclusive(&vol->lcnbmp_lock);
return 0;
}
static errno_t ntfs_set_nr_mft_records(ntfs_volume *vol)
{
s64 nr_free;
errno_t err;
ntfs_debug("Entering.");
lck_rw_lock_exclusive(&vol->mftbmp_lock);
lck_spin_lock(&vol->mft_ni->size_lock);
vol->nr_mft_records = vol->mft_ni->data_size >>
vol->mft_record_size_shift;
lck_spin_unlock(&vol->mft_ni->size_lock);
err = ntfs_get_nr_set_bits(vol->mftbmp_ni->vn,
vol->mft_ni->initialized_size >>
vol->mft_record_size_shift, &nr_free);
if (err) {
ntfs_error(vol->mp, "Failed to get vnode for $MFT/$BITMAP.");
lck_rw_unlock_exclusive(&vol->mftbmp_lock);
return err;
}
nr_free = vol->nr_mft_records - nr_free;
if (nr_free < 0)
nr_free = 0;
vol->nr_free_mft_records = nr_free;
ntfs_debug("Done (nr_mft_records %lld, nr_free_mft_records %lld).",
(long long)vol->nr_mft_records, (long long)nr_free);
lck_rw_unlock_exclusive(&vol->mftbmp_lock);
return 0;
}
static void ntfs_statfs(ntfs_volume *vol, struct vfsstatfs *sfs)
{
ntfs_debug("Entering.");
sfs->f_bsize = vol->cluster_size;
sfs->f_iosize = ubc_upl_maxbufsize();
sfs->f_blocks = (u64)vol->nr_clusters;
sfs->f_bfree = (u64)vol->nr_free_clusters;
sfs->f_bavail = (u64)vol->nr_free_clusters;
sfs->f_bused = (u64)(vol->nr_clusters - vol->nr_free_clusters);
sfs->f_files = (u64)vol->nr_mft_records;
sfs->f_ffree = (u64)vol->nr_free_mft_records;
sfs->f_fssubtype = (u32)vol->major_ver << 8 | vol->minor_ver;
ntfs_debug("Done.");
}
static int ntfs_unmount_callback_recycle(vnode_t vn, void *data __unused)
{
ntfs_debug("Entering for mft_no 0x%llx.",
(unsigned long long)NTFS_I(vn)->mft_no);
(void)vnode_recycle(vn);
ntfs_debug("Done.");
return VNODE_RETURNED;
}
static void ntfs_unmount_inode_detach(ntfs_inode **pni)
{
ntfs_inode *ni = *pni;
if (ni) {
ntfs_debug("Entering for mft_no 0x%llx.",
(unsigned long long)ni->mft_no);
OSDecrementAtomic(&ni->nr_refs);
if (ni->vn)
vnode_rele(ni->vn);
*pni = NULL;
ntfs_debug("Done.");
}
}
static int ntfs_unmount(mount_t mp, int mnt_flags,
vfs_context_t context __unused)
{
ntfs_volume *vol = NTFS_MP(mp);
ntfs_inode *ni;
int vflags, err;
BOOL force;
ntfs_debug("Entering.");
vflags = err = 0;
force = FALSE;
if (mnt_flags & MNT_FORCE) {
vflags |= FORCECLOSE;
force = TRUE;
}
err = vflush(mp, NULLVP, vflags|SKIPROOT|SKIPSYSTEM);
if (err) {
ntfs_warning(mp, "Cannot unmount (vflush() returned error "
"%d). Are there open files keeping the "
"volume busy?\n", err);
goto done;
}
(void)vnode_iterate(mp, 0, ntfs_unmount_callback_recycle, NULL);
if (!NVolReadOnly(vol)) {
if (!NVolErrors(vol)) {
if (ntfs_volume_flags_clear(vol, VOLUME_IS_DIRTY))
ntfs_warning(mp, "Failed to clear dirty bit "
"in volume information "
"flags. Run chkdsk.");
} else
ntfs_warning(mp, "Volume has errors. Leaving volume "
"marked dirty. Run chkdsk.");
}
if (vol->major_ver >= 3) {
ntfs_unmount_inode_detach(&vol->usnjrnl_j_ni);
ntfs_unmount_inode_detach(&vol->usnjrnl_max_ni);
ntfs_unmount_inode_detach(&vol->usnjrnl_ni);
ntfs_unmount_inode_detach(&vol->quota_q_ni);
ntfs_unmount_inode_detach(&vol->quota_ni);
ntfs_unmount_inode_detach(&vol->objid_o_ni);
ntfs_unmount_inode_detach(&vol->objid_ni);
ntfs_unmount_inode_detach(&vol->extend_ni);
ntfs_unmount_inode_detach(&vol->secure_sds_ni);
ntfs_unmount_inode_detach(&vol->secure_sdh_ni);
ntfs_unmount_inode_detach(&vol->secure_sii_ni);
ntfs_unmount_inode_detach(&vol->secure_ni);
}
ntfs_unmount_inode_detach(&vol->vol_ni);
ntfs_unmount_inode_detach(&vol->root_ni);
ntfs_unmount_inode_detach(&vol->lcnbmp_ni);
ntfs_unmount_inode_detach(&vol->mftbmp_ni);
ntfs_unmount_inode_detach(&vol->logfile_ni);
if (vol->mftmirr_ni && vol->mftmirr_ni->vn)
vnode_update_identity(vol->mftmirr_ni->vn, NULL, NULL, 0, 0,
VNODE_UPDATE_PARENT);
ni = vol->mft_ni;
if (ni && ni->vn)
vnode_update_identity(ni->vn, NULL, NULL, 0, 0,
VNODE_UPDATE_PARENT);
ntfs_unmount_inode_detach(&vol->mftmirr_ni);
if (ni) {
if (ni->vn)
ntfs_unmount_inode_detach(&vol->mft_ni);
else {
ntfs_inode_reclaim(ni);
vol->mft_ni = NULL;
}
}
err = vflush(mp, NULLVP, vflags);
if (err && !force) {
ntfs_error(mp, "There are busy vnodes after unmounting! "
"Forcibly closing and reclaiming them.");
(void)vflush(mp, NULLVP, FORCECLOSE);
}
vfs_setfsprivate(mp, NULL);
lck_mtx_lock(&ntfs_lock);
if (vol->upcase && vol->upcase == ntfs_default_upcase) {
vol->upcase = NULL;
if (!--ntfs_default_upcase_users) {
OSFree(ntfs_default_upcase, ntfs_default_upcase_size,
ntfs_malloc_tag);
ntfs_default_upcase = NULL;
}
}
if (NVolCompressionEnabled(vol)) {
if (!--ntfs_compression_users) {
OSFree(ntfs_compression_buffer,
ntfs_compression_buffer_size,
ntfs_malloc_tag);
ntfs_compression_buffer = NULL;
}
}
lck_mtx_unlock(&ntfs_lock);
if (vol->attrdef)
OSFree(vol->attrdef, vol->attrdef_size, ntfs_malloc_tag);
if (vol->upcase)
OSFree(vol->upcase, vol->upcase_len << NTFSCHAR_SIZE_SHIFT,
ntfs_malloc_tag);
if (vol->name)
OSFree(vol->name, vol->name_size, ntfs_malloc_tag);
lck_rw_destroy(&vol->mftbmp_lock, ntfs_lock_grp);
lck_rw_destroy(&vol->lcnbmp_lock, ntfs_lock_grp);
lck_mtx_destroy(&vol->rename_lock, ntfs_lock_grp);
lck_rw_destroy(&vol->secure_lock, ntfs_lock_grp);
lck_spin_destroy(&vol->security_id_lock, ntfs_lock_grp);
OSFree(vol, sizeof(ntfs_volume), ntfs_malloc_tag);
ntfs_debug("Done.");
err = 0;
OSKextReleaseKextWithLoadTag(OSKextGetCurrentLoadTag());
done:
return err;
}
struct ntfs_sync_args {
int sync;
int err;
};
static int ntfs_sync_callback(vnode_t vn, void *arg)
{
ntfs_inode *ni = NTFS_I(vn);
ntfs_volume *vol = ni->vol;
struct ntfs_sync_args *args = (struct ntfs_sync_args*)arg;
if (ni != vol->mft_ni && ni != vol->mftmirr_ni) {
errno_t err;
err = ntfs_inode_sync(ni, args->sync, TRUE);
if (err && err != ENOENT) {
if (!args->err || args->err == ENOTSUP)
args->err = err;
}
}
return VNODE_RETURNED;
}
static void ntfs_sync_helper(ntfs_inode *ni, struct ntfs_sync_args *args,
const BOOL skip_mft_record_sync)
{
errno_t err;
err = vnode_getwithref(ni->vn);
if (err) {
ntfs_error(ni->vol->mp, "Failed to get vnode for $MFT%s "
"(error %d).",
(ni == ni->vol->mft_ni) ? "" : "Mirr",
(int)err);
goto err;
}
err = ntfs_inode_sync(ni, args->sync, skip_mft_record_sync);
vnode_put(ni->vn);
if (err && err != ENOENT) {
ntfs_error(ni->vol->mp, "Failed to sync $MFT%s (error %d).",
(ni == ni->vol->mft_ni) ? "" : "Mirr",
(int)err);
goto err;
}
return;
err:
if (!args->err || args->err == ENOTSUP)
args->err = err;
return;
}
static int ntfs_sync(struct mount *mp, int waitfor, vfs_context_t context)
{
ntfs_volume *vol;
struct ntfs_sync_args args;
ntfs_debug("Entering.");
args.sync = (waitfor == MNT_WAIT) ? IO_SYNC : 0;
args.err = 0;
(void)vnode_iterate(mp, 0, ntfs_sync_callback, (void*)&args);
vol = NTFS_MP(mp);
ntfs_sync_helper(vol->mftmirr_ni, &args, TRUE);
ntfs_sync_helper(vol->mft_ni, &args, TRUE);
ntfs_sync_helper(vol->mftmirr_ni, &args, FALSE);
ntfs_sync_helper(vol->mft_ni, &args, FALSE);
if (!args.err)
ntfs_debug("Done.");
else
ntfs_error(mp, "Failed to sync volume (error %d).", args.err);
return args.err;
}
static errno_t ntfs_remount(mount_t mp,
ntfs_mount_options_1_0 *opts)
{
errno_t err = 0;
ntfs_volume *vol = NTFS_MP(mp);
ntfs_debug("Entering.");
if (((opts->flags & NTFS_MNT_OPT_CASE_SENSITIVE) &&
!NVolCaseSensitive(vol)) ||
(!(opts->flags & NTFS_MNT_OPT_CASE_SENSITIVE) &&
NVolCaseSensitive(vol))) {
ntfs_error(mp, "Cannot change case sensitivity semantics via "
"remount. You need to unmount and then mount "
"again with the desired options.");
err = EINVAL;
goto err_exit;
}
if (vfs_iswriteupgrade(mp)) {
static const char es[] = ". Cannot remount read-write. To "
"fix this problem boot into Windows, run "
"chkdsk c: /f /v /x from the command prompt "
"(replace c: with the drive letter of this "
"volume), then reboot into Mac OS X and mount "
"the volume again.";
if (NVolErrors(vol)) {
ntfs_error(mp, "Volume has errors and is read-only%s",
es);
goto EROFS_exit;
}
if (vol->vol_flags & VOLUME_MUST_MOUNT_RO_MASK) {
ntfs_error(mp, "Volume has unsupported flags set "
"(0x%x) and is read-only%s",
(unsigned)le16_to_cpu(vol->vol_flags),
es);
goto EROFS_exit;
}
if (ntfs_volume_flags_set(vol, VOLUME_IS_DIRTY)) {
ntfs_error(mp, "Failed to set dirty bit in volume "
"information flags%s", es);
goto EROFS_exit;
}
if (ntfs_logfile_empty(vol->logfile_ni)) {
ntfs_error(mp, "Failed to empty journal $LogFile%s",
es);
NVolSetErrors(vol);
goto EROFS_exit;
}
if (ntfs_quotas_mark_out_of_date(vol)) {
ntfs_error(mp, "Failed to mark quotas out of date%s",
es);
NVolSetErrors(vol);
goto EROFS_exit;
}
if (ntfs_usnjrnl_stamp(vol)) {
ntfs_error(mp, "Failed to stamp transation log "
"($UsnJrnl)%s", es);
NVolSetErrors(vol);
goto EROFS_exit;
}
NVolClearReadOnly(vol);
} else if (!NVolReadOnly(vol) && vfs_isrdonly(mp)) {
err = ntfs_sync(mp, MNT_WAIT, NULL);
if (err) {
ntfs_error(mp, "Failed to sync volume (error %d). "
"Cannot remount read-only.", err);
goto err_exit;
}
if (!NVolErrors(vol)) {
if (ntfs_volume_flags_clear(vol, VOLUME_IS_DIRTY))
ntfs_warning(mp, "Failed to clear dirty bit "
"in volume information "
"flags. Run chkdsk.");
err = ntfs_sync(mp, MNT_WAIT, NULL);
if (err) {
ntfs_error(mp, "Failed to sync volume (error "
"%d). Cannot remount "
"read-only.", err);
(void)ntfs_volume_flags_set(vol,
VOLUME_IS_DIRTY);
goto err_exit;
}
} else
ntfs_warning(mp, "Volume has errors. Leaving volume "
"marked dirty. Run chkdsk.");
NVolSetReadOnly(vol);
}
ntfs_debug("Done.");
return 0;
EROFS_exit:
err = EROFS;
err_exit:
OSKextReleaseKextWithLoadTag(OSKextGetCurrentLoadTag());
return err;
}
static int ntfs_mount(mount_t mp, vnode_t dev_vn, user_addr_t data,
vfs_context_t context)
{
daddr64_t nr_blocks;
struct vfsstatfs *sfs = vfs_statfs(mp);
ntfs_volume *vol;
buf_t buf;
kauth_cred_t cred;
dev_t dev;
NTFS_BOOT_SECTOR *bs;
errno_t err, err2;
u32 blocksize;
ntfs_mount_options_header opts_hdr;
ntfs_mount_options_1_0 opts;
ntfs_debug("Entering.");
OSKextRetainKextWithLoadTag(OSKextGetCurrentLoadTag());
cred = vfs_context_proc(context) ? vfs_context_ucred(context) : NOCRED;
err = copyin(data, (caddr_t)&opts_hdr, sizeof(opts_hdr));
if (err) {
ntfs_error(mp, "Failed to copy mount options header from user "
"space (error %d).", err);
goto unload_exit;
}
ntfs_debug("Mount options header version %d.%d.", opts_hdr.major_ver,
opts_hdr.minor_ver);
switch (opts_hdr.major_ver) {
case 1:
if (opts_hdr.minor_ver != 0)
ntfs_warning(mp, "Your version of /sbin/mount_ntfs is "
"newer than this driver, ignoring any "
"new options.");
err = copyin((data + sizeof(opts_hdr) + 7) & ~7,
(caddr_t)&opts, sizeof(opts));
if (err) {
ntfs_error(mp, "Failed to copy NTFS mount options "
"from user space (error %d).", err);
goto unload_exit;
}
break;
case 0:
bzero(&opts, sizeof(opts));
break;
default:
ntfs_warning(mp, "Your version of /sbin/mount_ntfs is not "
"compatible with this driver, ignoring NTFS "
"specific mount options.");
bzero(&opts, sizeof(opts));
break;
}
vfs_setflags(mp, MNT_IGNORE_OWNERSHIP);
if (vfs_isreload(mp)) {
ntfs_error(mp, "MNT_RELOAD is not supported yet.");
err = ENOTSUP;
goto unload_exit;
}
if (vfs_isupdate(mp))
return ntfs_remount(mp, &opts);
dev = vnode_specrdev(dev_vn);
vfs_setlocklocal(mp);
sfs->f_fsid.val[0] = (int32_t)dev;
sfs->f_fsid.val[1] = (int32_t)vfs_typenum(mp);
vol = OSMalloc(sizeof(ntfs_volume), ntfs_malloc_tag);
if (!vol) {
ntfs_error(mp, "Failed to allocate ntfs volume buffer.");
err = ENOMEM;
goto unload_exit;
}
*vol = (ntfs_volume) {
.mp = mp,
.dev = dev,
.dev_vn = dev_vn,
.fmask = 0022,
.dmask = 0022,
.mft_zone_multiplier = 1,
.on_errors = ON_ERRORS_CONTINUE,
};
lck_rw_init(&vol->mftbmp_lock, ntfs_lock_grp, ntfs_lock_attr);
lck_rw_init(&vol->lcnbmp_lock, ntfs_lock_grp, ntfs_lock_attr);
lck_mtx_init(&vol->rename_lock, ntfs_lock_grp, ntfs_lock_attr);
lck_rw_init(&vol->secure_lock, ntfs_lock_grp, ntfs_lock_attr);
lck_spin_init(&vol->security_id_lock, ntfs_lock_grp, ntfs_lock_attr);
vfs_setfsprivate(mp, vol);
if (vfs_isrdonly(mp))
NVolSetReadOnly(vol);
if (opts.flags & NTFS_MNT_OPT_CASE_SENSITIVE) {
ntfs_debug("Mounting volume case sensitive.");
NVolSetCaseSensitive(vol);
}
#if 0
NVolSetSparseEnabled(vol);
#endif
NVolSetCompressionEnabled(vol);
blocksize = vfs_devblocksize(mp);
if (blocksize > PAGE_SIZE) {
ntfs_error(mp, "Device has unsupported sector size (%u). "
"The maximum supported sector size on this "
"system is %u bytes.", blocksize, PAGE_SIZE);
err = ENOTSUP;
goto err;
}
if (blocksize < NTFS_BLOCK_SIZE) {
ntfs_debug("Setting device block size to NTFS_BLOCK_SIZE.");
err = ntfs_blocksize_set(mp, dev_vn, NTFS_BLOCK_SIZE, context);
if (err) {
ntfs_error(mp, "Failed to set device block size to "
"NTFS_BLOCK_SIZE (512 bytes) because "
"the DKIOCSETBLOCKSIZE ioctl returned "
"error %d).", err);
goto err;
}
blocksize = NTFS_BLOCK_SIZE;
} else
ntfs_debug("Device block size (%u) is greater than or equal "
"to NTFS_BLOCK_SIZE.", blocksize);
err = VNOP_IOCTL(dev_vn, DKIOCGETBLOCKCOUNT, (caddr_t)&nr_blocks, 0,
context);
if (err) {
ntfs_error(mp, "Failed to determine the size of the device "
"(DKIOCGETBLOCKCOUNT ioctl returned error "
"%d).", err);
err = ENXIO;
goto err;
}
vol->nr_blocks = nr_blocks;
#ifdef DEBUG
{
u64 dev_size, u;
char *suffix;
int shift = 0;
u8 blocksize_shift = ffs(blocksize) - 1;
dev_size = u = (u64)nr_blocks << blocksize_shift;
while ((u >>= 10) > 10 && shift < 40)
shift += 10;
switch (shift) {
case 0:
suffix = "bytes";
break;
case 10:
suffix = "kiB";
break;
case 20:
suffix = "MiB";
break;
case 30:
suffix = "GiB";
break;
default:
suffix = "TiB";
break;
}
ntfs_debug("Device size is %llu%s (%llu bytes).",
(unsigned long long)dev_size >> shift, suffix,
(unsigned long long)dev_size);
}
#endif
buf = NULL;
bs = NULL;
err = ntfs_boot_sector_read(vol, cred, &buf, &bs);
if (err) {
ntfs_error(mp, "Not an NTFS volume.");
goto err;
}
err = ntfs_boot_sector_parse(vol, bs);
err2 = buf_unmap(buf);
if (err2)
ntfs_error(mp, "Failed to unmap buffer of boot sector (error "
"%d).", err2);
buf_brelse(buf);
if (err) {
ntfs_error(mp, "%s NTFS file system.",
err == ENOTSUP ? "Unsupported" : "Invalid");
goto err;
}
if (vol->sector_size > blocksize) {
ntfs_debug("Setting device block size to sector size.");
err = ntfs_blocksize_set(mp, dev_vn, vol->sector_size, context);
if (err) {
ntfs_error(mp, "Failed to set device block size to "
"sector size (%u bytes) because "
"the DKIOCSETBLOCKSIZE ioctl returned "
"error %d).", vol->sector_size, err);
goto err;
}
blocksize = vol->sector_size;
}
ntfs_setup_allocators(vol);
err = ntfs_mft_inode_get(vol);
if (err)
goto err;
lck_mtx_lock(&ntfs_lock);
if (NVolCompressionEnabled(vol)) {
if (vol->cluster_size <= 4096) {
if (!ntfs_compression_buffer) {
ntfs_compression_buffer = OSMalloc(
ntfs_compression_buffer_size,
ntfs_malloc_tag);
if (!ntfs_compression_buffer) {
ntfs_error(mp, "Failed to allocate "
"buffer for "
"compression engine.");
NVolClearCompressionEnabled(vol);
lck_mtx_unlock(&ntfs_lock);
goto err;
}
}
ntfs_compression_users++;
} else {
ntfs_debug("Disabling compression because the cluster "
"size of %u bytes is above the "
"allowed maximum of 4096 bytes.",
(unsigned)vol->cluster_size);
NVolClearCompressionEnabled(vol);
}
}
if (!ntfs_default_upcase) {
ntfs_default_upcase = OSMalloc(ntfs_default_upcase_size,
ntfs_malloc_tag);
if (!ntfs_default_upcase) {
ntfs_error(mp, "Failed to allocate memory for default "
"upcase table.");
lck_mtx_unlock(&ntfs_lock);
err = ENOMEM;
goto err;
}
ntfs_upcase_table_generate(ntfs_default_upcase,
ntfs_default_upcase_size);
}
ntfs_default_upcase_users++;
lck_mtx_unlock(&ntfs_lock);
err = ntfs_system_inodes_get(vol);
lck_mtx_lock(&ntfs_lock);
if (!--ntfs_default_upcase_users) {
OSFree(ntfs_default_upcase, ntfs_default_upcase_size,
ntfs_malloc_tag);
ntfs_default_upcase = NULL;
}
lck_mtx_unlock(&ntfs_lock);
if (err) {
ntfs_error(mp, "Failed to load system files (error %d).", err);
goto err;
}
err = ntfs_set_nr_free_clusters(vol);
if (err)
goto err;
err = ntfs_set_nr_mft_records(vol);
if (err)
goto err;
ntfs_statfs(vol, sfs);
ntfs_debug("Done.");
return 0;
err:
ntfs_error(mp, "Mount failed (error %d).", err);
ntfs_unmount(mp, 0, context);
return err;
unload_exit:
OSKextReleaseKextWithLoadTag(OSKextGetCurrentLoadTag());
return err;
}
static int ntfs_root(mount_t mp, struct vnode **vpp,
vfs_context_t context __unused)
{
ntfs_volume *vol = NTFS_MP(mp);
vnode_t vn;
int err;
ntfs_debug("Entering.");
if (!vol || !vol->root_ni || !vol->root_ni->vn)
panic("%s(): Mount and/or root inode and/or vnode is not "
"loaded.\n", __FUNCTION__);
vn = vol->root_ni->vn;
err = vnode_getwithref(vn);
if (!err) {
*vpp = vn;
ntfs_debug("Done.");
} else {
*vpp = NULL;
ntfs_error(mp, "Cannot return root vnode because "
"vnode_getwithref() failed (error %d).", err);
}
return err;
}
static int ntfs_vget(mount_t mp, ino64_t ino, struct vnode **vpp,
vfs_context_t context __unused)
{
ntfs_inode *ni;
errno_t err;
ntfs_debug("Entering for ino 0x%llx.", (unsigned long long)ino);
if (ino < FILE_first_user) {
if (ino != 2) {
ntfs_debug("Removing core NTFS system file (mft_no "
"0x%x) from name space.",
(unsigned)ino);
err = ENOENT;
goto err;
}
ni = NTFS_MP(mp)->root_ni;
if (ni) {
err = vnode_getwithref(ni->vn);
if (!err)
goto done;
}
ino = FILE_root;
}
err = ntfs_inode_get(NTFS_MP(mp), ino, FALSE, LCK_RW_TYPE_SHARED, &ni,
NULL, NULL);
if (!err) {
lck_rw_unlock_shared(&ni->lock);
done:
ntfs_debug("Done.");
*vpp = ni->vn;
return err;
}
err:
*vpp = NULL;
if (err != ENOENT)
ntfs_error(mp, "Failed to get mft_no 0x%llx (error %d).",
(unsigned long long)ino, err);
else
ntfs_debug("Mft_no 0x%llx does not exist, returning ENOENT.",
(unsigned long long)ino);
return err;
}
static int ntfs_getattr(mount_t mp, struct vfs_attr *fsa,
vfs_context_t context __unused)
{
u64 nr_clusters, nr_free_clusters, nr_used_mft_records;
u64 nr_free_mft_records;
ntfs_volume *vol = NTFS_MP(mp);
struct vfsstatfs *sfs = vfs_statfs(mp);
ntfs_inode *ni;
ntfs_debug("Entering.");
lck_rw_lock_shared(&vol->mftbmp_lock);
lck_rw_lock_shared(&vol->lcnbmp_lock);
nr_clusters = vol->nr_clusters;
nr_free_clusters = vol->nr_free_clusters;
lck_rw_unlock_shared(&vol->lcnbmp_lock);
nr_free_mft_records = vol->nr_free_mft_records;
nr_used_mft_records = vol->nr_mft_records - nr_free_mft_records;
lck_rw_unlock_shared(&vol->mftbmp_lock);
VFSATTR_RETURN(fsa, f_objcount, nr_used_mft_records);
VFSATTR_RETURN(fsa, f_filecount, nr_used_mft_records -
(nr_used_mft_records / 4));
VFSATTR_RETURN(fsa, f_dircount, nr_used_mft_records / 4);
VFSATTR_RETURN(fsa, f_maxobjcount, NTFS_MAX_NR_MFT_RECORDS);
VFSATTR_RETURN(fsa, f_bsize, vol->cluster_size);
VFSATTR_RETURN(fsa, f_iosize, ubc_upl_maxbufsize());
VFSATTR_RETURN(fsa, f_blocks, nr_clusters);
VFSATTR_RETURN(fsa, f_bfree, nr_free_clusters);
VFSATTR_RETURN(fsa, f_bavail, nr_free_clusters);
VFSATTR_RETURN(fsa, f_bused, nr_clusters - nr_free_clusters);
nr_free_mft_records += (nr_free_clusters << vol->cluster_size_shift) >>
vol->mft_record_size_shift;
if (nr_free_mft_records > NTFS_MAX_NR_MFT_RECORDS - nr_used_mft_records)
nr_free_mft_records = NTFS_MAX_NR_MFT_RECORDS -
nr_used_mft_records;
VFSATTR_RETURN(fsa, f_ffree, nr_free_mft_records);
VFSATTR_RETURN(fsa, f_files, nr_used_mft_records + nr_free_mft_records);
VFSATTR_RETURN(fsa, f_fsid, sfs->f_fsid);
VFSATTR_RETURN(fsa, f_owner, sfs->f_owner);
if (VFSATTR_IS_ACTIVE(fsa, f_capabilities)) {
vol_capabilities_attr_t *ca = &fsa->f_capabilities;
ca->capabilities[VOL_CAPABILITIES_FORMAT] =
VOL_CAP_FMT_PERSISTENTOBJECTIDS |
VOL_CAP_FMT_SYMBOLICLINKS |
VOL_CAP_FMT_HARDLINKS |
VOL_CAP_FMT_JOURNAL |
VOL_CAP_FMT_SPARSE_FILES |
VOL_CAP_FMT_ZERO_RUNS |
(NVolCaseSensitive(vol) ?
VOL_CAP_FMT_CASE_SENSITIVE : 0) |
VOL_CAP_FMT_CASE_PRESERVING |
VOL_CAP_FMT_FAST_STATFS |
VOL_CAP_FMT_2TB_FILESIZE |
VOL_CAP_FMT_HIDDEN_FILES |
VOL_CAP_FMT_PATH_FROM_ID |
0;
ca->valid[VOL_CAPABILITIES_FORMAT] =
VOL_CAP_FMT_PERSISTENTOBJECTIDS |
VOL_CAP_FMT_SYMBOLICLINKS |
VOL_CAP_FMT_HARDLINKS |
VOL_CAP_FMT_JOURNAL |
VOL_CAP_FMT_JOURNAL_ACTIVE |
VOL_CAP_FMT_NO_ROOT_TIMES |
VOL_CAP_FMT_SPARSE_FILES |
VOL_CAP_FMT_ZERO_RUNS |
VOL_CAP_FMT_CASE_SENSITIVE |
VOL_CAP_FMT_CASE_PRESERVING |
VOL_CAP_FMT_FAST_STATFS |
VOL_CAP_FMT_2TB_FILESIZE |
VOL_CAP_FMT_OPENDENYMODES |
VOL_CAP_FMT_HIDDEN_FILES |
VOL_CAP_FMT_PATH_FROM_ID |
0;
ca->capabilities[VOL_CAPABILITIES_INTERFACES] =
VOL_CAP_INT_ATTRLIST |
VOL_CAP_INT_VOL_RENAME |
VOL_CAP_INT_ADVLOCK |
VOL_CAP_INT_FLOCK |
VOL_CAP_INT_NAMEDSTREAMS |
VOL_CAP_INT_EXTENDED_ATTR |
0;
ca->valid[VOL_CAPABILITIES_INTERFACES] =
VOL_CAP_INT_SEARCHFS |
VOL_CAP_INT_ATTRLIST |
VOL_CAP_INT_NFSEXPORT |
VOL_CAP_INT_READDIRATTR |
VOL_CAP_INT_EXCHANGEDATA |
VOL_CAP_INT_COPYFILE |
VOL_CAP_INT_ALLOCATE |
VOL_CAP_INT_VOL_RENAME |
VOL_CAP_INT_ADVLOCK |
VOL_CAP_INT_FLOCK |
VOL_CAP_INT_EXTENDED_SECURITY |
VOL_CAP_INT_USERACCESS |
VOL_CAP_INT_MANLOCK |
VOL_CAP_INT_NAMEDSTREAMS |
VOL_CAP_INT_EXTENDED_ATTR |
0;
ca->capabilities[VOL_CAPABILITIES_RESERVED1] = 0;
ca->valid[VOL_CAPABILITIES_RESERVED1] = 0;
ca->capabilities[VOL_CAPABILITIES_RESERVED2] = 0;
ca->valid[VOL_CAPABILITIES_RESERVED2] = 0;
VFSATTR_SET_SUPPORTED(fsa, f_capabilities);
}
if (VFSATTR_IS_ACTIVE(fsa, f_attributes)) {
vol_attributes_attr_t *aa = &fsa->f_attributes;
aa->validattr.commonattr =
ATTR_CMN_NAME |
ATTR_CMN_DEVID |
ATTR_CMN_FSID |
ATTR_CMN_OBJTYPE |
ATTR_CMN_OBJTAG |
ATTR_CMN_OBJID |
ATTR_CMN_OBJPERMANENTID |
ATTR_CMN_PAROBJID |
ATTR_CMN_SCRIPT |
ATTR_CMN_CRTIME |
ATTR_CMN_MODTIME |
ATTR_CMN_CHGTIME |
ATTR_CMN_ACCTIME |
ATTR_CMN_BKUPTIME |
ATTR_CMN_FNDRINFO |
ATTR_CMN_OWNERID |
ATTR_CMN_GRPID |
ATTR_CMN_ACCESSMASK |
ATTR_CMN_FLAGS |
ATTR_CMN_USERACCESS |
ATTR_CMN_FILEID |
ATTR_CMN_PARENTID;
aa->nativeattr.commonattr =
ATTR_CMN_NAME |
ATTR_CMN_DEVID |
ATTR_CMN_FSID |
ATTR_CMN_OBJTYPE |
ATTR_CMN_OBJTAG |
ATTR_CMN_OBJID |
ATTR_CMN_OBJPERMANENTID |
ATTR_CMN_PAROBJID |
ATTR_CMN_SCRIPT |
ATTR_CMN_CRTIME |
ATTR_CMN_MODTIME |
ATTR_CMN_CHGTIME |
ATTR_CMN_ACCTIME |
ATTR_CMN_BKUPTIME |
ATTR_CMN_FNDRINFO |
ATTR_CMN_OWNERID |
ATTR_CMN_GRPID |
ATTR_CMN_ACCESSMASK |
ATTR_CMN_FLAGS |
ATTR_CMN_NAMEDATTRCOUNT |
ATTR_CMN_NAMEDATTRLIST |
ATTR_CMN_USERACCESS |
ATTR_CMN_EXTENDED_SECURITY |
ATTR_CMN_UUID |
ATTR_CMN_GRPUUID |
ATTR_CMN_FILEID |
ATTR_CMN_PARENTID;
aa->validattr.volattr =
ATTR_VOL_FSTYPE |
ATTR_VOL_SIGNATURE |
ATTR_VOL_SIZE |
ATTR_VOL_SPACEFREE |
ATTR_VOL_SPACEAVAIL |
ATTR_VOL_MINALLOCATION |
ATTR_VOL_ALLOCATIONCLUMP |
ATTR_VOL_IOBLOCKSIZE |
ATTR_VOL_OBJCOUNT |
ATTR_VOL_FILECOUNT |
ATTR_VOL_DIRCOUNT |
ATTR_VOL_MAXOBJCOUNT |
ATTR_VOL_MOUNTPOINT |
ATTR_VOL_NAME |
ATTR_VOL_MOUNTFLAGS |
ATTR_VOL_MOUNTEDDEVICE |
ATTR_VOL_ENCODINGSUSED |
ATTR_VOL_CAPABILITIES |
ATTR_VOL_ATTRIBUTES;
aa->nativeattr.volattr =
ATTR_VOL_FSTYPE |
ATTR_VOL_SIGNATURE |
ATTR_VOL_SIZE |
ATTR_VOL_SPACEFREE |
ATTR_VOL_SPACEAVAIL |
ATTR_VOL_MINALLOCATION |
ATTR_VOL_ALLOCATIONCLUMP |
ATTR_VOL_IOBLOCKSIZE |
ATTR_VOL_OBJCOUNT |
ATTR_VOL_MAXOBJCOUNT |
ATTR_VOL_MOUNTPOINT |
ATTR_VOL_NAME |
ATTR_VOL_MOUNTFLAGS |
ATTR_VOL_MOUNTEDDEVICE |
ATTR_VOL_ENCODINGSUSED |
ATTR_VOL_CAPABILITIES |
ATTR_VOL_ATTRIBUTES;
aa->validattr.dirattr =
ATTR_DIR_MOUNTSTATUS;
aa->nativeattr.dirattr =
ATTR_DIR_MOUNTSTATUS;
aa->validattr.fileattr =
ATTR_FILE_LINKCOUNT |
ATTR_FILE_TOTALSIZE |
ATTR_FILE_ALLOCSIZE |
ATTR_FILE_IOBLOCKSIZE |
ATTR_FILE_CLUMPSIZE |
ATTR_FILE_DEVTYPE |
ATTR_FILE_DATALENGTH |
ATTR_FILE_DATAALLOCSIZE |
ATTR_FILE_RSRCLENGTH |
ATTR_FILE_RSRCALLOCSIZE |
0;
aa->nativeattr.fileattr =
ATTR_FILE_LINKCOUNT |
ATTR_FILE_IOBLOCKSIZE |
ATTR_FILE_CLUMPSIZE |
ATTR_FILE_DEVTYPE |
ATTR_FILE_DATALENGTH |
ATTR_FILE_DATAALLOCSIZE |
ATTR_FILE_RSRCLENGTH |
ATTR_FILE_RSRCALLOCSIZE |
0;
aa->validattr.forkattr =
0;
aa->nativeattr.forkattr =
0;
VFSATTR_SET_SUPPORTED(fsa, f_attributes);
}
ni = vol->root_ni;
lck_rw_lock_shared(&ni->lock);
VFSATTR_RETURN(fsa, f_create_time, ni->creation_time);
VFSATTR_RETURN(fsa, f_modify_time, ni->last_mft_change_time);
VFSATTR_RETURN(fsa, f_access_time, ni->last_access_time);
if (VFSATTR_IS_ACTIVE(fsa, f_backup_time)) {
if (NInoValidBackupTime(ni)) {
VFSATTR_RETURN(fsa, f_backup_time, ni->backup_time);
lck_rw_unlock_shared(&ni->lock);
} else {
errno_t err;
if (!lck_rw_lock_shared_to_exclusive(&ni->lock))
lck_rw_lock_exclusive(&ni->lock);
err = ntfs_inode_afpinfo_read(ni);
if (err) {
ntfs_error(vol->mp, "Failed to obtain AfpInfo "
"for mft_no 0x%llx (error "
"%d).",
(unsigned long long)ni->mft_no,
err);
lck_rw_unlock_exclusive(&ni->lock);
return err;
}
if (!NInoValidBackupTime(ni))
panic("%s(): !NInoValidBackupTime(base_ni)\n",
__FUNCTION__);
VFSATTR_RETURN(fsa, f_backup_time, ni->backup_time);
lck_rw_unlock_exclusive(&ni->lock);
}
} else
lck_rw_unlock_shared(&ni->lock);
VFSATTR_RETURN(fsa, f_fssubtype, (u32)vol->major_ver << 8 |
vol->minor_ver);
if (VFSATTR_IS_ACTIVE(fsa, f_vol_name)) {
(void)strlcpy(fsa->f_vol_name, vol->name, MAXPATHLEN - 1);
VFSATTR_SET_SUPPORTED(fsa, f_vol_name);
}
VFSATTR_RETURN(fsa, f_signature, 0x4e54);
VFSATTR_RETURN(fsa, f_carbon_fsid, 0);
ntfs_debug("Done.");
return 0;
}
static errno_t ntfs_volume_rename(ntfs_volume *vol, char *name)
{
ntfs_inode *ni = vol->vol_ni;
MFT_RECORD *m;
ntfs_attr_search_ctx *ctx;
ATTR_RECORD *a;
u8 *utf8_name = NULL;
ntfschar *ntfs_name = NULL;
size_t utf8_name_size, ntfs_name_size;
signed ntfs_name_len = 0;
errno_t err;
ntfs_debug("Entering (old name: %s, new name: %s).", vol->name, name);
utf8_name_size = strlen(name) + 1;
if (utf8_name_size == vol->name_size &&
!strncmp(vol->name, name, vol->name_size)) {
ntfs_debug("The new name is the same as the old name, "
"ignoring the rename request.");
return 0;
}
if (utf8_name_size > 1) {
ntfs_name_len = utf8_to_ntfs(vol, (u8*)name, utf8_name_size,
&ntfs_name, &ntfs_name_size);
if (ntfs_name_len < 0) {
err = -ntfs_name_len;
ntfs_error(vol->mp, "Failed to convert volume name to "
"little endian, 2-byte, composed "
"Unicode (error %d).", (int)err);
goto err;
}
ntfs_name_len <<= NTFSCHAR_SIZE_SHIFT;
err = ntfs_attr_size_bounds_check(vol, AT_VOLUME_NAME,
ntfs_name_len);
if (err) {
if (err == ERANGE) {
ntfs_error(vol->mp, "Specified name is too "
"long (%d little endian, "
"2-byte, composed Unicode "
"characters).",
ntfs_name_len <<
NTFSCHAR_SIZE_SHIFT);
err = ENAMETOOLONG;
} else {
ntfs_error(vol->mp, "$VOLUME_NAME attribute "
"is not defined on the NTFS "
"volume. Possible "
"corruption! You should run "
"chkdsk.");
err = EIO;
}
goto err;
}
}
utf8_name = OSMalloc(utf8_name_size, ntfs_malloc_tag);
if (!utf8_name) {
ntfs_error(vol->mp, "Not enough memory to make a copy of the "
"new name.");
err = ENOMEM;
goto err;
}
if (strlcpy((char*)utf8_name, name, utf8_name_size) >= utf8_name_size)
panic("%s(): strlcpy() failed\n", __FUNCTION__);
err = vnode_getwithref(ni->vn);
if (err) {
ntfs_error(vol->mp, "Failed to get vnode for $Volume.");
goto err;
}
err = ntfs_mft_record_map(ni, &m);
if (err) {
ntfs_error(vol->mp, "Failed to map mft record for $Volume "
"(error %d).", err);
m = NULL;
ctx = NULL;
goto put_err;
}
ctx = ntfs_attr_search_ctx_get(ni, m);
if (!ctx) {
ntfs_error(vol->mp, "Not enough memory to get attribute "
"search context.");
err = ENOMEM;
goto put_err;
}
err = ntfs_attr_lookup(AT_VOLUME_NAME, AT_UNNAMED, 0, 0, NULL, 0, ctx);
m = ctx->m;
a = ctx->a;
if (err || a->non_resident || a->flags) {
if (err != ENOENT) {
if (!err)
goto name_err;
ntfs_error(vol->mp, "Failed to lookup volume name "
"attribute (error %d).", err);
goto put_err;
}
if (!ntfs_name) {
ntfs_debug("Volume has no name and new name is the "
"empty string, nothing to do.");
goto done;
}
ntfs_debug("Volume has no name. Creating new volume name "
"attribute.");
err = ntfs_resident_attr_record_insert(ni, ctx, AT_VOLUME_NAME,
NULL, 0, ntfs_name, ntfs_name_len);
if (err || ctx->is_error) {
if (!err)
err = ctx->error;
ntfs_error(vol->mp, "Failed to %s $Volume (error %d).",
ctx->is_error ?
"remap extent mft record of" :
"insert volume name attribute in", err);
goto put_err;
}
} else {
u8 *val = (u8*)a + le16_to_cpu(a->value_offset);
if (val < (u8*)a || val + le32_to_cpu(a->value_length) >
(u8*)a + le32_to_cpu(a->length) ||
(u8*)a + le32_to_cpu(a->length) >
(u8*)m + vol->mft_record_size)
goto name_err;
if (!ntfs_name) {
ntfs_debug("New name is the empty string. Removing "
"the existing $VOLUME_NAME attribute.");
err = ntfs_attr_record_delete(ni, ctx);
if (!err)
goto done;
ntfs_warning(vol->mp, "Failed to delete volume name "
"attribute (error %d). Truncating it "
"to zero length instead.", err);
}
retry_resize:
err = ntfs_resident_attr_value_resize(m, a, ntfs_name_len);
if (err) {
if (err != ENOSPC)
panic("%s(): err != ENOSPC\n", __FUNCTION__);
if (!NInoAttrList(ni)) {
err = ntfs_attr_list_add(ni, m, ctx);
if (err || ctx->is_error) {
if (!err)
err = ctx->error;
ntfs_error(vol->mp, "Failed to %s "
"$Volume (error %d).",
ctx->is_error ?
"remap extent mft "
"record of" :
"add attribute list "
"attribute to", err);
goto put_err;
}
m = ctx->m;
a = ctx->a;
goto retry_resize;
}
if (ntfs_attr_record_is_only_one(m, a))
panic("%s(): err == ENOSPC && "
"ntfs_attr_record_is_only_one"
"()\n", __FUNCTION__);
lck_rw_lock_shared(&ni->attr_list_rl.lock);
err = ntfs_attr_record_move(ctx);
lck_rw_unlock_shared(&ni->attr_list_rl.lock);
if (err) {
ntfs_error(vol->mp, "Failed to move volume "
"name attribute to an extent "
"mft record (error %d).", err);
goto put_err;
}
m = ctx->m;
a = ctx->a;
goto retry_resize;
}
if (ntfs_name)
memcpy((u8*)a + le16_to_cpu(a->value_offset),
ntfs_name, ntfs_name_len);
}
if (ntfs_name)
OSFree(ntfs_name, ntfs_name_size, ntfs_malloc_tag);
NInoSetMrecNeedsDirtying(ctx->ni);
done:
name = vol->name;
ntfs_name_size = vol->name_size;
if (utf8_name_size < vol->name_size)
vol->name_size = utf8_name_size;
vol->name = (char*)utf8_name;
vol->name_size = utf8_name_size;
ntfs_attr_search_ctx_put(ctx);
ntfs_mft_record_unmap(ni);
(void)vnode_put(ni->vn);
OSFree(name, ntfs_name_size, ntfs_malloc_tag);
ntfs_debug("Done.");
return 0;
name_err:
ntfs_error(vol->mp, "Volume name attribute is corrupt. Run chkdsk.");
NVolSetErrors(vol);
err = EIO;
put_err:
if (ctx)
ntfs_attr_search_ctx_put(ctx);
if (m)
ntfs_mft_record_unmap(ni);
(void)vnode_put(ni->vn);
err:
if (utf8_name)
OSFree(utf8_name, utf8_name_size, ntfs_malloc_tag);
if (ntfs_name)
OSFree(ntfs_name, ntfs_name_size, ntfs_malloc_tag);
return err;
}
static int ntfs_setattr(struct mount *mp, struct vfs_attr *fsa,
vfs_context_t context)
{
kauth_cred_t cred = vfs_context_ucred(context);
errno_t err;
ntfs_debug("Entering.");
if (!kauth_cred_issuser(cred) && (kauth_cred_getuid(cred) !=
vfs_statfs(mp)->f_owner))
return EACCES;
if (!VFSATTR_IS_ACTIVE(fsa, f_vol_name))
return 0;
if (!fsa->f_vol_name)
panic("%s(): !fsa->f_vol_name\n", __FUNCTION__);
err = ntfs_volume_rename(NTFS_MP(mp), fsa->f_vol_name);
if (err) {
ntfs_error(mp, "Failed to set the name of the volume to %s "
"(error %d).", fsa->f_vol_name, err);
return err;
}
VFSATTR_SET_SUPPORTED(fsa, f_vol_name);
ntfs_debug("Done.");
return 0;
}
static struct vfsops ntfs_vfsops = {
.vfs_mount = ntfs_mount,
.vfs_unmount = ntfs_unmount,
.vfs_root = ntfs_root,
.vfs_getattr = ntfs_getattr,
.vfs_sync = ntfs_sync,
.vfs_vget = ntfs_vget,
.vfs_setattr = ntfs_setattr,
};
static struct vnodeopv_desc *ntfs_vnodeopv_desc_list[1] = {
&ntfs_vnodeopv_desc,
};
static lck_grp_attr_t *ntfs_lock_grp_attr;
lck_grp_t *ntfs_lock_grp;
lck_attr_t *ntfs_lock_attr;
OSMallocTag ntfs_malloc_tag;
static vfstable_t ntfs_vfstable;
extern kern_return_t ntfs_module_start(kmod_info_t *ki __unused,
void *data __unused);
kern_return_t ntfs_module_start(kmod_info_t *ki __unused, void *data __unused)
{
errno_t err;
struct vfs_fsentry vfe;
printf("NTFS driver " VERSION " [Flags: R/W"
#ifdef DEBUG
" DEBUG"
#endif
"].\n");
if (ntfs_lock_grp_attr || ntfs_lock_grp || ntfs_lock_attr ||
ntfs_malloc_tag)
panic("%s(): Lock(s) and/or malloc tag already initialized.\n",
__FUNCTION__);
ntfs_lock_grp_attr = lck_grp_attr_alloc_init();
if (!ntfs_lock_grp_attr) {
lck_err:
printf("NTFS: Failed to allocate a lock element.\n");
goto dbg_err;
}
#ifdef DEBUG
lck_grp_attr_setstat(ntfs_lock_grp_attr);
#endif
ntfs_lock_grp = lck_grp_alloc_init("com.apple.filesystems.ntfs",
ntfs_lock_grp_attr);
if (!ntfs_lock_grp)
goto lck_err;
ntfs_lock_attr = lck_attr_alloc_init();
if (!ntfs_lock_attr)
goto lck_err;
#ifdef DEBUG
lck_attr_setdebug(ntfs_lock_attr);
#endif
ntfs_malloc_tag = OSMalloc_Tagalloc("com.apple.filesystems.ntfs",
OSMT_DEFAULT);
if (!ntfs_malloc_tag) {
printf("NTFS: OSMalloc_Tagalloc() failed.\n");
goto dbg_err;
}
lck_mtx_init(&ntfs_lock, ntfs_lock_grp, ntfs_lock_attr);
ntfs_debug_init();
ntfs_debug("Debug messages are enabled.");
err = ntfs_default_sds_entries_init();
if (err)
goto sds_err;
err = ntfs_inode_hash_init();
if (err)
goto hash_err;
vfe = (struct vfs_fsentry) {
.vfe_vfsops = &ntfs_vfsops,
.vfe_vopcnt = 1,
.vfe_opvdescs = ntfs_vnodeopv_desc_list,
.vfe_fsname = "ntfs",
.vfe_flags = VFS_TBLNATIVEXATTR | VFS_TBL64BITREADY |
VFS_TBLLOCALVOL | VFS_TBLNOTYPENUM |
VFS_TBLFSNODELOCK | VFS_TBLTHREADSAFE,
};
err = vfs_fsadd(&vfe, &ntfs_vfstable);
if (!err) {
ntfs_debug("NTFS driver registered successfully.");
return KERN_SUCCESS;
}
ntfs_error(NULL, "vfs_fsadd() failed (error %d).", (int)err);
ntfs_inode_hash_deinit();
hash_err:
OSFree(ntfs_file_sds_entry, 0x60 * 4, ntfs_malloc_tag);
ntfs_file_sds_entry = NULL;
sds_err:
ntfs_debug_deinit();
lck_mtx_destroy(&ntfs_lock, ntfs_lock_grp);
dbg_err:
if (ntfs_malloc_tag) {
OSMalloc_Tagfree(ntfs_malloc_tag);
ntfs_malloc_tag = NULL;
}
if (ntfs_lock_attr) {
lck_attr_free(ntfs_lock_attr);
ntfs_lock_attr = NULL;
}
if (ntfs_lock_grp) {
lck_grp_free(ntfs_lock_grp);
ntfs_lock_grp = NULL;
}
if (ntfs_lock_grp_attr) {
lck_grp_attr_free(ntfs_lock_grp_attr);
ntfs_lock_grp_attr = NULL;
}
printf("NTFS: Failed to register the NTFS driver.\n");
return KERN_FAILURE;
}
extern kern_return_t ntfs_module_stop(kmod_info_t *ki __unused,
void *data __unused);
kern_return_t ntfs_module_stop(kmod_info_t *ki __unused, void *data __unused)
{
errno_t err;
if (!ntfs_lock_grp_attr || !ntfs_lock_grp || !ntfs_lock_attr ||
!ntfs_malloc_tag)
panic("%s(): Lock(s) and/or malloc tag not yet initialized.\n",
__FUNCTION__);
ntfs_debug("Unregistering NTFS driver.");
err = vfs_fsremove(ntfs_vfstable);
if (err) {
if (err == EBUSY)
printf("NTFS: Failed to unregister the NTFS driver "
"because there are mounted NTFS "
"volumes.\n");
else
printf("NTFS: Failed to unregister the NTFS driver "
"because vfs_fsremove() failed (error "
"%d).\n", err);
return KERN_FAILURE;
}
ntfs_inode_hash_deinit();
OSFree(ntfs_file_sds_entry, 0x60 * 4, ntfs_malloc_tag);
ntfs_file_sds_entry = NULL;
ntfs_debug("Done.");
ntfs_debug_deinit();
lck_mtx_destroy(&ntfs_lock, ntfs_lock_grp);
OSMalloc_Tagfree(ntfs_malloc_tag);
ntfs_malloc_tag = NULL;
lck_attr_free(ntfs_lock_attr);
ntfs_lock_attr = NULL;
lck_grp_free(ntfs_lock_grp);
ntfs_lock_grp = NULL;
lck_grp_attr_free(ntfs_lock_grp_attr);
ntfs_lock_grp_attr = NULL;
return KERN_SUCCESS;
}