aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaulo Alcantara <pcacjr@gmail.com>2011-07-11 17:17:26 +0000
committerPaulo Alcantara <pcacjr@gmail.com>2011-09-07 07:19:06 +0000
commit64bb9bac9ef39378179d5fae889bfaa188b7477c (patch)
tree1f2a17764df850904d0cc57306642c05dd637c43
parent3db0b76af102e3306c9a3a804fedb4b57670251b (diff)
downloadsyslinux-64bb9bac9ef39378179d5fae889bfaa188b7477c.tar.gz
syslinux-64bb9bac9ef39378179d5fae889bfaa188b7477c.tar.xz
syslinux-64bb9bac9ef39378179d5fae889bfaa188b7477c.zip
ntfs: implement ntfs_iget()
Return an allocated inode of a given UTF-16LE LFN passed as 2nd argument. Signed-off-by: Paulo Alcantara <pcacjr@gmail.com>
-rw-r--r--core/fs/ntfs/ntfs.c474
-rw-r--r--core/fs/ntfs/ntfs.h12
2 files changed, 462 insertions, 24 deletions
diff --git a/core/fs/ntfs/ntfs.c b/core/fs/ntfs/ntfs.c
index f4adf6db..cc6a89d8 100644
--- a/core/fs/ntfs/ntfs.c
+++ b/core/fs/ntfs/ntfs.c
@@ -105,19 +105,181 @@ static ATTR_RECORD *attr_lookup(uint32_t type, const MFT_RECORD *mrec)
return attr;
}
+static bool ntfs_match_longname(const char *str, unsigned long mft_no,
+ struct fs_info *fs)
+{
+ MFT_RECORD *mrec;
+ sector_t block = 0;
+ ATTR_RECORD *attr;
+ FILE_NAME_ATTR *fn;
+ uint8_t len;
+ unsigned char c = -1; /* Nonzero: we have not yet seen NULL */
+ uint16_t cp;
+ const uint16_t *match;
+
+ mrec = mft_record_lookup(mft_no, fs, &block);
+ if (!mrec) {
+ printf("No MFT record found!\n");
+ goto out;
+ }
+
+ attr = attr_lookup(NTFS_AT_FILENAME, mrec);
+ if (!attr) {
+ printf("No attribute found!\n");
+ goto out;
+ }
+
+ fn = (FILE_NAME_ATTR *)((uint8_t *)attr + attr->data.resident.value_offset);
+ len = fn->file_name_len;
+ match = fn->file_name;
+
+ dprintf("Matching: %s\n", str);
+
+ while (len) {
+ cp = *match++;
+ len--;
+ if (!cp)
+ break;
+
+ c = *str++;
+ if (cp != codepage.uni[0][c] && cp != codepage.uni[1][c])
+ goto out;
+ }
+
+ if (*str)
+ goto out;
+
+ while (len--)
+ if (*match++ != 0xffff)
+ goto out;
+
+ return true;
+
+out:
+ return false;
+}
+
+enum {
+ MAP_UNSPEC,
+ MAP_START = 1 << 0,
+ MAP_END = 1 << 1,
+ MAP_ALLOCATED = 1 << 2,
+ MAP_UNALLOCATED = 1 << 3,
+ MAP_MASK = 0x0000000F,
+};
+
+struct mapping_chunk {
+ uint64_t cur_vcn; /* Current Virtual Cluster Number */
+ uint8_t vcn_len; /* Virtual Cluster Number length in bytes */
+ uint64_t next_vcn; /* Next Virtual Cluster Number */
+ uint8_t lcn_len; /* Logical Cluster Number length in bytes */
+ int64_t cur_lcn; /* Logical Cluster Number offset */
+ uint32_t flags; /* Specific flags of this chunk */
+};
+
+/* 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;
+ int byte_shift = 8;
+ int mask;
+ uint8_t val;
+ int64_t res;
+
+ 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 */
+
+ chunk->cur_vcn = chunk->next_vcn;
+
+ count = *buf;
+ v = count & 0x0F;
+ l = count >> 4;
+
+ if (v > 8 || l > 8) /* more than 8 bytes ? */
+ goto out;
+
+ chunk->vcn_len = v;
+ chunk->lcn_len = l;
+
+ byte = (uint8_t *)buf + v;
+ count = v;
+
+ res = 0LL;
+ while (count--) {
+ val = *byte--;
+ mask = val >> (byte_shift - 1);
+ res = (res << byte_shift) | ((val + mask) ^ mask);
+ }
+
+ chunk->next_vcn += res;
+
+ 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->cur_lcn += res;
+ if (!chunk->cur_lcn) { /* is LCN 0 ? */
+ /* then VCNS from cur_vcn to next_vcn - 1 are unallocated */
+ chunk->flags |= MAP_UNALLOCATED;
+ } else {
+ /* otherwise they're all allocated */
+ chunk->flags |= MAP_ALLOCATED;
+ }
+
+ *offset += v + l + 1;
+
+ return 0;
+
+out:
+ return -1;
+}
+
static int index_inode_setup(struct fs_info *fs, unsigned long mft_no,
struct inode *inode)
{
MFT_RECORD *mrec;
sector_t block = 0;
ATTR_RECORD *attr;
+ FILE_NAME_ATTR *fn;
+ bool infile = false;
+ uint32_t dir_mask, root_mask, file_mask;
+ uint32_t dir, root, file;
uint32_t len;
INDEX_ROOT *ir;
uint32_t clust_size;
+ uint8_t *attr_len;
+ struct mapping_chunk chunk;
+ uint8_t *stream;
+ uint32_t offset;
+ int err;
mrec = mft_record_lookup(mft_no, fs, &block);
if (!mrec) {
- printf("No MFT record found!\n");
+ dprintf("No MFT record found!\n");
goto out;
}
@@ -127,44 +289,308 @@ static int index_inode_setup(struct fs_info *fs, unsigned long mft_no,
NTFS_PVT(inode)->start_cluster = block >> NTFS_SB(fs)->clust_shift;
NTFS_PVT(inode)->here = block;
- attr = attr_lookup(NTFS_AT_INDEX_ROOT, mrec);
+ attr = attr_lookup(NTFS_AT_FILENAME, mrec);
if (!attr) {
- printf("No attribute found!\n");
+ dprintf("No attribute found!\n");
+ goto out;
+ }
+
+ fn = (FILE_NAME_ATTR *)((uint8_t *)attr +
+ attr->data.resident.value_offset);
+ dprintf("File attributes: 0x%X\n", fn->file_attrs);
+
+ dir_mask = NTFS_FILE_ATTR_ARCHIVE |
+ NTFS_FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT;
+ root_mask = NTFS_FILE_ATTR_READONLY | NTFS_FILE_ATTR_HIDDEN |
+ NTFS_FILE_ATTR_SYSTEM |
+ NTFS_FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT;
+ file_mask = NTFS_FILE_ATTR_ARCHIVE;
+
+ dir = fn->file_attrs & ~dir_mask;
+ root = fn->file_attrs & ~root_mask;
+ file = fn->file_attrs & ~file_mask;
+
+ dprintf("dir = 0x%X\n", dir);
+ dprintf("root= 0x%X\n", root);
+ dprintf("file = 0x%X\n", file);
+ if (((!dir && root) || (!dir && !root)) && !file)
+ infile = true;
+
+ if (!infile) { /* directory stuff */
+ dprintf("Got a directory.\n");
+ attr = attr_lookup(NTFS_AT_INDEX_ROOT, mrec);
+ if (!attr) {
+ dprintf("No attribute found!\n");
+ goto out;
+ }
+
+ /* note: INDEX_ROOT is always resident */
+ ir = (INDEX_ROOT *)((uint8_t *)attr +
+ attr->data.resident.value_offset);
+ len = attr->data.resident.value_len;
+ if ((uint8_t *)ir + len > (uint8_t *)mrec +
+ NTFS_SB(fs)->mft_record_size) {
+ dprintf("Corrupt index\n");
+ goto out;
+ }
+
+ NTFS_PVT(inode)->itype.index.collation_rule = ir->collation_rule;
+ NTFS_PVT(inode)->itype.index.block_size = ir->index_block_size;
+ NTFS_PVT(inode)->itype.index.block_size_shift =
+ ilog2(NTFS_PVT(inode)->itype.index.block_size);
+
+ /* determine the size of a vcn in the index */
+ clust_size = NTFS_PVT(inode)->itype.index.block_size;
+ if (NTFS_SB(fs)->clust_size <= clust_size) {
+ NTFS_PVT(inode)->itype.index.vcn_size = NTFS_SB(fs)->clust_size;
+ NTFS_PVT(inode)->itype.index.vcn_size_shift =
+ NTFS_SB(fs)->clust_shift;
+ } else {
+ NTFS_PVT(inode)->itype.index.vcn_size = BLOCK_SIZE(fs);
+ NTFS_PVT(inode)->itype.index.vcn_size_shift = BLOCK_SHIFT(fs);
+ }
+
+ inode->mode = DT_DIR;
+ } else { /* file stuff */
+ dprintf("Got a file.\n");
+ attr = attr_lookup(NTFS_AT_DATA, mrec);
+ if (!attr) {
+ dprintf("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;
+
+ memset((void *)&chunk, 0, sizeof(chunk));
+ chunk.cur_vcn = attr->data.non_resident.lowest_vcn;
+ chunk.cur_lcn = 0LL;
+
+ stream = (uint8_t *)attr +
+ attr->data.non_resident.mapping_pairs_offset;
+ offset = 0U;
+
+ for (;;) {
+ err = parse_data_run(stream, &offset, attr_len, &chunk);
+ if (err) {
+ printf("Non-resident $DATA attribute without any run\n");
+ goto out;
+ }
+
+ if (chunk.flags & MAP_UNALLOCATED)
+ continue;
+ if (chunk.flags & (MAP_ALLOCATED | MAP_END))
+ break;
+ }
+
+ if (chunk.flags & MAP_END) {
+ printf("No mapping found\n");
+ goto out;
+ }
+
+ NTFS_PVT(inode)->data.non_resident.start_vcn = chunk.cur_vcn;
+ NTFS_PVT(inode)->data.non_resident.next_vcn = chunk.cur_vcn;
+ NTFS_PVT(inode)->data.non_resident.vcn_no = chunk.vcn_len;
+ NTFS_PVT(inode)->data.non_resident.lcn = chunk.cur_lcn;
+ inode->size = attr->data.non_resident.initialized_size;
+ }
+
+ inode->mode = DT_REG;
+ }
+
+ return 0;
+
+out:
+ return -1;
+}
+
+static struct inode *index_lookup(const char *dname, struct inode *dir)
+{
+ struct fs_info *fs = dir->fs;
+ MFT_RECORD *mrec;
+ sector_t block;
+ ATTR_RECORD *attr;
+ INDEX_ROOT *ir;
+ uint32_t len;
+ INDEX_ENTRY *ie;
+ uint8_t vcn_count;
+ INDEX_BLOCK *iblock;
+ int64_t vcn;
+ unsigned block_count;
+ uint8_t *stream;
+ uint32_t offset;
+ uint8_t *attr_len;
+ struct mapping_chunk chunk;
+ int err;
+ struct inode *inode;
+
+ block = NTFS_PVT(dir)->start;
+ dprintf("index_lookup() - mft record number: %d\n", NTFS_PVT(dir)->mft_no);
+ mrec = mft_record_lookup(NTFS_PVT(dir)->mft_no, fs, &block);
+ if (!mrec) {
+ dprintf("No MFT record found!\n");
goto out;
}
- NTFS_PVT(inode)->type = attr->type;
+ attr = attr_lookup(NTFS_AT_INDEX_ROOT, mrec);
+ if (!attr) {
+ dprintf("No attribute found!\n");
+ goto out;
+ }
- /* note: INDEX_ROOT is always resident */
ir = (INDEX_ROOT *)((uint8_t *)attr +
- attr->data.resident.value_offset);
+ attr->data.resident.value_offset);
len = attr->data.resident.value_len;
- if ((uint8_t *)ir + len > (uint8_t *)mrec + NTFS_SB(fs)->mft_record_size) {
- printf("Index is corrupt!\n");
+ /* sanity check */
+ if ((uint8_t *)ir + len > (uint8_t *)mrec + NTFS_SB(fs)->mft_record_size)
+ goto index_err;
+
+ ie = (INDEX_ENTRY *)((uint8_t *)&ir->index +
+ ir->index.entries_offset);
+ for (;; ie = (INDEX_ENTRY *)((uint8_t *)ie + ie->len)) {
+ /* bounds checks */
+ if ((uint8_t *)ie < (uint8_t *)mrec ||
+ (uint8_t *)ie + sizeof(INDEX_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
+ */
+ dprintf("(0) ie->flags: 0x%X\n", ie->flags);
+ if (ie->flags & INDEX_ENTRY_END)
+ break;
+
+ if (ntfs_match_longname(dname, ie->data.dir.indexed_file, fs)) {
+ dprintf("Filename matches up!\n");
+ dprintf("MFT record number = %d\n", ie->data.dir.indexed_file);
+ goto found;
+ }
+ }
+
+ /* check for the presence of a child node */
+ if (!(ie->flags & INDEX_ENTRY_NODE)) {
+ dprintf("No child node, aborting...\n");
goto out;
}
- NTFS_PVT(inode)->itype.index.collation_rule = ir->collation_rule;
- NTFS_PVT(inode)->itype.index.block_size = ir->index_block_size;
- NTFS_PVT(inode)->itype.index.block_size_shift =
- ilog2(NTFS_PVT(inode)->itype.index.block_size);
+ /* then descend into child node */
- /* determine the size of a vcn in the index */
- clust_size = NTFS_PVT(inode)->itype.index.block_size;
- if (NTFS_SB(fs)->clust_size <= clust_size) {
- NTFS_PVT(inode)->itype.index.vcn_size = NTFS_SB(fs)->clust_size;
- NTFS_PVT(inode)->itype.index.vcn_size_shift = NTFS_SB(fs)->clust_shift;
- } else {
- NTFS_PVT(inode)->itype.index.vcn_size = BLOCK_SIZE(fs);
- NTFS_PVT(inode)->itype.index.vcn_size_shift = BLOCK_SHIFT(fs);
+ attr = attr_lookup(NTFS_AT_INDEX_ALLOCATION, mrec);
+ if (!attr) {
+ dprintf("No attribute found!\n");
+ goto out;
}
- inode->mode = DT_DIR;
+ if (!attr->non_resident) {
+ dprintf("WTF ?! $INDEX_ALLOCATION isn't really resident.\n");
+ goto out;
+ }
- return 0;
+ attr_len = (uint8_t *)attr + attr->len;
+
+ memset((void *)&chunk, 0, sizeof(chunk));
+ chunk.cur_vcn = attr->data.non_resident.lowest_vcn;
+ chunk.cur_lcn = 0LL;
+
+ stream = (uint8_t *)attr + attr->data.non_resident.mapping_pairs_offset;
+ offset = 0U;
+
+ for (;;) {
+ err = parse_data_run(stream, &offset, attr_len, &chunk);
+ if (err)
+ goto not_found;
+
+ if (chunk.flags & MAP_UNALLOCATED)
+ continue;
+ if (chunk.flags & MAP_END)
+ break;
+
+ if (chunk.flags & MAP_ALLOCATED) {
+ dprintf("%d cluster(s) starting at 0x%X\n", chunk.vcn_len,
+ chunk.cur_lcn);
+
+ vcn_count = 0;
+ vcn = chunk.cur_vcn;
+ while (vcn_count++ < chunk.vcn_len) {
+ block = (chunk.cur_lcn + vcn) << NTFS_SB(fs)->clust_shift;
+ iblock = (INDEX_BLOCK *)get_cache(fs->fs_dev, block);
+ if (iblock->magic != NTFS_MAGIC_INDX) {
+ dprintf("Not a valid INDX record\n");
+ goto out;
+ }
+
+ /* get the index entries region cached */
+ block_count = iblock->index.allocated_size >> BLOCK_SHIFT(fs);
+ while (block_count--)
+ (void)get_cache(fs->fs_dev, ++block);
+
+ ie = (INDEX_ENTRY *)((uint8_t *)&iblock->index +
+ iblock->index.entries_offset);
+ for (;; ie = (INDEX_ENTRY *)((uint8_t *)ie + ie->len)) {
+ /* bounds checks */
+ if ((uint8_t *)ie < (uint8_t *)iblock || (uint8_t *)ie +
+ sizeof(INDEX_ENTRY_HEADER) >
+ (uint8_t *)&iblock->index + iblock->index.index_len ||
+ (uint8_t *)ie + ie->len >
+ (uint8_t *)&iblock->index + iblock->index.index_len)
+ goto index_err;
+
+ dprintf("Entry length 0x%x (%d)\n", ie->len, ie->len);
+
+ /* last entry cannot contain a key */
+ dprintf("(1) ie->flags: 0x%X\n", ie->flags);
+ if (ie->flags & INDEX_ENTRY_END)
+ break;
+
+ if (ntfs_match_longname(dname, ie->data.dir.indexed_file,
+ fs)) {
+ dprintf("Filename matches up!\n");
+ dprintf("MFT record number = %d\n",
+ ie->data.dir.indexed_file);
+ goto found;
+ }
+ }
+
+ ++vcn; /* go to the next VCN */
+ }
+ }
+ }
+
+not_found:
+ dprintf("Index not found\n");
out:
- return -1;
+ return NULL;
+
+found:
+ dprintf("--------------- Found index -------------------\n");
+ inode = new_ntfs_inode(fs);
+ err = index_inode_setup(fs, ie->data.dir.indexed_file, inode);
+ if (err) {
+ free(inode);
+ goto out;
+ }
+
+ return inode;
+
+index_err:
+ dprintf("Corrupt index. Aborting lookup...\n");
+ goto out;
+}
+
+static struct inode *ntfs_iget(const char *dname, struct inode *parent)
+{
+ return index_lookup(dname, parent);
}
static struct inode *ntfs_iget_root(struct fs_info *fs)
@@ -244,6 +670,6 @@ const struct fs_ops ntfs_fs_ops = {
.load_config = NULL,
.readdir = NULL,
.iget_root = ntfs_iget_root,
- .iget = NULL,
+ .iget = ntfs_iget,
.next_extent = NULL,
};
diff --git a/core/fs/ntfs/ntfs.h b/core/fs/ntfs/ntfs.h
index e42ceb02..ac48c84b 100644
--- a/core/fs/ntfs/ntfs.h
+++ b/core/fs/ntfs/ntfs.h
@@ -72,6 +72,18 @@ struct ntfs_inode {
uint32_t name_len;
uint32_t attr_list_size;
uint8_t *attr_list;
+ uint8_t non_resident;
+ union { /* Non-resident $DATA attribute */
+ struct { /* Used only if non_resident flags isn't set */
+ uint32_t offset; /* Data offset */
+ } resident;
+ struct { /* Used only if non_resident is set */
+ uint64_t start_vcn; /* Starting Virtual Cluster Number */
+ uint64_t next_vcn; /* Next Virtual Cluster Number */
+ uint8_t vcn_no; /* Number of Virtual Cluster Numbers */
+ int64_t lcn; /* Logical Cluster Number offset */
+ } non_resident;
+ } data;
union {
struct { /* It is a directory, $MFT, or an index inode */
uint32_t block_size;