diff options
author | Jonathan Maw <jonathan.maw@codethink.co.uk> | 2012-11-29 11:58:47 +0000 |
---|---|---|
committer | Jonathan Maw <jonathan.maw@codethink.co.uk> | 2012-11-29 11:58:47 +0000 |
commit | 0846de0e1795a30512f5575388d163ea548125de (patch) | |
tree | e1bc03fa83c61165f48ffe3f528ac5496332872a /core/fs | |
parent | ede8500d7c710e4ddf650f1a1ef3ccf2a89e5313 (diff) | |
parent | 7307d60063ee4303da4de45f9d984fdc8df92146 (diff) | |
download | syslinux-baserock/genivi/morph.tar.gz |
Merge the latest changes from upstreambaserock/genivi/morph
Diffstat (limited to 'core/fs')
-rw-r--r-- | core/fs/btrfs/btrfs.c | 5 | ||||
-rw-r--r-- | core/fs/cache.c | 4 | ||||
-rw-r--r-- | core/fs/chdir.c | 110 | ||||
-rw-r--r-- | core/fs/ext2/ext2.c | 3 | ||||
-rw-r--r-- | core/fs/fat/fat.c | 35 | ||||
-rw-r--r-- | core/fs/fs.c | 13 | ||||
-rw-r--r-- | core/fs/lib/searchconfig.c | 3 | ||||
-rw-r--r-- | core/fs/ntfs/ntfs.c | 1388 | ||||
-rw-r--r-- | core/fs/ntfs/ntfs.h | 485 | ||||
-rw-r--r-- | core/fs/ntfs/runlist.h | 83 |
10 files changed, 2068 insertions, 61 deletions
diff --git a/core/fs/btrfs/btrfs.c b/core/fs/btrfs/btrfs.c index b6a14e3b..aeb7614a 100644 --- a/core/fs/btrfs/btrfs.c +++ b/core/fs/btrfs/btrfs.c @@ -602,12 +602,15 @@ static void btrfs_get_fs_tree(struct fs_info *fs) do { do { struct btrfs_root_ref *ref; + int pathlen; if (btrfs_comp_keys_type(&search_key, &path.item.key)) break; ref = (struct btrfs_root_ref *)path.data; - if (!strcmp((char*)(ref + 1), SubvolName)) { + pathlen = path.item.size - sizeof(struct btrfs_root_ref); + + if (!strncmp((char*)(ref + 1), SubvolName, pathlen)) { subvol_ok = true; break; } diff --git a/core/fs/cache.c b/core/fs/cache.c index 0d7891be..3b21fc26 100644 --- a/core/fs/cache.c +++ b/core/fs/cache.c @@ -37,10 +37,10 @@ void cache_init(struct device *dev, int block_size_shift) dev->cache_head = head = (struct cache *) (data + (dev->cache_entries << block_size_shift)); - cache = dev->cache_head + 1; /* First cache descriptor */ + cache = head + 1; /* First cache descriptor */ head->prev = &cache[dev->cache_entries-1]; - head->next->prev = dev->cache_head; + head->prev->next = head; head->block = -1; head->data = NULL; diff --git a/core/fs/chdir.c b/core/fs/chdir.c index 9e8dfd2e..903cabce 100644 --- a/core/fs/chdir.c +++ b/core/fs/chdir.c @@ -1,6 +1,7 @@ #include <stdio.h> #include <stdbool.h> #include <string.h> +#include <dprintf.h> #include "fs.h" #include "cache.h" @@ -16,57 +17,70 @@ void pm_realpath(com32sys_t *regs) realpath(dst, src, FILENAME_MAX); } -#define EMIT(x) \ -do { \ - if (++n < bufsize) \ - *q++ = (x); \ -} while (0) - -static size_t join_paths(char *dst, size_t bufsize, - const char *s1, const char *s2) +static size_t copy_string(char *buf, size_t ix, size_t bufsize, const char *src) { - const char *list[2]; - int i; char c; - const char *p; - char *q = dst; - size_t n = 0; - bool slash = false; - - list[0] = s1; - list[1] = s2; - - for (i = 0; i < 2; i++) { - p = list[i]; - - while ((c = *p++)) { - if (c == '/') { - if (!slash) - EMIT(c); - slash = true; - } else { - EMIT(c); - slash = false; - } - } + + while ((c = *src++)) { + if (ix+1 < bufsize) + buf[ix] = c; + ix++; } - if (bufsize) - *q = '\0'; + if (ix < bufsize) + buf[ix] = '\0'; - return n; + return ix; +} + +static size_t generic_inode_to_path(struct inode *inode, char *dst, size_t bufsize) +{ + size_t s = 0; + + dprintf("inode %p name %s\n", inode, inode->name); + + if (inode->parent) { + if (!inode->name) /* Only the root should have no name */ + return -1; + + s = generic_inode_to_path(inode->parent, dst, bufsize); + if (s == (size_t)-1) + return s; /* Error! */ + + s = copy_string(dst, s, bufsize, "/"); + s = copy_string(dst, s, bufsize, inode->name); + } + + return s; } size_t realpath(char *dst, const char *src, size_t bufsize) { + int rv; + struct file *file; + size_t s; + + dprintf("realpath: input: %s\n", src); + if (this_fs->fs_ops->realpath) { - return this_fs->fs_ops->realpath(this_fs, dst, src, bufsize); + s = this_fs->fs_ops->realpath(this_fs, dst, src, bufsize); } else { - /* Filesystems with "common" pathname resolution */ - return join_paths(dst, bufsize, - src[0] == '/' ? "" : this_fs->cwd_name, - src); + rv = searchdir(src); + if (rv < 0) { + dprintf("realpath: searchpath failure\n"); + return -1; + } + + file = handle_to_file(rv); + s = generic_inode_to_path(file->inode, dst, bufsize); + if (s == 0) + s = copy_string(dst, 0, bufsize, "/"); + + _close_file(file); } + + dprintf("realpath: output: %s\n", dst); + return s; } int chdir(const char *src) @@ -74,6 +88,10 @@ int chdir(const char *src) int rv; struct file *file; char cwd_buf[CURRENTDIR_MAX]; + size_t s; + + dprintf("chdir: from %s (inode %p) add %s\n", + this_fs->cwd_name, this_fs->cwd, src); if (this_fs->fs_ops->chdir) return this_fs->fs_ops->chdir(this_fs, src); @@ -94,10 +112,20 @@ int chdir(const char *src) _close_file(file); /* Save the current working directory */ - realpath(cwd_buf, src, CURRENTDIR_MAX); + s = generic_inode_to_path(this_fs->cwd, cwd_buf, CURRENTDIR_MAX-1); /* Make sure the cwd_name ends in a slash, it's supposed to be a prefix */ - join_paths(this_fs->cwd_name, CURRENTDIR_MAX, cwd_buf, "/"); + if (s < 1 || cwd_buf[s-1] != '/') + cwd_buf[s++] = '/'; + + if (s >= CURRENTDIR_MAX) + s = CURRENTDIR_MAX - 1; + + cwd_buf[s++] = '\0'; + memcpy(this_fs->cwd_name, cwd_buf, s); + + dprintf("chdir: final %s (inode %p)\n", + this_fs->cwd_name, this_fs->cwd); return 0; } diff --git a/core/fs/ext2/ext2.c b/core/fs/ext2/ext2.c index 716670c6..7988faaf 100644 --- a/core/fs/ext2/ext2.c +++ b/core/fs/ext2/ext2.c @@ -164,6 +164,9 @@ static struct inode *ext2_iget_by_inr(struct fs_info *fs, uint32_t inr) struct inode *inode; e_inode = ext2_get_inode(fs, inr); + if (!e_inode) + return NULL; + if (!(inode = alloc_inode(fs, inr, sizeof(struct ext2_pvt_inode)))) return NULL; fill_inode(inode, e_inode); diff --git a/core/fs/fat/fat.c b/core/fs/fat/fat.c index d3079269..b08923cf 100644 --- a/core/fs/fat/fat.c +++ b/core/fs/fat/fat.c @@ -220,24 +220,30 @@ static sector_t next_sector(struct file *file) return sector; } -/* - * Mangle a filename pointed to by src into a buffer pointed to by dst; - * ends on encountering any whitespace. +/** + * mangle_name: + * + * Mangle a filename pointed to by src into a buffer pointed + * to by dst; ends on encountering any whitespace. + * dst is preserved. + * + * This verifies that a filename is < FILENAME_MAX characters, + * doesn't contain whitespace, zero-pads the output buffer, + * and removes redundant slashes. + * + * Unlike the generic version, this also converts backslashes to + * forward slashes. * */ static void vfat_mangle_name(char *dst, const char *src) { char *p = dst; + int i = FILENAME_MAX-1; char c; - int i = FILENAME_MAX -1; - /* - * Copy the filename, converting backslash to slash and - * collapsing duplicate separators. - */ while (not_whitespace(c = *src)) { - if (c == '\\') - c = '/'; + if (c == '\\') + c = '/'; if (c == '/') { if (src[1] == '/' || src[1] == '\\') { @@ -250,16 +256,13 @@ static void vfat_mangle_name(char *dst, const char *src) *dst++ = *src++; } - /* Strip terminal slashes or whitespace */ while (1) { if (dst == p) break; - if (*(dst-1) == '/' && dst-1 == p) /* it's the '/' case */ - break; - if (dst-2 == p && *(dst-2) == '.' && *(dst-1) == '.' ) /* the '..' case */ - break; - if ((*(dst-1) != '/') && (*(dst-1) != '.')) + if (dst[-1] != '/') break; + if ((dst[-1] == '/') && ((dst - 1) == p)) + break; dst--; i++; diff --git a/core/fs/fs.c b/core/fs/fs.c index ad2fb370..21f5dba0 100644 --- a/core/fs/fs.c +++ b/core/fs/fs.c @@ -37,6 +37,8 @@ void put_inode(struct inode *inode) while (inode && --inode->refcnt == 0) { struct inode *dead = inode; inode = inode->parent; + if (dead->name) + free((char *)dead->name); free(dead); } } @@ -207,6 +209,9 @@ int searchdir(const char *name) char *part, *p, echar; int symlink_count = MAX_SYMLINK_CNT; + dprintf("searchdir: %s root: %p cwd: %p\n", + name, this_fs->root, this_fs->cwd); + if (!(file = alloc_file())) goto err_no_close; file->fs = this_fs; @@ -305,6 +310,9 @@ int searchdir(const char *name) goto got_link; } + inode->name = strdup(part); + dprintf("path component: %s\n", inode->name); + inode->parent = parent; parent = NULL; @@ -349,6 +357,8 @@ int open_file(const char *name, struct com32_filedata *filedata) struct file *file; char mangled_name[FILENAME_MAX]; + dprintf("open_file %s\n", name); + mangle_name(mangled_name, name); rv = searchdir(mangled_name); @@ -376,6 +386,8 @@ void pm_open_file(com32sys_t *regs) const char *name = MK_PTR(regs->es, regs->esi.w[0]); char mangled_name[FILENAME_MAX]; + dprintf("pm_open_file %s\n", name); + mangle_name(mangled_name, name); rv = searchdir(mangled_name); if (rv < 0) { @@ -470,6 +482,7 @@ void fs_init(com32sys_t *regs) if (fs.fs_ops->iget_root) { fs.root = fs.fs_ops->iget_root(&fs); fs.cwd = get_inode(fs.root); + dprintf("init: root inode %p, cwd inode %p\n", fs.root, fs.cwd); } SectorShift = fs.sector_shift; diff --git a/core/fs/lib/searchconfig.c b/core/fs/lib/searchconfig.c index 24bfde31..f18836a8 100644 --- a/core/fs/lib/searchconfig.c +++ b/core/fs/lib/searchconfig.c @@ -25,7 +25,8 @@ int search_config(const char *search_directories[], const char *filenames[]) "%s%s%s", sd, (*sd && sd[strlen(sd)-1] == '/') ? "" : "/", sf); - realpath(ConfigName, confignamebuf, FILENAME_MAX); + if (realpath(ConfigName, confignamebuf, FILENAME_MAX) == (size_t)-1) + continue; regs.edi.w[0] = OFFS_WRT(ConfigName, 0); dprintf("Config search: %s\n", ConfigName); call16(core_open, ®s, ®s); diff --git a/core/fs/ntfs/ntfs.c b/core/fs/ntfs/ntfs.c new file mode 100644 index 00000000..500d0fd3 --- /dev/null +++ b/core/fs/ntfs/ntfs.c @@ -0,0 +1,1388 @@ +/* + * 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; + +/*** 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; +} + +static struct ntfs_mft_record *ntfs_mft_record_lookup_3_0(struct fs_info *fs, + uint32_t file, block_t *blk) +{ + const uint64_t mft_record_size = NTFS_SB(fs)->mft_record_size; + uint8_t *buf; + const block_t mft_blk = NTFS_SB(fs)->mft_blk; + block_t cur_blk; + block_t right_blk; + uint64_t offset; + uint64_t next_offset; + const uint32_t mft_record_shift = ilog2(mft_record_size); + const uint32_t clust_byte_shift = NTFS_SB(fs)->clust_byte_shift; + uint64_t lcn; + int err; + struct ntfs_mft_record *mrec; + + dprintf("in %s()\n", __func__); + + buf = (uint8_t *)malloc(mft_record_size); + if (!buf) + malloc_error("uint8_t *"); + + /* determine MFT record's LCN and block number */ + lcn = NTFS_SB(fs)->mft_lcn + (file << mft_record_shift >> clust_byte_shift); + cur_blk = (lcn << clust_byte_shift >> BLOCK_SHIFT(fs)) - mft_blk; + offset = (file << mft_record_shift) % BLOCK_SIZE(fs); + for (;;) { + right_blk = cur_blk + mft_blk; + err = ntfs_read(fs, buf, mft_record_size, mft_record_size, &right_blk, + &offset, &next_offset, &lcn); + if (err) { + printf("Error while reading from cache.\n"); + break; + } + + ntfs_fixups_writeback(fs, (struct ntfs_record *)buf); + + mrec = (struct ntfs_mft_record *)buf; + /* check if it has a valid magic number */ + if (mrec->magic == NTFS_MAGIC_FILE) { + if (blk) + *blk = cur_blk; /* update record starting block */ + + return mrec; /* found MFT record */ + } + + if (next_offset >= BLOCK_SIZE(fs)) { + /* try the next FS block */ + offset = 0; + cur_blk = right_blk - mft_blk + 1; + } else { + /* there's still content to fetch in the current block */ + cur_blk = right_blk - mft_blk; + offset = next_offset; /* update FS block offset */ + } + } + + free(buf); + + return NULL; +} + +static struct ntfs_mft_record *ntfs_mft_record_lookup_3_1(struct fs_info *fs, + uint32_t file, block_t *blk) +{ + const uint64_t mft_record_size = NTFS_SB(fs)->mft_record_size; + uint8_t *buf; + const block_t mft_blk = NTFS_SB(fs)->mft_blk; + block_t cur_blk; + block_t right_blk; + uint64_t offset; + uint64_t next_offset; + const uint32_t mft_record_shift = ilog2(mft_record_size); + const uint32_t clust_byte_shift = NTFS_SB(fs)->clust_byte_shift; + uint64_t lcn; + int err; + struct ntfs_mft_record *mrec; + + dprintf("in %s()\n", __func__); + + buf = (uint8_t *)malloc(mft_record_size); + if (!buf) + malloc_error("uint8_t *"); + + lcn = NTFS_SB(fs)->mft_lcn + (file << mft_record_shift >> clust_byte_shift); + cur_blk = (lcn << clust_byte_shift >> BLOCK_SHIFT(fs)) - mft_blk; + offset = (file << mft_record_shift) % BLOCK_SIZE(fs); + for (;;) { + right_blk = cur_blk + NTFS_SB(fs)->mft_blk; + err = ntfs_read(fs, buf, mft_record_size, mft_record_size, &right_blk, + &offset, &next_offset, &lcn); + if (err) { + printf("Error while reading from cache.\n"); + break; + } + + ntfs_fixups_writeback(fs, (struct ntfs_record *)buf); + + mrec = (struct ntfs_mft_record *)buf; + /* Check if the NTFS 3.1 MFT record number matches */ + if (mrec->magic == NTFS_MAGIC_FILE && mrec->mft_record_no == file) { + if (blk) + *blk = cur_blk; /* update record starting block */ + + return mrec; /* found MFT record */ + } + + if (next_offset >= BLOCK_SIZE(fs)) { + /* try the next FS block */ + offset = 0; + cur_blk = right_blk - NTFS_SB(fs)->mft_blk + 1; + } else { + /* there's still content to fetch in the current block */ + cur_blk = right_blk - NTFS_SB(fs)->mft_blk; + offset = next_offset; /* update FS block offset */ + } + } + + free(buf); + + return NULL; +} + +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; + int byte_shift = 8; + int mask; + uint8_t val; + 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--) { + val = *byte--; + mask = val >> (byte_shift - 1); + res = (res << byte_shift) | ((val + mask) ^ mask); + } + + 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 = (uint32_t)((uint8_t *)&ir->index + + ir->index.entries_offset); + } + +idx_root_next_entry: + if (readdir_state->in_idx_root) { + ie = (struct ntfs_idx_entry *)(uint8_t *)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 = (uint32_t)((uint8_t *)ie + 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, + .load_config = generic_load_config, + .readdir = ntfs_readdir, + .iget_root = ntfs_iget_root, + .iget = ntfs_iget, + .next_extent = ntfs_next_extent, +}; diff --git a/core/fs/ntfs/ntfs.h b/core/fs/ntfs/ntfs.h new file mode 100644 index 00000000..721a78d7 --- /dev/null +++ b/core/fs/ntfs/ntfs.h @@ -0,0 +1,485 @@ +/* + * 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. +*/ + +#include "runlist.h" + +#ifndef _NTFS_H_ +#define _NTFS_H_ + +struct ntfs_bpb { + uint8_t jmp_boot[3]; + char oem_name[8]; + uint16_t sector_size; + uint8_t sec_per_clust; + uint16_t res_sectors; + uint8_t zero_0[3]; + uint16_t zero_1; + uint8_t media; + uint16_t zero_2; + uint16_t unused_0; + uint16_t unused_1; + uint32_t unused_2; + uint32_t zero_3; + uint32_t unused_3; + uint64_t total_sectors; + uint64_t mft_lclust; + uint64_t mft_mirr_lclust; + int8_t clust_per_mft_record; + uint8_t unused_4[3]; + uint8_t clust_per_idx_record; + uint8_t unused_5[3]; + uint64_t vol_serial; + uint32_t unused_6; + + uint8_t pad[428]; /* padding to a sector boundary (512 bytes) */ +} __attribute__((__packed__)); + +/* Function type for an NTFS-version-dependent MFT record lookup */ +struct ntfs_mft_record; +typedef struct ntfs_mft_record *f_mft_record_lookup(struct fs_info *, + uint32_t, block_t *); + +struct ntfs_sb_info { + block_t mft_blk; /* The first MFT record block */ + uint64_t mft_lcn; /* LCN of the first MFT record */ + unsigned mft_size; /* The MFT size in sectors */ + uint64_t mft_record_size; /* MFT record size in bytes */ + + uint8_t clust_per_idx_record; /* Clusters per Index Record */ + + unsigned long long clusters; /* Total number of clusters */ + + unsigned clust_shift; /* Based on sectors */ + unsigned clust_byte_shift; /* Based on bytes */ + unsigned clust_mask; + unsigned clust_size; + + uint8_t major_ver; /* Major version from $Volume */ + uint8_t minor_ver; /* Minor version from $Volume */ + + /* NTFS-version-dependent MFT record lookup function to use */ + f_mft_record_lookup *mft_record_lookup; +} __attribute__((__packed__)); + +/* The NTFS in-memory inode structure */ +struct ntfs_inode { + int64_t initialized_size; + int64_t allocated_size; + unsigned long mft_no; /* Number of the mft record / inode */ + uint16_t seq_no; /* Sequence number of the mft record */ + uint32_t type; /* Attribute type of this inode */ + 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 */ + struct runlist *rlist; + } non_resident; + } data; + uint32_t start_cluster; /* Starting cluster address */ + sector_t start; /* Starting sector */ + sector_t offset; /* Current sector offset */ + sector_t here; /* Sector corresponding to offset */ +}; + +/* This is structure is used to keep a state for ntfs_readdir() callers. + * As NTFS stores directory entries in a complex way, this is structure + * ends up saving a state required to find out where we must start from + * for the next ntfs_readdir() call. + */ +struct ntfs_readdir_state { + unsigned long mft_no; /* MFT record number */ + bool in_idx_root; /* It's true if we're still in the INDEX root */ + uint32_t idx_blks_count; /* Number of read INDX blocks */ + uint32_t entries_count; /* Number of read INDEX entries */ + int64_t last_vcn; /* Last VCN of the INDX block */ +}; + +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 vcn; + int64_t lcn; + uint64_t len; + uint32_t flags; +}; + +/* System defined attributes (32-bit) + * Each attribute type has a corresponding attribute name (in Unicode) + */ +enum { + NTFS_AT_UNUSED = 0x00, + NTFS_AT_STANDARD_INFORMATION = 0x10, + NTFS_AT_ATTR_LIST = 0x20, + NTFS_AT_FILENAME = 0x30, + NTFS_AT_OBJ_ID = 0x40, + NTFS_AT_SECURITY_DESCP = 0x50, + NTFS_AT_VOL_NAME = 0x60, + NTFS_AT_VOL_INFO = 0x70, + NTFS_AT_DATA = 0x80, + NTFS_AT_INDEX_ROOT = 0x90, + NTFS_AT_INDEX_ALLOCATION = 0xA0, + NTFS_AT_BITMAP = 0xB0, + NTFS_AT_REPARSE_POINT = 0xC0, + NTFS_AT_EA_INFO = 0xD0, + NTFS_AT_EA = 0xE0, + NTFS_AT_PROPERTY_SET = 0xF0, + NTFS_AT_LOGGED_UTIL_STREAM = 0x100, + NTFS_AT_FIRST_USER_DEFINED_ATTR = 0x1000, + NTFS_AT_END = 0xFFFFFFFF, +}; + +/* NTFS File Permissions (also called attributes in DOS terminology) */ +enum { + NTFS_FILE_ATTR_READONLY = 0x00000001, + NTFS_FILE_ATTR_HIDDEN = 0x00000002, + NTFS_FILE_ATTR_SYSTEM = 0x00000004, + NTFS_FILE_ATTR_DIRECTORY = 0x00000010, + NTFS_FILE_ATTR_ARCHIVE = 0x00000020, + NTFS_FILE_ATTR_DEVICE = 0x00000040, + NTFS_FILE_ATTR_NORMAL = 0x00000080, + NTFS_FILE_ATTR_TEMPORARY = 0x00000100, + NTFS_FILE_ATTR_SPARSE_FILE = 0x00000200, + NTFS_FILE_ATTR_REPARSE_POINT = 0x00000400, + NTFS_FILE_ATTR_COMPRESSED = 0x00000800, + NTFS_FILE_ATTR_OFFLINE = 0x00001000, + NTFS_FILE_ATTR_NOT_CONTENT_INDEXED = 0x00002000, + NTFS_FILE_ATTR_ENCRYPTED = 0x00004000, + NTFS_FILE_ATTR_VALID_FLAGS = 0x00007FB7, + NTFS_FILE_ATTR_VALID_SET_FLAGS = 0x000031A7, + NTFS_FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT = 0x10000000, + NTFS_FILE_ATTR_DUP_VIEW_INDEX_PRESENT = 0x20000000, +}; + +/* + * Magic identifiers present at the beginning of all ntfs record containing + * records (like mft records for example). + */ +enum { + /* Found in $MFT/$DATA */ + NTFS_MAGIC_FILE = 0x454C4946, /* MFT entry */ + NTFS_MAGIC_INDX = 0x58444E49, /* Index buffer */ + NTFS_MAGIC_HOLE = 0x454C4F48, + + /* Found in $LogFile/$DATA */ + NTFS_MAGIC_RSTR = 0x52545352, + NTFS_MAGIC_RCRD = 0x44524352, + /* Found in $LogFile/$DATA (May be found in $MFT/$DATA, also ?) */ + NTFS_MAGIC_CHKDSK = 0x444B4843, + /* Found in all ntfs record containing records. */ + NTFS_MAGIC_BAAD = 0x44414142, + NTFS_MAGIC_EMPTY = 0xFFFFFFFF, /* Record is empty */ +}; + +struct ntfs_record { + uint32_t magic; + uint16_t usa_ofs; + uint16_t usa_count; +} __attribute__((__packed__)) NTFS_RECORD; + +/* The $MFT metadata file types */ +enum ntfs_system_file { + FILE_MFT = 0, + FILE_MFTMirr = 1, + FILE_LogFile = 2, + FILE_Volume = 3, + FILE_AttrDef = 4, + FILE_root = 5, + FILE_Bitmap = 6, + FILE_Boot = 7, + FILE_BadClus = 8, + FILE_Secure = 9, + FILE_UpCase = 10, + FILE_Extend = 11, + FILE_reserved12 = 12, + FILE_reserved13 = 13, + FILE_reserved14 = 14, + FILE_reserved15 = 15, + FILE_reserved16 = 16, +}; + +enum { + MFT_RECORD_IN_USE = 0x0001, + MFT_RECORD_IS_DIRECTORY = 0x0002, +} __attribute__((__packed__)); + +struct ntfs_mft_record { + uint32_t magic; + uint16_t usa_ofs; + uint16_t usa_count; + uint64_t lsn; + uint16_t seq_no; + uint16_t link_count; + uint16_t attrs_offset; + uint16_t flags; /* MFT record flags */ + uint32_t bytes_in_use; + uint32_t bytes_allocated; + uint64_t base_mft_record; + uint16_t next_attr_instance; + uint16_t reserved; + uint32_t mft_record_no; +} __attribute__((__packed__)); /* 48 bytes */ + +/* This is the version without the NTFS 3.1+ specific fields */ +struct ntfs_mft_record_old { + uint32_t magic; + uint16_t usa_ofs; + uint16_t usa_count; + uint64_t lsn; + uint16_t seq_no; + uint16_t link_count; + uint16_t attrs_offset; + uint16_t flags; /* MFT record flags */ + uint32_t bytes_in_use; + uint32_t bytes_allocated; + uint64_t base_mft_record; + uint16_t next_attr_instance; +} __attribute__((__packed__)); /* 42 bytes */ + +enum { + ATTR_DEF_INDEXABLE = 0x02, + ATTR_DEF_MULTIPLE = 0x04, + ATTR_DEF_NOT_ZERO = 0x08, + ATTR_DEF_INDEXED_UNIQUE = 0x10, + ATTR_DEF_NAMED_UNIQUE = 0x20, + ATTR_DEF_RESIDENT = 0x40, + ATTR_DEF_ALWAYS_LOG = 0x80, +}; + +struct ntfs_attr_record { + uint32_t type; /* Attr. type code */ + uint32_t len; + uint8_t non_resident; + uint8_t name_len; + uint16_t name_offset; + uint16_t flags; /* Attr. flags */ + uint16_t instance; + union { + struct { /* Resident attribute */ + uint32_t value_len; + uint16_t value_offset; + uint8_t flags; /* Flags of resident attributes */ + int8_t reserved; + } __attribute__((__packed__)) resident; + struct { /* Non-resident attributes */ + uint64_t lowest_vcn; + uint64_t highest_vcn; + uint16_t mapping_pairs_offset; + uint8_t compression_unit; + uint8_t reserved[5]; + int64_t allocated_size; + int64_t data_size; /* Byte size of the attribute value. + * Note: it can be larger than + * allocated_size if attribute value is + * compressed or sparse. + */ + int64_t initialized_size; + int64_t compressed_size; + } __attribute__((__packed__)) non_resident; + } __attribute__((__packed__)) data; +} __attribute__((__packed__)); + +/* Attribute: Attribute List (0x20) + * Note: it can be either resident or non-resident + */ +struct ntfs_attr_list_entry { + uint32_t type; + uint16_t length; + uint8_t name_length; + uint8_t name_offset; + uint64_t lowest_vcn; + uint64_t mft_ref; + uint16_t instance; + uint16_t name[0]; +} __attribute__((__packed__)); + +#define NTFS_MAX_FILE_NAME_LEN 255 + +/* Possible namespaces for filenames in ntfs (8-bit) */ +enum { + FILE_NAME_POSIX = 0x00, + FILE_NAME_WIN32 = 0x01, + FILE_NAME_DOS = 0x02, + FILE_NAME_WIN32_AND_DOS = 0x03, +} __attribute__((__packed__)); + +/* Attribute: Filename (0x30) + * Note: always resident + */ +struct ntfs_filename_attr { + uint64_t parent_directory; + int64_t ctime; + int64_t atime; + int64_t mtime; + int64_t rtime; + uint64_t allocated_size; + uint64_t data_size; + uint32_t file_attrs; + union { + struct { + uint16_t packed_ea_size; + uint16_t reserved; /* reserved for alignment */ + } __attribute__((__packed__)) ea; + struct { + uint32_t reparse_point_tag; + } __attribute__((__packed__)) rp; + } __attribute__((__packed__)) type; + uint8_t file_name_len; + uint8_t file_name_type; + uint16_t file_name[0]; /* File name in Unicode */ +} __attribute__((__packed__)); + +/* Attribute: Volume Name (0x60) + * Note: always resident + * Note: Present only in FILE_volume + */ +struct ntfs_vol_name { + uint16_t name[0]; /* The name of the volume in Unicode */ +} __attribute__((__packed__)); + +/* Attribute: Volume Information (0x70) + * Note: always resident + * Note: present only in FILE_Volume + */ +struct ntfs_vol_info { + uint64_t reserved; + uint8_t major_ver; + uint8_t minor_ver; + uint16_t flags; /* Volume flags */ +} __attribute__((__packed__)); + +/* Attribute: Data attribute (0x80) + * Note: can be either resident or non-resident + */ +struct ntfs_data_attr { + uint8_t data[0]; +} __attribute__((__packed__)); + +/* Index header flags (8-bit) */ +enum { + SMALL_INDEX = 0, + LARGE_INDEX = 1, + LEAF_NODE = 0, + INDEX_NODE = 1, + NODE_MASK = 1, +} __attribute__((__packed__)); + +/* Header for the indexes, describing the INDEX_ENTRY records, which + * follow the struct ntfs_idx_header. + */ +struct ntfs_idx_header { + uint32_t entries_offset; + uint32_t index_len; + uint32_t allocated_size; + uint8_t flags; /* Index header flags */ + uint8_t reserved[3]; /* Align to 8-byte boundary */ +} __attribute__((__packed__)); + +/* Attribute: Index Root (0x90) + * Note: always resident + */ +struct ntfs_idx_root { + uint32_t type; /* It is $FILE_NAME for directories, zero for view indexes. + * No other values allowed. + */ + uint32_t collation_rule; + uint32_t index_block_size; + uint8_t clust_per_index_block; + uint8_t reserved[3]; + struct ntfs_idx_header index; +} __attribute__((__packed__)); + +/* Attribute: Index allocation (0xA0) + * Note: always non-resident, of course! :-) + */ +struct ntfs_idx_allocation { + uint32_t magic; + uint16_t usa_ofs; /* Update Sequence Array offsets */ + uint16_t usa_count; /* Update Sequence Array number in bytes */ + int64_t lsn; + int64_t index_block_vcn; /* Virtual cluster number of the index block */ + struct ntfs_idx_header index; +} __attribute__((__packed__)); + +enum { + INDEX_ENTRY_NODE = 1, + INDEX_ENTRY_END = 2, + /* force enum bit width to 16-bit */ + INDEX_ENTRY_SPACE_FILTER = 0xFFFF, +} __attribute__((__packed__)); + +struct ntfs_idx_entry_header { + union { + struct { /* Only valid when INDEX_ENTRY_END is not set */ + uint64_t indexed_file; + } __attribute__((__packed__)) dir; + struct { /* Used for views/indexes to find the entry's data */ + uint16_t data_offset; + uint16_t data_len; + uint32_t reservedV; + } __attribute__((__packed__)) vi; + } __attribute__((__packed__)) data; + uint16_t len; + uint16_t key_len; + uint16_t flags; /* Index entry flags */ + uint16_t reserved; /* Align to 8-byte boundary */ +} __attribute__((__packed__)); + +struct ntfs_idx_entry { + union { + struct { /* Only valid when INDEX_ENTRY_END is not set */ + uint64_t indexed_file; + } __attribute__((__packed__)) dir; + struct { /* Used for views/indexes to find the entry's data */ + uint16_t data_offset; + uint16_t data_len; + uint32_t reservedV; + } __attribute__((__packed__)) vi; + } __attribute__((__packed__)) data; + uint16_t len; + uint16_t key_len; + uint16_t flags; /* Index entry flags */ + uint16_t reserved; /* Align to 8-byte boundary */ + union { + struct ntfs_filename_attr file_name; + //SII_INDEX_KEY sii; + //SDH_INDEX_KEY sdh; + //GUID object_id; + //REPARSE_INDEX_KEY reparse; + //SID sid; + uint32_t owner_id; + } __attribute__((__packed__)) key; +} __attribute__((__packed__)); + +static inline struct ntfs_sb_info *NTFS_SB(struct fs_info *fs) +{ + return fs->fs_info; +} + +#define NTFS_PVT(i) ((struct ntfs_inode *)((i)->pvt)) + +#endif /* _NTFS_H_ */ diff --git a/core/fs/ntfs/runlist.h b/core/fs/ntfs/runlist.h new file mode 100644 index 00000000..4938696b --- /dev/null +++ b/core/fs/ntfs/runlist.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011 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. +*/ + +#ifndef _RUNLIST_H_ +#define _RUNLIST_H_ + +struct runlist_element { + uint64_t vcn; + int64_t lcn; + uint64_t len; +}; + +struct runlist { + struct runlist_element run; + struct runlist *next; +}; + +static struct runlist *tail; + +static inline bool runlist_is_empty(struct runlist *rlist) +{ + return !rlist; +} + +static inline struct runlist *runlist_alloc(void) +{ + struct runlist *rlist; + + rlist = malloc(sizeof *rlist); + if (!rlist) + malloc_error("runlist structure"); + + rlist->next = NULL; + + return rlist; +} + +static inline void runlist_append(struct runlist **rlist, + struct runlist_element *elem) +{ + struct runlist *n = runlist_alloc(); + + n->run = *elem; + + if (runlist_is_empty(*rlist)) { + *rlist = n; + tail = n; + } else { + tail->next = n; + tail = n; + } +} + +static inline struct runlist *runlist_remove(struct runlist **rlist) +{ + struct runlist *ret; + + if (runlist_is_empty(*rlist)) + return NULL; + + ret = *rlist; + *rlist = ret->next; + + return ret; +} + +#endif /* _RUNLIST_H_ */ |