[syslinux:master] ntfs: implement ntfs_iget()

syslinux-bot for Paulo Alcantara pcacjr at gmail.com
Sat Dec 17 20:54:56 PST 2011


Commit-ID:  64bb9bac9ef39378179d5fae889bfaa188b7477c
Gitweb:     http://www.syslinux.org/commit/64bb9bac9ef39378179d5fae889bfaa188b7477c
Author:     Paulo Alcantara <pcacjr at gmail.com>
AuthorDate: Mon, 11 Jul 2011 17:17:26 +0000
Committer:  Paulo Alcantara <pcacjr at gmail.com>
CommitDate: Wed, 7 Sep 2011 07:19:06 +0000

ntfs: implement ntfs_iget()

Return an allocated inode of a given UTF-16LE LFN passed as 2nd
argument.

Signed-off-by: Paulo Alcantara <pcacjr at gmail.com>

---
 core/fs/ntfs/ntfs.c |  474 ++++++++++++++++++++++++++++++++++++++++++++++++---
 core/fs/ntfs/ntfs.h |   12 ++
 2 files changed, 462 insertions(+), 24 deletions(-)

diff --git a/core/fs/ntfs/ntfs.c b/core/fs/ntfs/ntfs.c
index f4adf6d..cc6a89d 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 e42ceb0..ac48c84 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;


More information about the Syslinux-commits mailing list