diff options
author | H. Peter Anvin <hpa@linux.intel.com> | 2011-12-13 15:34:59 -0800 |
---|---|---|
committer | H. Peter Anvin <hpa@linux.intel.com> | 2011-12-13 15:34:59 -0800 |
commit | 120523b681d08245d68d9db08998fad4018713db (patch) | |
tree | b826a7dde33dd98299b25344620481a02d08ed8a | |
parent | a0ded54115637b7d239867a79095d20781bb0014 (diff) | |
parent | d2240176af6ac239216b160611ed74d481095a4b (diff) | |
download | syslinux-120523b681d08245d68d9db08998fad4018713db.tar.gz |
Merge remote-tracking branch 'pcacjr/ntfs-for-hpa'
32 files changed, 2938 insertions, 96 deletions
diff --git a/core/fs/ntfs/ntfs.c b/core/fs/ntfs/ntfs.c new file mode 100644 index 00000000..9a0408d4 --- /dev/null +++ b/core/fs/ntfs/ntfs.c @@ -0,0 +1,1208 @@ +/* + * Copyright (C) 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; + + 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; + + 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; + + 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 on 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; + + 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 on 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 struct ntfs_attr_record *ntfs_attr_lookup(uint32_t type, + const struct ntfs_mft_record *mrec) +{ + struct ntfs_attr_record *attr; + + /* sanity check */ + if (!mrec || type == NTFS_AT_END) + return 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) + return NULL; + + if (attr->type == type) + break; + } + + return attr; +} + +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; + + 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; + + 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 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; + struct ntfs_attr_record *attr; + enum dirent_type d_type; + uint32_t len; + struct ntfs_idx_root *ir; + uint8_t *attr_len; + struct mapping_chunk chunk; + int err; + uint8_t *stream; + uint32_t offset; + + mrec = NTFS_SB(fs)->mft_record_lookup(fs, mft_no, &start_blk); + if (!mrec) { + printf("No MFT record found.\n"); + goto out; + } + + 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(NTFS_AT_INDEX_ROOT, mrec); + if (!attr) { + printf("No attribute found.\n"); + goto out; + } + + /* note: struct ntfs_idx_root is always resident */ + ir = (struct ntfs_idx_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) { + printf("Corrupt index.\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(NTFS_AT_DATA, mrec); + 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; + block_t blk; + uint64_t blk_offset; + struct ntfs_attr_record *attr; + struct ntfs_idx_root *ir; + uint32_t len; + 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; + + mrec = NTFS_SB(fs)->mft_record_lookup(fs, NTFS_PVT(dir)->mft_no, NULL); + if (!mrec) { + printf("No MFT record found.\n"); + goto out; + } + + attr = ntfs_attr_lookup(NTFS_AT_INDEX_ROOT, mrec); + if (!attr) { + printf("No attribute found.\n"); + goto out; + } + + ir = (struct ntfs_idx_root *)((uint8_t *)attr + + attr->data.resident.value_offset); + len = attr->data.resident.value_len; + /* sanity check */ + if ((uint8_t *)ir + len > (uint8_t *)mrec + NTFS_SB(fs)->mft_record_size) + goto index_err; + + 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(NTFS_AT_INDEX_ALLOCATION, mrec); + 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 on 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; + + /* Do case-sensitive compares for Posix file names */ + if (ie->key.file_name.file_name_type == FILE_NAME_POSIX) { + if (ie->key.file_name.file_name[0] > *dname) + break; + } else { + if (tolower(ie->key.file_name.file_name[0]) > + tolower(*dname)) + 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: + dprintf("%s not found!\n", dname); + + 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; + } + + dprintf("%s found!\n", dname); + + 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); + + 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; + struct ntfs_attr_record *attr; + char *p; + + 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; + } + + attr = ntfs_attr_lookup(NTFS_AT_DATA, mrec); + 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; + 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]; + + mrec = NTFS_SB(fs)->mft_record_lookup(fs, NTFS_PVT(inode)->mft_no, NULL); + if (!mrec) { + printf("No MFT record found.\n"); + goto out; + } + + attr = ntfs_attr_lookup(NTFS_AT_INDEX_ROOT, mrec); + if (!attr) { + printf("No attribute found.\n"); + goto out; + } + + ir = (struct ntfs_idx_root *)((uint8_t *)attr + + attr->data.resident.value_offset); + len = attr->data.resident.value_len; + /* sanity check */ + if ((uint8_t *)ir + len > (uint8_t *)mrec + NTFS_SB(fs)->mft_record_size) + goto index_err; + + 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(NTFS_AT_INDEX_ALLOCATION, mrec); + 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 on 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 on 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++; + 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 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; + struct ntfs_attr_record *attr; + struct ntfs_vol_info *vol_info; + struct inode *inode; + int err; + + /* 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; + } + + /* Fetch the volume information attribute */ + attr = ntfs_attr_lookup(NTFS_AT_VOL_INFO, mrec); + 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; + + read_count = disk->rdwr_sectors(disk, &ntfs, 0, 1, 0); + if (!read_count) + return -1; + + /* sanity check */ + 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..6620a27d --- /dev/null +++ b/core/fs/ntfs/ntfs.h @@ -0,0 +1,464 @@ +/* + * Copyright (C) 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 _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 initialized_size; + int64_t compressed_size; + } __attribute__((__packed__)) non_resident; + } __attribute__((__packed__)) data; +} __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..c1942338 --- /dev/null +++ b/core/fs/ntfs/runlist.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 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_ */ diff --git a/core/ldlinux.asm b/core/ldlinux.asm index f62f55b2..d4c5eec4 100644 --- a/core/ldlinux.asm +++ b/core/ldlinux.asm @@ -39,6 +39,8 @@ ROOT_FS_OPS: dd ext2_fs_ops extern btrfs_fs_ops dd btrfs_fs_ops + extern ntfs_fs_ops + dd ntfs_fs_ops dd 0 %include "diskfs.inc" diff --git a/dos/Makefile b/dos/Makefile index 5e5fc63f..f9420084 100644 --- a/dos/Makefile +++ b/dos/Makefile @@ -27,7 +27,7 @@ INCLUDES = -include code16.h -nostdinc -iwithprefix include \ -I. -I.. -I../libfat -I ../libinstaller -I ../libinstaller/getopt SRCS = syslinux.c \ - ../libinstaller/fat.c \ + ../libinstaller/fs.c \ ../libinstaller/syslxmod.c \ ../libinstaller/syslxopt.c \ ../libinstaller/setadv.c \ diff --git a/dos/syslinux.c b/dos/syslinux.c index b5fdfc52..fa4bf387 100644 --- a/dos/syslinux.c +++ b/dos/syslinux.c @@ -31,6 +31,7 @@ #include "sysexits.h" #include "syslxopt.h" #include "syslxint.h" +#include "syslxfs.h" char *program = "syslinux.com"; /* Name of program */ uint16_t dos_version; @@ -638,7 +639,7 @@ int main(int argc, char *argv[]) /* * Check to see that what we got was indeed an MS-DOS boot sector/superblock */ - if ((errmsg = syslinux_check_bootsect(sectbuf))) { + if ((errmsg = syslinux_check_bootsect(sectbuf, NULL))) { unlock_device(0); puts(errmsg); putchar('\n'); @@ -749,7 +750,7 @@ int main(int argc, char *argv[]) read_device(dev_fd, sectbuf, 1, 0); /* Copy the syslinux code into the boot sector */ - syslinux_make_bootsect(sectbuf); + syslinux_make_bootsect(sectbuf, VFAT); /* Write new boot sector */ if (opt.bootsecfile) { diff --git a/extlinux/Makefile b/extlinux/Makefile index 12213d88..865c7a64 100644 --- a/extlinux/Makefile +++ b/extlinux/Makefile @@ -11,7 +11,7 @@ ## ----------------------------------------------------------------------- ## -## Linux vfat, ext2/ext3/ext4 and btrfs installer +## Linux vfat, ntfs, ext2/ext3/ext4 and btrfs installer ## topdir = .. diff --git a/extlinux/main.c b/extlinux/main.c index e574051b..d85d03c8 100755 --- a/extlinux/main.c +++ b/extlinux/main.c @@ -14,7 +14,7 @@ /* * extlinux.c * - * Install the syslinux boot block on an fat, ext2/3/4 and btrfs filesystem + * Install the syslinux boot block on an fat, ntfs, ext2/3/4 and btrfs filesystem */ #define _GNU_SOURCE /* Enable everything */ @@ -45,9 +45,11 @@ #include "btrfs.h" #include "fat.h" +#include "ntfs.h" #include "../version.h" #include "syslxint.h" #include "syslxcom.h" /* common functions shared with extlinux and syslinux */ +#include "syslxfs.h" #include "setadv.h" #include "syslxopt.h" /* unified options */ @@ -214,7 +216,7 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd) sector_t *sectp; uint64_t totalbytes, totalsectors; int nsect; - struct boot_sector *sbs; + struct fat_boot_sector *sbs; char *dirpath, *subpath, *xdirpath; int rv; @@ -271,7 +273,7 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd) early bootstrap share code with the FAT version. */ dprintf("heads = %u, sect = %u\n", geo.heads, geo.sectors); - sbs = (struct boot_sector *)syslinux_bootsect; + sbs = (struct fat_boot_sector *)syslinux_bootsect; totalsectors = totalbytes >> SECTOR_SHIFT; if (totalsectors >= 65536) { @@ -292,7 +294,7 @@ int patch_file_and_bootblock(int fd, const char *dir, int devfd) nsect = (boot_image_len + SECTOR_SIZE - 1) >> SECTOR_SHIFT; nsect += 2; /* Two sectors for the ADV */ sectp = alloca(sizeof(sector_t) * nsect); - if (fs_type == EXT2 || fs_type == VFAT) { + if (fs_type == EXT2 || fs_type == VFAT || fs_type == NTFS) { if (sectmap(fd, sectp, nsect)) { perror("bmap"); exit(1); @@ -323,7 +325,8 @@ int install_bootblock(int fd, const char *device) { struct ext2_super_block sb; struct btrfs_super_block sb2; - struct boot_sector sb3; + struct fat_boot_sector sb3; + struct ntfs_boot_sector sb4; bool ok = false; if (fs_type == EXT2) { @@ -348,20 +351,39 @@ int install_bootblock(int fd, const char *device) } if (fat_check_sb_fields(&sb3)) ok = true; + } else if (fs_type == NTFS) { + if (xpread(fd, &sb4, sizeof(sb4), 0) != sizeof(sb4)) { + perror("reading ntfs superblock"); + return 1; + } + + if (ntfs_check_sb_fields(&sb4)) + ok = true; } if (!ok) { - fprintf(stderr, "no fat, ext2/3/4 or btrfs superblock found on %s\n", + fprintf(stderr, "no fat, ntfs, ext2/3/4 or btrfs superblock found on %s\n", device); return 1; } if (fs_type == VFAT) { - struct boot_sector *sbs = (struct boot_sector *)syslinux_bootsect; - if (xpwrite(fd, &sbs->bsHead, bsHeadLen, 0) != bsHeadLen || - xpwrite(fd, &sbs->bsCode, bsCodeLen, - offsetof(struct boot_sector, bsCode)) != bsCodeLen) { + struct fat_boot_sector *sbs = (struct fat_boot_sector *)syslinux_bootsect; + if (xpwrite(fd, &sbs->FAT_bsHead, FAT_bsHeadLen, 0) != FAT_bsHeadLen || + xpwrite(fd, &sbs->FAT_bsCode, FAT_bsCodeLen, + offsetof(struct fat_boot_sector, FAT_bsCode)) != FAT_bsCodeLen) { perror("writing fat bootblock"); return 1; } + } else if (fs_type == NTFS) { + struct ntfs_boot_sector *sbs = + (struct ntfs_boot_sector *)syslinux_bootsect; + if (xpwrite(fd, &sbs->NTFS_bsHead, + NTFS_bsHeadLen, 0) != NTFS_bsHeadLen || + xpwrite(fd, &sbs->NTFS_bsCode, NTFS_bsCodeLen, + offsetof(struct ntfs_boot_sector, + NTFS_bsCode)) != NTFS_bsCodeLen) { + perror("writing ntfs bootblock"); + return 1; + } } else { if (xpwrite(fd, syslinux_bootsect, syslinux_bootsect_len, 0) != syslinux_bootsect_len) { @@ -754,7 +776,7 @@ static char * get_default_subvol(char * rootdir, char * subvol) int install_file(const char *path, int devfd, struct stat *rst) { - if (fs_type == EXT2 || fs_type == VFAT) + if (fs_type == EXT2 || fs_type == VFAT || fs_type == NTFS) return ext2_fat_install_file(path, devfd, rst); else if (fs_type == BTRFS) return btrfs_install_file(path, devfd, rst); @@ -828,6 +850,16 @@ static const char *find_device(const char *mtab_file, dev_t dev) done = true; break; } + case NTFS: + if ((!strcmp(mnt->mnt_type, "fuseblk") /* ntfs-3g */ || + !strcmp(mnt->mnt_type, "ntfs")) && + !stat(mnt->mnt_fsname, &dst) && + dst.st_rdev == dev) { + done = true; + break; + } + + break; case NONE: break; } @@ -910,9 +942,12 @@ static int open_device(const char *path, struct stat *st, const char **_devname) fs_type = BTRFS; else if (sfs.f_type == MSDOS_SUPER_MAGIC) fs_type = VFAT; + else if (sfs.f_type == NTFS_SB_MAGIC || + sfs.f_type == FUSE_SUPER_MAGIC /* ntfs-3g */) + fs_type = NTFS; if (!fs_type) { - fprintf(stderr, "%s: not a fat, ext2/3/4 or btrfs filesystem: %s\n", + fprintf(stderr, "%s: not a fat, ntfs, ext2/3/4 or btrfs filesystem: %s\n", program, path); return -1; } diff --git a/extlinux/ntfs.h b/extlinux/ntfs.h new file mode 100644 index 00000000..d907d452 --- /dev/null +++ b/extlinux/ntfs.h @@ -0,0 +1,19 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 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, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef _NTFS_H_ +#define _NTFS_H_ + +#define NTFS_SB_MAGIC 0x5346544E +#define FUSE_SUPER_MAGIC 0x65735546 + +#endif /* _NTFS_H_ */ diff --git a/libinstaller/fat.c b/libinstaller/fs.c index 9cde00c2..179629e9 100644 --- a/libinstaller/fat.c +++ b/libinstaller/fs.c @@ -1,7 +1,8 @@ /* ----------------------------------------------------------------------- * * - * Copyright 1998-2008 H. Peter Anvin - All Rights Reserved - * Copyright 2009-2010 Intel Corporation; author H. Peter Anvin + * Copyright 1998-2011 H. Peter Anvin - All Rights Reserved + * Copyright 2009-2011 Intel Corporation; author H. Peter Anvin + * Copyright 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 @@ -12,7 +13,7 @@ * ----------------------------------------------------------------------- */ /* - * fat.c - Initial sanity check for FAT-based installers + * fs.c - Generic sanity check for FAT/NTFS-based installers */ #define _XOPEN_SOURCE 500 /* Required on glibc 2.x */ @@ -25,45 +26,41 @@ #include "syslinux.h" #include "syslxint.h" +#include "syslxcom.h" +#include "syslxfs.h" -void syslinux_make_bootsect(void *bs) +void syslinux_make_bootsect(void *bs, int fs_type) { - struct boot_sector *bootsect = bs; - const struct boot_sector *sbs = - (const struct boot_sector *)boot_sector; - - memcpy(&bootsect->bsHead, &sbs->bsHead, bsHeadLen); - memcpy(&bootsect->bsCode, &sbs->bsCode, bsCodeLen); + if (fs_type == VFAT) { + struct fat_boot_sector *bootsect = bs; + const struct fat_boot_sector *sbs = + (const struct fat_boot_sector *)boot_sector; + + memcpy(&bootsect->FAT_bsHead, &sbs->FAT_bsHead, FAT_bsHeadLen); + memcpy(&bootsect->FAT_bsCode, &sbs->FAT_bsCode, FAT_bsCodeLen); + } else if (fs_type == NTFS) { + struct ntfs_boot_sector *bootsect = bs; + const struct ntfs_boot_sector *sbs = + (const struct ntfs_boot_sector *)boot_sector; + + memcpy(&bootsect->NTFS_bsHead, &sbs->NTFS_bsHead, NTFS_bsHeadLen); + memcpy(&bootsect->NTFS_bsCode, &sbs->NTFS_bsCode, NTFS_bsCodeLen); + } } -/* - * Check to see that what we got was indeed an MS-DOS boot sector/superblock; - * Return NULL if OK and otherwise an error message; - */ -const char *syslinux_check_bootsect(const void *bs) +static const char *check_fat_bootsect(const void *bs, int *fs_type) { int sectorsize; + const struct fat_boot_sector *sectbuf = bs; long long sectors, fatsectors, dsectors; long long clusters; int rootdirents, clustersize; - const struct boot_sector *sectbuf = bs; - - /* Must be 0xF0 or 0xF8..0xFF */ - if (get_8(§buf->bsMedia) != 0xF0 && get_8(§buf->bsMedia) < 0xF8) - return "invalid media signature (not a FAT filesystem?)"; sectorsize = get_16(§buf->bsBytesPerSec); - if (sectorsize == SECTOR_SIZE) - ; /* ok */ - else if (sectorsize >= 512 && sectorsize <= 4096 && - (sectorsize & (sectorsize - 1)) == 0) - return "unsupported sectors size"; - else - return "impossible sector size"; clustersize = get_8(§buf->bsSecPerClust); if (clustersize == 0 || (clustersize & (clustersize - 1))) - return "impossible cluster size"; + return "impossible cluster size on an FAT volume"; sectors = get_16(§buf->bsSectors); sectors = sectors ? sectors : get_32(§buf->bsHugeSectors); @@ -79,16 +76,19 @@ const char *syslinux_check_bootsect(const void *bs) dsectors -= (rootdirents + sectorsize / 32 - 1) / sectorsize; if (dsectors < 0) - return "negative number of data sectors"; - - if (fatsectors == 0) - return "zero FAT sectors"; + return "negative number of data sectors on an FAT volume"; clusters = dsectors / clustersize; + fatsectors = get_16(§buf->bsFATsecs); + fatsectors = fatsectors ? fatsectors : get_32(§buf->bs32.FATSz32); + fatsectors *= get_8(§buf->bsFATs); + + if (!fatsectors) + return "zero FAT sectors"; + if (clusters < 0xFFF5) { /* FAT12 or FAT16 */ - if (!get_16(§buf->bsFATsecs)) return "zero FAT sectors (FAT12/16)"; @@ -100,10 +100,10 @@ const char *syslinux_check_bootsect(const void *bs) if (clusters < 0xFF5) return "less than 4084 clusters but claims FAT16"; } else if (!memcmp(§buf->bs16.FileSysType, "FAT32 ", 8)) { - return "less than 65525 clusters but claims FAT32"; + return "less than 65525 clusters but claims FAT32"; } else if (memcmp(§buf->bs16.FileSysType, "FAT ", 8)) { - static char fserr[] = - "filesystem type \"????????\" not supported"; + static char fserr[] = "filesystem type \"????????\" not " + "supported"; memcpy(fserr + 17, §buf->bs16.FileSysType, 8); return fserr; } @@ -119,8 +119,54 @@ const char *syslinux_check_bootsect(const void *bs) memcmp(§buf->bs32.FileSysType, "FAT32 ", 8)) return "missing FAT32 signature"; } else { - return "impossibly large number of clusters"; + return "impossibly large number of clusters on an FAT volume"; } + if (fs_type) + *fs_type = VFAT; + + return NULL; +} + +static const char *check_ntfs_bootsect(const void *bs, int *fs_type) +{ + const struct ntfs_boot_sector *sectbuf = bs; + + if (memcmp(§buf->bsOemName, "NTFS ", 8) && + memcmp(§buf->bsOemName, "MSWIN4.0", 8) && + memcmp(§buf->bsOemName, "MSWIN4.1", 8)) + return "unknown OEM name but claims NTFS"; + + if (fs_type) + *fs_type = NTFS; + return NULL; } + +const char *syslinux_check_bootsect(const void *bs, int *fs_type) +{ + uint8_t media_sig; + int sectorsize; + const struct fat_boot_sector *sectbuf = bs; + const char *retval; + + media_sig = get_8(§buf->bsMedia); + /* Must be 0xF0 or 0xF8-0xFF for FAT/NTFS volumes */ + if (media_sig != 0xF0 && media_sig < 0xF8) + return "invalid media signature (not an FAT/NTFS volume?)"; + + sectorsize = get_16(§buf->bsBytesPerSec); + if (sectorsize == SECTOR_SIZE) ; /* ok */ + else if (sectorsize >= 512 && sectorsize <= 4096 && + (sectorsize & (sectorsize - 1)) == 0) + return "unsupported sectors size"; + else + return "impossible sector size"; + + if (ntfs_check_zero_fields((struct ntfs_boot_sector *)bs)) + retval = check_ntfs_bootsect(bs, fs_type); + else + retval = check_fat_bootsect(bs, fs_type); + + return retval; +} diff --git a/libinstaller/linuxioctl.h b/libinstaller/linuxioctl.h index 7ef919a3..c339480a 100644 --- a/libinstaller/linuxioctl.h +++ b/libinstaller/linuxioctl.h @@ -15,7 +15,6 @@ #include <linux/hdreg.h> /* Hard disk geometry */ #include <linux/fs.h> /* FIGETBSZ, FIBMAP, FS_IOC_FIEMAP */ -#include <linux/msdos_fs.h> /* FAT_IOCTL_SET_ATTRIBUTES */ #undef SECTOR_SIZE /* Defined in msdos_fs.h for no good reason */ #undef SECTOR_BITS @@ -25,9 +24,7 @@ # define FAT_IOCTL_GET_ATTRIBUTES _IOR('r', 0x10, __u32) #endif -#ifndef FAT_IOCTL_SET_ATTRIBUTES -# define FAT_IOCTL_SET_ATTRIBUTES _IOW('r', 0x11, __u32) -#endif +#define FAT_IOCTL_SET_ATTRIBUTES _IOW('r', 0x11, __u32) #include <linux/fiemap.h> /* FIEMAP definitions */ diff --git a/libinstaller/setadv.c b/libinstaller/setadv.c index 9cf6f188..214f7fc1 100644 --- a/libinstaller/setadv.c +++ b/libinstaller/setadv.c @@ -30,6 +30,7 @@ #include <errno.h> #include "syslxint.h" #include "syslxcom.h" +#include "syslxfs.h" unsigned char syslinux_adv[2 * ADV_SIZE]; diff --git a/libinstaller/syslinux.h b/libinstaller/syslinux.h index 710d30e5..8b86f881 100644 --- a/libinstaller/syslinux.h +++ b/libinstaller/syslinux.h @@ -40,10 +40,10 @@ extern const int syslinux_mbr_mtime; #define SECTOR_SIZE (1 << SECTOR_SHIFT) /* This takes a boot sector and merges in the syslinux fields */ -void syslinux_make_bootsect(void *); +void syslinux_make_bootsect(void *bs, int fs_type); /* Check to see that what we got was indeed an MS-DOS boot sector/superblock */ -const char *syslinux_check_bootsect(const void *bs); +const char *syslinux_check_bootsect(const void *bs, int *fs_type); /* This patches the boot sector and ldlinux.sys based on a sector map */ typedef uint64_t sector_t; diff --git a/libinstaller/syslxcom.c b/libinstaller/syslxcom.c index 1de85aa5..651f982d 100644 --- a/libinstaller/syslxcom.c +++ b/libinstaller/syslxcom.c @@ -30,8 +30,10 @@ #include <sys/types.h> #include <sys/mount.h> #include <sys/vfs.h> + #include "linuxioctl.h" #include "syslxcom.h" +#include "syslxfs.h" const char *program; @@ -133,6 +135,8 @@ void clear_attributes(int fd) ioctl(fd, FAT_IOCTL_SET_ATTRIBUTES, &attr); break; } + case NTFS: + break; default: break; } @@ -163,6 +167,8 @@ void set_attributes(int fd) ioctl(fd, FAT_IOCTL_SET_ATTRIBUTES, &attr); break; } + case NTFS: + break; default: break; } diff --git a/libinstaller/syslxcom.h b/libinstaller/syslxcom.h index bf186ca6..8b3b4614 100644 --- a/libinstaller/syslxcom.h +++ b/libinstaller/syslxcom.h @@ -3,15 +3,6 @@ #include "syslinux.h" -/* Global fs_type for handling fat, ext2/3/4 and btrfs */ -enum filesystem { - NONE, - EXT2, - BTRFS, - VFAT, -}; - -extern int fs_type; extern const char *program; ssize_t xpread(int fd, void *buf, size_t count, off_t offset); ssize_t xpwrite(int fd, const void *buf, size_t count, off_t offset); diff --git a/libinstaller/syslxfs.h b/libinstaller/syslxfs.h new file mode 100644 index 00000000..7a231461 --- /dev/null +++ b/libinstaller/syslxfs.h @@ -0,0 +1,26 @@ +/* + * Copyright 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, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef _SYSLXFS_H_ +#define _SYSLXFS_H_ + +/* Global fs_type for handling fat, ntfs, ext2/3/4 and btrfs */ +enum filesystem { + NONE, + EXT2, + BTRFS, + VFAT, + NTFS, +}; + +extern int fs_type; + +#endif /* _SYSLXFS_H_ */ diff --git a/libinstaller/syslxint.h b/libinstaller/syslxint.h index 7c9da516..2e317d0e 100644 --- a/libinstaller/syslxint.h +++ b/libinstaller/syslxint.h @@ -2,6 +2,7 @@ * * Copyright 2007-2008 H. Peter Anvin - All Rights Reserved * Copyright 2009-2011 Intel Corporation; author: H. Peter Anvin + * Copyright 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 @@ -192,7 +193,7 @@ struct syslinux_extent { } __attribute__((packed)); /* FAT bootsector format, also used by other disk-based derivatives */ -struct boot_sector { +struct fat_boot_sector { uint8_t bsJump[3]; char bsOemName[8]; uint16_t bsBytesPerSec; @@ -241,13 +242,68 @@ struct boot_sector { uint16_t bsSignature; } __attribute__ ((packed)); -#define bsHead bsJump -#define bsHeadLen offsetof(struct boot_sector, bsBytesPerSec) -#define bsCode bs32.Code /* The common safe choice */ -#define bsCodeLen (offsetof(struct boot_sector, bsSignature) - \ - offsetof(struct boot_sector, bsCode)) +/* NTFS bootsector format */ +struct ntfs_boot_sector { + uint8_t bsJump[3]; + char bsOemName[8]; + uint16_t bsBytesPerSec; + uint8_t bsSecPerClust; + uint16_t bsResSectors; + uint8_t bsZeroed_0[3]; + uint16_t bsZeroed_1; + uint8_t bsMedia; + uint16_t bsZeroed_2; + uint16_t bsUnused_0; + uint16_t bsUnused_1; + uint32_t bsUnused_2; + uint32_t bsZeroed_3; + uint32_t bsUnused_3; + uint64_t bsTotalSectors; + uint64_t bsMFTLogicalClustNr; + uint64_t bsMFTMirrLogicalClustNr; + uint8_t bsClustPerMFTrecord; + uint8_t bsUnused_4[3]; + uint8_t bsClustPerIdxBuf; + uint8_t bsUnused_5[3]; + uint64_t bsVolSerialNr; + uint32_t bsUnused_6; + + uint8_t Code[420]; + + uint32_t bsMagic; + uint16_t bsForwardPtr; + uint16_t bsSignature; +} __attribute__((packed)); + +#define FAT_bsHead bsJump +#define FAT_bsHeadLen offsetof(struct fat_boot_sector, bsBytesPerSec) +#define FAT_bsCode bs32.Code /* The common safe choice */ +#define FAT_bsCodeLen (offsetof(struct fat_boot_sector, bsSignature) - \ + offsetof(struct fat_boot_sector, FAT_bsCode)) + +#define NTFS_bsHead bsJump +#define NTFS_bsHeadLen offsetof(struct ntfs_boot_sector, bsOemName) +#define NTFS_bsCode Code +#define NTFS_bsCodeLen (offsetof(struct ntfs_boot_sector, bsSignature) - \ + offsetof(struct ntfs_boot_sector, NTFS_bsCode)) + +/* Check if there are specific zero fields in an NTFS boot sector */ +static inline int ntfs_check_zero_fields(const struct ntfs_boot_sector *sb) +{ + return !sb->bsResSectors && (!sb->bsZeroed_0[0] && !sb->bsZeroed_0[1] && + !sb->bsZeroed_0[2]) && !sb->bsZeroed_1 && !sb->bsZeroed_2 && + !sb->bsZeroed_3; +} + +static inline int ntfs_check_sb_fields(const struct ntfs_boot_sector *sb) +{ + return ntfs_check_zero_fields(sb) && + (!memcmp(sb->bsOemName, "NTFS ", 8) || + !memcmp(sb->bsOemName, "MSWIN4.0", 8) || + !memcmp(sb->bsOemName, "MSWIN4.1", 8)); +} -static inline int fat_check_sb_fields(const struct boot_sector *sb) +static inline int fat_check_sb_fields(const struct fat_boot_sector *sb) { return sb->bsResSectors && sb->bsFATs && (!memcmp(sb->bs16.FileSysType, "FAT12 ", 8) || diff --git a/libinstaller/syslxmod.c b/libinstaller/syslxmod.c index 8847b736..24368459 100644 --- a/libinstaller/syslxmod.c +++ b/libinstaller/syslxmod.c @@ -109,7 +109,7 @@ int syslinux_patch(const sector_t *sectp, int nsectors, int nsect = ((boot_image_len + SECTOR_SIZE - 1) >> SECTOR_SHIFT) + 2; uint32_t csum; int i, dw, nptrs; - struct boot_sector *sbs = (struct boot_sector *)boot_sector; + struct fat_boot_sector *sbs = (struct fat_boot_sector *)boot_sector; uint64_t *advptrs; if (nsectors < nsect) diff --git a/libinstaller/syslxopt.c b/libinstaller/syslxopt.c index 7ceb3ba2..e081a00e 100644 --- a/libinstaller/syslxopt.c +++ b/libinstaller/syslxopt.c @@ -25,6 +25,7 @@ #include <sysexits.h> #include "../version.h" #include "syslxcom.h" +#include "syslxfs.h" #include "syslxopt.h" /* These are the options we can set their values */ diff --git a/linux/Makefile b/linux/Makefile index 82bf1118..08a3ed49 100644 --- a/linux/Makefile +++ b/linux/Makefile @@ -11,7 +11,7 @@ ## ----------------------------------------------------------------------- ## -## Linux FAT installer +## Linux FAT/NTFS installer ## topdir = .. @@ -28,7 +28,7 @@ SRCS = syslinux.c \ ../libinstaller/syslxcom.c \ ../libinstaller/setadv.c \ ../libinstaller/advio.c \ - ../libinstaller/fat.c \ + ../libinstaller/fs.c \ ../libinstaller/syslxmod.c \ ../libinstaller/bootsect_bin.c \ ../libinstaller/ldlinux_bin.c diff --git a/linux/syslinux.c b/linux/syslinux.c index c7a9ecc4..4b13b7fe 100755 --- a/linux/syslinux.c +++ b/linux/syslinux.c @@ -69,6 +69,7 @@ #include <getopt.h> #include <sysexits.h> #include "syslxcom.h" +#include "syslxfs.h" #include "setadv.h" #include "syslxopt.h" /* unified options */ @@ -294,14 +295,14 @@ int main(int argc, char *argv[]) die("can't combine an offset with a block device"); } - fs_type = VFAT; xpread(dev_fd, sectbuf, SECTOR_SIZE, opt.offset); fsync(dev_fd); /* - * Check to see that what we got was indeed an MS-DOS boot sector/superblock + * Check to see that what we got was indeed an FAT/NTFS + * boot sector/superblock */ - if ((errmsg = syslinux_check_bootsect(sectbuf))) { + if ((errmsg = syslinux_check_bootsect(sectbuf, &fs_type))) { fprintf(stderr, "%s: %s\n", opt.device, errmsg); exit(1); } @@ -357,10 +358,17 @@ int main(int argc, char *argv[]) mntpath = mntname; } - if (do_mount(dev_fd, &mnt_cookie, mntpath, "vfat") && - do_mount(dev_fd, &mnt_cookie, mntpath, "msdos")) { - rmdir(mntpath); - die("mount failed"); + if (fs_type == VFAT) { + if (do_mount(dev_fd, &mnt_cookie, mntpath, "vfat") && + do_mount(dev_fd, &mnt_cookie, mntpath, "msdos")) { + rmdir(mntpath); + die("failed on mounting fat volume"); + } + } else if (fs_type == NTFS) { + if (do_mount(dev_fd, &mnt_cookie, mntpath, "ntfs-3g")) { + rmdir(mntpath); + die("failed on mounting ntfs volume"); + } } ldlinux_path = alloca(strlen(mntpath) + strlen(subdir) + 1); @@ -474,7 +482,7 @@ umount: xpread(dev_fd, sectbuf, SECTOR_SIZE, opt.offset); /* Copy the syslinux code into the boot sector */ - syslinux_make_bootsect(sectbuf); + syslinux_make_bootsect(sectbuf, fs_type); /* Write new boot sector */ xpwrite(dev_fd, sectbuf, SECTOR_SIZE, opt.offset); diff --git a/mtools/Makefile b/mtools/Makefile index 3e172fd6..78cea1e2 100755 --- a/mtools/Makefile +++ b/mtools/Makefile @@ -8,7 +8,7 @@ CFLAGS = $(GCCWARN) -D_FILE_OFFSET_BITS=64 $(OPTFLAGS) $(INCLUDES) LDFLAGS = SRCS = syslinux.c \ - ../libinstaller/fat.c \ + ../libinstaller/fs.c \ ../libinstaller/syslxmod.c \ ../libinstaller/syslxopt.c \ ../libinstaller/setadv.c \ diff --git a/mtools/syslinux.c b/mtools/syslinux.c index ac189c61..c65021bb 100755 --- a/mtools/syslinux.c +++ b/mtools/syslinux.c @@ -41,6 +41,7 @@ #include "libfat.h" #include "setadv.h" #include "syslxopt.h" +#include "syslxfs.h" char *program; /* Name of program */ pid_t mypid; @@ -197,7 +198,7 @@ int main(int argc, char *argv[]) /* * Check to see that what we got was indeed an MS-DOS boot sector/superblock */ - if ((errmsg = syslinux_check_bootsect(sectbuf))) { + if ((errmsg = syslinux_check_bootsect(sectbuf, NULL))) { die(errmsg); } @@ -356,7 +357,7 @@ int main(int argc, char *argv[]) xpread(dev_fd, sectbuf, SECTOR_SIZE, opt.offset); /* Copy the syslinux code into the boot sector */ - syslinux_make_bootsect(sectbuf); + syslinux_make_bootsect(sectbuf, VFAT); /* Write new boot sector */ xpwrite(dev_fd, sectbuf, SECTOR_SIZE, opt.offset); diff --git a/win/ntfssect.c b/win/ntfssect.c new file mode 100644 index 00000000..8c2bccaa --- /dev/null +++ b/win/ntfssect.c @@ -0,0 +1,355 @@ +/* -------------------------------------------------------------------------- * + * + * Copyright 2011 Shao Miller - All Rights Reserved + * + * 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, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ------------------------------------------------------------------------- */ + +/**** + * ntfssect.c + * + * Fetch NTFS file cluster & sector information via Windows + * + * With special thanks to Mark Roddy for his article: + * http://www.wd-3.com/archive/luserland.htm + */ + +#include <windows.h> +#include <winioctl.h> +#include <stddef.h> +#include <string.h> + +#include "ntfssect.h" + +/*** Macros */ +#define M_ERR(msg) (NtfsSectLastErrorMessage = (msg)) + +/*** Function declarations */ +static DWORD NtfsSectGetVolumeHandle( + CHAR * VolumeName, + S_NTFSSECT_VOLINFO * VolumeInfo + ); +static DWORD NtfsSectGetVolumePartitionLba(S_NTFSSECT_VOLINFO * VolumeInfo); + +/*** Objects */ +CHAR * NtfsSectLastErrorMessage; + +/*** Function definitions */ +DWORD M_NTFSSECT_API NtfsSectGetFileVcnExtent( + HANDLE File, + LARGE_INTEGER * Vcn, + S_NTFSSECT_EXTENT * Extent + ) { + BOOL bad, ok; + DWORD output_size, rc; + STARTING_VCN_INPUT_BUFFER input; + RETRIEVAL_POINTERS_BUFFER output; + + bad = ( + File == INVALID_HANDLE_VALUE || + !Vcn || + Vcn->QuadPart < 0 || + !Extent + ); + if (bad) + return ERROR_INVALID_PARAMETER; + + input.StartingVcn = *Vcn; + ok = DeviceIoControl( + File, + FSCTL_GET_RETRIEVAL_POINTERS, + &input, + sizeof input, + &output, + sizeof output, + &output_size, + NULL + ); + rc = GetLastError(); + switch (rc) { + case NO_ERROR: + case ERROR_MORE_DATA: + Extent->FirstVcn = output.StartingVcn; + Extent->NextVcn = output.Extents[0].NextVcn; + Extent->FirstLcn = output.Extents[0].Lcn; + return ERROR_SUCCESS; + + case ERROR_HANDLE_EOF: + break; + + default: + M_ERR("NtfsSectGetFileVcnExtent(): Unknown status!"); + } + + return rc; + } + +/* Internal use only */ +static DWORD NtfsSectGetVolumeHandle( + CHAR * VolumeName, + S_NTFSSECT_VOLINFO * VolumeInfo + ) { + #define M_VOL_PREFIX "\\\\.\\" + CHAR volname[sizeof M_VOL_PREFIX - 1 + MAX_PATH + 1] = M_VOL_PREFIX; + CHAR * const volname_short = volname + sizeof M_VOL_PREFIX - 1; + CHAR * c; + DWORD rc; + + /* Prefix "\\.\" onto the passed volume name */ + strcpy(volname + sizeof M_VOL_PREFIX - 1, VolumeName); + + /* Find the last non-null character */ + for (c = volname_short; *c; ++c) + ; + + /* Remove trailing back-slash */ + if (c[-1] == '\\') + c[-1] = 0; + + /* Open the volume */ + VolumeInfo->Handle = CreateFile( + volname, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL + ); + rc = GetLastError(); + if (VolumeInfo->Handle == INVALID_HANDLE_VALUE) { + M_ERR("Unable to open volume handle!"); + goto err_handle; + } + + return ERROR_SUCCESS; + + CloseHandle(VolumeInfo->Handle); + err_handle: + + return rc; + } + +DWORD M_NTFSSECT_API NtfsSectGetVolumeInfo( + CHAR * VolumeName, + S_NTFSSECT_VOLINFO * VolumeInfo + ) { + S_NTFSSECT_XPFUNCS xp_funcs; + DWORD rc, free_clusts, total_clusts; + BOOL ok; + + if (!VolumeName || !VolumeInfo) + return ERROR_INVALID_PARAMETER; + + rc = NtfsSectGetVolumeHandle(VolumeName, VolumeInfo); + if (rc != ERROR_SUCCESS) + goto err_handle; + + rc = NtfsSectLoadXpFuncs(&xp_funcs); + if (rc != ERROR_SUCCESS) + goto err_xp_funcs; + + ok = xp_funcs.GetDiskFreeSpace( + VolumeName, + &VolumeInfo->SectorsPerCluster, + &VolumeInfo->BytesPerSector, + &free_clusts, + &total_clusts + ); + rc = GetLastError(); + if (!ok) { + M_ERR("GetDiskFreeSpace() failed!"); + goto err_freespace; + } + + rc = NtfsSectGetVolumePartitionLba(VolumeInfo); + if (rc != ERROR_SUCCESS) + goto err_lba; + + VolumeInfo->Size = sizeof *VolumeInfo; + rc = ERROR_SUCCESS; + + err_lba: + + err_freespace: + + NtfsSectUnloadXpFuncs(&xp_funcs); + err_xp_funcs: + + if (rc != ERROR_SUCCESS) { + CloseHandle(VolumeInfo->Handle); + VolumeInfo->Handle = INVALID_HANDLE_VALUE; + } + err_handle: + + return rc; + } + +DWORD M_NTFSSECT_API NtfsSectGetVolumeInfoFromFileName( + CHAR * FileName, + S_NTFSSECT_VOLINFO * VolumeInfo + ) { + S_NTFSSECT_XPFUNCS xp_funcs; + DWORD rc; + CHAR volname[MAX_PATH + 1]; + BOOL ok; + + if (!FileName || !VolumeInfo) + return ERROR_INVALID_PARAMETER; + + rc = NtfsSectLoadXpFuncs(&xp_funcs); + if (rc != ERROR_SUCCESS) { + goto err_xp_funcs; + } + + ok = xp_funcs.GetVolumePathName( + FileName, + volname, + sizeof volname + ); + rc = GetLastError(); + if (!ok) { + M_ERR("GetVolumePathName() failed!"); + goto err_volname; + } + + rc = NtfsSectGetVolumeInfo(volname, VolumeInfo); + + err_volname: + + NtfsSectUnloadXpFuncs(&xp_funcs); + err_xp_funcs: + + return rc; + } + +/* Internal use only */ +static DWORD NtfsSectGetVolumePartitionLba(S_NTFSSECT_VOLINFO * VolumeInfo) { + BOOL ok; + VOLUME_DISK_EXTENTS vol_disk_extents; + DWORD output_size, rc; + + ok = DeviceIoControl( + VolumeInfo->Handle, + IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + NULL, + 0, + &vol_disk_extents, + sizeof vol_disk_extents, + &output_size, + NULL + ); + rc = GetLastError(); + if (!ok) { + M_ERR("Couldn't fetch volume disk extent(s)!"); + goto err_vol_disk_extents; + } + + if (vol_disk_extents.NumberOfDiskExtents != 1) { + M_ERR("Unsupported number of volume disk extents!"); + goto err_num_of_extents; + } + + VolumeInfo->PartitionLba.QuadPart = ( + vol_disk_extents.Extents[0].StartingOffset.QuadPart / + VolumeInfo->BytesPerSector + ); + + return ERROR_SUCCESS; + + err_num_of_extents: + + err_vol_disk_extents: + + return rc; + } + +DWORD M_NTFSSECT_API NtfsSectLcnToLba( + const S_NTFSSECT_VOLINFO * VolumeInfo, + const LARGE_INTEGER * Lcn, + LARGE_INTEGER * Lba + ) { + BOOL bad; + bad = ( + !VolumeInfo || + !VolumeInfo->BytesPerSector || + !VolumeInfo->SectorsPerCluster || + !Lcn || + Lcn->QuadPart < 0 || + !Lba + ); + if (bad) + return ERROR_INVALID_PARAMETER; + + Lba->QuadPart = ( + VolumeInfo->PartitionLba.QuadPart + + Lcn->QuadPart * + VolumeInfo->SectorsPerCluster + ); + return ERROR_SUCCESS; + } + +DWORD M_NTFSSECT_API NtfsSectLoadXpFuncs(S_NTFSSECT_XPFUNCS * XpFuncs) { + DWORD rc; + + if (!XpFuncs) + return ERROR_INVALID_PARAMETER; + + XpFuncs->Size = sizeof *XpFuncs; + + XpFuncs->Kernel32 = LoadLibrary("kernel32.dll"); + rc = GetLastError(); + if (!XpFuncs->Kernel32) { + M_ERR("KERNEL32.DLL not found!"); + goto err; + } + + XpFuncs->GetVolumePathName = (F_KERNEL32_GETVOLUMEPATHNAME *) ( + GetProcAddress( + XpFuncs->Kernel32, + "GetVolumePathNameA" + ) + ); + rc = GetLastError(); + if (!XpFuncs->GetVolumePathName) { + M_ERR("GetVolumePathName() not found in KERNEL32.DLL!"); + goto err; + } + + XpFuncs->GetDiskFreeSpace = (F_KERNEL32_GETDISKFREESPACE *) ( + GetProcAddress( + XpFuncs->Kernel32, + "GetDiskFreeSpaceA" + ) + ); + rc = GetLastError(); + if (!XpFuncs->GetDiskFreeSpace) { + M_ERR("GetDiskFreeSpace() not found in KERNEL32.DLL!"); + goto err; + } + + return ERROR_SUCCESS; + + err: + NtfsSectUnloadXpFuncs(XpFuncs); + return rc; + } + +VOID M_NTFSSECT_API NtfsSectUnloadXpFuncs(S_NTFSSECT_XPFUNCS * XpFuncs) { + if (!XpFuncs) + return; + + XpFuncs->GetDiskFreeSpace = NULL; + XpFuncs->GetVolumePathName = NULL; + if (XpFuncs->Kernel32) + FreeLibrary(XpFuncs->Kernel32); + XpFuncs->Kernel32 = NULL; + XpFuncs->Size = 0; + return; + } + diff --git a/win/ntfssect.h b/win/ntfssect.h new file mode 100644 index 00000000..246c26da --- /dev/null +++ b/win/ntfssect.h @@ -0,0 +1,152 @@ +/* -------------------------------------------------------------------------- * + * + * Copyright 2011 Shao Miller - All Rights Reserved + * + * 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, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ------------------------------------------------------------------------- */ +#ifndef M_NTFSSECT_H_ + +/**** + * ntfssect.h + * + * Fetch NTFS file cluster & sector information via Windows + * + * With special thanks to Mark Roddy for his article: + * http://www.wd-3.com/archive/luserland.htm + */ + +/*** Macros */ +#define M_NTFSSECT_H_ +#define M_NTFSSECT_API + +/*** Object types */ + +/* An "extent;" a contiguous range of file data */ +typedef struct S_NTFSSECT_EXTENT_ S_NTFSSECT_EXTENT; + +/* Volume info relevant to file cluster & sector info */ +typedef struct S_NTFSSECT_VOLINFO_ S_NTFSSECT_VOLINFO; + +/* Stores function pointers to some Windows functions */ +typedef struct S_NTFSSECT_XPFUNCS_ S_NTFSSECT_XPFUNCS; + +/*** Function types */ + +/* The function type for Kernel32.dll's GetDiskFreeSpace() */ +typedef BOOL WINAPI F_KERNEL32_GETDISKFREESPACE( + LPCTSTR, + LPDWORD, + LPDWORD, + LPDWORD, + LPDWORD + ); + +/* The function type for Kernel32.dll's GetVolumePathName() */ +typedef BOOL WINAPI F_KERNEL32_GETVOLUMEPATHNAME(LPCTSTR, LPCTSTR, DWORD); + +/*** Function declarations */ + +/** + * Fetch the extent containing a particular VCN + * + * @v File + * @v Vcn + * @v Extent + * @ret DWORD + */ +DWORD M_NTFSSECT_API NtfsSectGetFileVcnExtent( + HANDLE File, + LARGE_INTEGER * Vcn, + S_NTFSSECT_EXTENT * Extent + ); + +/** + * Populate a volume info object + * + * @v VolumeName + * @v VolumeInfo + * @ret DWORD + */ +DWORD M_NTFSSECT_API NtfsSectGetVolumeInfo( + CHAR * VolumeName, + S_NTFSSECT_VOLINFO * VolumeInfo + ); + +/** + * Populate a volume info object + * + * @v FileName + * @v VolumeInfo + * @ret DWORD + */ +DWORD M_NTFSSECT_API NtfsSectGetVolumeInfoFromFileName( + CHAR * FileName, + S_NTFSSECT_VOLINFO * VolumeInfo + ); + +/** + * Convert a volume LCN to an absolute disk LBA + * + * @v VolumeInfo + * @v Lcn + * @v Lba + * @ret DWORD + */ +DWORD M_NTFSSECT_API NtfsSectLcnToLba( + const S_NTFSSECT_VOLINFO * VolumeInfo, + const LARGE_INTEGER * Lcn, + LARGE_INTEGER * Lba + ); + +/** + * Load some helper XP functions + * + * @v XpFuncs + * @ret DWORD + */ +DWORD M_NTFSSECT_API NtfsSectLoadXpFuncs(S_NTFSSECT_XPFUNCS * XpFuncs); + +/** + * Unload some helper XP functions + * + * @v XpFuncs + * @ret DWORD + */ +VOID M_NTFSSECT_API NtfsSectUnloadXpFuncs(S_NTFSSECT_XPFUNCS * XpFuncs); + +/*** Object declarations */ + +/** + * The last error message set by one of our functions. + * Obviously not per-thread + */ +extern CHAR * NtfsSectLastErrorMessage; + +/*** Struct/union definitions */ +struct S_NTFSSECT_EXTENT_ { + LARGE_INTEGER FirstVcn; + LARGE_INTEGER NextVcn; + LARGE_INTEGER FirstLcn; + }; + +struct S_NTFSSECT_VOLINFO_ { + DWORD Size; + HANDLE Handle; + DWORD BytesPerSector; + DWORD SectorsPerCluster; + LARGE_INTEGER PartitionLba; + }; + +struct S_NTFSSECT_XPFUNCS_ { + DWORD Size; + HMODULE Kernel32; + F_KERNEL32_GETVOLUMEPATHNAME * GetVolumePathName; + F_KERNEL32_GETDISKFREESPACE * GetDiskFreeSpace; + }; + +#endif /* M_NTFSSECT_H_ */ diff --git a/win/ntfstest.c b/win/ntfstest.c new file mode 100644 index 00000000..1fc27182 --- /dev/null +++ b/win/ntfstest.c @@ -0,0 +1,163 @@ +/* -------------------------------------------------------------------------- * + * + * Copyright 2011 Shao Miller - All Rights Reserved + * + * 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, Inc., 53 Temple Place Ste 330, + * Boston MA 02111-1307, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ------------------------------------------------------------------------- */ + +/**** + * ntfstest.c + * + * (C) Shao Miller, 2011 + * + * Tests ntfssect.c functions + * + * With special thanks to Mark Roddy for his article: + * http://www.wd-3.com/archive/luserland.htm + */ +#include <windows.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "ntfssect.h" + +/*** Object types */ + +/*** Function declarations */ +static void show_usage(void); +static void show_last_err(void); +static void show_err(DWORD); + +/*** Struct/union definitions */ + +/*** Function definitions */ + +/** Program entry-point */ +int main(int argc, char ** argv) { + int rc; + DWORD err; + S_NTFSSECT_VOLINFO vol_info; + HANDLE file; + LARGE_INTEGER vcn, lba; + S_NTFSSECT_EXTENT extent; + LONGLONG len; + BOOL ok; + + if (argc != 2) { + rc = EXIT_FAILURE; + show_usage(); + goto err_args; + } + + /* Get volume info */ + err = NtfsSectGetVolumeInfoFromFileName(argv[1], &vol_info); + if (err != ERROR_SUCCESS) { + show_err(err); + goto err_vol_info; + } + printf( + "Volume has %d bytes per sector, %d sectors per cluster\n", + vol_info.BytesPerSector, + vol_info.SectorsPerCluster + ); + + /* Open the file for reading */ + file = CreateFile( + argv[1], + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + 0, + NULL + ); + if (file == INVALID_HANDLE_VALUE) { + rc = EXIT_FAILURE; + show_last_err(); + goto err_file; + } + + /* For each extent */ + for ( + vcn.QuadPart = 0; + NtfsSectGetFileVcnExtent(file, &vcn, &extent) == ERROR_SUCCESS; + vcn = extent.NextVcn + ) { + len = extent.NextVcn.QuadPart - extent.FirstVcn.QuadPart; + printf("Extent @ VCN #%lld,", extent.FirstVcn.QuadPart); + printf(" %lld clusters long:\n", len); + printf(" VCN #%lld -", extent.FirstVcn.QuadPart); + printf(" #%lld\n", extent.FirstVcn.QuadPart + len - 1); + printf(" LCN #%lld -", extent.FirstLcn.QuadPart); + printf(" #%lld\n", extent.FirstLcn.QuadPart + len - 1); + err = NtfsSectLcnToLba( + &vol_info, + &extent.FirstLcn, + &lba + ); + if (err == ERROR_SUCCESS) { + printf(" LBA #%lld -", lba.QuadPart); + printf( + " #%lld\n", + lba.QuadPart + len * vol_info.SectorsPerCluster + ); + } + continue; + } + + rc = EXIT_SUCCESS; + + CloseHandle(file); + err_file: + + CloseHandle(vol_info.Handle); + err_vol_info: + + err_args: + + return rc; + } + +/** Display usage */ +static void show_usage(void) { + static const char usage_text[] = "\ + File sector info . . . . . . . . . . . . . . . . . . . . Shao Miller, 2011\n\ +\n\ + Usage: NTFSTEST.EXE <filename>\n\ +\n\ + Attempts to dump cluster and sector info for <filename>.\n"; + + printf(usage_text); + return; + } + +static void show_last_err(void) { + show_err(GetLastError()); + return; + } + +/** Display an error */ +static void show_err(DWORD err_code) { + void * buf; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + err_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &buf, + 0, + NULL + ); + fprintf(stderr, "Error: %s\n", buf); + LocalFree(buf); + return; + } + diff --git a/win/ntfstest.rc b/win/ntfstest.rc new file mode 100644 index 00000000..462d21f1 --- /dev/null +++ b/win/ntfstest.rc @@ -0,0 +1,26 @@ + +1 VERSIONINFO +FILEVERSION 0,0,0,1 +PRODUCTVERSION 0,0,0,1 +FILEOS 0x40004 +FILETYPE 0x1 + { + BLOCK "StringFileInfo" + { + BLOCK "040904B0" + { + VALUE "CompanyName", "Shao Miller" + VALUE "FileDescription", "NTFS File Sector Info" + VALUE "FileVersion", "0.0.0.1 (Aug-12-2011)" + VALUE "InternalName", "NTFSTest" + VALUE "LegalCopyright", "© 2011 Shao Miller. All rights reserved." + VALUE "OriginalFilename", "NTFSTEST.EXE" + VALUE "ProductName", "Utilities" + VALUE "ProductVersion", "0.0.0.1" + } + } + } + +#if 0 +1 ICON "ntfstest.ico" +#endif diff --git a/win/syslinux.c b/win/syslinux.c index 0e833d8d..26e5a278 100644 --- a/win/syslinux.c +++ b/win/syslinux.c @@ -27,6 +27,8 @@ #include "setadv.h" #include "sysexits.h" #include "syslxopt.h" +#include "syslxfs.h" +#include "ntfssect.h" #ifdef __GNUC__ # define noreturn void __attribute__((noreturn)) @@ -254,6 +256,7 @@ int main(int argc, char *argv[]) int ldlinux_sectors; uint32_t ldlinux_cluster; int nsectors; + int fs_type; if (!checkver()) { fprintf(stderr, @@ -326,8 +329,10 @@ int main(int argc, char *argv[]) exit(1); } - /* Check to see that what we got was indeed an MS-DOS boot sector/superblock */ - if ((errmsg = syslinux_check_bootsect(sectbuf))) { + /* Check to see that what we got was indeed an FAT/NTFS + * boot sector/superblock + */ + if ((errmsg = syslinux_check_bootsect(sectbuf, &fs_type))) { fprintf(stderr, "%s\n", errmsg); exit(1); } @@ -379,6 +384,38 @@ int main(int argc, char *argv[]) ldlinux_sectors = (syslinux_ldlinux_len + 2 * ADV_SIZE + SECTOR_SIZE - 1) >> SECTOR_SHIFT; sectors = calloc(ldlinux_sectors, sizeof *sectors); + if (fs_type == NTFS) { + DWORD err; + S_NTFSSECT_VOLINFO vol_info; + LARGE_INTEGER vcn, lba, len; + S_NTFSSECT_EXTENT extent; + + err = NtfsSectGetVolumeInfo(drive_name + 4, &vol_info); + if (err != ERROR_SUCCESS) { + error("Could not fetch NTFS volume info"); + exit(1); + } + secp = sectors; + nsectors = 0; + for (vcn.QuadPart = 0; + NtfsSectGetFileVcnExtent(f_handle, &vcn, &extent) == ERROR_SUCCESS; + vcn = extent.NextVcn) { + err = NtfsSectLcnToLba(&vol_info, &extent.FirstLcn, &lba); + if (err != ERROR_SUCCESS) { + error("Could not translate LDLINUX.SYS LCN to disk LBA"); + exit(1); + } + lba.QuadPart -= vol_info.PartitionLba.QuadPart; + len.QuadPart = ((extent.NextVcn.QuadPart - + extent.FirstVcn.QuadPart) * + vol_info.SectorsPerCluster); + while (len.QuadPart-- && nsectors < ldlinux_sectors) { + *secp++ = lba.QuadPart++; + nsectors++; + } + } + goto map_done; + } fs = libfat_open(libfat_readfile, (intptr_t) d_handle); ldlinux_cluster = libfat_searchdir(fs, 0, "LDLINUX SYS", NULL); secp = sectors; @@ -390,6 +427,7 @@ int main(int argc, char *argv[]) s = libfat_nextsector(fs, s); } libfat_close(fs); +map_done: /* * Patch ldlinux.sys and the boot sector @@ -472,7 +510,7 @@ int main(int argc, char *argv[]) } /* Make the syslinux boot sector */ - syslinux_make_bootsect(sectbuf); + syslinux_make_bootsect(sectbuf, fs_type); /* Write the syslinux boot sector into the boot sector */ if (opt.bootsecfile) { diff --git a/win32/Makefile b/win32/Makefile index d4133ff8..f960998a 100644 --- a/win32/Makefile +++ b/win32/Makefile @@ -47,9 +47,9 @@ WINCC_IS_GOOD := $(shell $(WINCC) $(WINCFLAGS) $(WINLDFLAGS) \ .SUFFIXES: .c .obj .lib .exe .i .s .S -SRCS = ../win/syslinux.c +SRCS = ../win/syslinux.c ../win/ntfssect.c OBJS = $(patsubst %.c,%.obj,$(notdir $(SRCS))) -LIBSRC = ../libinstaller/fat.c \ +LIBSRC = ../libinstaller/fs.c \ ../libinstaller/syslxmod.c \ ../libinstaller/syslxopt.c \ ../libinstaller/setadv.c \ diff --git a/win32/ntfstest/Makefile b/win32/ntfstest/Makefile new file mode 100644 index 00000000..00e89cf9 --- /dev/null +++ b/win32/ntfstest/Makefile @@ -0,0 +1,87 @@ +## ----------------------------------------------------------------------- +## +## Copyright 1998-2008 H. Peter Anvin - All Rights Reserved +## Copyright 2010 Intel Corporation; author: H. Peter Anvin +## +## 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, Inc., 53 Temple Place Ste 330, +## Boston MA 02111-1307, USA; either version 2 of the License, or +## (at your option) any later version; incorporated herein by reference. +## +## ----------------------------------------------------------------------- + +# +# Makefile for Win32 NTFS file cluster test +# +# This is separated out mostly so we can have a different set of Makefile +# variables. +# + +OSTYPE = $(shell uname -msr) +ifeq ($(findstring CYGWIN,$(OSTYPE)),CYGWIN) +## Compiling on Cygwin +WINPREFIX := +WINCFLAGS := -mno-cygwin $(GCCWARN) -Os -fomit-frame-pointer -D_FILE_OFFSET_BITS=64 +WINLDFLAGS := -mno-cygwin -Os -s +else +## Compiling on some variant of MinGW +ifeq ($(findstring MINGW32,$(OSTYPE)),MINGW32) +WINPREFIX := +else +WINPREFIX := $(shell ../find-mingw32.sh gcc) +endif +WINCFLAGS := $(GCCWARN) -Wno-sign-compare -Os -fomit-frame-pointer \ + -D_FILE_OFFSET_BITS=64 +WINLDFLAGS := -Os -s +endif +WINCFLAGS += -I. -I../../win + +WINCC := $(WINPREFIX)gcc +WINAR := $(WINPREFIX)ar +WINRANLIB := $(WINPREFIX)ranlib +WINDRES := $(WINPREFIX)windres + +WINCC_IS_GOOD := $(shell $(WINCC) $(WINCFLAGS) $(WINLDFLAGS) \ + -o hello.exe ../../win/hello.c >/dev/null 2>&1 ; echo $$?) + +.SUFFIXES: .c .obj .lib .exe .i .s .S .rc .res + +SRCS = ../../win/ntfstest.c ../../win/ntfssect.c +RCS = ../../win/ntfstest.rc +OBJS = $(patsubst %.c,%.obj,$(notdir $(SRCS))) +RESS = $(patsubst %.rc,%.res,$(notdir $(RCS))) + +VPATH = .:../../win + +TARGETS = ntfstest.exe + +ifeq ($(WINCC_IS_GOOD),0) +all: $(TARGETS) +else +all: + rm -f $(TARGETS) +endif + +tidy dist: + -rm -f *.o *.obj *.lib *.i *.s *.a .*.d *.tmp *_bin.c hello.exe + +clean: tidy + +spotless: clean + -rm -f *~ $(TARGETS) + +ntfstest.exe: $(OBJS) $(RESS) + $(WINCC) $(WINLDFLAGS) -o $@ $^ + + +%.obj: %.c + $(WINCC) $(UMAKEDEPS) $(WINCFLAGS) -c -o $@ $< +%.i: %.c + $(WINCC) $(UMAKEDEPS) $(WINCFLAGS) -E -o $@ $< +%.s: %.c + $(WINCC) $(UMAKEDEPS) $(WINCFLAGS) -S -o $@ $< +%.res: %.rc + $(WINDRES) -O COFF $< $@ + +-include .*.d *.tmp diff --git a/win64/Makefile b/win64/Makefile index 0bc746d5..fe60793c 100644 --- a/win64/Makefile +++ b/win64/Makefile @@ -37,9 +37,9 @@ WINCC_IS_GOOD := $(shell $(WINCC) $(WINCFLAGS) $(WINLDFLAGS) \ .SUFFIXES: .c .obj .lib .exe .i .s .S -SRCS = ../win/syslinux.c +SRCS = ../win/syslinux.c ../win/ntfssect.c OBJS = $(patsubst %.c,%.obj,$(notdir $(SRCS))) -LIBSRC = ../libinstaller/fat.c \ +LIBSRC = ../libinstaller/fs.c \ ../libinstaller/syslxmod.c \ ../libinstaller/syslxopt.c \ ../libinstaller/setadv.c \ diff --git a/win64/ntfstest/Makefile b/win64/ntfstest/Makefile new file mode 100644 index 00000000..5b975be5 --- /dev/null +++ b/win64/ntfstest/Makefile @@ -0,0 +1,76 @@ +## ----------------------------------------------------------------------- +## +## Copyright 1998-2008 H. Peter Anvin - All Rights Reserved +## Copyright 2010 Intel Corporation; author: H. Peter Anvin +## +## 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, Inc., 53 Temple Place Ste 330, +## Boston MA 02111-1307, USA; either version 2 of the License, or +## (at your option) any later version; incorporated herein by reference. +## +## ----------------------------------------------------------------------- + +# +# Makefile for Win64 NTFS file cluster test +# +# This is separated out mostly so we can have a different set of Makefile +# variables. +# + +OSTYPE = $(shell uname -msr) +# Don't know how to do a native compile here... +WINPREFIX := $(shell ../find-mingw64.sh gcc) +WINCFLAGS := $(GCCWARN) -Wno-sign-compare -Os -fomit-frame-pointer \ + -D_FILE_OFFSET_BITS=64 +WINLDFLAGS := -Os -s +WINCFLAGS += -I. -I../../win + +WINCC := $(WINPREFIX)gcc +WINAR := $(WINPREFIX)ar +WINRANLIB := $(WINPREFIX)ranlib +WINDRES := $(WINPREFIX)windres + +WINCC_IS_GOOD := $(shell $(WINCC) $(WINCFLAGS) $(WINLDFLAGS) \ + -o hello.exe ../../win/hello.c >/dev/null 2>&1 ; echo $$?) + +.SUFFIXES: .c .obj .lib .exe .i .s .S .rc .res + +SRCS = ../../win/ntfstest.c ../../win/ntfssect.c +RCS = ../../win/ntfstest.rc +OBJS = $(patsubst %.c,%.obj,$(notdir $(SRCS))) +RESS = $(patsubst %.rc,%.res,$(notdir $(RCS))) + +VPATH = .:../../win + +TARGETS = ntfstest64.exe + +ifeq ($(WINCC_IS_GOOD),0) +all: $(TARGETS) +else +all: + rm -f $(TARGETS) +endif + +tidy dist: + -rm -f *.o *.obj *.lib *.i *.s *.a .*.d *.tmp *_bin.c hello.exe + +clean: tidy + +spotless: clean + -rm -f *~ $(TARGETS) + +ntfstest64.exe: $(OBJS) $(RESS) + $(WINCC) $(WINLDFLAGS) -o $@ $^ + + +%.obj: %.c + $(WINCC) $(UMAKEDEPS) $(WINCFLAGS) -c -o $@ $< +%.i: %.c + $(WINCC) $(UMAKEDEPS) $(WINCFLAGS) -E -o $@ $< +%.s: %.c + $(WINCC) $(UMAKEDEPS) $(WINCFLAGS) -S -o $@ $< +%.res: %.rc + $(WINDRES) -O COFF $< $@ + +-include .*.d *.tmp |