#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#if defined(__sun) && defined (__SVR4)
#include <sys/mnttab.h>
#endif
#include "param.h"
#include "compat.h"
#include "volume.h"
#include "attrib.h"
#include "mft.h"
#include "bootsect.h"
#include "device.h"
#include "debug.h"
#include "inode.h"
#include "runlist.h"
#include "logfile.h"
#include "dir.h"
#include "logging.h"
#include "cache.h"
#include "realpath.h"
#include "misc.h"
#include "security.h"
const char *ntfs_home =
"News, support and information: https://github.com/tuxera/ntfs-3g/\n";
static const char *invalid_ntfs_msg =
"The device '%s' doesn't seem to have a valid NTFS.\n"
"Maybe the wrong device is used? Or the whole disk instead of a\n"
"partition (e.g. /dev/sda, not /dev/sda1)? Or the other way around?\n";
static const char *corrupt_volume_msg =
"NTFS is either inconsistent, or there is a hardware fault, or it's a\n"
"SoftRAID/FakeRAID hardware. In the first case run chkdsk /f on Windows\n"
"then reboot into Windows twice. The usage of the /f parameter is very\n"
"important! If the device is a SoftRAID/FakeRAID then first activate\n"
"it and mount a different device under the /dev/mapper/ directory, (e.g.\n"
"/dev/mapper/nvidia_eahaabcc1). Please see the 'dmraid' documentation\n"
"for more details.\n";
static const char *hibernated_volume_msg =
"The NTFS partition is in an unsafe state. Please resume and shutdown\n"
"Windows fully (no hibernation or fast restarting), or mount the volume\n"
"read-only with the 'ro' mount option.\n";
static const char *fallback_readonly_msg =
"Falling back to read-only mount because the NTFS partition is in an\n"
"unsafe state. Please resume and shutdown Windows fully (no hibernation\n"
"or fast restarting.)\n";
static const char *unclean_journal_msg =
"Write access is denied because the disk wasn't safely powered\n"
"off and the 'norecover' mount option was specified.\n";
static const char *opened_volume_msg =
"Mount is denied because the NTFS volume is already exclusively opened.\n"
"The volume may be already mounted, or another software may use it which\n"
"could be identified for example by the help of the 'fuser' command.\n";
static const char *fakeraid_msg =
"Either the device is missing or it's powered down, or you have\n"
"SoftRAID hardware and must use an activated, different device under\n"
"/dev/mapper/, (e.g. /dev/mapper/nvidia_eahaabcc1) to mount NTFS.\n"
"Please see the 'dmraid' documentation for help.\n";
static const char *access_denied_msg =
"Please check '%s' and the ntfs-3g binary permissions,\n"
"and the mounting user ID. More explanation is provided at\n"
"https://github.com/tuxera/ntfs-3g/wiki/NTFS-3G-FAQ\n";
ntfs_volume *ntfs_volume_alloc(void)
{
return ntfs_calloc(sizeof(ntfs_volume));
}
static void ntfs_attr_free(ntfs_attr **na)
{
if (na && *na) {
ntfs_attr_close(*na);
*na = NULL;
}
}
static int ntfs_inode_free(ntfs_inode **ni)
{
int ret = -1;
if (ni && *ni) {
ret = ntfs_inode_close(*ni);
*ni = NULL;
}
return ret;
}
static void ntfs_error_set(int *err)
{
if (!*err)
*err = errno;
}
static int __ntfs_volume_release(ntfs_volume *v)
{
int err = 0;
if (ntfs_close_secure(v))
ntfs_error_set(&err);
if (ntfs_inode_free(&v->vol_ni))
ntfs_error_set(&err);
if (v->lcnbmp_ni && NInoDirty(v->lcnbmp_ni))
ntfs_inode_sync(v->lcnbmp_ni);
ntfs_attr_free(&v->lcnbmp_na);
if (ntfs_inode_free(&v->lcnbmp_ni))
ntfs_error_set(&err);
if (v->mft_ni && NInoDirty(v->mft_ni))
ntfs_inode_sync(v->mft_ni);
ntfs_attr_free(&v->mftbmp_na);
ntfs_attr_free(&v->mft_na);
if (ntfs_inode_free(&v->mft_ni))
ntfs_error_set(&err);
if (v->mftmirr_ni && NInoDirty(v->mftmirr_ni))
ntfs_inode_sync(v->mftmirr_ni);
ntfs_attr_free(&v->mftmirr_na);
if (ntfs_inode_free(&v->mftmirr_ni))
ntfs_error_set(&err);
if (v->dev) {
struct ntfs_device *dev = v->dev;
if (dev->d_ops->sync(dev))
ntfs_error_set(&err);
if (dev->d_ops->close(dev))
ntfs_error_set(&err);
}
ntfs_free_lru_caches(v);
free(v->vol_name);
free(v->upcase);
if (v->locase) free(v->locase);
free(v->attrdef);
free(v);
errno = err;
return errno ? -1 : 0;
}
static int ntfs_attr_setup_flag(ntfs_inode *ni)
{
STANDARD_INFORMATION *si;
s64 lth;
int r;
si = (STANDARD_INFORMATION*)ntfs_attr_readall(ni,
AT_STANDARD_INFORMATION, AT_UNNAMED, 0, <h);
if (si) {
if ((u64)lth >= offsetof(STANDARD_INFORMATION, owner_id))
ni->flags = si->file_attributes;
free(si);
r = 0;
} else {
ntfs_log_error("Failed to get standard information of $MFT\n");
r = -1;
}
return (r);
}
static int ntfs_mft_load(ntfs_volume *vol)
{
VCN next_vcn, last_vcn, highest_vcn;
s64 l;
MFT_RECORD *mb = NULL;
ntfs_attr_search_ctx *ctx = NULL;
ATTR_RECORD *a;
int eo;
vol->mft_ni = ntfs_inode_allocate(vol);
mb = ntfs_malloc(vol->mft_record_size);
if (!vol->mft_ni || !mb) {
ntfs_log_perror("Error allocating memory for $MFT");
goto error_exit;
}
vol->mft_ni->mft_no = 0;
vol->mft_ni->mrec = mb;
l = ntfs_mst_pread(vol->dev, vol->mft_lcn << vol->cluster_size_bits, 1,
vol->mft_record_size, mb);
if (l != 1) {
if (l != -1)
errno = EIO;
ntfs_log_perror("Error reading $MFT");
goto error_exit;
}
if (ntfs_mft_record_check(vol, 0, mb))
goto error_exit;
ctx = ntfs_attr_get_search_ctx(vol->mft_ni, NULL);
if (!ctx)
goto error_exit;
if (ntfs_attr_lookup(AT_ATTRIBUTE_LIST, AT_UNNAMED, 0, 0, 0, NULL, 0,
ctx)) {
if (errno != ENOENT) {
ntfs_log_error("$MFT has corrupt attribute list.\n");
goto io_error_exit;
}
goto mft_has_no_attr_list;
}
NInoSetAttrList(vol->mft_ni);
l = ntfs_get_attribute_value_length(ctx->attr);
if (l <= 0 || l > 0x40000) {
ntfs_log_error("$MFT/$ATTR_LIST invalid length (%lld).\n",
(long long)l);
goto io_error_exit;
}
vol->mft_ni->attr_list_size = l;
vol->mft_ni->attr_list = ntfs_malloc(l);
if (!vol->mft_ni->attr_list)
goto error_exit;
l = ntfs_get_attribute_value(vol, ctx->attr, vol->mft_ni->attr_list);
if (!l) {
ntfs_log_error("Failed to get value of $MFT/$ATTR_LIST.\n");
goto io_error_exit;
}
if ((l != vol->mft_ni->attr_list_size)
|| (l < (s64)offsetof(ATTR_LIST_ENTRY, name))) {
ntfs_log_error("Partial read of $MFT/$ATTR_LIST (%lld != "
"%u or < %d).\n", (long long)l,
vol->mft_ni->attr_list_size,
(int)offsetof(ATTR_LIST_ENTRY, name));
goto io_error_exit;
}
mft_has_no_attr_list:
if (ntfs_attr_setup_flag(vol->mft_ni))
goto error_exit;
vol->mft_na = ntfs_attr_open(vol->mft_ni, AT_DATA, AT_UNNAMED, 0);
if (!vol->mft_na) {
ntfs_log_perror("Failed to open ntfs attribute");
goto error_exit;
}
ntfs_attr_reinit_search_ctx(ctx);
last_vcn = vol->mft_na->allocated_size >> vol->cluster_size_bits;
highest_vcn = next_vcn = 0;
a = NULL;
while (!ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, next_vcn, NULL, 0,
ctx)) {
runlist_element *nrl;
a = ctx->attr;
if (!a->non_resident) {
ntfs_log_error("$MFT must be non-resident.\n");
goto io_error_exit;
}
if (a->flags & ATTR_COMPRESSION_MASK ||
a->flags & ATTR_IS_ENCRYPTED) {
ntfs_log_error("$MFT must be uncompressed and "
"unencrypted.\n");
goto io_error_exit;
}
nrl = ntfs_mapping_pairs_decompress(vol, a, vol->mft_na->rl);
if (!nrl) {
ntfs_log_perror("ntfs_mapping_pairs_decompress() failed");
goto error_exit;
}
if (nrl->lcn != vol->mft_lcn) {
ntfs_log_perror("The MFT is not self-contained");
goto error_exit;
}
vol->mft_na->rl = nrl;
highest_vcn = sle64_to_cpu(a->highest_vcn);
next_vcn = highest_vcn + 1;
if (next_vcn <= 0)
break;
if (next_vcn < sle64_to_cpu(a->lowest_vcn)) {
ntfs_log_error("$MFT has corrupt attribute list.\n");
goto io_error_exit;
}
}
if (!a) {
ntfs_log_error("$MFT/$DATA attribute not found.\n");
goto io_error_exit;
}
if (highest_vcn && highest_vcn != last_vcn - 1) {
ntfs_log_error("Failed to load runlist for $MFT/$DATA.\n");
ntfs_log_error("highest_vcn = 0x%llx, last_vcn - 1 = 0x%llx\n",
(long long)highest_vcn, (long long)last_vcn - 1);
goto io_error_exit;
}
ntfs_attr_put_search_ctx(ctx);
ctx = NULL;
vol->mft_ni->data_size = vol->mft_na->data_size;
vol->mft_ni->allocated_size = vol->mft_na->allocated_size;
set_nino_flag(vol->mft_ni, KnownSize);
vol->mftbmp_na = ntfs_attr_open(vol->mft_ni, AT_BITMAP, AT_UNNAMED, 0);
if (!vol->mftbmp_na) {
ntfs_log_perror("Failed to open $MFT/$BITMAP");
goto error_exit;
}
return 0;
io_error_exit:
errno = EIO;
error_exit:
eo = errno;
if (ctx)
ntfs_attr_put_search_ctx(ctx);
if (vol->mft_na) {
ntfs_attr_close(vol->mft_na);
vol->mft_na = NULL;
}
if (vol->mft_ni) {
ntfs_inode_close(vol->mft_ni);
vol->mft_ni = NULL;
}
errno = eo;
return -1;
}
static int ntfs_mftmirr_load(ntfs_volume *vol)
{
int err;
vol->mftmirr_ni = ntfs_inode_open(vol, FILE_MFTMirr);
if (!vol->mftmirr_ni) {
ntfs_log_perror("Failed to open inode $MFTMirr");
return -1;
}
vol->mftmirr_na = ntfs_attr_open(vol->mftmirr_ni, AT_DATA, AT_UNNAMED, 0);
if (!vol->mftmirr_na) {
ntfs_log_perror("Failed to open $MFTMirr/$DATA");
goto error_exit;
}
if (ntfs_attr_map_runlist(vol->mftmirr_na, 0) < 0) {
ntfs_log_perror("Failed to map runlist of $MFTMirr/$DATA");
goto error_exit;
}
if (vol->mftmirr_na->rl->lcn != vol->mftmirr_lcn) {
ntfs_log_error("Bad $MFTMirr lcn 0x%llx, want 0x%llx\n",
(long long)vol->mftmirr_na->rl->lcn,
(long long)vol->mftmirr_lcn);
goto error_exit;
}
return 0;
error_exit:
err = errno;
if (vol->mftmirr_na) {
ntfs_attr_close(vol->mftmirr_na);
vol->mftmirr_na = NULL;
}
ntfs_inode_close(vol->mftmirr_ni);
vol->mftmirr_ni = NULL;
errno = err;
return -1;
}
ntfs_volume *ntfs_volume_startup(struct ntfs_device *dev,
ntfs_mount_flags flags)
{
LCN mft_zone_size, mft_lcn;
s64 br;
ntfs_volume *vol;
NTFS_BOOT_SECTOR *bs;
int eo;
if (!dev || !dev->d_ops || !dev->d_name) {
errno = EINVAL;
ntfs_log_perror("%s: dev = %p", __FUNCTION__, dev);
return NULL;
}
bs = ntfs_malloc(sizeof(NTFS_BOOT_SECTOR));
if (!bs)
return NULL;
vol = ntfs_volume_alloc();
if (!vol)
goto error_exit;
vol->upcase_len = ntfs_upcase_build_default(&vol->upcase);
if (!vol->upcase_len || !vol->upcase)
goto error_exit;
vol->locase = (ntfschar*)NULL;
NVolSetCaseSensitive(vol);
NVolSetShowSysFiles(vol);
NVolSetShowHidFiles(vol);
NVolClearHideDotFiles(vol);
#if DEFAULT_COMPRESSION
NVolSetCompression(vol);
#else
NVolClearCompression(vol);
#endif
if (flags & NTFS_MNT_RDONLY)
NVolSetReadOnly(vol);
if ((dev->d_ops->open)(dev, NVolReadOnly(vol) ? O_RDONLY: O_RDWR)) {
if (!NVolReadOnly(vol) && (errno == EROFS)) {
if ((dev->d_ops->open)(dev, O_RDONLY)) {
ntfs_log_perror("Error opening read-only '%s'",
dev->d_name);
goto error_exit;
} else {
ntfs_log_info("Error opening '%s' read-write\n",
dev->d_name);
NVolSetReadOnly(vol);
}
} else {
ntfs_log_perror("Error opening '%s'", dev->d_name);
goto error_exit;
}
}
vol->dev = dev;
br = ntfs_pread(dev, 0, sizeof(NTFS_BOOT_SECTOR), bs);
if (br != sizeof(NTFS_BOOT_SECTOR)) {
if (br != -1)
errno = EINVAL;
if (!br)
ntfs_log_error("Failed to read bootsector (size=0)\n");
else
ntfs_log_perror("Error reading bootsector");
goto error_exit;
}
if (!ntfs_boot_sector_is_ntfs(bs)) {
errno = EINVAL;
goto error_exit;
}
if (ntfs_boot_sector_parse(vol, bs) < 0)
goto error_exit;
free(bs);
bs = NULL;
if (ntfs_device_block_size_set(vol->dev, vol->sector_size))
ntfs_log_debug("Failed to set the device block size to the "
"sector size. This may affect performance "
"but should be harmless otherwise. Error: "
"%s\n", strerror(errno));
vol->full_zones = 0;
mft_zone_size = vol->nr_clusters >> 3;
vol->mft_zone_start = vol->mft_zone_pos = vol->mft_lcn;
ntfs_log_debug("mft_zone_pos = 0x%llx\n", (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_log_debug("mft_zone_start = 0x%llx\n", (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;
if (!mft_zone_size) {
errno = EINVAL;
goto error_exit;
}
vol->mft_zone_end = vol->mft_lcn + mft_zone_size;
}
ntfs_log_debug("mft_zone_end = 0x%llx\n", (long long)vol->mft_zone_end);
vol->data1_zone_pos = vol->mft_zone_end;
ntfs_log_debug("data1_zone_pos = %lld\n", (long long)vol->data1_zone_pos);
vol->data2_zone_pos = 0;
ntfs_log_debug("data2_zone_pos = %lld\n", (long long)vol->data2_zone_pos);
vol->mft_data_pos = 24;
if (ntfs_mft_load(vol) < 0) {
ntfs_log_perror("Failed to load $MFT");
goto error_exit;
}
if (ntfs_mftmirr_load(vol) < 0) {
ntfs_log_perror("Failed to load $MFTMirr");
goto error_exit;
}
return vol;
error_exit:
eo = errno;
free(bs);
if (vol)
__ntfs_volume_release(vol);
errno = eo;
return NULL;
}
static int ntfs_volume_check_logfile(ntfs_volume *vol)
{
ntfs_inode *ni;
ntfs_attr *na = NULL;
RESTART_PAGE_HEADER *rp = NULL;
int err = 0;
ni = ntfs_inode_open(vol, FILE_LogFile);
if (!ni) {
ntfs_log_perror("Failed to open inode FILE_LogFile");
errno = EIO;
return -1;
}
na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
ntfs_log_perror("Failed to open $FILE_LogFile/$DATA");
err = EIO;
goto out;
}
if (!ntfs_check_logfile(na, &rp) || !ntfs_is_logfile_clean(na, rp))
err = EOPNOTSUPP;
if (rp
&& (rp->major_ver == const_cpu_to_le16(2))
&& (rp->minor_ver == const_cpu_to_le16(0))) {
ntfs_log_error("Metadata kept in Windows cache, refused to mount.\n");
err = EPERM;
}
free(rp);
ntfs_attr_close(na);
out:
if (ntfs_inode_close(ni))
ntfs_error_set(&err);
if (err) {
errno = err;
return -1;
}
return 0;
}
static ntfs_inode *ntfs_hiberfile_open(ntfs_volume *vol)
{
u64 inode;
ntfs_inode *ni_root;
ntfs_inode *ni_hibr = NULL;
ntfschar *unicode = NULL;
int unicode_len;
const char *hiberfile = "hiberfil.sys";
if (!vol) {
errno = EINVAL;
return NULL;
}
ni_root = ntfs_inode_open(vol, FILE_root);
if (!ni_root) {
ntfs_log_debug("Couldn't open the root directory.\n");
return NULL;
}
unicode_len = ntfs_mbstoucs(hiberfile, &unicode);
if (unicode_len < 0) {
ntfs_log_perror("Couldn't convert 'hiberfil.sys' to Unicode");
goto out;
}
inode = ntfs_inode_lookup_by_name(ni_root, unicode, unicode_len);
if (inode == (u64)-1) {
ntfs_log_debug("Couldn't find file '%s'.\n", hiberfile);
goto out;
}
inode = MREF(inode);
ni_hibr = ntfs_inode_open(vol, inode);
if (!ni_hibr) {
ntfs_log_debug("Couldn't open inode %lld.\n", (long long)inode);
goto out;
}
out:
if (ntfs_inode_close(ni_root)) {
ntfs_inode_close(ni_hibr);
ni_hibr = NULL;
}
free(unicode);
return ni_hibr;
}
#define NTFS_HIBERFILE_HEADER_SIZE 4096
int ntfs_volume_check_hiberfile(ntfs_volume *vol, int verbose)
{
ntfs_inode *ni;
ntfs_attr *na = NULL;
int bytes_read, err;
char *buf = NULL;
ni = ntfs_hiberfile_open(vol);
if (!ni) {
if (errno == ENOENT)
return 0;
return -1;
}
buf = ntfs_malloc(NTFS_HIBERFILE_HEADER_SIZE);
if (!buf)
goto out;
na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
ntfs_log_perror("Failed to open hiberfil.sys data attribute");
goto out;
}
bytes_read = ntfs_attr_pread(na, 0, NTFS_HIBERFILE_HEADER_SIZE, buf);
if (bytes_read == -1) {
ntfs_log_perror("Failed to read hiberfil.sys");
goto out;
}
if (bytes_read < NTFS_HIBERFILE_HEADER_SIZE) {
if (verbose)
ntfs_log_error("Hibernated non-system partition, "
"refused to mount.\n");
errno = EPERM;
goto out;
}
if ((memcmp(buf, "hibr", 4) == 0)
|| (memcmp(buf, "HIBR", 4) == 0)) {
if (verbose)
ntfs_log_error("Windows is hibernated, refused to mount.\n");
errno = EPERM;
goto out;
}
errno = 0;
out:
if (na)
ntfs_attr_close(na);
free(buf);
err = errno;
if (ntfs_inode_close(ni))
ntfs_error_set(&err);
errno = err;
return errno ? -1 : 0;
}
static int fix_txf_data(ntfs_volume *vol)
{
void *txf_data;
s64 txf_data_size;
ntfs_inode *ni;
ntfs_attr *na;
int res;
res = 0;
ntfs_log_debug("Loading root directory\n");
ni = ntfs_inode_open(vol, FILE_root);
if (!ni) {
ntfs_log_perror("Failed to open root directory");
res = -1;
} else {
na = ntfs_attr_open(ni, AT_LOGGED_UTILITY_STREAM, TXF_DATA, 9);
if (na) {
if (NAttrNonResident(na)) {
ntfs_log_debug("Making $TXF_DATA resident\n");
txf_data = ntfs_attr_readall(ni,
AT_LOGGED_UTILITY_STREAM,
TXF_DATA, 9, &txf_data_size);
if (txf_data) {
if (ntfs_attr_truncate(na, 0)
|| (ntfs_attr_pwrite(na, 0,
txf_data_size, txf_data)
!= txf_data_size))
res = -1;
free(txf_data);
}
if (res)
ntfs_log_error("Failed to make $TXF_DATA resident\n");
else
ntfs_log_error("$TXF_DATA made resident\n");
}
ntfs_attr_close(na);
}
if (ntfs_inode_close(ni)) {
ntfs_log_perror("Failed to close root");
res = -1;
}
}
return (res);
}
ntfs_volume *ntfs_device_mount(struct ntfs_device *dev, ntfs_mount_flags flags)
{
s64 l;
ntfs_volume *vol;
u8 *m = NULL, *m2 = NULL;
ntfs_attr_search_ctx *ctx = NULL;
ntfs_inode *ni;
ntfs_attr *na;
ATTR_RECORD *a;
VOLUME_INFORMATION *vinf;
ntfschar *vname;
u32 record_size;
int i, j, eo;
unsigned int k;
u32 u;
BOOL need_fallback_ro;
need_fallback_ro = FALSE;
vol = ntfs_volume_startup(dev, flags);
if (!vol)
return NULL;
m = ntfs_malloc(vol->mftmirr_size << vol->mft_record_size_bits);
m2 = ntfs_malloc(vol->mftmirr_size << vol->mft_record_size_bits);
if (!m || !m2)
goto error_exit;
l = ntfs_attr_mst_pread(vol->mft_na, 0, vol->mftmirr_size,
vol->mft_record_size, m);
if (l != vol->mftmirr_size) {
if (l == -1)
ntfs_log_perror("Failed to read $MFT");
else {
ntfs_log_error("Failed to read $MFT, unexpected length "
"(%lld != %d).\n", (long long)l,
vol->mftmirr_size);
errno = EIO;
}
goto error_exit;
}
for (i = 0; (i < l) && (i < FILE_first_user); ++i)
if (ntfs_mft_record_check(vol, FILE_MFT + i,
(MFT_RECORD*)(m + i*vol->mft_record_size)))
goto error_exit;
l = ntfs_attr_mst_pread(vol->mftmirr_na, 0, vol->mftmirr_size,
vol->mft_record_size, m2);
if (l != vol->mftmirr_size) {
if (l == -1) {
ntfs_log_perror("Failed to read $MFTMirr");
goto error_exit;
}
vol->mftmirr_size = l;
}
for (i = 0; (i < l) && (i < FILE_first_user); ++i)
if (ntfs_mft_record_check(vol, FILE_MFT + i,
(MFT_RECORD*)(m2 + i*vol->mft_record_size)))
goto error_exit;
ntfs_log_debug("Comparing $MFTMirr to $MFT...\n");
for (i = 0; (i < vol->mftmirr_size) && (i < FILE_first_user); ++i) {
MFT_RECORD *mrec, *mrec2;
const char *ESTR[12] = { "$MFT", "$MFTMirr", "$LogFile",
"$Volume", "$AttrDef", "root directory", "$Bitmap",
"$Boot", "$BadClus", "$Secure", "$UpCase", "$Extend" };
const char *s;
if (i < 12)
s = ESTR[i];
else if (i < 16)
s = "system file";
else
s = "mft record";
mrec = (MFT_RECORD*)(m + i * vol->mft_record_size);
if (mrec->flags & MFT_RECORD_IN_USE) {
if (ntfs_is_baad_record(mrec->magic)) {
ntfs_log_error("$MFT error: Incomplete multi "
"sector transfer detected in "
"'%s'.\n", s);
goto io_error_exit;
}
if (!ntfs_is_mft_record(mrec->magic)) {
ntfs_log_error("$MFT error: Invalid mft "
"record for '%s'.\n", s);
goto io_error_exit;
}
}
mrec2 = (MFT_RECORD*)(m2 + i * vol->mft_record_size);
if (mrec2->flags & MFT_RECORD_IN_USE) {
if (ntfs_is_baad_record(mrec2->magic)) {
ntfs_log_error("$MFTMirr error: Incomplete "
"multi sector transfer "
"detected in '%s'.\n", s);
goto io_error_exit;
}
if (!ntfs_is_mft_record(mrec2->magic)) {
ntfs_log_error("$MFTMirr error: Invalid mft "
"record for '%s'.\n", s);
goto io_error_exit;
}
}
record_size = ntfs_mft_record_get_data_size(mrec);
if ((record_size <= sizeof(MFT_RECORD))
|| (record_size > vol->mft_record_size)
|| memcmp(mrec, mrec2, record_size)) {
ntfs_log_error("$MFTMirr does not match $MFT (record "
"%d).\n", i);
goto io_error_exit;
}
}
free(m2);
free(m);
m = m2 = NULL;
ntfs_log_debug("Loading $Bitmap...\n");
vol->lcnbmp_ni = ntfs_inode_open(vol, FILE_Bitmap);
if (!vol->lcnbmp_ni) {
ntfs_log_perror("Failed to open inode FILE_Bitmap");
goto error_exit;
}
vol->lcnbmp_na = ntfs_attr_open(vol->lcnbmp_ni, AT_DATA, AT_UNNAMED, 0);
if (!vol->lcnbmp_na) {
ntfs_log_perror("Failed to open ntfs attribute");
goto error_exit;
}
if (vol->lcnbmp_na->data_size > vol->lcnbmp_na->allocated_size) {
ntfs_log_error("Corrupt cluster map size (%lld > %lld)\n",
(long long)vol->lcnbmp_na->data_size,
(long long)vol->lcnbmp_na->allocated_size);
goto io_error_exit;
}
ntfs_log_debug("Loading $UpCase...\n");
ni = ntfs_inode_open(vol, FILE_UpCase);
if (!ni) {
ntfs_log_perror("Failed to open inode FILE_UpCase");
goto error_exit;
}
na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
ntfs_log_perror("Failed to open ntfs attribute");
ntfs_inode_close(ni);
goto error_exit;
}
if ((na->data_size - 2) & ~0x1fffeULL) {
ntfs_log_error("Error: Upcase table is invalid (want size even "
"<= 131072).\n");
errno = EINVAL;
goto bad_upcase;
}
if (vol->upcase_len != na->data_size >> 1) {
vol->upcase_len = na->data_size >> 1;
free(vol->upcase);
vol->upcase = ntfs_malloc(na->data_size);
if (!vol->upcase)
goto bad_upcase;
}
l = ntfs_attr_pread(na, 0, na->data_size, vol->upcase);
if (l != na->data_size) {
ntfs_log_error("Failed to read $UpCase, unexpected length "
"(%lld != %lld).\n", (long long)l,
(long long)na->data_size);
errno = EIO;
goto bad_upcase;
}
ntfs_attr_close(na);
if (ntfs_inode_close(ni)) {
ntfs_log_perror("Failed to close $UpCase");
goto error_exit;
}
k = 0x20;
while ((k < vol->upcase_len)
&& (k < 0x7f)
&& (le16_to_cpu(vol->upcase[k])
== ((k < 'a') || (k > 'z') ? k : k + 'A' - 'a')))
k++;
if (k < 0x7f) {
ntfs_log_error("Corrupted file $UpCase\n");
goto io_error_exit;
}
ntfs_log_debug("Loading $Volume...\n");
vol->vol_ni = ntfs_inode_open(vol, FILE_Volume);
if (!vol->vol_ni) {
ntfs_log_perror("Failed to open inode FILE_Volume");
goto error_exit;
}
ctx = ntfs_attr_get_search_ctx(vol->vol_ni, NULL);
if (!ctx)
goto error_exit;
if (ntfs_attr_lookup(AT_VOLUME_INFORMATION, AT_UNNAMED, 0, 0, 0, NULL,
0, ctx)) {
ntfs_log_perror("$VOLUME_INFORMATION attribute not found in "
"$Volume");
goto error_exit;
}
a = ctx->attr;
if (a->non_resident) {
ntfs_log_error("Attribute $VOLUME_INFORMATION must be "
"resident but it isn't.\n");
errno = EIO;
goto error_exit;
}
vinf = (VOLUME_INFORMATION*)(le16_to_cpu(a->value_offset) + (char*)a);
if ((char*)vinf + le32_to_cpu(a->value_length) > (char*)ctx->mrec +
le32_to_cpu(ctx->mrec->bytes_in_use) ||
le16_to_cpu(a->value_offset) + le32_to_cpu(
a->value_length) > le32_to_cpu(a->length)) {
ntfs_log_error("$VOLUME_INFORMATION in $Volume is corrupt.\n");
errno = EIO;
goto error_exit;
}
vol->major_ver = vinf->major_ver;
vol->minor_ver = vinf->minor_ver;
vol->flags = vinf->flags;
ntfs_attr_reinit_search_ctx(ctx);
if (ntfs_attr_lookup(AT_VOLUME_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0,
ctx)) {
if (errno != ENOENT) {
ntfs_log_perror("Failed to lookup of $VOLUME_NAME in "
"$Volume failed");
goto error_exit;
}
vol->vol_name = ntfs_malloc(1);
if (!vol->vol_name)
goto error_exit;
vol->vol_name[0] = '\0';
} else {
a = ctx->attr;
if (a->non_resident) {
ntfs_log_error("$VOLUME_NAME must be resident.\n");
errno = EIO;
goto error_exit;
}
vname = (ntfschar*)(le16_to_cpu(a->value_offset) + (char*)a);
u = le32_to_cpu(a->value_length) / 2;
vol->vol_name = NULL;
if (ntfs_ucstombs(vname, u, &vol->vol_name, 0) == -1) {
ntfs_log_perror("Volume name could not be converted "
"to current locale");
ntfs_log_debug("Forcing name into ASCII by replacing "
"non-ASCII characters with underscores.\n");
vol->vol_name = ntfs_malloc(u + 1);
if (!vol->vol_name)
goto error_exit;
for (j = 0; j < (s32)u; j++) {
u16 uc = le16_to_cpu(vname[j]);
if (uc > 0xff)
uc = (u16)'_';
vol->vol_name[j] = (char)uc;
}
vol->vol_name[u] = '\0';
}
}
ntfs_attr_put_search_ctx(ctx);
ctx = NULL;
ntfs_log_debug("Loading $AttrDef...\n");
ni = ntfs_inode_open(vol, FILE_AttrDef);
if (!ni) {
ntfs_log_perror("Failed to open $AttrDef");
goto error_exit;
}
na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
ntfs_log_perror("Failed to open ntfs attribute");
goto error_exit;
}
if ((u64)na->data_size > 0xffffffLL) {
ntfs_log_error("Attribute definition table is too big (max "
"24-bit allowed).\n");
errno = EINVAL;
goto error_exit;
}
vol->attrdef_len = na->data_size;
vol->attrdef = ntfs_malloc(na->data_size);
if (!vol->attrdef)
goto error_exit;
l = ntfs_attr_pread(na, 0, na->data_size, vol->attrdef);
if (l != na->data_size) {
ntfs_log_error("Failed to read $AttrDef, unexpected length "
"(%lld != %lld).\n", (long long)l,
(long long)na->data_size);
errno = EIO;
goto error_exit;
}
ntfs_attr_close(na);
if (ntfs_inode_close(ni)) {
ntfs_log_perror("Failed to close $AttrDef");
goto error_exit;
}
if (ntfs_open_secure(vol))
goto error_exit;
if (!(flags & (NTFS_MNT_RDONLY | NTFS_MNT_FORENSIC))) {
if (!(flags & NTFS_MNT_IGNORE_HIBERFILE) &&
ntfs_volume_check_hiberfile(vol, 1) < 0) {
if (flags & NTFS_MNT_MAY_RDONLY)
need_fallback_ro = TRUE;
else
goto error_exit;
}
if (ntfs_volume_check_logfile(vol) < 0) {
if (!(flags & NTFS_MNT_RECOVER) || (errno == EPERM)) {
if (flags & NTFS_MNT_MAY_RDONLY)
need_fallback_ro = TRUE;
else
goto error_exit;
} else {
ntfs_log_info("The file system wasn't safely "
"closed on Windows. Fixing.\n");
if (ntfs_logfile_reset(vol))
goto error_exit;
}
}
if (!(flags & NTFS_MNT_RDONLY) && !need_fallback_ro) {
if (fix_txf_data(vol))
goto error_exit;
}
}
if (need_fallback_ro) {
NVolSetReadOnly(vol);
ntfs_log_error("%s", fallback_readonly_msg);
}
return vol;
bad_upcase :
ntfs_attr_close(na);
ntfs_inode_close(ni);
goto error_exit;
io_error_exit:
errno = EIO;
error_exit:
eo = errno;
if (ctx)
ntfs_attr_put_search_ctx(ctx);
free(m);
free(m2);
__ntfs_volume_release(vol);
errno = eo;
return NULL;
}
int ntfs_set_shown_files(ntfs_volume *vol,
BOOL show_sys_files, BOOL show_hid_files,
BOOL hide_dot_files)
{
int res;
res = -1;
if (vol) {
NVolClearShowSysFiles(vol);
NVolClearShowHidFiles(vol);
NVolClearHideDotFiles(vol);
if (show_sys_files)
NVolSetShowSysFiles(vol);
if (show_hid_files)
NVolSetShowHidFiles(vol);
if (hide_dot_files)
NVolSetHideDotFiles(vol);
res = 0;
}
if (res)
ntfs_log_error("Failed to set file visibility\n");
return (res);
}
int ntfs_set_ignore_case(ntfs_volume *vol)
{
int res;
res = -1;
if (vol && vol->upcase) {
vol->locase = ntfs_locase_table_build(vol->upcase,
vol->upcase_len);
if (vol->locase) {
NVolClearCaseSensitive(vol);
res = 0;
}
}
if (res)
ntfs_log_error("Failed to set ignore_case mode\n");
return (res);
}
ntfs_volume *ntfs_mount(const char *name __attribute__((unused)),
ntfs_mount_flags flags __attribute__((unused)))
{
#ifndef NO_NTFS_DEVICE_DEFAULT_IO_OPS
struct ntfs_device *dev;
ntfs_volume *vol;
dev = ntfs_device_alloc(name, 0, &ntfs_device_default_io_ops, NULL);
if (!dev)
return NULL;
vol = ntfs_device_mount(dev, flags);
if (!vol) {
int eo = errno;
ntfs_device_free(dev);
errno = eo;
} else
ntfs_create_lru_caches(vol);
return vol;
#else
errno = EOPNOTSUPP;
return NULL;
#endif
}
int ntfs_umount(ntfs_volume *vol, const BOOL force __attribute__((unused)))
{
struct ntfs_device *dev;
int ret;
if (!vol) {
errno = EINVAL;
return -1;
}
dev = vol->dev;
ret = __ntfs_volume_release(vol);
ntfs_device_free(dev);
return ret;
}
#ifdef HAVE_MNTENT_H
static int ntfs_mntent_check(const char *file, unsigned long *mnt_flags)
{
struct mntent *mnt;
char *real_file = NULL, *real_fsname = NULL;
FILE *f;
int err = 0;
real_file = ntfs_malloc(PATH_MAX + 1);
if (!real_file)
return -1;
real_fsname = ntfs_malloc(PATH_MAX + 1);
if (!real_fsname) {
err = errno;
goto exit;
}
if (!ntfs_realpath_canonicalize(file, real_file)) {
err = errno;
goto exit;
}
f = setmntent("/proc/mounts", "r");
if (!f && !(f = setmntent(MOUNTED, "r"))) {
err = errno;
goto exit;
}
while ((mnt = getmntent(f))) {
if (!ntfs_realpath_canonicalize(mnt->mnt_fsname, real_fsname))
continue;
if (!strcmp(real_file, real_fsname))
break;
}
endmntent(f);
if (!mnt)
goto exit;
*mnt_flags = NTFS_MF_MOUNTED;
if (!strcmp(mnt->mnt_dir, "/"))
*mnt_flags |= NTFS_MF_ISROOT;
#ifdef HAVE_HASMNTOPT
if (hasmntopt(mnt, "ro") && !hasmntopt(mnt, "rw"))
*mnt_flags |= NTFS_MF_READONLY;
#endif
exit:
free(real_file);
free(real_fsname);
if (err) {
errno = err;
return -1;
}
return 0;
}
#else
#if defined(__sun) && defined (__SVR4)
static int ntfs_mntent_check(const char *file, unsigned long *mnt_flags)
{
struct mnttab *mnt = NULL;
char *real_file = NULL, *real_fsname = NULL;
FILE *f;
int err = 0;
real_file = (char*)ntfs_malloc(PATH_MAX + 1);
if (!real_file)
return -1;
real_fsname = (char*)ntfs_malloc(PATH_MAX + 1);
mnt = (struct mnttab*)ntfs_malloc(MNT_LINE_MAX + 1);
if (!real_fsname || !mnt) {
err = errno;
goto exit;
}
if (!ntfs_realpath_canonicalize(file, real_file)) {
err = errno;
goto exit;
}
if (!(f = fopen(MNTTAB, "r"))) {
err = errno;
goto exit;
}
while (!getmntent(f, mnt)) {
if (!ntfs_realpath_canonicalize(mnt->mnt_special, real_fsname))
continue;
if (!strcmp(real_file, real_fsname)) {
*mnt_flags = NTFS_MF_MOUNTED;
if (!strcmp(mnt->mnt_mountp, "/"))
*mnt_flags |= NTFS_MF_ISROOT;
if (hasmntopt(mnt, "ro") && !hasmntopt(mnt, "rw"))
*mnt_flags |= NTFS_MF_READONLY;
break;
}
}
fclose(f);
exit:
free(mnt);
free(real_file);
free(real_fsname);
if (err) {
errno = err;
return -1;
}
return 0;
}
#endif
#endif
int ntfs_check_if_mounted(const char *file __attribute__((unused)),
unsigned long *mnt_flags)
{
*mnt_flags = 0;
#if defined(HAVE_MNTENT_H) || (defined(__sun) && defined (__SVR4))
return ntfs_mntent_check(file, mnt_flags);
#else
return 0;
#endif
}
int ntfs_version_is_supported(ntfs_volume *vol)
{
u8 major, minor;
if (!vol) {
errno = EINVAL;
return -1;
}
major = vol->major_ver;
minor = vol->minor_ver;
if (NTFS_V1_1(major, minor) || NTFS_V1_2(major, minor))
return 0;
if (NTFS_V2_X(major, minor))
return 0;
if (NTFS_V3_0(major, minor) || NTFS_V3_1(major, minor))
return 0;
errno = EOPNOTSUPP;
return -1;
}
int ntfs_logfile_reset(ntfs_volume *vol)
{
ntfs_inode *ni;
ntfs_attr *na;
int eo;
if (!vol) {
errno = EINVAL;
return -1;
}
ni = ntfs_inode_open(vol, FILE_LogFile);
if (!ni) {
ntfs_log_perror("Failed to open inode FILE_LogFile");
return -1;
}
na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0);
if (!na) {
eo = errno;
ntfs_log_perror("Failed to open $FILE_LogFile/$DATA");
goto error_exit;
}
if (ntfs_empty_logfile(na)) {
eo = errno;
ntfs_attr_close(na);
goto error_exit;
}
ntfs_attr_close(na);
return ntfs_inode_close(ni);
error_exit:
ntfs_inode_close(ni);
errno = eo;
return -1;
}
int ntfs_volume_write_flags(ntfs_volume *vol, const le16 flags)
{
ATTR_RECORD *a;
VOLUME_INFORMATION *c;
ntfs_attr_search_ctx *ctx;
int ret = -1;
if (!vol || !vol->vol_ni) {
errno = EINVAL;
return -1;
}
ctx = ntfs_attr_get_search_ctx(vol->vol_ni, NULL);
if (!ctx)
return -1;
if (ntfs_attr_lookup(AT_VOLUME_INFORMATION, AT_UNNAMED, 0, 0, 0, NULL,
0, ctx)) {
ntfs_log_error("Attribute $VOLUME_INFORMATION was not found "
"in $Volume!\n");
goto err_out;
}
a = ctx->attr;
if (a->non_resident) {
ntfs_log_error("Attribute $VOLUME_INFORMATION must be resident "
"but it isn't.\n");
errno = EIO;
goto err_out;
}
c = (VOLUME_INFORMATION*)(le16_to_cpu(a->value_offset) + (char*)a);
if ((char*)c + le32_to_cpu(a->value_length) > (char*)ctx->mrec +
le32_to_cpu(ctx->mrec->bytes_in_use) ||
le16_to_cpu(a->value_offset) +
le32_to_cpu(a->value_length) > le32_to_cpu(a->length)) {
ntfs_log_error("Attribute $VOLUME_INFORMATION in $Volume is "
"corrupt!\n");
errno = EIO;
goto err_out;
}
vol->flags = c->flags = flags & VOLUME_FLAGS_MASK;
ntfs_inode_mark_dirty(vol->vol_ni);
if (ntfs_inode_sync(vol->vol_ni))
goto err_out;
ret = 0;
err_out:
ntfs_attr_put_search_ctx(ctx);
return ret;
}
int ntfs_volume_error(int err)
{
int ret;
switch (err) {
case 0:
ret = NTFS_VOLUME_OK;
break;
case EINVAL:
ret = NTFS_VOLUME_NOT_NTFS;
break;
case EIO:
ret = NTFS_VOLUME_CORRUPT;
break;
case EPERM:
ret = NTFS_VOLUME_HIBERNATED;
break;
case EOPNOTSUPP:
ret = NTFS_VOLUME_UNCLEAN_UNMOUNT;
break;
case EBUSY:
ret = NTFS_VOLUME_LOCKED;
break;
case ENXIO:
ret = NTFS_VOLUME_RAID;
break;
case EACCES:
ret = NTFS_VOLUME_NO_PRIVILEGE;
break;
default:
ret = NTFS_VOLUME_UNKNOWN_REASON;
break;
}
return ret;
}
void ntfs_mount_error(const char *volume, const char *mntpoint, int err)
{
switch (err) {
case NTFS_VOLUME_NOT_NTFS:
ntfs_log_error(invalid_ntfs_msg, volume);
break;
case NTFS_VOLUME_CORRUPT:
ntfs_log_error("%s", corrupt_volume_msg);
break;
case NTFS_VOLUME_HIBERNATED:
ntfs_log_error(hibernated_volume_msg, volume, mntpoint);
break;
case NTFS_VOLUME_UNCLEAN_UNMOUNT:
ntfs_log_error("%s", unclean_journal_msg);
break;
case NTFS_VOLUME_LOCKED:
ntfs_log_error("%s", opened_volume_msg);
break;
case NTFS_VOLUME_RAID:
ntfs_log_error("%s", fakeraid_msg);
break;
case NTFS_VOLUME_NO_PRIVILEGE:
ntfs_log_error(access_denied_msg, volume);
break;
}
}
int ntfs_set_locale(void)
{
#ifndef __HAIKU__
const char *locale;
locale = setlocale(LC_ALL, "");
if (!locale) {
locale = setlocale(LC_ALL, NULL);
ntfs_log_error("Couldn't set local environment, using default "
"'%s'.\n", locale);
return 1;
}
#endif
return 0;
}
int ntfs_volume_get_free_space(ntfs_volume *vol)
{
ntfs_attr *na;
int ret;
ret = -1;
vol->free_clusters = ntfs_attr_get_free_bits(vol->lcnbmp_na);
if (vol->free_clusters < 0) {
ntfs_log_perror("Failed to read NTFS $Bitmap");
} else {
na = vol->mftbmp_na;
vol->free_mft_records = ntfs_attr_get_free_bits(na);
if (vol->free_mft_records >= 0)
vol->free_mft_records += (na->allocated_size - na->data_size) << 3;
if (vol->free_mft_records < 0)
ntfs_log_perror("Failed to calculate free MFT records");
else {
NVolSetFreeSpaceKnown(vol);
ret = 0;
}
}
return (ret);
}
int ntfs_volume_rename(ntfs_volume *vol, const ntfschar *label, int label_len)
{
ntfs_attr *na;
char *old_vol_name;
char *new_vol_name = NULL;
int new_vol_name_len;
int err;
if (NVolReadOnly(vol)) {
ntfs_log_error("Refusing to change label on read-only mounted "
"volume.\n");
errno = EROFS;
return -1;
}
label_len *= sizeof(ntfschar);
if (label_len > 0x100) {
ntfs_log_error("New label is too long. Maximum %u characters "
"allowed.\n",
(unsigned)(0x100 / sizeof(ntfschar)));
errno = ERANGE;
return -1;
}
na = ntfs_attr_open(vol->vol_ni, AT_VOLUME_NAME, AT_UNNAMED, 0);
if (!na) {
if (errno != ENOENT) {
err = errno;
ntfs_log_perror("Lookup of $VOLUME_NAME attribute "
"failed");
goto err_out;
}
if (ntfs_attr_add(vol->vol_ni, AT_VOLUME_NAME, AT_UNNAMED, 0,
(const u8*) label, label_len))
{
err = errno;
ntfs_log_perror("Encountered error while adding "
"$VOLUME_NAME attribute");
goto err_out;
}
}
else {
s64 written;
if (NAttrNonResident(na)) {
err = errno;
ntfs_log_error("Error: Attribute $VOLUME_NAME must be "
"resident.\n");
goto err_out;
}
if (na->data_size != label_len) {
if (ntfs_attr_truncate(na, label_len)) {
err = errno;
ntfs_log_perror("Error resizing resident "
"attribute");
goto err_out;
}
}
if (label_len) {
written = ntfs_attr_pwrite(na, 0, label_len, label);
if (written == -1) {
err = errno;
ntfs_log_perror("Error when writing "
"$VOLUME_NAME data");
goto err_out;
}
else if (written != label_len) {
err = EIO;
ntfs_log_error("Partial write when writing "
"$VOLUME_NAME data.");
goto err_out;
}
}
}
new_vol_name_len =
ntfs_ucstombs(label, label_len, &new_vol_name, 0);
if (new_vol_name_len == -1) {
err = errno;
ntfs_log_perror("Error while decoding new volume name");
goto err_out;
}
old_vol_name = vol->vol_name;
vol->vol_name = new_vol_name;
free(old_vol_name);
err = 0;
err_out:
if (na)
ntfs_attr_close(na);
if (err)
errno = err;
return err ? -1 : 0;
}