blob: 4c0a09c82b4a6c3a697d025c04e3e0c76cc99681 [file] [log] [blame]
/*
* Copyright (C) 2011-2012 Paulo Alcantara <pcacjr@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/* Note: No support for compressed files */
#include <dprintf.h>
#include <stdio.h>
#include <string.h>
#include <sys/dirent.h>
#include <cache.h>
#include <core.h>
#include <disk.h>
#include <fs.h>
#include <ilog2.h>
#include <klibc/compiler.h>
#include <ctype.h>
#include "codepage.h"
#include "ntfs.h"
#include "runlist.h"
static struct ntfs_readdir_state *readdir_state;
/*** Function declarations */
static f_mft_record_lookup ntfs_mft_record_lookup_3_0;
static f_mft_record_lookup ntfs_mft_record_lookup_3_1;
static inline enum dirent_type get_inode_mode(struct ntfs_mft_record *mrec);
static inline struct ntfs_attr_record * ntfs_attr_lookup(struct fs_info *fs, uint32_t type, struct ntfs_mft_record **mmrec, struct ntfs_mft_record *mrec);
static inline uint8_t *mapping_chunk_init(struct ntfs_attr_record *attr,struct mapping_chunk *chunk,uint32_t *offset);
static int parse_data_run(const void *stream, uint32_t *offset, uint8_t *attr_len, struct mapping_chunk *chunk);
/*** Function definitions */
/* Check if there are specific zero fields in an NTFS boot sector */
static inline int ntfs_check_zero_fields(const struct ntfs_bpb *sb)
{
return !sb->res_sectors && (!sb->zero_0[0] && !sb->zero_0[1] &&
!sb->zero_0[2]) && !sb->zero_1 && !sb->zero_2 &&
!sb->zero_3;
}
static inline int ntfs_check_sb_fields(const struct ntfs_bpb *sb)
{
return ntfs_check_zero_fields(sb) &&
(!memcmp(sb->oem_name, "NTFS ", 8) ||
!memcmp(sb->oem_name, "MSWIN4.0", 8) ||
!memcmp(sb->oem_name, "MSWIN4.1", 8));
}
static inline struct inode *new_ntfs_inode(struct fs_info *fs)
{
struct inode *inode;
inode = alloc_inode(fs, 0, sizeof(struct ntfs_inode));
if (!inode)
malloc_error("inode structure");
return inode;
}
static void ntfs_fixups_writeback(struct fs_info *fs, struct ntfs_record *nrec)
{
uint16_t *usa;
uint16_t usa_no;
uint16_t usa_count;
uint16_t *blk;
dprintf("in %s()\n", __func__);
if (nrec->magic != NTFS_MAGIC_FILE && nrec->magic != NTFS_MAGIC_INDX)
return;
/* get the Update Sequence Array offset */
usa = (uint16_t *)((uint8_t *)nrec + nrec->usa_ofs);
/* get the Update Sequence Array Number and skip it */
usa_no = *usa++;
/* get the Update Sequene Array count */
usa_count = nrec->usa_count - 1; /* exclude the USA number */
/* make it to point to the last two bytes of the RECORD's first sector */
blk = (uint16_t *)((uint8_t *)nrec + SECTOR_SIZE(fs) - 2);
while (usa_count--) {
if (*blk != usa_no)
break;
*blk = *usa++;
blk = (uint16_t *)((uint8_t *)blk + SECTOR_SIZE(fs));
}
}
/* read content from cache */
static int ntfs_read(struct fs_info *fs, void *buf, size_t len, uint64_t count,
block_t *blk, uint64_t *blk_offset,
uint64_t *blk_next_offset, uint64_t *lcn)
{
uint8_t *data;
uint64_t offset = *blk_offset;
const uint32_t clust_byte_shift = NTFS_SB(fs)->clust_byte_shift;
const uint64_t blk_size = UINT64_C(1) << BLOCK_SHIFT(fs);
uint64_t bytes;
uint64_t lbytes;
uint64_t loffset;
uint64_t k;
dprintf("in %s()\n", __func__);
if (count > len)
goto out;
data = (uint8_t *)get_cache(fs->fs_dev, *blk);
if (!data)
goto out;
if (!offset)
offset = (*lcn << clust_byte_shift) % blk_size;
dprintf("LCN: 0x%X\n", *lcn);
dprintf("offset: 0x%X\n", offset);
bytes = count; /* bytes to copy */
lbytes = blk_size - offset; /* bytes left to copy */
if (lbytes >= bytes) {
/* so there's room enough, then copy the whole content */
memcpy(buf, data + offset, bytes);
loffset = offset;
offset += count;
} else {
dprintf("bytes: %u\n", bytes);
dprintf("bytes left: %u\n", lbytes);
/* otherwise, let's copy it partially... */
k = 0;
while (bytes) {
memcpy(buf + k, data + offset, lbytes);
bytes -= lbytes;
loffset = offset;
offset += lbytes;
k += lbytes;
if (offset >= blk_size) {
/* then fetch a new FS block */
data = (uint8_t *)get_cache(fs->fs_dev, ++*blk);
if (!data)
goto out;
lbytes = bytes;
loffset = offset;
offset = 0;
}
}
}
if (loffset >= blk_size)
loffset = 0; /* it must be aligned on a block boundary */
*blk_offset = loffset;
if (blk_next_offset)
*blk_next_offset = offset;
*lcn += blk_size / count; /* update LCN */
return 0;
out:
return -1;
}
/* AndyAlex: read and validate single MFT record. Keep in mind that MFT itself can be fragmented */
static struct ntfs_mft_record *ntfs_mft_record_lookup_any(struct fs_info *fs,
uint32_t file, block_t *out_blk, bool is_v31)
{
const uint64_t mft_record_size = NTFS_SB(fs)->mft_record_size;
uint8_t *buf = NULL;
const uint32_t mft_record_shift = ilog2(mft_record_size);
const uint32_t clust_byte_shift = NTFS_SB(fs)->clust_byte_shift;
uint64_t next_offset = 0;
uint64_t lcn = 0;
block_t blk = 0;
uint64_t offset = 0;
struct ntfs_mft_record *mrec = NULL, *lmrec = NULL;
uint64_t start_blk = 0;
struct ntfs_attr_record *attr = NULL;
uint8_t *stream = NULL;
uint32_t attr_offset = 0;
uint8_t *attr_len = NULL;
struct mapping_chunk chunk;
int err = 0;
/* determine MFT record's LCN */
uint64_t vcn = (file << mft_record_shift >> clust_byte_shift);
dprintf("in %s(%s)\n", __func__,(is_v31?"v3.1":"v3.0"));
if (0==vcn) {
lcn = NTFS_SB(fs)->mft_lcn;
} else do {
dprintf("%s: looking for VCN %u for MFT record %u\n", __func__,(unsigned)vcn,(unsigned)file);
mrec = NTFS_SB(fs)->mft_record_lookup(fs, 0, &start_blk);
if (!mrec) {dprintf("%s: read MFT(0) failed\n", __func__); break;}
lmrec = mrec;
if (get_inode_mode(mrec) != DT_REG) {dprintf("%s: $MFT is not a file\n", __func__); break;}
attr = ntfs_attr_lookup(fs, NTFS_AT_DATA, &mrec, lmrec);
if (!attr) {dprintf("%s: $MFT have no data attr\n", __func__); break;}
if (!attr->non_resident) {dprintf("%s: $MFT data attr is resident\n", __func__); break;}
attr_len = (uint8_t *)attr + attr->len;
stream = mapping_chunk_init(attr, &chunk, &attr_offset);
while (true) {
err = parse_data_run(stream, &attr_offset, attr_len, &chunk);
if (err) {dprintf("%s: $MFT data run parse failed with error %d\n", __func__,err); break;}
if (chunk.flags & MAP_UNALLOCATED) continue;
if (chunk.flags & MAP_END) break;
if (chunk.flags & MAP_ALLOCATED) {
dprintf("%s: Chunk: VCN=%u, LCN=%u, len=%u\n", __func__,(unsigned)chunk.vcn,(unsigned)chunk.lcn,(unsigned)chunk.len);
if ((vcn>=chunk.vcn)&&(vcn<chunk.vcn+chunk.len)) {
lcn=vcn-chunk.vcn+chunk.lcn;
dprintf("%s: VCN %u for MFT record %u maps to lcn %u\n", __func__,(unsigned)vcn,(unsigned)file,(unsigned)lcn);
break;
}
chunk.vcn += chunk.len;
}
}
} while(false);
if (mrec!=NULL) free(mrec);
mrec = NULL;
if (0==lcn) {
dprintf("%s: unable to map VCN %u for MFT record %u\n", __func__,(unsigned)vcn,(unsigned)file);
return NULL;
}
/* determine MFT record's block number */
blk = (lcn << clust_byte_shift >> BLOCK_SHIFT(fs));
offset = (file << mft_record_shift) % BLOCK_SIZE(fs);
/* Allocate buffer */
buf = (uint8_t *)malloc(mft_record_size);
if (!buf) {malloc_error("uint8_t *");return 0;}
/* Read block */
err = ntfs_read(fs, buf, mft_record_size, mft_record_size, &blk,
&offset, &next_offset, &lcn);
if (err) {
dprintf("%s: error read block %u from cache\n", __func__, blk);
printf("Error while reading from cache.\n");
free(buf);
return NULL;
}
/* Process fixups and make structure pointer */
ntfs_fixups_writeback(fs, (struct ntfs_record *)buf);
mrec = (struct ntfs_mft_record *)buf;
/* check if it has a valid magic number and record number */
if (mrec->magic != NTFS_MAGIC_FILE) mrec = NULL;
if (mrec && is_v31) if (mrec->mft_record_no != file) mrec = NULL;
if (mrec!=NULL) {
if (out_blk) {
*out_blk = (file << mft_record_shift >> BLOCK_SHIFT(fs)); /* update record starting block */
}
return mrec; /* found MFT record */
}
/* Invalid record */
dprintf("%s: MFT record %u is invalid\n", __func__, (unsigned)file);
free(buf);
return NULL;
}
static struct ntfs_mft_record *ntfs_mft_record_lookup_3_0(struct fs_info *fs,
uint32_t file, block_t *blk)
{
return ntfs_mft_record_lookup_any(fs,file,blk,false);
}
static struct ntfs_mft_record *ntfs_mft_record_lookup_3_1(struct fs_info *fs,
uint32_t file, block_t *blk)
{
return ntfs_mft_record_lookup_any(fs,file,blk,true);
}
static bool ntfs_filename_cmp(const char *dname, struct ntfs_idx_entry *ie)
{
const uint16_t *entry_fn;
uint8_t entry_fn_len;
unsigned i;
dprintf("in %s()\n", __func__);
entry_fn = ie->key.file_name.file_name;
entry_fn_len = ie->key.file_name.file_name_len;
if (strlen(dname) != entry_fn_len)
return false;
/* Do case-sensitive compares for Posix file names */
if (ie->key.file_name.file_name_type == FILE_NAME_POSIX) {
for (i = 0; i < entry_fn_len; i++)
if (entry_fn[i] != dname[i])
return false;
} else {
for (i = 0; i < entry_fn_len; i++)
if (tolower(entry_fn[i]) != tolower(dname[i]))
return false;
}
return true;
}
static inline uint8_t *mapping_chunk_init(struct ntfs_attr_record *attr,
struct mapping_chunk *chunk,
uint32_t *offset)
{
memset(chunk, 0, sizeof *chunk);
*offset = 0U;
return (uint8_t *)attr + attr->data.non_resident.mapping_pairs_offset;
}
/* Parse data runs.
*
* return 0 on success or -1 on failure.
*/
static int parse_data_run(const void *stream, uint32_t *offset,
uint8_t *attr_len, struct mapping_chunk *chunk)
{
uint8_t *buf; /* Pointer to the zero-terminated byte stream */
uint8_t count; /* The count byte */
uint8_t v, l; /* v is the number of changed low-order VCN bytes;
* l is the number of changed low-order LCN bytes
*/
uint8_t *byte;
const int byte_shift = 8;
int mask;
int64_t res;
(void)attr_len;
dprintf("in %s()\n", __func__);
chunk->flags &= ~MAP_MASK;
buf = (uint8_t *)stream + *offset;
if (buf > attr_len || !*buf) {
chunk->flags |= MAP_END; /* we're done */
return 0;
}
if (!*offset)
chunk->flags |= MAP_START; /* initial chunk */
count = *buf;
v = count & 0x0F;
l = count >> 4;
if (v > 8 || l > 8) /* more than 8 bytes ? */
goto out;
byte = (uint8_t *)buf + v;
count = v;
res = 0LL;
while (count--)
res = (res << byte_shift) | *byte--;
chunk->len = res; /* get length data */
byte = (uint8_t *)buf + v + l;
count = l;
mask = 0xFFFFFFFF;
res = 0LL;
if (*byte & 0x80)
res |= (int64_t)mask; /* sign-extend it */
while (count--)
res = (res << byte_shift) | *byte--;
chunk->lcn += res;
/* are VCNS from cur_vcn to next_vcn - 1 unallocated ? */
if (!chunk->lcn)
chunk->flags |= MAP_UNALLOCATED;
else
chunk->flags |= MAP_ALLOCATED;
*offset += v + l + 1;
return 0;
out:
return -1;
}
static struct ntfs_mft_record *
ntfs_attr_list_lookup(struct fs_info *fs, struct ntfs_attr_record *attr,
uint32_t type, struct ntfs_mft_record *mrec)
{
uint8_t *attr_len;
struct mapping_chunk chunk;
uint32_t offset;
uint8_t *stream;
int err;
const uint64_t blk_size = UINT64_C(1) << BLOCK_SHIFT(fs);
uint8_t buf[blk_size];
uint64_t blk_offset;
int64_t vcn;
int64_t lcn;
int64_t last_lcn;
block_t blk;
struct ntfs_attr_list_entry *attr_entry;
uint32_t len = 0;
struct ntfs_mft_record *retval;
uint64_t start_blk = 0;
dprintf("in %s()\n", __func__);
if (attr->non_resident)
goto handle_non_resident_attr;
attr_entry = (struct ntfs_attr_list_entry *)
((uint8_t *)attr + attr->data.resident.value_offset);
len = attr->data.resident.value_len;
for (; (uint8_t *)attr_entry < (uint8_t *)attr + len;
attr_entry = (struct ntfs_attr_list_entry *)((uint8_t *)attr_entry +
attr_entry->length)) {
dprintf("<$ATTRIBUTE_LIST> Attribute type: 0x%X\n",
attr_entry->type);
if (attr_entry->type == type)
goto found; /* We got the attribute! :-) */
}
printf("No attribute found.\n");
goto out;
handle_non_resident_attr:
attr_len = (uint8_t *)attr + attr->len;
stream = mapping_chunk_init(attr, &chunk, &offset);
do {
err = parse_data_run(stream, &offset, attr_len, &chunk);
if (err) {
printf("parse_data_run()\n");
goto out;
}
if (chunk.flags & MAP_UNALLOCATED)
continue;
if (chunk.flags & MAP_END)
break;
if (chunk.flags & MAP_ALLOCATED) {
vcn = 0;
lcn = chunk.lcn;
while (vcn < chunk.len) {
blk = (lcn + vcn) << NTFS_SB(fs)->clust_byte_shift >>
BLOCK_SHIFT(fs);
blk_offset = 0;
last_lcn = lcn;
lcn += vcn;
err = ntfs_read(fs, buf, blk_size, blk_size, &blk,
&blk_offset, NULL, (uint64_t *)&lcn);
if (err) {
printf("Error while reading from cache.\n");
goto out;
}
attr_entry = (struct ntfs_attr_list_entry *)&buf;
len = attr->data.non_resident.data_size;
for (; (uint8_t *)attr_entry < (uint8_t *)&buf[0] + len;
attr_entry = (struct ntfs_attr_list_entry *)
((uint8_t *)attr_entry + attr_entry->length)) {
dprintf("<$ATTRIBUTE_LIST> Attribute type: 0x%x\n",
attr_entry->type);
if (attr_entry->type == type)
goto found; /* We got the attribute! :-) */
}
lcn = last_lcn; /* restore original LCN */
/* go to the next VCN */
vcn += (blk_size / (1 << NTFS_SB(fs)->clust_byte_shift));
}
}
} while (!(chunk.flags & MAP_END));
printf("No attribute found.\n");
out:
return NULL;
found:
/* At this point we have the attribute we were looking for. Now we
* will look for the MFT record that stores information about this
* attribute.
*/
/* Check if the attribute type we're looking for is in the same
* MFT record. If so, we do not need to look it up again - return it.
*/
if (mrec->mft_record_no == attr_entry->mft_ref)
return mrec;
retval = NTFS_SB(fs)->mft_record_lookup(fs, attr_entry->mft_ref,
&start_blk);
if (!retval) {
printf("No MFT record found!\n");
goto out;
}
/* return the found MFT record */
return retval;
}
static struct ntfs_attr_record *
__ntfs_attr_lookup(struct fs_info *fs, uint32_t type,
struct ntfs_mft_record **mrec)
{
struct ntfs_mft_record *_mrec = *mrec;
struct ntfs_attr_record *attr;
struct ntfs_attr_record *attr_list_attr;
dprintf("in %s()\n", __func__);
if (!_mrec || type == NTFS_AT_END)
goto out;
again:
attr_list_attr = NULL;
attr = (struct ntfs_attr_record *)((uint8_t *)_mrec + _mrec->attrs_offset);
/* walk through the file attribute records */
for (;; attr = (struct ntfs_attr_record *)((uint8_t *)attr + attr->len)) {
if (attr->type == NTFS_AT_END)
break;
if (attr->type == NTFS_AT_ATTR_LIST) {
dprintf("MFT record #%lu has an $ATTRIBUTE_LIST attribute.\n",
_mrec->mft_record_no);
attr_list_attr = attr;
continue;
}
if (attr->type == type)
break;
}
/* if the record has an $ATTRIBUTE_LIST attribute associated
* with it, then we need to look for the wanted attribute in
* it as well.
*/
if (attr->type == NTFS_AT_END && attr_list_attr) {
struct ntfs_mft_record *retval;
retval = ntfs_attr_list_lookup(fs, attr_list_attr, type, _mrec);
if (!retval)
goto out;
_mrec = retval;
goto again;
} else if (attr->type == NTFS_AT_END && !attr_list_attr) {
attr = NULL;
}
return attr;
out:
return NULL;
}
static inline struct ntfs_attr_record *
ntfs_attr_lookup(struct fs_info *fs, uint32_t type,
struct ntfs_mft_record **mmrec,
struct ntfs_mft_record *mrec)
{
struct ntfs_mft_record *_mrec = mrec;
struct ntfs_mft_record *other = *mmrec;
struct ntfs_attr_record *retval = NULL;
if (mrec == other)
return __ntfs_attr_lookup(fs, type, &other);
retval = __ntfs_attr_lookup(fs, type, &_mrec);
if (!retval) {
_mrec = other;
retval = __ntfs_attr_lookup(fs, type, &other);
if (!retval)
other = _mrec;
} else if (retval && (_mrec != mrec)) {
other = _mrec;
}
return retval;
}
static inline enum dirent_type get_inode_mode(struct ntfs_mft_record *mrec)
{
return mrec->flags & MFT_RECORD_IS_DIRECTORY ? DT_DIR : DT_REG;
}
static int index_inode_setup(struct fs_info *fs, unsigned long mft_no,
struct inode *inode)
{
uint64_t start_blk = 0;
struct ntfs_mft_record *mrec, *lmrec;
struct ntfs_attr_record *attr;
enum dirent_type d_type;
uint8_t *attr_len;
struct mapping_chunk chunk;
int err;
uint8_t *stream;
uint32_t offset;
dprintf("in %s()\n", __func__);
mrec = NTFS_SB(fs)->mft_record_lookup(fs, mft_no, &start_blk);
if (!mrec) {
printf("No MFT record found.\n");
goto out;
}
lmrec = mrec;
NTFS_PVT(inode)->mft_no = mft_no;
NTFS_PVT(inode)->seq_no = mrec->seq_no;
NTFS_PVT(inode)->start_cluster = start_blk >> NTFS_SB(fs)->clust_shift;
NTFS_PVT(inode)->here = start_blk;
d_type = get_inode_mode(mrec);
if (d_type == DT_DIR) { /* directory stuff */
dprintf("Got a directory.\n");
attr = ntfs_attr_lookup(fs, NTFS_AT_INDEX_ROOT, &mrec, lmrec);
if (!attr) {
printf("No attribute found.\n");
goto out;
}
/* check if we have a previous allocated state structure */
if (readdir_state) {
free(readdir_state);
readdir_state = NULL;
}
/* allocate our state structure */
readdir_state = malloc(sizeof *readdir_state);
if (!readdir_state)
malloc_error("ntfs_readdir_state structure");
readdir_state->mft_no = mft_no;
/* obviously, the ntfs_readdir() caller will start from INDEX root */
readdir_state->in_idx_root = true;
} else if (d_type == DT_REG) { /* file stuff */
dprintf("Got a file.\n");
attr = ntfs_attr_lookup(fs, NTFS_AT_DATA, &mrec, lmrec);
if (!attr) {
printf("No attribute found.\n");
goto out;
}
NTFS_PVT(inode)->non_resident = attr->non_resident;
NTFS_PVT(inode)->type = attr->type;
if (!attr->non_resident) {
NTFS_PVT(inode)->data.resident.offset =
(uint32_t)((uint8_t *)attr + attr->data.resident.value_offset);
inode->size = attr->data.resident.value_len;
} else {
attr_len = (uint8_t *)attr + attr->len;
stream = mapping_chunk_init(attr, &chunk, &offset);
NTFS_PVT(inode)->data.non_resident.rlist = NULL;
for (;;) {
err = parse_data_run(stream, &offset, attr_len, &chunk);
if (err) {
printf("parse_data_run()\n");
goto out;
}
if (chunk.flags & MAP_UNALLOCATED)
continue;
if (chunk.flags & MAP_END)
break;
if (chunk.flags & MAP_ALLOCATED) {
/* append new run to the runlist */
runlist_append(&NTFS_PVT(inode)->data.non_resident.rlist,
(struct runlist_element *)&chunk);
/* update for next VCN */
chunk.vcn += chunk.len;
}
}
if (runlist_is_empty(NTFS_PVT(inode)->data.non_resident.rlist)) {
printf("No mapping found\n");
goto out;
}
inode->size = attr->data.non_resident.initialized_size;
}
}
inode->mode = d_type;
free(mrec);
return 0;
out:
free(mrec);
return -1;
}
static struct inode *ntfs_index_lookup(const char *dname, struct inode *dir)
{
struct fs_info *fs = dir->fs;
struct ntfs_mft_record *mrec, *lmrec;
block_t blk;
uint64_t blk_offset;
struct ntfs_attr_record *attr;
struct ntfs_idx_root *ir;
struct ntfs_idx_entry *ie;
const uint64_t blk_size = UINT64_C(1) << BLOCK_SHIFT(fs);
uint8_t buf[blk_size];
struct ntfs_idx_allocation *iblk;
int err;
uint8_t *stream;
uint8_t *attr_len;
struct mapping_chunk chunk;
uint32_t offset;
int64_t vcn;
int64_t lcn;
int64_t last_lcn;
struct inode *inode;
dprintf("in %s()\n", __func__);
mrec = NTFS_SB(fs)->mft_record_lookup(fs, NTFS_PVT(dir)->mft_no, NULL);
if (!mrec) {
printf("No MFT record found.\n");
goto out;
}
lmrec = mrec;
attr = ntfs_attr_lookup(fs, NTFS_AT_INDEX_ROOT, &mrec, lmrec);
if (!attr) {
printf("No attribute found.\n");
goto out;
}
ir = (struct ntfs_idx_root *)((uint8_t *)attr +
attr->data.resident.value_offset);
ie = (struct ntfs_idx_entry *)((uint8_t *)&ir->index +
ir->index.entries_offset);
for (;; ie = (struct ntfs_idx_entry *)((uint8_t *)ie + ie->len)) {
/* bounds checks */
if ((uint8_t *)ie < (uint8_t *)mrec ||
(uint8_t *)ie + sizeof(struct ntfs_idx_entry_header) >
(uint8_t *)&ir->index + ir->index.index_len ||
(uint8_t *)ie + ie->len >
(uint8_t *)&ir->index + ir->index.index_len)
goto index_err;
/* last entry cannot contain a key. it can however contain
* a pointer to a child node in the B+ tree so we just break out
*/
if (ie->flags & INDEX_ENTRY_END)
break;
if (ntfs_filename_cmp(dname, ie))
goto found;
}
/* check for the presence of a child node */
if (!(ie->flags & INDEX_ENTRY_NODE)) {
printf("No child node, aborting...\n");
goto out;
}
/* then descend into child node */
attr = ntfs_attr_lookup(fs, NTFS_AT_INDEX_ALLOCATION, &mrec, lmrec);
if (!attr) {
printf("No attribute found.\n");
goto out;
}
if (!attr->non_resident) {
printf("WTF ?! $INDEX_ALLOCATION isn't really resident.\n");
goto out;
}
attr_len = (uint8_t *)attr + attr->len;
stream = mapping_chunk_init(attr, &chunk, &offset);
do {
err = parse_data_run(stream, &offset, attr_len, &chunk);
if (err)
break;
if (chunk.flags & MAP_UNALLOCATED)
continue;
if (chunk.flags & MAP_ALLOCATED) {
dprintf("%d cluster(s) starting at 0x%08llX\n", chunk.len,
chunk.lcn);
vcn = 0;
lcn = chunk.lcn;
while (vcn < chunk.len) {
blk = (lcn + vcn) << NTFS_SB(fs)->clust_shift <<
SECTOR_SHIFT(fs) >> BLOCK_SHIFT(fs);
blk_offset = 0;
last_lcn = lcn;
lcn += vcn;
err = ntfs_read(fs, &buf, blk_size, blk_size, &blk,
&blk_offset, NULL, (uint64_t *)&lcn);
if (err) {
printf("Error while reading from cache.\n");
goto not_found;
}
ntfs_fixups_writeback(fs, (struct ntfs_record *)&buf);
iblk = (struct ntfs_idx_allocation *)&buf;
if (iblk->magic != NTFS_MAGIC_INDX) {
printf("Not a valid INDX record.\n");
goto not_found;
}
ie = (struct ntfs_idx_entry *)((uint8_t *)&iblk->index +
iblk->index.entries_offset);
for (;; ie = (struct ntfs_idx_entry *)((uint8_t *)ie +
ie->len)) {
/* bounds checks */
if ((uint8_t *)ie < (uint8_t *)iblk || (uint8_t *)ie +
sizeof(struct ntfs_idx_entry_header) >
(uint8_t *)&iblk->index + iblk->index.index_len ||
(uint8_t *)ie + ie->len >
(uint8_t *)&iblk->index + iblk->index.index_len)
goto index_err;
/* last entry cannot contain a key */
if (ie->flags & INDEX_ENTRY_END)
break;
if (ntfs_filename_cmp(dname, ie))
goto found;
}
lcn = last_lcn; /* restore the original LCN */
/* go to the next VCN */
vcn += (blk_size / (1 << NTFS_SB(fs)->clust_byte_shift));
}
}
} while (!(chunk.flags & MAP_END));
not_found:
dprintf("Index not found\n");
out:
free(mrec);
return NULL;
found:
dprintf("Index found\n");
inode = new_ntfs_inode(fs);
err = index_inode_setup(fs, ie->data.dir.indexed_file, inode);
if (err) {
printf("Error in index_inode_setup()\n");
free(inode);
goto out;
}
free(mrec);
return inode;
index_err:
printf("Corrupt index. Aborting lookup...\n");
goto out;
}
/* Convert an UTF-16LE LFN to OEM LFN */
static uint8_t ntfs_cvt_filename(char *filename,
const struct ntfs_idx_entry *ie)
{
const uint16_t *entry_fn;
uint8_t entry_fn_len;
unsigned i;
entry_fn = ie->key.file_name.file_name;
entry_fn_len = ie->key.file_name.file_name_len;
for (i = 0; i < entry_fn_len; i++)
filename[i] = (char)entry_fn[i];
filename[i] = '\0';
return entry_fn_len;
}
static int ntfs_next_extent(struct inode *inode, uint32_t lstart)
{
struct fs_info *fs = inode->fs;
struct ntfs_sb_info *sbi = NTFS_SB(fs);
sector_t pstart = 0;
struct runlist *rlist;
struct runlist *ret;
const uint32_t sec_size = SECTOR_SIZE(fs);
const uint32_t sec_shift = SECTOR_SHIFT(fs);
dprintf("in %s()\n", __func__);
if (!NTFS_PVT(inode)->non_resident) {
pstart = (sbi->mft_blk + NTFS_PVT(inode)->here) << BLOCK_SHIFT(fs) >>
sec_shift;
inode->next_extent.len = (inode->size + sec_size - 1) >> sec_shift;
} else {
rlist = NTFS_PVT(inode)->data.non_resident.rlist;
if (!lstart || lstart >= NTFS_PVT(inode)->here) {
if (runlist_is_empty(rlist))
goto out; /* nothing to do ;-) */
ret = runlist_remove(&rlist);
NTFS_PVT(inode)->here =
((ret->run.len << sbi->clust_byte_shift) >> sec_shift);
pstart = ret->run.lcn << sbi->clust_shift;
inode->next_extent.len =
((ret->run.len << sbi->clust_byte_shift) + sec_size - 1) >>
sec_shift;
NTFS_PVT(inode)->data.non_resident.rlist = rlist;
free(ret);
ret = NULL;
}
}
inode->next_extent.pstart = pstart;
return 0;
out:
return -1;
}
static uint32_t ntfs_getfssec(struct file *file, char *buf, int sectors,
bool *have_more)
{
uint8_t non_resident;
uint32_t ret;
struct fs_info *fs = file->fs;
struct inode *inode = file->inode;
struct ntfs_mft_record *mrec, *lmrec;
struct ntfs_attr_record *attr;
char *p;
dprintf("in %s()\n", __func__);
non_resident = NTFS_PVT(inode)->non_resident;
ret = generic_getfssec(file, buf, sectors, have_more);
if (!ret)
return ret;
if (!non_resident) {
mrec = NTFS_SB(fs)->mft_record_lookup(fs, NTFS_PVT(inode)->mft_no,
NULL);
if (!mrec) {
printf("No MFT record found.\n");
goto out;
}
lmrec = mrec;
attr = ntfs_attr_lookup(fs, NTFS_AT_DATA, &mrec, lmrec);
if (!attr) {
printf("No attribute found.\n");
goto out;
}
p = (char *)((uint8_t *)attr + attr->data.resident.value_offset);
/* p now points to the data offset, so let's copy it into buf */
memcpy(buf, p, inode->size);
ret = inode->size;
free(mrec);
}
return ret;
out:
free(mrec);
return 0;
}
static inline bool is_filename_printable(const char *s)
{
return s && (*s != '.' && *s != '$');
}
static int ntfs_readdir(struct file *file, struct dirent *dirent)
{
struct fs_info *fs = file->fs;
struct inode *inode = file->inode;
struct ntfs_mft_record *mrec, *lmrec;
block_t blk;
uint64_t blk_offset;
const uint64_t blk_size = UINT64_C(1) << BLOCK_SHIFT(fs);
struct ntfs_attr_record *attr;
struct ntfs_idx_root *ir;
uint32_t count;
int len;
struct ntfs_idx_entry *ie = NULL;
uint8_t buf[BLOCK_SIZE(fs)];
struct ntfs_idx_allocation *iblk;
int err;
uint8_t *stream;
uint8_t *attr_len;
struct mapping_chunk chunk;
uint32_t offset;
int64_t vcn;
int64_t lcn;
char filename[NTFS_MAX_FILE_NAME_LEN + 1];
dprintf("in %s()\n", __func__);
mrec = NTFS_SB(fs)->mft_record_lookup(fs, NTFS_PVT(inode)->mft_no, NULL);
if (!mrec) {
printf("No MFT record found.\n");
goto out;
}
lmrec = mrec;
attr = ntfs_attr_lookup(fs, NTFS_AT_INDEX_ROOT, &mrec, lmrec);
if (!attr) {
printf("No attribute found.\n");
goto out;
}
ir = (struct ntfs_idx_root *)((uint8_t *)attr +
attr->data.resident.value_offset);
if (!file->offset && readdir_state->in_idx_root)
file->offset = ir->index.entries_offset;
idx_root_next_entry:
if (readdir_state->in_idx_root) {
ie = (struct ntfs_idx_entry *)((uint8_t *)&ir->index + file->offset);
if (ie->flags & INDEX_ENTRY_END) {
file->offset = 0;
readdir_state->in_idx_root = false;
readdir_state->idx_blks_count = 1;
readdir_state->entries_count = 0;
readdir_state->last_vcn = 0;
goto descend_into_child_node;
}
file->offset += ie->len;
len = ntfs_cvt_filename(filename, ie);
if (!is_filename_printable(filename))
goto idx_root_next_entry;
goto done;
}
descend_into_child_node:
if (!(ie->flags & INDEX_ENTRY_NODE))
goto out;
attr = ntfs_attr_lookup(fs, NTFS_AT_INDEX_ALLOCATION, &mrec, lmrec);
if (!attr)
goto out;
if (!attr->non_resident) {
printf("WTF ?! $INDEX_ALLOCATION isn't really resident.\n");
goto out;
}
attr_len = (uint8_t *)attr + attr->len;
next_run:
stream = mapping_chunk_init(attr, &chunk, &offset);
count = readdir_state->idx_blks_count;
while (count--) {
err = parse_data_run(stream, &offset, attr_len, &chunk);
if (err) {
printf("Error while parsing data runs.\n");
goto out;
}
if (chunk.flags & MAP_UNALLOCATED)
break;
if (chunk.flags & MAP_END)
goto out;
}
if (chunk.flags & MAP_UNALLOCATED) {
readdir_state->idx_blks_count++;
goto next_run;
}
next_vcn:
vcn = readdir_state->last_vcn;
if (vcn >= chunk.len) {
readdir_state->last_vcn = 0;
readdir_state->idx_blks_count++;
goto next_run;
}
lcn = chunk.lcn;
blk = (lcn + vcn) << NTFS_SB(fs)->clust_shift << SECTOR_SHIFT(fs) >>
BLOCK_SHIFT(fs);
blk_offset = 0;
err = ntfs_read(fs, &buf, blk_size, blk_size, &blk, &blk_offset, NULL,
(uint64_t *)&lcn);
if (err) {
printf("Error while reading from cache.\n");
goto not_found;
}
ntfs_fixups_writeback(fs, (struct ntfs_record *)&buf);
iblk = (struct ntfs_idx_allocation *)&buf;
if (iblk->magic != NTFS_MAGIC_INDX) {
printf("Not a valid INDX record.\n");
goto not_found;
}
idx_block_next_entry:
ie = (struct ntfs_idx_entry *)((uint8_t *)&iblk->index +
iblk->index.entries_offset);
count = readdir_state->entries_count;
for ( ; count--; ie = (struct ntfs_idx_entry *)((uint8_t *)ie + ie->len)) {
/* bounds checks */
if ((uint8_t *)ie < (uint8_t *)iblk || (uint8_t *)ie +
sizeof(struct ntfs_idx_entry_header) >
(uint8_t *)&iblk->index + iblk->index.index_len ||
(uint8_t *)ie + ie->len >
(uint8_t *)&iblk->index + iblk->index.index_len)
goto index_err;
/* last entry cannot contain a key */
if (ie->flags & INDEX_ENTRY_END) {
/* go to the next VCN */
readdir_state->last_vcn += (blk_size / (1 <<
NTFS_SB(fs)->clust_byte_shift));
readdir_state->entries_count = 0;
goto next_vcn;
}
}
readdir_state->entries_count++;
/* Need to check if this entry has INDEX_ENTRY_END flag set. If
* so, then it won't contain a indexed_file file, so continue the
* lookup on the next VCN/LCN (if any).
*/
if (ie->flags & INDEX_ENTRY_END)
goto next_vcn;
len = ntfs_cvt_filename(filename, ie);
if (!is_filename_printable(filename))
goto idx_block_next_entry;
goto done;
out:
readdir_state->in_idx_root = true;
free(mrec);
return -1;
done:
dirent->d_ino = ie->data.dir.indexed_file;
dirent->d_off = file->offset;
dirent->d_reclen = offsetof(struct dirent, d_name) + len + 1;
free(mrec);
mrec = NTFS_SB(fs)->mft_record_lookup(fs, ie->data.dir.indexed_file, NULL);
if (!mrec) {
printf("No MFT record found.\n");
goto out;
}
dirent->d_type = get_inode_mode(mrec);
memcpy(dirent->d_name, filename, len + 1);
free(mrec);
return 0;
not_found:
printf("Index not found\n");
goto out;
index_err:
printf("Corrupt index. Aborting lookup...\n");
goto out;
}
static inline struct inode *ntfs_iget(const char *dname, struct inode *parent)
{
return ntfs_index_lookup(dname, parent);
}
static struct inode *ntfs_iget_root(struct fs_info *fs)
{
uint64_t start_blk;
struct ntfs_mft_record *mrec, *lmrec;
struct ntfs_attr_record *attr;
struct ntfs_vol_info *vol_info;
struct inode *inode;
int err;
dprintf("in %s()\n", __func__);
/* Fetch the $Volume MFT record */
start_blk = 0;
mrec = NTFS_SB(fs)->mft_record_lookup(fs, FILE_Volume, &start_blk);
if (!mrec) {
printf("Could not fetch $Volume MFT record!\n");
goto err_mrec;
}
lmrec = mrec;
/* Fetch the volume information attribute */
attr = ntfs_attr_lookup(fs, NTFS_AT_VOL_INFO, &mrec, lmrec);
if (!attr) {
printf("Could not find volume info attribute!\n");
goto err_attr;
}
/* Note NTFS version and choose version-dependent functions */
vol_info = (void *)((char *)attr + attr->data.resident.value_offset);
NTFS_SB(fs)->major_ver = vol_info->major_ver;
NTFS_SB(fs)->minor_ver = vol_info->minor_ver;
if (vol_info->major_ver == 3 && vol_info->minor_ver == 0)
NTFS_SB(fs)->mft_record_lookup = ntfs_mft_record_lookup_3_0;
else if (vol_info->major_ver == 3 && vol_info->minor_ver == 1 &&
mrec->mft_record_no == FILE_Volume)
NTFS_SB(fs)->mft_record_lookup = ntfs_mft_record_lookup_3_1;
/* Free MFT record */
free(mrec);
mrec = NULL;
inode = new_ntfs_inode(fs);
inode->fs = fs;
err = index_inode_setup(fs, FILE_root, inode);
if (err)
goto err_setup;
NTFS_PVT(inode)->start = NTFS_PVT(inode)->here;
return inode;
err_setup:
free(inode);
err_attr:
free(mrec);
err_mrec:
return NULL;
}
/* Initialize the filesystem metadata and return blk size in bits */
static int ntfs_fs_init(struct fs_info *fs)
{
int read_count;
struct ntfs_bpb ntfs;
struct ntfs_sb_info *sbi;
struct disk *disk = fs->fs_dev->disk;
uint8_t mft_record_shift;
dprintf("in %s()\n", __func__);
read_count = disk->rdwr_sectors(disk, &ntfs, 0, 1, 0);
if (!read_count)
return -1;
if (!ntfs_check_sb_fields(&ntfs))
return -1;
SECTOR_SHIFT(fs) = disk->sector_shift;
/* Note: ntfs.clust_per_mft_record can be a negative number.
* If negative, it represents a shift count, else it represents
* a multiplier for the cluster size.
*/
mft_record_shift = ntfs.clust_per_mft_record < 0 ?
-ntfs.clust_per_mft_record :
ilog2(ntfs.sec_per_clust) + SECTOR_SHIFT(fs) +
ilog2(ntfs.clust_per_mft_record);
SECTOR_SIZE(fs) = 1 << SECTOR_SHIFT(fs);
sbi = malloc(sizeof *sbi);
if (!sbi)
malloc_error("ntfs_sb_info structure");
fs->fs_info = sbi;
sbi->clust_shift = ilog2(ntfs.sec_per_clust);
sbi->clust_byte_shift = sbi->clust_shift + SECTOR_SHIFT(fs);
sbi->clust_mask = ntfs.sec_per_clust - 1;
sbi->clust_size = ntfs.sec_per_clust << SECTOR_SHIFT(fs);
sbi->mft_record_size = 1 << mft_record_shift;
sbi->clust_per_idx_record = ntfs.clust_per_idx_record;
BLOCK_SHIFT(fs) = ilog2(ntfs.clust_per_idx_record) + sbi->clust_byte_shift;
BLOCK_SIZE(fs) = 1 << BLOCK_SHIFT(fs);
sbi->mft_lcn = ntfs.mft_lclust;
sbi->mft_blk = ntfs.mft_lclust << sbi->clust_shift << SECTOR_SHIFT(fs) >>
BLOCK_SHIFT(fs);
/* 16 MFT entries reserved for metadata files (approximately 16 KiB) */
sbi->mft_size = mft_record_shift << sbi->clust_shift << 4;
sbi->clusters = ntfs.total_sectors << SECTOR_SHIFT(fs) >> sbi->clust_shift;
if (sbi->clusters > 0xFFFFFFFFFFF4ULL)
sbi->clusters = 0xFFFFFFFFFFF4ULL;
/*
* Assume NTFS version 3.0 to begin with. If we find that the
* volume is a different version later on, we will adjust at
* that time.
*/
sbi->major_ver = 3;
sbi->minor_ver = 0;
sbi->mft_record_lookup = ntfs_mft_record_lookup_3_0;
/* Initialize the cache */
cache_init(fs->fs_dev, BLOCK_SHIFT(fs));
return BLOCK_SHIFT(fs);
}
const struct fs_ops ntfs_fs_ops = {
.fs_name = "ntfs",
.fs_flags = FS_USEMEM | FS_THISIND,
.fs_init = ntfs_fs_init,
.searchdir = NULL,
.getfssec = ntfs_getfssec,
.close_file = generic_close_file,
.mangle_name = generic_mangle_name,
.open_config = generic_open_config,
.readdir = ntfs_readdir,
.iget_root = ntfs_iget_root,
.iget = ntfs_iget,
.next_extent = ntfs_next_extent,
.fs_uuid = NULL,
};