From: Paulo Alcantara Date: Mon, 11 Jul 2011 17:17:26 +0000 (+0000) Subject: ntfs: implement ntfs_iget() X-Git-Tag: syslinux-4.06-pre1~4^2~32 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=64bb9bac9ef39378179d5fae889bfaa188b7477c;p=profile%2Fivi%2Fsyslinux.git ntfs: implement ntfs_iget() Return an allocated inode of a given UTF-16LE LFN passed as 2nd argument. Signed-off-by: Paulo Alcantara --- 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;