diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/Makefile | 5 | ||||
-rw-r--r-- | core/bootsect.inc | 16 | ||||
-rw-r--r-- | core/comboot.inc | 15 | ||||
-rw-r--r-- | core/diskboot.inc | 2 | ||||
-rw-r--r-- | core/diskfs.inc | 4 | ||||
-rw-r--r-- | core/diskstart.inc | 40 | ||||
-rw-r--r-- | core/fs/btrfs/btrfs.c | 5 | ||||
-rw-r--r-- | core/fs/cache.c | 4 | ||||
-rw-r--r-- | core/fs/chdir.c | 110 | ||||
-rw-r--r-- | core/fs/ext2/ext2.c | 3 | ||||
-rw-r--r-- | core/fs/fat/fat.c | 35 | ||||
-rw-r--r-- | core/fs/fs.c | 13 | ||||
-rw-r--r-- | core/fs/lib/searchconfig.c | 3 | ||||
-rw-r--r-- | core/fs/ntfs/ntfs.c | 1388 | ||||
-rw-r--r-- | core/fs/ntfs/ntfs.h | 485 | ||||
-rw-r--r-- | core/fs/ntfs/runlist.h | 83 | ||||
-rw-r--r-- | core/head.inc | 4 | ||||
-rw-r--r-- | core/include/fs.h | 1 | ||||
-rw-r--r-- | core/init.inc | 24 | ||||
-rw-r--r-- | core/isolinux.asm | 173 | ||||
-rw-r--r-- | core/ldlinux.asm | 2 | ||||
-rw-r--r-- | core/syslinux.ld | 9 | ||||
-rw-r--r-- | core/ui.inc | 7 |
23 files changed, 2144 insertions, 287 deletions
diff --git a/core/Makefile b/core/Makefile index 33ad7e95..112fe3a8 100644 --- a/core/Makefile +++ b/core/Makefile @@ -20,7 +20,8 @@ MAKEFLAGS += -r MAKE += -r topdir = .. -include $(topdir)/MCONFIG.embedded +MAKEDIR = $(topdir)/mk +include $(MAKEDIR)/embedded.mk -include $(topdir)/version.mk OPTFLAGS = @@ -109,7 +110,7 @@ ldlinux.bss: ldlinux.bin dd if=$< of=$@ bs=512 count=1 ldlinux.sys: ldlinux.bin - dd if=$< of=$@ bs=512 skip=1 + dd if=$< of=$@ bs=512 skip=2 codepage.cp: ../codepage/$(CODEPAGE).cp cp -f $< $@ diff --git a/core/bootsect.inc b/core/bootsect.inc index 6c204096..9e47e1a5 100644 --- a/core/bootsect.inc +++ b/core/bootsect.inc @@ -1,7 +1,7 @@ ;; ----------------------------------------------------------------------- ;; ;; Copyright 1994-2009 H. Peter Anvin - All Rights Reserved -;; Copyright 2009 Intel Corporation; author: H. Peter Anvin +;; Copyright 2009-2012 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 @@ -38,6 +38,7 @@ SuperSize equ $+1 load_bootsec: mov edi,free_high_memory mov [trackbuf+4],edi ; Copy from this address + mov eax,0xA0000 ; Maximum load xor dx,dx ; No padding mov bx,abort_check ; Don't print dots, but allow abort call load_high @@ -45,6 +46,9 @@ load_bootsec: sub edi,free_high_memory mov [trackbuf+8],edi ; Save length + cmp edi,0xA0000-7C00h + ja bs_too_big + mov eax,7C00h ; Entry point mov [trackbuf],eax ; Copy to this address @@ -237,3 +241,13 @@ replace_stub: .csip equ $-4 section .text16 +bs_too_big: + call close + mov si,err_bs_too_big + jmp abort_load + + section .data16 +err_bs_too_big db "Too large for a bootstrap (need LINUX instead of KERNEL?)" + db CR, LF, 0 + + section .text16 diff --git a/core/comboot.inc b/core/comboot.inc index d6f670c9..61c8a3b7 100644 --- a/core/comboot.inc +++ b/core/comboot.inc @@ -651,21 +651,8 @@ comapi_cleanup: ret ; -; INT 22h AX=000Dh Clean up then replace bootstrap +; INT 22h AX=000Dh Obsolete ; -comapi_chainboot: - call comapi_cleanup - mov eax,P_EDI - mov [trackbuf+4],eax ; Copy from - mov eax,P_ECX - mov [trackbuf+8],eax ; Total bytes - mov eax,7C00h - mov [trackbuf],eax ; Copy to - push eax ; Entry point on stack - mov esi,P_ESI - mov edx,P_EBX - mov bx,P_DS - jmp replace_bootstrap_one ; ; INT 22h AX=000Eh Get configuration file name diff --git a/core/diskboot.inc b/core/diskboot.inc index 68672e4a..141986e8 100644 --- a/core/diskboot.inc +++ b/core/diskboot.inc @@ -278,7 +278,7 @@ Sect1Ptr1 equ $-4 cmp dword [ldlinux_magic+4],LDLINUX_MAGIC^HEXDATE jne kaboom - ; Go for it! This also normalizes CS:IP. + ; Go for it! jmp ldlinux_ent ; diff --git a/core/diskfs.inc b/core/diskfs.inc index fc80a153..41391e7f 100644 --- a/core/diskfs.inc +++ b/core/diskfs.inc @@ -30,8 +30,8 @@ LDLINUX_MAGIC equ 0x3eb202fe ; A random number to identify ourselves with ; This indicates the general format of the last few bytes in the boot sector BS_MAGIC_VER equ 0x1b << 9 -SECTOR_SHIFT equ 9 -SECTOR_SIZE equ (1 << SECTOR_SHIFT) +MIN_SECTOR_SHIFT equ 9 +MIN_SECTOR_SIZE equ (1 << MIN_SECTOR_SHIFT) ; ; The following structure is used for "virtual kernels"; i.e. LILO-style diff --git a/core/diskstart.inc b/core/diskstart.inc index 02505a6b..b2ef2b63 100644 --- a/core/diskstart.inc +++ b/core/diskstart.inc @@ -23,6 +23,14 @@ Sect1Ptr1_VAL equ 0xfeedface %include "diskboot.inc" ; =========================================================================== +; Padding after the (minimum) 512-byte boot sector so that the rest of +; the file has aligned sectors, even if they are larger than 512 bytes. +; =========================================================================== + + section .init +align_pad zb 512 + +; =========================================================================== ; Start of LDLINUX.SYS ; =========================================================================== @@ -110,13 +118,15 @@ ldlinux_ent: ; Checksum data thus far ; mov si,ldlinux_sys - mov cx,SECTOR_SIZE >> 2 + mov cx,[bsBytesPerSec] + shr cx,2 mov edx,-LDLINUX_MAGIC .checksum: lodsd add edx,eax loop .checksum mov [CheckSum],edx ; Save intermediate result + movzx ebx,si ; Start of the next sector ; ; Tell the user if we're using EBIOS or CBIOS @@ -132,6 +142,7 @@ print_bios: call writestr_early section .earlybss + alignb 2 %define HAVE_BIOSNAME 1 BIOSName resw 1 @@ -140,8 +151,9 @@ BIOSName resw 1 ; Now we read the rest of LDLINUX.SYS. ; load_rest: + push bx ; LSW of load address + lea esi,[SectorPtrs] - mov ebx,TEXT_START+2*SECTOR_SIZE ; Where we start loading mov cx,[DataSectors] dec cx ; Minus this sector @@ -157,7 +169,7 @@ load_rest: xor bx,bx call getlinsec pop ebx - shl ebp,SECTOR_SHIFT + imul bp,[bsBytesPerSec] ; Will be < 64K add ebx,ebp add si,10 jmp .get_chunk @@ -170,9 +182,11 @@ load_rest: ; by the time we get to the end it should all cancel out. ; verify_checksum: - mov si,ldlinux_sys + SECTOR_SIZE - mov ecx,[LDLDwords] - sub ecx,SECTOR_SIZE >> 2 + pop si ; LSW of load address + movzx eax,word [bsBytesPerSec] + shr ax,2 + mov ecx,[LDLDwords] ; Total dwords + sub ecx,eax ; ... minus one sector mov eax,[CheckSum] .checksum: add eax,[si] @@ -260,7 +274,7 @@ getlinsec_ebios: add eax,edi ; Advance sector pointer adc edx,0 sub bp,di ; Sectors left - shl di,SECTOR_SHIFT ; 512-byte sectors + imul di,[bsBytesPerSec] add bx,di ; Advance buffer pointer and bp,bp jnz .loop @@ -350,7 +364,7 @@ getlinsec_cbios: jc .error .resume: movzx ecx,al ; ECX <- sectors transferred - shl ax,SECTOR_SHIFT ; Convert sectors in AL to bytes in AX + imul ax,[bsBytesPerSec] ; Convert sectors in AL to bytes in AX pop bx add bx,ax pop bp @@ -418,10 +432,10 @@ safedumpregs: rl_checkpt equ $ ; Must be <= 8000h -rl_checkpt_off equ ($-$$) +rl_checkpt_off equ $-ldlinux_sys %ifndef DEPEND - %if rl_checkpt_off > 3F6h ; Need one extent - %assign rl_checkpt_overflow rl_checkpt_off - 3F6h + %if rl_checkpt_off > 512-10 ; Need minimum one extent + %assign rl_checkpt_overflow rl_checkpt_off - (512-10) %error Sector 1 overflow by rl_checkpt_overflow bytes %endif %endif @@ -434,8 +448,8 @@ rl_checkpt_off equ ($-$$) ; alignz 2 MaxInitDataSize equ 96 << 10 -MaxLMA equ TEXT_START+SECTOR_SIZE+MaxInitDataSize -SectorPtrs zb 10*(MaxInitDataSize >> SECTOR_SHIFT) +MaxLMA equ LDLINUX_SYS+MaxInitDataSize +SectorPtrs zb 10*(MaxInitDataSize >> MIN_SECTOR_SHIFT) SectorPtrsEnd equ $ ; ---------------------------------------------------------------------------- diff --git a/core/fs/btrfs/btrfs.c b/core/fs/btrfs/btrfs.c index b6a14e3b..aeb7614a 100644 --- a/core/fs/btrfs/btrfs.c +++ b/core/fs/btrfs/btrfs.c @@ -602,12 +602,15 @@ static void btrfs_get_fs_tree(struct fs_info *fs) do { do { struct btrfs_root_ref *ref; + int pathlen; if (btrfs_comp_keys_type(&search_key, &path.item.key)) break; ref = (struct btrfs_root_ref *)path.data; - if (!strcmp((char*)(ref + 1), SubvolName)) { + pathlen = path.item.size - sizeof(struct btrfs_root_ref); + + if (!strncmp((char*)(ref + 1), SubvolName, pathlen)) { subvol_ok = true; break; } diff --git a/core/fs/cache.c b/core/fs/cache.c index 0d7891be..3b21fc26 100644 --- a/core/fs/cache.c +++ b/core/fs/cache.c @@ -37,10 +37,10 @@ void cache_init(struct device *dev, int block_size_shift) dev->cache_head = head = (struct cache *) (data + (dev->cache_entries << block_size_shift)); - cache = dev->cache_head + 1; /* First cache descriptor */ + cache = head + 1; /* First cache descriptor */ head->prev = &cache[dev->cache_entries-1]; - head->next->prev = dev->cache_head; + head->prev->next = head; head->block = -1; head->data = NULL; diff --git a/core/fs/chdir.c b/core/fs/chdir.c index 9e8dfd2e..903cabce 100644 --- a/core/fs/chdir.c +++ b/core/fs/chdir.c @@ -1,6 +1,7 @@ #include <stdio.h> #include <stdbool.h> #include <string.h> +#include <dprintf.h> #include "fs.h" #include "cache.h" @@ -16,57 +17,70 @@ void pm_realpath(com32sys_t *regs) realpath(dst, src, FILENAME_MAX); } -#define EMIT(x) \ -do { \ - if (++n < bufsize) \ - *q++ = (x); \ -} while (0) - -static size_t join_paths(char *dst, size_t bufsize, - const char *s1, const char *s2) +static size_t copy_string(char *buf, size_t ix, size_t bufsize, const char *src) { - const char *list[2]; - int i; char c; - const char *p; - char *q = dst; - size_t n = 0; - bool slash = false; - - list[0] = s1; - list[1] = s2; - - for (i = 0; i < 2; i++) { - p = list[i]; - - while ((c = *p++)) { - if (c == '/') { - if (!slash) - EMIT(c); - slash = true; - } else { - EMIT(c); - slash = false; - } - } + + while ((c = *src++)) { + if (ix+1 < bufsize) + buf[ix] = c; + ix++; } - if (bufsize) - *q = '\0'; + if (ix < bufsize) + buf[ix] = '\0'; - return n; + return ix; +} + +static size_t generic_inode_to_path(struct inode *inode, char *dst, size_t bufsize) +{ + size_t s = 0; + + dprintf("inode %p name %s\n", inode, inode->name); + + if (inode->parent) { + if (!inode->name) /* Only the root should have no name */ + return -1; + + s = generic_inode_to_path(inode->parent, dst, bufsize); + if (s == (size_t)-1) + return s; /* Error! */ + + s = copy_string(dst, s, bufsize, "/"); + s = copy_string(dst, s, bufsize, inode->name); + } + + return s; } size_t realpath(char *dst, const char *src, size_t bufsize) { + int rv; + struct file *file; + size_t s; + + dprintf("realpath: input: %s\n", src); + if (this_fs->fs_ops->realpath) { - return this_fs->fs_ops->realpath(this_fs, dst, src, bufsize); + s = this_fs->fs_ops->realpath(this_fs, dst, src, bufsize); } else { - /* Filesystems with "common" pathname resolution */ - return join_paths(dst, bufsize, - src[0] == '/' ? "" : this_fs->cwd_name, - src); + rv = searchdir(src); + if (rv < 0) { + dprintf("realpath: searchpath failure\n"); + return -1; + } + + file = handle_to_file(rv); + s = generic_inode_to_path(file->inode, dst, bufsize); + if (s == 0) + s = copy_string(dst, 0, bufsize, "/"); + + _close_file(file); } + + dprintf("realpath: output: %s\n", dst); + return s; } int chdir(const char *src) @@ -74,6 +88,10 @@ int chdir(const char *src) int rv; struct file *file; char cwd_buf[CURRENTDIR_MAX]; + size_t s; + + dprintf("chdir: from %s (inode %p) add %s\n", + this_fs->cwd_name, this_fs->cwd, src); if (this_fs->fs_ops->chdir) return this_fs->fs_ops->chdir(this_fs, src); @@ -94,10 +112,20 @@ int chdir(const char *src) _close_file(file); /* Save the current working directory */ - realpath(cwd_buf, src, CURRENTDIR_MAX); + s = generic_inode_to_path(this_fs->cwd, cwd_buf, CURRENTDIR_MAX-1); /* Make sure the cwd_name ends in a slash, it's supposed to be a prefix */ - join_paths(this_fs->cwd_name, CURRENTDIR_MAX, cwd_buf, "/"); + if (s < 1 || cwd_buf[s-1] != '/') + cwd_buf[s++] = '/'; + + if (s >= CURRENTDIR_MAX) + s = CURRENTDIR_MAX - 1; + + cwd_buf[s++] = '\0'; + memcpy(this_fs->cwd_name, cwd_buf, s); + + dprintf("chdir: final %s (inode %p)\n", + this_fs->cwd_name, this_fs->cwd); return 0; } diff --git a/core/fs/ext2/ext2.c b/core/fs/ext2/ext2.c index 716670c6..7988faaf 100644 --- a/core/fs/ext2/ext2.c +++ b/core/fs/ext2/ext2.c @@ -164,6 +164,9 @@ static struct inode *ext2_iget_by_inr(struct fs_info *fs, uint32_t inr) struct inode *inode; e_inode = ext2_get_inode(fs, inr); + if (!e_inode) + return NULL; + if (!(inode = alloc_inode(fs, inr, sizeof(struct ext2_pvt_inode)))) return NULL; fill_inode(inode, e_inode); diff --git a/core/fs/fat/fat.c b/core/fs/fat/fat.c index d3079269..b08923cf 100644 --- a/core/fs/fat/fat.c +++ b/core/fs/fat/fat.c @@ -220,24 +220,30 @@ static sector_t next_sector(struct file *file) return sector; } -/* - * Mangle a filename pointed to by src into a buffer pointed to by dst; - * ends on encountering any whitespace. +/** + * mangle_name: + * + * Mangle a filename pointed to by src into a buffer pointed + * to by dst; ends on encountering any whitespace. + * dst is preserved. + * + * This verifies that a filename is < FILENAME_MAX characters, + * doesn't contain whitespace, zero-pads the output buffer, + * and removes redundant slashes. + * + * Unlike the generic version, this also converts backslashes to + * forward slashes. * */ static void vfat_mangle_name(char *dst, const char *src) { char *p = dst; + int i = FILENAME_MAX-1; char c; - int i = FILENAME_MAX -1; - /* - * Copy the filename, converting backslash to slash and - * collapsing duplicate separators. - */ while (not_whitespace(c = *src)) { - if (c == '\\') - c = '/'; + if (c == '\\') + c = '/'; if (c == '/') { if (src[1] == '/' || src[1] == '\\') { @@ -250,16 +256,13 @@ static void vfat_mangle_name(char *dst, const char *src) *dst++ = *src++; } - /* Strip terminal slashes or whitespace */ while (1) { if (dst == p) break; - if (*(dst-1) == '/' && dst-1 == p) /* it's the '/' case */ - break; - if (dst-2 == p && *(dst-2) == '.' && *(dst-1) == '.' ) /* the '..' case */ - break; - if ((*(dst-1) != '/') && (*(dst-1) != '.')) + if (dst[-1] != '/') break; + if ((dst[-1] == '/') && ((dst - 1) == p)) + break; dst--; i++; diff --git a/core/fs/fs.c b/core/fs/fs.c index ad2fb370..21f5dba0 100644 --- a/core/fs/fs.c +++ b/core/fs/fs.c @@ -37,6 +37,8 @@ void put_inode(struct inode *inode) while (inode && --inode->refcnt == 0) { struct inode *dead = inode; inode = inode->parent; + if (dead->name) + free((char *)dead->name); free(dead); } } @@ -207,6 +209,9 @@ int searchdir(const char *name) char *part, *p, echar; int symlink_count = MAX_SYMLINK_CNT; + dprintf("searchdir: %s root: %p cwd: %p\n", + name, this_fs->root, this_fs->cwd); + if (!(file = alloc_file())) goto err_no_close; file->fs = this_fs; @@ -305,6 +310,9 @@ int searchdir(const char *name) goto got_link; } + inode->name = strdup(part); + dprintf("path component: %s\n", inode->name); + inode->parent = parent; parent = NULL; @@ -349,6 +357,8 @@ int open_file(const char *name, struct com32_filedata *filedata) struct file *file; char mangled_name[FILENAME_MAX]; + dprintf("open_file %s\n", name); + mangle_name(mangled_name, name); rv = searchdir(mangled_name); @@ -376,6 +386,8 @@ void pm_open_file(com32sys_t *regs) const char *name = MK_PTR(regs->es, regs->esi.w[0]); char mangled_name[FILENAME_MAX]; + dprintf("pm_open_file %s\n", name); + mangle_name(mangled_name, name); rv = searchdir(mangled_name); if (rv < 0) { @@ -470,6 +482,7 @@ void fs_init(com32sys_t *regs) if (fs.fs_ops->iget_root) { fs.root = fs.fs_ops->iget_root(&fs); fs.cwd = get_inode(fs.root); + dprintf("init: root inode %p, cwd inode %p\n", fs.root, fs.cwd); } SectorShift = fs.sector_shift; diff --git a/core/fs/lib/searchconfig.c b/core/fs/lib/searchconfig.c index 24bfde31..f18836a8 100644 --- a/core/fs/lib/searchconfig.c +++ b/core/fs/lib/searchconfig.c @@ -25,7 +25,8 @@ int search_config(const char *search_directories[], const char *filenames[]) "%s%s%s", sd, (*sd && sd[strlen(sd)-1] == '/') ? "" : "/", sf); - realpath(ConfigName, confignamebuf, FILENAME_MAX); + if (realpath(ConfigName, confignamebuf, FILENAME_MAX) == (size_t)-1) + continue; regs.edi.w[0] = OFFS_WRT(ConfigName, 0); dprintf("Config search: %s\n", ConfigName); call16(core_open, ®s, ®s); diff --git a/core/fs/ntfs/ntfs.c b/core/fs/ntfs/ntfs.c new file mode 100644 index 00000000..500d0fd3 --- /dev/null +++ b/core/fs/ntfs/ntfs.c @@ -0,0 +1,1388 @@ +/* + * Copyright (C) 2011-2012 Paulo Alcantara <pcacjr@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* Note: No support for compressed files */ + +#include <dprintf.h> +#include <stdio.h> +#include <string.h> +#include <sys/dirent.h> +#include <cache.h> +#include <core.h> +#include <disk.h> +#include <fs.h> +#include <ilog2.h> +#include <klibc/compiler.h> +#include <ctype.h> + +#include "codepage.h" +#include "ntfs.h" +#include "runlist.h" + +static struct ntfs_readdir_state *readdir_state; + +/*** Function declarations */ +static f_mft_record_lookup ntfs_mft_record_lookup_3_0; +static f_mft_record_lookup ntfs_mft_record_lookup_3_1; + +/*** Function definitions */ + +/* Check if there are specific zero fields in an NTFS boot sector */ +static inline int ntfs_check_zero_fields(const struct ntfs_bpb *sb) +{ + return !sb->res_sectors && (!sb->zero_0[0] && !sb->zero_0[1] && + !sb->zero_0[2]) && !sb->zero_1 && !sb->zero_2 && + !sb->zero_3; +} + +static inline int ntfs_check_sb_fields(const struct ntfs_bpb *sb) +{ + return ntfs_check_zero_fields(sb) && + (!memcmp(sb->oem_name, "NTFS ", 8) || + !memcmp(sb->oem_name, "MSWIN4.0", 8) || + !memcmp(sb->oem_name, "MSWIN4.1", 8)); +} + +static inline struct inode *new_ntfs_inode(struct fs_info *fs) +{ + struct inode *inode; + + inode = alloc_inode(fs, 0, sizeof(struct ntfs_inode)); + if (!inode) + malloc_error("inode structure"); + + return inode; +} + +static void ntfs_fixups_writeback(struct fs_info *fs, struct ntfs_record *nrec) +{ + uint16_t *usa; + uint16_t usa_no; + uint16_t usa_count; + uint16_t *blk; + + dprintf("in %s()\n", __func__); + + if (nrec->magic != NTFS_MAGIC_FILE && nrec->magic != NTFS_MAGIC_INDX) + return; + + /* get the Update Sequence Array offset */ + usa = (uint16_t *)((uint8_t *)nrec + nrec->usa_ofs); + /* get the Update Sequence Array Number and skip it */ + usa_no = *usa++; + /* get the Update Sequene Array count */ + usa_count = nrec->usa_count - 1; /* exclude the USA number */ + /* make it to point to the last two bytes of the RECORD's first sector */ + blk = (uint16_t *)((uint8_t *)nrec + SECTOR_SIZE(fs) - 2); + + while (usa_count--) { + if (*blk != usa_no) + break; + + *blk = *usa++; + blk = (uint16_t *)((uint8_t *)blk + SECTOR_SIZE(fs)); + } +} + +/* read content from cache */ +static int ntfs_read(struct fs_info *fs, void *buf, size_t len, uint64_t count, + block_t *blk, uint64_t *blk_offset, + uint64_t *blk_next_offset, uint64_t *lcn) +{ + uint8_t *data; + uint64_t offset = *blk_offset; + const uint32_t clust_byte_shift = NTFS_SB(fs)->clust_byte_shift; + const uint64_t blk_size = UINT64_C(1) << BLOCK_SHIFT(fs); + uint64_t bytes; + uint64_t lbytes; + uint64_t loffset; + uint64_t k; + + dprintf("in %s()\n", __func__); + + if (count > len) + goto out; + + data = (uint8_t *)get_cache(fs->fs_dev, *blk); + if (!data) + goto out; + + if (!offset) + offset = (*lcn << clust_byte_shift) % blk_size; + + dprintf("LCN: 0x%X\n", *lcn); + dprintf("offset: 0x%X\n", offset); + + bytes = count; /* bytes to copy */ + lbytes = blk_size - offset; /* bytes left to copy */ + if (lbytes >= bytes) { + /* so there's room enough, then copy the whole content */ + memcpy(buf, data + offset, bytes); + loffset = offset; + offset += count; + } else { + dprintf("bytes: %u\n", bytes); + dprintf("bytes left: %u\n", lbytes); + /* otherwise, let's copy it partially... */ + k = 0; + while (bytes) { + memcpy(buf + k, data + offset, lbytes); + bytes -= lbytes; + loffset = offset; + offset += lbytes; + k += lbytes; + if (offset >= blk_size) { + /* then fetch a new FS block */ + data = (uint8_t *)get_cache(fs->fs_dev, ++*blk); + if (!data) + goto out; + + lbytes = bytes; + loffset = offset; + offset = 0; + } + } + } + + if (loffset >= blk_size) + loffset = 0; /* it must be aligned on a block boundary */ + + *blk_offset = loffset; + + if (blk_next_offset) + *blk_next_offset = offset; + + *lcn += blk_size / count; /* update LCN */ + + return 0; + +out: + return -1; +} + +static struct ntfs_mft_record *ntfs_mft_record_lookup_3_0(struct fs_info *fs, + uint32_t file, block_t *blk) +{ + const uint64_t mft_record_size = NTFS_SB(fs)->mft_record_size; + uint8_t *buf; + const block_t mft_blk = NTFS_SB(fs)->mft_blk; + block_t cur_blk; + block_t right_blk; + uint64_t offset; + uint64_t next_offset; + const uint32_t mft_record_shift = ilog2(mft_record_size); + const uint32_t clust_byte_shift = NTFS_SB(fs)->clust_byte_shift; + uint64_t lcn; + int err; + struct ntfs_mft_record *mrec; + + dprintf("in %s()\n", __func__); + + buf = (uint8_t *)malloc(mft_record_size); + if (!buf) + malloc_error("uint8_t *"); + + /* determine MFT record's LCN and block number */ + lcn = NTFS_SB(fs)->mft_lcn + (file << mft_record_shift >> clust_byte_shift); + cur_blk = (lcn << clust_byte_shift >> BLOCK_SHIFT(fs)) - mft_blk; + offset = (file << mft_record_shift) % BLOCK_SIZE(fs); + for (;;) { + right_blk = cur_blk + mft_blk; + err = ntfs_read(fs, buf, mft_record_size, mft_record_size, &right_blk, + &offset, &next_offset, &lcn); + if (err) { + printf("Error while reading from cache.\n"); + break; + } + + ntfs_fixups_writeback(fs, (struct ntfs_record *)buf); + + mrec = (struct ntfs_mft_record *)buf; + /* check if it has a valid magic number */ + if (mrec->magic == NTFS_MAGIC_FILE) { + if (blk) + *blk = cur_blk; /* update record starting block */ + + return mrec; /* found MFT record */ + } + + if (next_offset >= BLOCK_SIZE(fs)) { + /* try the next FS block */ + offset = 0; + cur_blk = right_blk - mft_blk + 1; + } else { + /* there's still content to fetch in the current block */ + cur_blk = right_blk - mft_blk; + offset = next_offset; /* update FS block offset */ + } + } + + free(buf); + + return NULL; +} + +static struct ntfs_mft_record *ntfs_mft_record_lookup_3_1(struct fs_info *fs, + uint32_t file, block_t *blk) +{ + const uint64_t mft_record_size = NTFS_SB(fs)->mft_record_size; + uint8_t *buf; + const block_t mft_blk = NTFS_SB(fs)->mft_blk; + block_t cur_blk; + block_t right_blk; + uint64_t offset; + uint64_t next_offset; + const uint32_t mft_record_shift = ilog2(mft_record_size); + const uint32_t clust_byte_shift = NTFS_SB(fs)->clust_byte_shift; + uint64_t lcn; + int err; + struct ntfs_mft_record *mrec; + + dprintf("in %s()\n", __func__); + + buf = (uint8_t *)malloc(mft_record_size); + if (!buf) + malloc_error("uint8_t *"); + + lcn = NTFS_SB(fs)->mft_lcn + (file << mft_record_shift >> clust_byte_shift); + cur_blk = (lcn << clust_byte_shift >> BLOCK_SHIFT(fs)) - mft_blk; + offset = (file << mft_record_shift) % BLOCK_SIZE(fs); + for (;;) { + right_blk = cur_blk + NTFS_SB(fs)->mft_blk; + err = ntfs_read(fs, buf, mft_record_size, mft_record_size, &right_blk, + &offset, &next_offset, &lcn); + if (err) { + printf("Error while reading from cache.\n"); + break; + } + + ntfs_fixups_writeback(fs, (struct ntfs_record *)buf); + + mrec = (struct ntfs_mft_record *)buf; + /* Check if the NTFS 3.1 MFT record number matches */ + if (mrec->magic == NTFS_MAGIC_FILE && mrec->mft_record_no == file) { + if (blk) + *blk = cur_blk; /* update record starting block */ + + return mrec; /* found MFT record */ + } + + if (next_offset >= BLOCK_SIZE(fs)) { + /* try the next FS block */ + offset = 0; + cur_blk = right_blk - NTFS_SB(fs)->mft_blk + 1; + } else { + /* there's still content to fetch in the current block */ + cur_blk = right_blk - NTFS_SB(fs)->mft_blk; + offset = next_offset; /* update FS block offset */ + } + } + + free(buf); + + return NULL; +} + +static bool ntfs_filename_cmp(const char *dname, struct ntfs_idx_entry *ie) +{ + const uint16_t *entry_fn; + uint8_t entry_fn_len; + unsigned i; + + dprintf("in %s()\n", __func__); + + entry_fn = ie->key.file_name.file_name; + entry_fn_len = ie->key.file_name.file_name_len; + + if (strlen(dname) != entry_fn_len) + return false; + + /* Do case-sensitive compares for Posix file names */ + if (ie->key.file_name.file_name_type == FILE_NAME_POSIX) { + for (i = 0; i < entry_fn_len; i++) + if (entry_fn[i] != dname[i]) + return false; + } else { + for (i = 0; i < entry_fn_len; i++) + if (tolower(entry_fn[i]) != tolower(dname[i])) + return false; + } + + return true; +} + +static inline uint8_t *mapping_chunk_init(struct ntfs_attr_record *attr, + struct mapping_chunk *chunk, + uint32_t *offset) +{ + memset(chunk, 0, sizeof *chunk); + *offset = 0U; + + return (uint8_t *)attr + attr->data.non_resident.mapping_pairs_offset; +} + +/* Parse data runs. + * + * return 0 on success or -1 on failure. + */ +static int parse_data_run(const void *stream, uint32_t *offset, + uint8_t *attr_len, struct mapping_chunk *chunk) +{ + uint8_t *buf; /* Pointer to the zero-terminated byte stream */ + uint8_t count; /* The count byte */ + uint8_t v, l; /* v is the number of changed low-order VCN bytes; + * l is the number of changed low-order LCN bytes + */ + uint8_t *byte; + int byte_shift = 8; + int mask; + uint8_t val; + int64_t res; + + (void)attr_len; + + dprintf("in %s()\n", __func__); + + chunk->flags &= ~MAP_MASK; + + buf = (uint8_t *)stream + *offset; + if (buf > attr_len || !*buf) { + chunk->flags |= MAP_END; /* we're done */ + return 0; + } + + if (!*offset) + chunk->flags |= MAP_START; /* initial chunk */ + + count = *buf; + v = count & 0x0F; + l = count >> 4; + + if (v > 8 || l > 8) /* more than 8 bytes ? */ + goto out; + + byte = (uint8_t *)buf + v; + count = v; + + res = 0LL; + while (count--) { + val = *byte--; + mask = val >> (byte_shift - 1); + res = (res << byte_shift) | ((val + mask) ^ mask); + } + + chunk->len = res; /* get length data */ + + byte = (uint8_t *)buf + v + l; + count = l; + + mask = 0xFFFFFFFF; + res = 0LL; + if (*byte & 0x80) + res |= (int64_t)mask; /* sign-extend it */ + + while (count--) + res = (res << byte_shift) | *byte--; + + chunk->lcn += res; + /* are VCNS from cur_vcn to next_vcn - 1 unallocated ? */ + if (!chunk->lcn) + chunk->flags |= MAP_UNALLOCATED; + else + chunk->flags |= MAP_ALLOCATED; + + *offset += v + l + 1; + + return 0; + +out: + return -1; +} + +static struct ntfs_mft_record * +ntfs_attr_list_lookup(struct fs_info *fs, struct ntfs_attr_record *attr, + uint32_t type, struct ntfs_mft_record *mrec) +{ + uint8_t *attr_len; + struct mapping_chunk chunk; + uint32_t offset; + uint8_t *stream; + int err; + const uint64_t blk_size = UINT64_C(1) << BLOCK_SHIFT(fs); + uint8_t buf[blk_size]; + uint64_t blk_offset; + int64_t vcn; + int64_t lcn; + int64_t last_lcn; + block_t blk; + struct ntfs_attr_list_entry *attr_entry; + uint32_t len = 0; + struct ntfs_mft_record *retval; + uint64_t start_blk = 0; + + dprintf("in %s()\n", __func__); + + if (attr->non_resident) + goto handle_non_resident_attr; + + attr_entry = (struct ntfs_attr_list_entry *) + ((uint8_t *)attr + attr->data.resident.value_offset); + len = attr->data.resident.value_len; + for (; (uint8_t *)attr_entry < (uint8_t *)attr + len; + attr_entry = (struct ntfs_attr_list_entry *)((uint8_t *)attr_entry + + attr_entry->length)) { + dprintf("<$ATTRIBUTE_LIST> Attribute type: 0x%X\n", + attr_entry->type); + if (attr_entry->type == type) + goto found; /* We got the attribute! :-) */ + } + + printf("No attribute found.\n"); + goto out; + +handle_non_resident_attr: + attr_len = (uint8_t *)attr + attr->len; + stream = mapping_chunk_init(attr, &chunk, &offset); + do { + err = parse_data_run(stream, &offset, attr_len, &chunk); + if (err) { + printf("parse_data_run()\n"); + goto out; + } + + if (chunk.flags & MAP_UNALLOCATED) + continue; + if (chunk.flags & MAP_END) + break; + if (chunk.flags & MAP_ALLOCATED) { + vcn = 0; + lcn = chunk.lcn; + while (vcn < chunk.len) { + blk = (lcn + vcn) << NTFS_SB(fs)->clust_byte_shift >> + BLOCK_SHIFT(fs); + blk_offset = 0; + last_lcn = lcn; + lcn += vcn; + err = ntfs_read(fs, buf, blk_size, blk_size, &blk, + &blk_offset, NULL, (uint64_t *)&lcn); + if (err) { + printf("Error while reading from cache.\n"); + goto out; + } + + attr_entry = (struct ntfs_attr_list_entry *)&buf; + len = attr->data.non_resident.data_size; + for (; (uint8_t *)attr_entry < (uint8_t *)&buf[0] + len; + attr_entry = (struct ntfs_attr_list_entry *) + ((uint8_t *)attr_entry + attr_entry->length)) { + dprintf("<$ATTRIBUTE_LIST> Attribute type: 0x%x\n", + attr_entry->type); + if (attr_entry->type == type) + goto found; /* We got the attribute! :-) */ + } + + lcn = last_lcn; /* restore original LCN */ + /* go to the next VCN */ + vcn += (blk_size / (1 << NTFS_SB(fs)->clust_byte_shift)); + } + } + } while (!(chunk.flags & MAP_END)); + + printf("No attribute found.\n"); + +out: + return NULL; + +found: + /* At this point we have the attribute we were looking for. Now we + * will look for the MFT record that stores information about this + * attribute. + */ + + /* Check if the attribute type we're looking for is in the same + * MFT record. If so, we do not need to look it up again - return it. + */ + if (mrec->mft_record_no == attr_entry->mft_ref) + return mrec; + + retval = NTFS_SB(fs)->mft_record_lookup(fs, attr_entry->mft_ref, + &start_blk); + if (!retval) { + printf("No MFT record found!\n"); + goto out; + } + + /* return the found MFT record */ + return retval; +} + +static struct ntfs_attr_record * +__ntfs_attr_lookup(struct fs_info *fs, uint32_t type, + struct ntfs_mft_record **mrec) +{ + struct ntfs_mft_record *_mrec = *mrec; + struct ntfs_attr_record *attr; + struct ntfs_attr_record *attr_list_attr; + + dprintf("in %s()\n", __func__); + + if (!_mrec || type == NTFS_AT_END) + goto out; + +again: + attr_list_attr = NULL; + + attr = (struct ntfs_attr_record *)((uint8_t *)_mrec + _mrec->attrs_offset); + /* walk through the file attribute records */ + for (;; attr = (struct ntfs_attr_record *)((uint8_t *)attr + attr->len)) { + if (attr->type == NTFS_AT_END) + break; + + if (attr->type == NTFS_AT_ATTR_LIST) { + dprintf("MFT record #%lu has an $ATTRIBUTE_LIST attribute.\n", + _mrec->mft_record_no); + attr_list_attr = attr; + continue; + } + + if (attr->type == type) + break; + } + + /* if the record has an $ATTRIBUTE_LIST attribute associated + * with it, then we need to look for the wanted attribute in + * it as well. + */ + if (attr->type == NTFS_AT_END && attr_list_attr) { + struct ntfs_mft_record *retval; + + retval = ntfs_attr_list_lookup(fs, attr_list_attr, type, _mrec); + if (!retval) + goto out; + + _mrec = retval; + goto again; + } else if (attr->type == NTFS_AT_END && !attr_list_attr) { + attr = NULL; + } + + return attr; + +out: + return NULL; +} + +static inline struct ntfs_attr_record * +ntfs_attr_lookup(struct fs_info *fs, uint32_t type, + struct ntfs_mft_record **mmrec, + struct ntfs_mft_record *mrec) +{ + struct ntfs_mft_record *_mrec = mrec; + struct ntfs_mft_record *other = *mmrec; + struct ntfs_attr_record *retval = NULL; + + if (mrec == other) + return __ntfs_attr_lookup(fs, type, &other); + + retval = __ntfs_attr_lookup(fs, type, &_mrec); + if (!retval) { + _mrec = other; + retval = __ntfs_attr_lookup(fs, type, &other); + if (!retval) + other = _mrec; + } else if (retval && (_mrec != mrec)) { + other = _mrec; + } + + return retval; +} + +static inline enum dirent_type get_inode_mode(struct ntfs_mft_record *mrec) +{ + return mrec->flags & MFT_RECORD_IS_DIRECTORY ? DT_DIR : DT_REG; +} + +static int index_inode_setup(struct fs_info *fs, unsigned long mft_no, + struct inode *inode) +{ + uint64_t start_blk = 0; + struct ntfs_mft_record *mrec, *lmrec; + struct ntfs_attr_record *attr; + enum dirent_type d_type; + uint8_t *attr_len; + struct mapping_chunk chunk; + int err; + uint8_t *stream; + uint32_t offset; + + dprintf("in %s()\n", __func__); + + mrec = NTFS_SB(fs)->mft_record_lookup(fs, mft_no, &start_blk); + if (!mrec) { + printf("No MFT record found.\n"); + goto out; + } + + lmrec = mrec; + + NTFS_PVT(inode)->mft_no = mft_no; + NTFS_PVT(inode)->seq_no = mrec->seq_no; + + NTFS_PVT(inode)->start_cluster = start_blk >> NTFS_SB(fs)->clust_shift; + NTFS_PVT(inode)->here = start_blk; + + d_type = get_inode_mode(mrec); + if (d_type == DT_DIR) { /* directory stuff */ + dprintf("Got a directory.\n"); + attr = ntfs_attr_lookup(fs, NTFS_AT_INDEX_ROOT, &mrec, lmrec); + if (!attr) { + printf("No attribute found.\n"); + goto out; + } + + /* check if we have a previous allocated state structure */ + if (readdir_state) { + free(readdir_state); + readdir_state = NULL; + } + + /* allocate our state structure */ + readdir_state = malloc(sizeof *readdir_state); + if (!readdir_state) + malloc_error("ntfs_readdir_state structure"); + + readdir_state->mft_no = mft_no; + /* obviously, the ntfs_readdir() caller will start from INDEX root */ + readdir_state->in_idx_root = true; + } else if (d_type == DT_REG) { /* file stuff */ + dprintf("Got a file.\n"); + attr = ntfs_attr_lookup(fs, NTFS_AT_DATA, &mrec, lmrec); + if (!attr) { + printf("No attribute found.\n"); + goto out; + } + + NTFS_PVT(inode)->non_resident = attr->non_resident; + NTFS_PVT(inode)->type = attr->type; + + if (!attr->non_resident) { + NTFS_PVT(inode)->data.resident.offset = + (uint32_t)((uint8_t *)attr + attr->data.resident.value_offset); + inode->size = attr->data.resident.value_len; + } else { + attr_len = (uint8_t *)attr + attr->len; + + stream = mapping_chunk_init(attr, &chunk, &offset); + NTFS_PVT(inode)->data.non_resident.rlist = NULL; + for (;;) { + err = parse_data_run(stream, &offset, attr_len, &chunk); + if (err) { + printf("parse_data_run()\n"); + goto out; + } + + if (chunk.flags & MAP_UNALLOCATED) + continue; + if (chunk.flags & MAP_END) + break; + if (chunk.flags & MAP_ALLOCATED) { + /* append new run to the runlist */ + runlist_append(&NTFS_PVT(inode)->data.non_resident.rlist, + (struct runlist_element *)&chunk); + /* update for next VCN */ + chunk.vcn += chunk.len; + } + } + + if (runlist_is_empty(NTFS_PVT(inode)->data.non_resident.rlist)) { + printf("No mapping found\n"); + goto out; + } + + inode->size = attr->data.non_resident.initialized_size; + } + } + + inode->mode = d_type; + + free(mrec); + + return 0; + +out: + free(mrec); + + return -1; +} + +static struct inode *ntfs_index_lookup(const char *dname, struct inode *dir) +{ + struct fs_info *fs = dir->fs; + struct ntfs_mft_record *mrec, *lmrec; + block_t blk; + uint64_t blk_offset; + struct ntfs_attr_record *attr; + struct ntfs_idx_root *ir; + struct ntfs_idx_entry *ie; + const uint64_t blk_size = UINT64_C(1) << BLOCK_SHIFT(fs); + uint8_t buf[blk_size]; + struct ntfs_idx_allocation *iblk; + int err; + uint8_t *stream; + uint8_t *attr_len; + struct mapping_chunk chunk; + uint32_t offset; + int64_t vcn; + int64_t lcn; + int64_t last_lcn; + struct inode *inode; + + dprintf("in %s()\n", __func__); + + mrec = NTFS_SB(fs)->mft_record_lookup(fs, NTFS_PVT(dir)->mft_no, NULL); + if (!mrec) { + printf("No MFT record found.\n"); + goto out; + } + + lmrec = mrec; + attr = ntfs_attr_lookup(fs, NTFS_AT_INDEX_ROOT, &mrec, lmrec); + if (!attr) { + printf("No attribute found.\n"); + goto out; + } + + ir = (struct ntfs_idx_root *)((uint8_t *)attr + + attr->data.resident.value_offset); + ie = (struct ntfs_idx_entry *)((uint8_t *)&ir->index + + ir->index.entries_offset); + for (;; ie = (struct ntfs_idx_entry *)((uint8_t *)ie + ie->len)) { + /* bounds checks */ + if ((uint8_t *)ie < (uint8_t *)mrec || + (uint8_t *)ie + sizeof(struct ntfs_idx_entry_header) > + (uint8_t *)&ir->index + ir->index.index_len || + (uint8_t *)ie + ie->len > + (uint8_t *)&ir->index + ir->index.index_len) + goto index_err; + + /* last entry cannot contain a key. it can however contain + * a pointer to a child node in the B+ tree so we just break out + */ + if (ie->flags & INDEX_ENTRY_END) + break; + + if (ntfs_filename_cmp(dname, ie)) + goto found; + } + + /* check for the presence of a child node */ + if (!(ie->flags & INDEX_ENTRY_NODE)) { + printf("No child node, aborting...\n"); + goto out; + } + + /* then descend into child node */ + + attr = ntfs_attr_lookup(fs, NTFS_AT_INDEX_ALLOCATION, &mrec, lmrec); + if (!attr) { + printf("No attribute found.\n"); + goto out; + } + + if (!attr->non_resident) { + printf("WTF ?! $INDEX_ALLOCATION isn't really resident.\n"); + goto out; + } + + attr_len = (uint8_t *)attr + attr->len; + stream = mapping_chunk_init(attr, &chunk, &offset); + do { + err = parse_data_run(stream, &offset, attr_len, &chunk); + if (err) + break; + + if (chunk.flags & MAP_UNALLOCATED) + continue; + + if (chunk.flags & MAP_ALLOCATED) { + dprintf("%d cluster(s) starting at 0x%08llX\n", chunk.len, + chunk.lcn); + + vcn = 0; + lcn = chunk.lcn; + while (vcn < chunk.len) { + blk = (lcn + vcn) << NTFS_SB(fs)->clust_shift << + SECTOR_SHIFT(fs) >> BLOCK_SHIFT(fs); + + blk_offset = 0; + last_lcn = lcn; + lcn += vcn; + err = ntfs_read(fs, &buf, blk_size, blk_size, &blk, + &blk_offset, NULL, (uint64_t *)&lcn); + if (err) { + printf("Error while reading from cache.\n"); + goto not_found; + } + + ntfs_fixups_writeback(fs, (struct ntfs_record *)&buf); + + iblk = (struct ntfs_idx_allocation *)&buf; + if (iblk->magic != NTFS_MAGIC_INDX) { + printf("Not a valid INDX record.\n"); + goto not_found; + } + + ie = (struct ntfs_idx_entry *)((uint8_t *)&iblk->index + + iblk->index.entries_offset); + for (;; ie = (struct ntfs_idx_entry *)((uint8_t *)ie + + ie->len)) { + /* bounds checks */ + if ((uint8_t *)ie < (uint8_t *)iblk || (uint8_t *)ie + + sizeof(struct ntfs_idx_entry_header) > + (uint8_t *)&iblk->index + iblk->index.index_len || + (uint8_t *)ie + ie->len > + (uint8_t *)&iblk->index + iblk->index.index_len) + goto index_err; + + /* last entry cannot contain a key */ + if (ie->flags & INDEX_ENTRY_END) + break; + + if (ntfs_filename_cmp(dname, ie)) + goto found; + } + + lcn = last_lcn; /* restore the original LCN */ + /* go to the next VCN */ + vcn += (blk_size / (1 << NTFS_SB(fs)->clust_byte_shift)); + } + } + } while (!(chunk.flags & MAP_END)); + +not_found: + dprintf("Index not found\n"); + +out: + free(mrec); + + return NULL; + +found: + dprintf("Index found\n"); + inode = new_ntfs_inode(fs); + err = index_inode_setup(fs, ie->data.dir.indexed_file, inode); + if (err) { + printf("Error in index_inode_setup()\n"); + free(inode); + goto out; + } + + free(mrec); + + return inode; + +index_err: + printf("Corrupt index. Aborting lookup...\n"); + goto out; +} + +/* Convert an UTF-16LE LFN to OEM LFN */ +static uint8_t ntfs_cvt_filename(char *filename, + const struct ntfs_idx_entry *ie) +{ + const uint16_t *entry_fn; + uint8_t entry_fn_len; + unsigned i; + + entry_fn = ie->key.file_name.file_name; + entry_fn_len = ie->key.file_name.file_name_len; + + for (i = 0; i < entry_fn_len; i++) + filename[i] = (char)entry_fn[i]; + + filename[i] = '\0'; + + return entry_fn_len; +} + +static int ntfs_next_extent(struct inode *inode, uint32_t lstart) +{ + struct fs_info *fs = inode->fs; + struct ntfs_sb_info *sbi = NTFS_SB(fs); + sector_t pstart = 0; + struct runlist *rlist; + struct runlist *ret; + const uint32_t sec_size = SECTOR_SIZE(fs); + const uint32_t sec_shift = SECTOR_SHIFT(fs); + + dprintf("in %s()\n", __func__); + + if (!NTFS_PVT(inode)->non_resident) { + pstart = (sbi->mft_blk + NTFS_PVT(inode)->here) << BLOCK_SHIFT(fs) >> + sec_shift; + inode->next_extent.len = (inode->size + sec_size - 1) >> sec_shift; + } else { + rlist = NTFS_PVT(inode)->data.non_resident.rlist; + + if (!lstart || lstart >= NTFS_PVT(inode)->here) { + if (runlist_is_empty(rlist)) + goto out; /* nothing to do ;-) */ + + ret = runlist_remove(&rlist); + + NTFS_PVT(inode)->here = + ((ret->run.len << sbi->clust_byte_shift) >> sec_shift); + + pstart = ret->run.lcn << sbi->clust_shift; + inode->next_extent.len = + ((ret->run.len << sbi->clust_byte_shift) + sec_size - 1) >> + sec_shift; + + NTFS_PVT(inode)->data.non_resident.rlist = rlist; + + free(ret); + ret = NULL; + } + } + + inode->next_extent.pstart = pstart; + + return 0; + +out: + return -1; +} + +static uint32_t ntfs_getfssec(struct file *file, char *buf, int sectors, + bool *have_more) +{ + uint8_t non_resident; + uint32_t ret; + struct fs_info *fs = file->fs; + struct inode *inode = file->inode; + struct ntfs_mft_record *mrec, *lmrec; + struct ntfs_attr_record *attr; + char *p; + + dprintf("in %s()\n", __func__); + + non_resident = NTFS_PVT(inode)->non_resident; + + ret = generic_getfssec(file, buf, sectors, have_more); + if (!ret) + return ret; + + if (!non_resident) { + mrec = NTFS_SB(fs)->mft_record_lookup(fs, NTFS_PVT(inode)->mft_no, + NULL); + if (!mrec) { + printf("No MFT record found.\n"); + goto out; + } + + lmrec = mrec; + attr = ntfs_attr_lookup(fs, NTFS_AT_DATA, &mrec, lmrec); + if (!attr) { + printf("No attribute found.\n"); + goto out; + } + + p = (char *)((uint8_t *)attr + attr->data.resident.value_offset); + + /* p now points to the data offset, so let's copy it into buf */ + memcpy(buf, p, inode->size); + + ret = inode->size; + + free(mrec); + } + + return ret; + +out: + free(mrec); + + return 0; +} + +static inline bool is_filename_printable(const char *s) +{ + return s && (*s != '.' && *s != '$'); +} + +static int ntfs_readdir(struct file *file, struct dirent *dirent) +{ + struct fs_info *fs = file->fs; + struct inode *inode = file->inode; + struct ntfs_mft_record *mrec, *lmrec; + block_t blk; + uint64_t blk_offset; + const uint64_t blk_size = UINT64_C(1) << BLOCK_SHIFT(fs); + struct ntfs_attr_record *attr; + struct ntfs_idx_root *ir; + uint32_t count; + int len; + struct ntfs_idx_entry *ie = NULL; + uint8_t buf[BLOCK_SIZE(fs)]; + struct ntfs_idx_allocation *iblk; + int err; + uint8_t *stream; + uint8_t *attr_len; + struct mapping_chunk chunk; + uint32_t offset; + int64_t vcn; + int64_t lcn; + char filename[NTFS_MAX_FILE_NAME_LEN + 1]; + + dprintf("in %s()\n", __func__); + + mrec = NTFS_SB(fs)->mft_record_lookup(fs, NTFS_PVT(inode)->mft_no, NULL); + if (!mrec) { + printf("No MFT record found.\n"); + goto out; + } + + lmrec = mrec; + attr = ntfs_attr_lookup(fs, NTFS_AT_INDEX_ROOT, &mrec, lmrec); + if (!attr) { + printf("No attribute found.\n"); + goto out; + } + + ir = (struct ntfs_idx_root *)((uint8_t *)attr + + attr->data.resident.value_offset); + + if (!file->offset && readdir_state->in_idx_root) { + file->offset = (uint32_t)((uint8_t *)&ir->index + + ir->index.entries_offset); + } + +idx_root_next_entry: + if (readdir_state->in_idx_root) { + ie = (struct ntfs_idx_entry *)(uint8_t *)file->offset; + if (ie->flags & INDEX_ENTRY_END) { + file->offset = 0; + readdir_state->in_idx_root = false; + readdir_state->idx_blks_count = 1; + readdir_state->entries_count = 0; + readdir_state->last_vcn = 0; + goto descend_into_child_node; + } + + file->offset = (uint32_t)((uint8_t *)ie + ie->len); + len = ntfs_cvt_filename(filename, ie); + if (!is_filename_printable(filename)) + goto idx_root_next_entry; + + goto done; + } + +descend_into_child_node: + if (!(ie->flags & INDEX_ENTRY_NODE)) + goto out; + + attr = ntfs_attr_lookup(fs, NTFS_AT_INDEX_ALLOCATION, &mrec, lmrec); + if (!attr) + goto out; + + if (!attr->non_resident) { + printf("WTF ?! $INDEX_ALLOCATION isn't really resident.\n"); + goto out; + } + + attr_len = (uint8_t *)attr + attr->len; + +next_run: + stream = mapping_chunk_init(attr, &chunk, &offset); + count = readdir_state->idx_blks_count; + while (count--) { + err = parse_data_run(stream, &offset, attr_len, &chunk); + if (err) { + printf("Error while parsing data runs.\n"); + goto out; + } + + if (chunk.flags & MAP_UNALLOCATED) + break; + if (chunk.flags & MAP_END) + goto out; + } + + if (chunk.flags & MAP_UNALLOCATED) { + readdir_state->idx_blks_count++; + goto next_run; + } + +next_vcn: + vcn = readdir_state->last_vcn; + if (vcn >= chunk.len) { + readdir_state->last_vcn = 0; + readdir_state->idx_blks_count++; + goto next_run; + } + + lcn = chunk.lcn; + blk = (lcn + vcn) << NTFS_SB(fs)->clust_shift << SECTOR_SHIFT(fs) >> + BLOCK_SHIFT(fs); + + blk_offset = 0; + err = ntfs_read(fs, &buf, blk_size, blk_size, &blk, &blk_offset, NULL, + (uint64_t *)&lcn); + if (err) { + printf("Error while reading from cache.\n"); + goto not_found; + } + + ntfs_fixups_writeback(fs, (struct ntfs_record *)&buf); + + iblk = (struct ntfs_idx_allocation *)&buf; + if (iblk->magic != NTFS_MAGIC_INDX) { + printf("Not a valid INDX record.\n"); + goto not_found; + } + +idx_block_next_entry: + ie = (struct ntfs_idx_entry *)((uint8_t *)&iblk->index + + iblk->index.entries_offset); + count = readdir_state->entries_count; + for ( ; count--; ie = (struct ntfs_idx_entry *)((uint8_t *)ie + ie->len)) { + /* bounds checks */ + if ((uint8_t *)ie < (uint8_t *)iblk || (uint8_t *)ie + + sizeof(struct ntfs_idx_entry_header) > + (uint8_t *)&iblk->index + iblk->index.index_len || + (uint8_t *)ie + ie->len > + (uint8_t *)&iblk->index + iblk->index.index_len) + goto index_err; + + /* last entry cannot contain a key */ + if (ie->flags & INDEX_ENTRY_END) { + /* go to the next VCN */ + readdir_state->last_vcn += (blk_size / (1 << + NTFS_SB(fs)->clust_byte_shift)); + readdir_state->entries_count = 0; + goto next_vcn; + } + } + + readdir_state->entries_count++; + + /* Need to check if this entry has INDEX_ENTRY_END flag set. If + * so, then it won't contain a indexed_file file, so continue the + * lookup on the next VCN/LCN (if any). + */ + if (ie->flags & INDEX_ENTRY_END) + goto next_vcn; + + len = ntfs_cvt_filename(filename, ie); + if (!is_filename_printable(filename)) + goto idx_block_next_entry; + + goto done; + +out: + readdir_state->in_idx_root = true; + + free(mrec); + + return -1; + +done: + dirent->d_ino = ie->data.dir.indexed_file; + dirent->d_off = file->offset; + dirent->d_reclen = offsetof(struct dirent, d_name) + len + 1; + + free(mrec); + + mrec = NTFS_SB(fs)->mft_record_lookup(fs, ie->data.dir.indexed_file, NULL); + if (!mrec) { + printf("No MFT record found.\n"); + goto out; + } + + dirent->d_type = get_inode_mode(mrec); + memcpy(dirent->d_name, filename, len + 1); + + free(mrec); + + return 0; + +not_found: + printf("Index not found\n"); + goto out; + +index_err: + printf("Corrupt index. Aborting lookup...\n"); + goto out; +} + +static inline struct inode *ntfs_iget(const char *dname, struct inode *parent) +{ + return ntfs_index_lookup(dname, parent); +} + +static struct inode *ntfs_iget_root(struct fs_info *fs) +{ + uint64_t start_blk; + struct ntfs_mft_record *mrec, *lmrec; + struct ntfs_attr_record *attr; + struct ntfs_vol_info *vol_info; + struct inode *inode; + int err; + + dprintf("in %s()\n", __func__); + + /* Fetch the $Volume MFT record */ + start_blk = 0; + mrec = NTFS_SB(fs)->mft_record_lookup(fs, FILE_Volume, &start_blk); + if (!mrec) { + printf("Could not fetch $Volume MFT record!\n"); + goto err_mrec; + } + + lmrec = mrec; + + /* Fetch the volume information attribute */ + attr = ntfs_attr_lookup(fs, NTFS_AT_VOL_INFO, &mrec, lmrec); + if (!attr) { + printf("Could not find volume info attribute!\n"); + goto err_attr; + } + + /* Note NTFS version and choose version-dependent functions */ + vol_info = (void *)((char *)attr + attr->data.resident.value_offset); + NTFS_SB(fs)->major_ver = vol_info->major_ver; + NTFS_SB(fs)->minor_ver = vol_info->minor_ver; + if (vol_info->major_ver == 3 && vol_info->minor_ver == 0) + NTFS_SB(fs)->mft_record_lookup = ntfs_mft_record_lookup_3_0; + else if (vol_info->major_ver == 3 && vol_info->minor_ver == 1 && + mrec->mft_record_no == FILE_Volume) + NTFS_SB(fs)->mft_record_lookup = ntfs_mft_record_lookup_3_1; + + /* Free MFT record */ + free(mrec); + mrec = NULL; + + inode = new_ntfs_inode(fs); + inode->fs = fs; + + err = index_inode_setup(fs, FILE_root, inode); + if (err) + goto err_setup; + + NTFS_PVT(inode)->start = NTFS_PVT(inode)->here; + + return inode; + +err_setup: + + free(inode); +err_attr: + + free(mrec); +err_mrec: + + return NULL; +} + +/* Initialize the filesystem metadata and return blk size in bits */ +static int ntfs_fs_init(struct fs_info *fs) +{ + int read_count; + struct ntfs_bpb ntfs; + struct ntfs_sb_info *sbi; + struct disk *disk = fs->fs_dev->disk; + uint8_t mft_record_shift; + + dprintf("in %s()\n", __func__); + + read_count = disk->rdwr_sectors(disk, &ntfs, 0, 1, 0); + if (!read_count) + return -1; + + if (!ntfs_check_sb_fields(&ntfs)) + return -1; + + SECTOR_SHIFT(fs) = disk->sector_shift; + + /* Note: ntfs.clust_per_mft_record can be a negative number. + * If negative, it represents a shift count, else it represents + * a multiplier for the cluster size. + */ + mft_record_shift = ntfs.clust_per_mft_record < 0 ? + -ntfs.clust_per_mft_record : + ilog2(ntfs.sec_per_clust) + SECTOR_SHIFT(fs) + + ilog2(ntfs.clust_per_mft_record); + + SECTOR_SIZE(fs) = 1 << SECTOR_SHIFT(fs); + + sbi = malloc(sizeof *sbi); + if (!sbi) + malloc_error("ntfs_sb_info structure"); + + fs->fs_info = sbi; + + sbi->clust_shift = ilog2(ntfs.sec_per_clust); + sbi->clust_byte_shift = sbi->clust_shift + SECTOR_SHIFT(fs); + sbi->clust_mask = ntfs.sec_per_clust - 1; + sbi->clust_size = ntfs.sec_per_clust << SECTOR_SHIFT(fs); + sbi->mft_record_size = 1 << mft_record_shift; + sbi->clust_per_idx_record = ntfs.clust_per_idx_record; + + BLOCK_SHIFT(fs) = ilog2(ntfs.clust_per_idx_record) + sbi->clust_byte_shift; + BLOCK_SIZE(fs) = 1 << BLOCK_SHIFT(fs); + + sbi->mft_lcn = ntfs.mft_lclust; + sbi->mft_blk = ntfs.mft_lclust << sbi->clust_shift << SECTOR_SHIFT(fs) >> + BLOCK_SHIFT(fs); + /* 16 MFT entries reserved for metadata files (approximately 16 KiB) */ + sbi->mft_size = mft_record_shift << sbi->clust_shift << 4; + + sbi->clusters = ntfs.total_sectors << SECTOR_SHIFT(fs) >> sbi->clust_shift; + if (sbi->clusters > 0xFFFFFFFFFFF4ULL) + sbi->clusters = 0xFFFFFFFFFFF4ULL; + + /* + * Assume NTFS version 3.0 to begin with. If we find that the + * volume is a different version later on, we will adjust at + * that time. + */ + sbi->major_ver = 3; + sbi->minor_ver = 0; + sbi->mft_record_lookup = ntfs_mft_record_lookup_3_0; + + /* Initialize the cache */ + cache_init(fs->fs_dev, BLOCK_SHIFT(fs)); + + return BLOCK_SHIFT(fs); +} + +const struct fs_ops ntfs_fs_ops = { + .fs_name = "ntfs", + .fs_flags = FS_USEMEM | FS_THISIND, + .fs_init = ntfs_fs_init, + .searchdir = NULL, + .getfssec = ntfs_getfssec, + .close_file = generic_close_file, + .mangle_name = generic_mangle_name, + .load_config = generic_load_config, + .readdir = ntfs_readdir, + .iget_root = ntfs_iget_root, + .iget = ntfs_iget, + .next_extent = ntfs_next_extent, +}; diff --git a/core/fs/ntfs/ntfs.h b/core/fs/ntfs/ntfs.h new file mode 100644 index 00000000..721a78d7 --- /dev/null +++ b/core/fs/ntfs/ntfs.h @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2011-2012 Paulo Alcantara <pcacjr@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "runlist.h" + +#ifndef _NTFS_H_ +#define _NTFS_H_ + +struct ntfs_bpb { + uint8_t jmp_boot[3]; + char oem_name[8]; + uint16_t sector_size; + uint8_t sec_per_clust; + uint16_t res_sectors; + uint8_t zero_0[3]; + uint16_t zero_1; + uint8_t media; + uint16_t zero_2; + uint16_t unused_0; + uint16_t unused_1; + uint32_t unused_2; + uint32_t zero_3; + uint32_t unused_3; + uint64_t total_sectors; + uint64_t mft_lclust; + uint64_t mft_mirr_lclust; + int8_t clust_per_mft_record; + uint8_t unused_4[3]; + uint8_t clust_per_idx_record; + uint8_t unused_5[3]; + uint64_t vol_serial; + uint32_t unused_6; + + uint8_t pad[428]; /* padding to a sector boundary (512 bytes) */ +} __attribute__((__packed__)); + +/* Function type for an NTFS-version-dependent MFT record lookup */ +struct ntfs_mft_record; +typedef struct ntfs_mft_record *f_mft_record_lookup(struct fs_info *, + uint32_t, block_t *); + +struct ntfs_sb_info { + block_t mft_blk; /* The first MFT record block */ + uint64_t mft_lcn; /* LCN of the first MFT record */ + unsigned mft_size; /* The MFT size in sectors */ + uint64_t mft_record_size; /* MFT record size in bytes */ + + uint8_t clust_per_idx_record; /* Clusters per Index Record */ + + unsigned long long clusters; /* Total number of clusters */ + + unsigned clust_shift; /* Based on sectors */ + unsigned clust_byte_shift; /* Based on bytes */ + unsigned clust_mask; + unsigned clust_size; + + uint8_t major_ver; /* Major version from $Volume */ + uint8_t minor_ver; /* Minor version from $Volume */ + + /* NTFS-version-dependent MFT record lookup function to use */ + f_mft_record_lookup *mft_record_lookup; +} __attribute__((__packed__)); + +/* The NTFS in-memory inode structure */ +struct ntfs_inode { + int64_t initialized_size; + int64_t allocated_size; + unsigned long mft_no; /* Number of the mft record / inode */ + uint16_t seq_no; /* Sequence number of the mft record */ + uint32_t type; /* Attribute type of this inode */ + uint8_t non_resident; + union { /* Non-resident $DATA attribute */ + struct { /* Used only if non_resident flags isn't set */ + uint32_t offset; /* Data offset */ + } resident; + struct { /* Used only if non_resident is set */ + struct runlist *rlist; + } non_resident; + } data; + uint32_t start_cluster; /* Starting cluster address */ + sector_t start; /* Starting sector */ + sector_t offset; /* Current sector offset */ + sector_t here; /* Sector corresponding to offset */ +}; + +/* This is structure is used to keep a state for ntfs_readdir() callers. + * As NTFS stores directory entries in a complex way, this is structure + * ends up saving a state required to find out where we must start from + * for the next ntfs_readdir() call. + */ +struct ntfs_readdir_state { + unsigned long mft_no; /* MFT record number */ + bool in_idx_root; /* It's true if we're still in the INDEX root */ + uint32_t idx_blks_count; /* Number of read INDX blocks */ + uint32_t entries_count; /* Number of read INDEX entries */ + int64_t last_vcn; /* Last VCN of the INDX block */ +}; + +enum { + MAP_UNSPEC, + MAP_START = 1 << 0, + MAP_END = 1 << 1, + MAP_ALLOCATED = 1 << 2, + MAP_UNALLOCATED = 1 << 3, + MAP_MASK = 0x0000000F, +}; + +struct mapping_chunk { + uint64_t vcn; + int64_t lcn; + uint64_t len; + uint32_t flags; +}; + +/* System defined attributes (32-bit) + * Each attribute type has a corresponding attribute name (in Unicode) + */ +enum { + NTFS_AT_UNUSED = 0x00, + NTFS_AT_STANDARD_INFORMATION = 0x10, + NTFS_AT_ATTR_LIST = 0x20, + NTFS_AT_FILENAME = 0x30, + NTFS_AT_OBJ_ID = 0x40, + NTFS_AT_SECURITY_DESCP = 0x50, + NTFS_AT_VOL_NAME = 0x60, + NTFS_AT_VOL_INFO = 0x70, + NTFS_AT_DATA = 0x80, + NTFS_AT_INDEX_ROOT = 0x90, + NTFS_AT_INDEX_ALLOCATION = 0xA0, + NTFS_AT_BITMAP = 0xB0, + NTFS_AT_REPARSE_POINT = 0xC0, + NTFS_AT_EA_INFO = 0xD0, + NTFS_AT_EA = 0xE0, + NTFS_AT_PROPERTY_SET = 0xF0, + NTFS_AT_LOGGED_UTIL_STREAM = 0x100, + NTFS_AT_FIRST_USER_DEFINED_ATTR = 0x1000, + NTFS_AT_END = 0xFFFFFFFF, +}; + +/* NTFS File Permissions (also called attributes in DOS terminology) */ +enum { + NTFS_FILE_ATTR_READONLY = 0x00000001, + NTFS_FILE_ATTR_HIDDEN = 0x00000002, + NTFS_FILE_ATTR_SYSTEM = 0x00000004, + NTFS_FILE_ATTR_DIRECTORY = 0x00000010, + NTFS_FILE_ATTR_ARCHIVE = 0x00000020, + NTFS_FILE_ATTR_DEVICE = 0x00000040, + NTFS_FILE_ATTR_NORMAL = 0x00000080, + NTFS_FILE_ATTR_TEMPORARY = 0x00000100, + NTFS_FILE_ATTR_SPARSE_FILE = 0x00000200, + NTFS_FILE_ATTR_REPARSE_POINT = 0x00000400, + NTFS_FILE_ATTR_COMPRESSED = 0x00000800, + NTFS_FILE_ATTR_OFFLINE = 0x00001000, + NTFS_FILE_ATTR_NOT_CONTENT_INDEXED = 0x00002000, + NTFS_FILE_ATTR_ENCRYPTED = 0x00004000, + NTFS_FILE_ATTR_VALID_FLAGS = 0x00007FB7, + NTFS_FILE_ATTR_VALID_SET_FLAGS = 0x000031A7, + NTFS_FILE_ATTR_DUP_FILE_NAME_INDEX_PRESENT = 0x10000000, + NTFS_FILE_ATTR_DUP_VIEW_INDEX_PRESENT = 0x20000000, +}; + +/* + * Magic identifiers present at the beginning of all ntfs record containing + * records (like mft records for example). + */ +enum { + /* Found in $MFT/$DATA */ + NTFS_MAGIC_FILE = 0x454C4946, /* MFT entry */ + NTFS_MAGIC_INDX = 0x58444E49, /* Index buffer */ + NTFS_MAGIC_HOLE = 0x454C4F48, + + /* Found in $LogFile/$DATA */ + NTFS_MAGIC_RSTR = 0x52545352, + NTFS_MAGIC_RCRD = 0x44524352, + /* Found in $LogFile/$DATA (May be found in $MFT/$DATA, also ?) */ + NTFS_MAGIC_CHKDSK = 0x444B4843, + /* Found in all ntfs record containing records. */ + NTFS_MAGIC_BAAD = 0x44414142, + NTFS_MAGIC_EMPTY = 0xFFFFFFFF, /* Record is empty */ +}; + +struct ntfs_record { + uint32_t magic; + uint16_t usa_ofs; + uint16_t usa_count; +} __attribute__((__packed__)) NTFS_RECORD; + +/* The $MFT metadata file types */ +enum ntfs_system_file { + FILE_MFT = 0, + FILE_MFTMirr = 1, + FILE_LogFile = 2, + FILE_Volume = 3, + FILE_AttrDef = 4, + FILE_root = 5, + FILE_Bitmap = 6, + FILE_Boot = 7, + FILE_BadClus = 8, + FILE_Secure = 9, + FILE_UpCase = 10, + FILE_Extend = 11, + FILE_reserved12 = 12, + FILE_reserved13 = 13, + FILE_reserved14 = 14, + FILE_reserved15 = 15, + FILE_reserved16 = 16, +}; + +enum { + MFT_RECORD_IN_USE = 0x0001, + MFT_RECORD_IS_DIRECTORY = 0x0002, +} __attribute__((__packed__)); + +struct ntfs_mft_record { + uint32_t magic; + uint16_t usa_ofs; + uint16_t usa_count; + uint64_t lsn; + uint16_t seq_no; + uint16_t link_count; + uint16_t attrs_offset; + uint16_t flags; /* MFT record flags */ + uint32_t bytes_in_use; + uint32_t bytes_allocated; + uint64_t base_mft_record; + uint16_t next_attr_instance; + uint16_t reserved; + uint32_t mft_record_no; +} __attribute__((__packed__)); /* 48 bytes */ + +/* This is the version without the NTFS 3.1+ specific fields */ +struct ntfs_mft_record_old { + uint32_t magic; + uint16_t usa_ofs; + uint16_t usa_count; + uint64_t lsn; + uint16_t seq_no; + uint16_t link_count; + uint16_t attrs_offset; + uint16_t flags; /* MFT record flags */ + uint32_t bytes_in_use; + uint32_t bytes_allocated; + uint64_t base_mft_record; + uint16_t next_attr_instance; +} __attribute__((__packed__)); /* 42 bytes */ + +enum { + ATTR_DEF_INDEXABLE = 0x02, + ATTR_DEF_MULTIPLE = 0x04, + ATTR_DEF_NOT_ZERO = 0x08, + ATTR_DEF_INDEXED_UNIQUE = 0x10, + ATTR_DEF_NAMED_UNIQUE = 0x20, + ATTR_DEF_RESIDENT = 0x40, + ATTR_DEF_ALWAYS_LOG = 0x80, +}; + +struct ntfs_attr_record { + uint32_t type; /* Attr. type code */ + uint32_t len; + uint8_t non_resident; + uint8_t name_len; + uint16_t name_offset; + uint16_t flags; /* Attr. flags */ + uint16_t instance; + union { + struct { /* Resident attribute */ + uint32_t value_len; + uint16_t value_offset; + uint8_t flags; /* Flags of resident attributes */ + int8_t reserved; + } __attribute__((__packed__)) resident; + struct { /* Non-resident attributes */ + uint64_t lowest_vcn; + uint64_t highest_vcn; + uint16_t mapping_pairs_offset; + uint8_t compression_unit; + uint8_t reserved[5]; + int64_t allocated_size; + int64_t data_size; /* Byte size of the attribute value. + * Note: it can be larger than + * allocated_size if attribute value is + * compressed or sparse. + */ + int64_t initialized_size; + int64_t compressed_size; + } __attribute__((__packed__)) non_resident; + } __attribute__((__packed__)) data; +} __attribute__((__packed__)); + +/* Attribute: Attribute List (0x20) + * Note: it can be either resident or non-resident + */ +struct ntfs_attr_list_entry { + uint32_t type; + uint16_t length; + uint8_t name_length; + uint8_t name_offset; + uint64_t lowest_vcn; + uint64_t mft_ref; + uint16_t instance; + uint16_t name[0]; +} __attribute__((__packed__)); + +#define NTFS_MAX_FILE_NAME_LEN 255 + +/* Possible namespaces for filenames in ntfs (8-bit) */ +enum { + FILE_NAME_POSIX = 0x00, + FILE_NAME_WIN32 = 0x01, + FILE_NAME_DOS = 0x02, + FILE_NAME_WIN32_AND_DOS = 0x03, +} __attribute__((__packed__)); + +/* Attribute: Filename (0x30) + * Note: always resident + */ +struct ntfs_filename_attr { + uint64_t parent_directory; + int64_t ctime; + int64_t atime; + int64_t mtime; + int64_t rtime; + uint64_t allocated_size; + uint64_t data_size; + uint32_t file_attrs; + union { + struct { + uint16_t packed_ea_size; + uint16_t reserved; /* reserved for alignment */ + } __attribute__((__packed__)) ea; + struct { + uint32_t reparse_point_tag; + } __attribute__((__packed__)) rp; + } __attribute__((__packed__)) type; + uint8_t file_name_len; + uint8_t file_name_type; + uint16_t file_name[0]; /* File name in Unicode */ +} __attribute__((__packed__)); + +/* Attribute: Volume Name (0x60) + * Note: always resident + * Note: Present only in FILE_volume + */ +struct ntfs_vol_name { + uint16_t name[0]; /* The name of the volume in Unicode */ +} __attribute__((__packed__)); + +/* Attribute: Volume Information (0x70) + * Note: always resident + * Note: present only in FILE_Volume + */ +struct ntfs_vol_info { + uint64_t reserved; + uint8_t major_ver; + uint8_t minor_ver; + uint16_t flags; /* Volume flags */ +} __attribute__((__packed__)); + +/* Attribute: Data attribute (0x80) + * Note: can be either resident or non-resident + */ +struct ntfs_data_attr { + uint8_t data[0]; +} __attribute__((__packed__)); + +/* Index header flags (8-bit) */ +enum { + SMALL_INDEX = 0, + LARGE_INDEX = 1, + LEAF_NODE = 0, + INDEX_NODE = 1, + NODE_MASK = 1, +} __attribute__((__packed__)); + +/* Header for the indexes, describing the INDEX_ENTRY records, which + * follow the struct ntfs_idx_header. + */ +struct ntfs_idx_header { + uint32_t entries_offset; + uint32_t index_len; + uint32_t allocated_size; + uint8_t flags; /* Index header flags */ + uint8_t reserved[3]; /* Align to 8-byte boundary */ +} __attribute__((__packed__)); + +/* Attribute: Index Root (0x90) + * Note: always resident + */ +struct ntfs_idx_root { + uint32_t type; /* It is $FILE_NAME for directories, zero for view indexes. + * No other values allowed. + */ + uint32_t collation_rule; + uint32_t index_block_size; + uint8_t clust_per_index_block; + uint8_t reserved[3]; + struct ntfs_idx_header index; +} __attribute__((__packed__)); + +/* Attribute: Index allocation (0xA0) + * Note: always non-resident, of course! :-) + */ +struct ntfs_idx_allocation { + uint32_t magic; + uint16_t usa_ofs; /* Update Sequence Array offsets */ + uint16_t usa_count; /* Update Sequence Array number in bytes */ + int64_t lsn; + int64_t index_block_vcn; /* Virtual cluster number of the index block */ + struct ntfs_idx_header index; +} __attribute__((__packed__)); + +enum { + INDEX_ENTRY_NODE = 1, + INDEX_ENTRY_END = 2, + /* force enum bit width to 16-bit */ + INDEX_ENTRY_SPACE_FILTER = 0xFFFF, +} __attribute__((__packed__)); + +struct ntfs_idx_entry_header { + union { + struct { /* Only valid when INDEX_ENTRY_END is not set */ + uint64_t indexed_file; + } __attribute__((__packed__)) dir; + struct { /* Used for views/indexes to find the entry's data */ + uint16_t data_offset; + uint16_t data_len; + uint32_t reservedV; + } __attribute__((__packed__)) vi; + } __attribute__((__packed__)) data; + uint16_t len; + uint16_t key_len; + uint16_t flags; /* Index entry flags */ + uint16_t reserved; /* Align to 8-byte boundary */ +} __attribute__((__packed__)); + +struct ntfs_idx_entry { + union { + struct { /* Only valid when INDEX_ENTRY_END is not set */ + uint64_t indexed_file; + } __attribute__((__packed__)) dir; + struct { /* Used for views/indexes to find the entry's data */ + uint16_t data_offset; + uint16_t data_len; + uint32_t reservedV; + } __attribute__((__packed__)) vi; + } __attribute__((__packed__)) data; + uint16_t len; + uint16_t key_len; + uint16_t flags; /* Index entry flags */ + uint16_t reserved; /* Align to 8-byte boundary */ + union { + struct ntfs_filename_attr file_name; + //SII_INDEX_KEY sii; + //SDH_INDEX_KEY sdh; + //GUID object_id; + //REPARSE_INDEX_KEY reparse; + //SID sid; + uint32_t owner_id; + } __attribute__((__packed__)) key; +} __attribute__((__packed__)); + +static inline struct ntfs_sb_info *NTFS_SB(struct fs_info *fs) +{ + return fs->fs_info; +} + +#define NTFS_PVT(i) ((struct ntfs_inode *)((i)->pvt)) + +#endif /* _NTFS_H_ */ diff --git a/core/fs/ntfs/runlist.h b/core/fs/ntfs/runlist.h new file mode 100644 index 00000000..4938696b --- /dev/null +++ b/core/fs/ntfs/runlist.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011 Paulo Alcantara <pcacjr@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _RUNLIST_H_ +#define _RUNLIST_H_ + +struct runlist_element { + uint64_t vcn; + int64_t lcn; + uint64_t len; +}; + +struct runlist { + struct runlist_element run; + struct runlist *next; +}; + +static struct runlist *tail; + +static inline bool runlist_is_empty(struct runlist *rlist) +{ + return !rlist; +} + +static inline struct runlist *runlist_alloc(void) +{ + struct runlist *rlist; + + rlist = malloc(sizeof *rlist); + if (!rlist) + malloc_error("runlist structure"); + + rlist->next = NULL; + + return rlist; +} + +static inline void runlist_append(struct runlist **rlist, + struct runlist_element *elem) +{ + struct runlist *n = runlist_alloc(); + + n->run = *elem; + + if (runlist_is_empty(*rlist)) { + *rlist = n; + tail = n; + } else { + tail->next = n; + tail = n; + } +} + +static inline struct runlist *runlist_remove(struct runlist **rlist) +{ + struct runlist *ret; + + if (runlist_is_empty(*rlist)) + return NULL; + + ret = *rlist; + *rlist = ret->next; + + return ret; +} + +#endif /* _RUNLIST_H_ */ diff --git a/core/head.inc b/core/head.inc index 18ce132c..71eb5744 100644 --- a/core/head.inc +++ b/core/head.inc @@ -20,8 +20,8 @@ %ifndef _HEAD_INC %define _HEAD_INC -%if __NASM_MAJOR__ < 2 - %error "NASM 2.00 or later required to compile correctly" +%if __NASM_MAJOR__ < 2 || (__NASM_MAJOR__ == 2 && __NASM_MINOR__ < 3) + %error "NASM 2.03 or later required to compile correctly" %endif %include "macros.inc" diff --git a/core/include/fs.h b/core/include/fs.h index ecd148da..e1f5733c 100644 --- a/core/include/fs.h +++ b/core/include/fs.h @@ -95,6 +95,7 @@ struct extent { struct inode { struct fs_info *fs; /* The filesystem this inode is associated with */ struct inode *parent; /* Parent directory, if any */ + const char *name; /* Name, valid for generic path search only */ int refcnt; int mode; /* FILE , DIR or SYMLINK */ uint32_t size; diff --git a/core/init.inc b/core/init.inc index e06ca96f..8c6a178f 100644 --- a/core/init.inc +++ b/core/init.inc @@ -69,9 +69,15 @@ check_escapes: shr edx,10 cmp ax,dx jae enough_ram - mov ax,dx mov si,err_noram mov cl,10 + push dx + div cl + add [si+err_noram.need-err_noram+2],ah + cbw + div cl + add [si+err_noram.need-err_noram],ax + pop ax div cl add [si+err_noram.size-err_noram+2],ah cbw @@ -83,15 +89,15 @@ enough_ram: skip_checks: section .data16 -err_noram db 'It appears your computer has less than ' +err_noram db 'It appears your computer has only ' .size db '000' - db 'K of low ("DOS")' - db CR, LF - db 'RAM. Syslinux needs at least this amount to boot. If you get' - db CR, LF - db 'this message in error, hold down the Ctrl key while' - db CR, LF - db 'booting, and I will take your word for it.', CR, LF, 0 + db 'K of low ("DOS") RAM.', CR, LF + db 'This version of Syslinux needs ' +.need db '000' + db 'K to boot. If you get this', CR, LF + db 'message in error, hold down the Ctrl key while' + db 'booting, and I', CR, LF + db 'will take your word for it.', CR, LF, 0 section .text16 ; diff --git a/core/isolinux.asm b/core/isolinux.asm index ca8ee3a3..7a871f0e 100644 --- a/core/isolinux.asm +++ b/core/isolinux.asm @@ -52,22 +52,6 @@ vk_append: resb max_cmd_len+1 ; Command line vk_end: equ $ ; Should be <= vk_size endstruc -; -; File structure. This holds the information for each currently open file. -; - struc open_file_t -file_sector resd 1 ; Sector pointer (0 = structure free) -file_bytesleft resd 1 ; Number of bytes left -file_left resd 1 ; Number of sectors left - resd 1 ; Unused - endstruc - -%ifndef DEPEND -%if (open_file_t_size & (open_file_t_size-1)) -%error "open_file_t is not a power of 2" -%endif -%endif - ; --------------------------------------------------------------------------- ; BEGIN CODE ; --------------------------------------------------------------------------- @@ -1195,138 +1179,6 @@ ROOT_FS_OPS: ; %include "ui.inc" -; -; Enable disk emulation. The kind of disk we emulate is dependent on the -; size of the file: 1200K, 1440K or 2880K floppy, otherwise harddisk. -; -is_disk_image: - TRACER CR - TRACER LF - TRACER 'D' - TRACER ':' - - mov edx,eax ; File size - mov di,img_table - mov cx,img_table_count - mov eax,[si+file_sector] ; Starting LBA of file - mov [dsp_lba],eax ; Location of file - mov byte [dsp_drive], 0 ; 00h floppy, 80h hard disk -.search_table: - TRACER 't' - mov eax,[di+4] - cmp edx,[di] - je .type_found - add di,8 - loop .search_table - - ; Hard disk image. Need to examine the partition table - ; in order to deduce the C/H/S geometry. Sigh. -.hard_disk_image: - TRACER 'h' - cmp edx,512 - jb .bad_image - - mov bx,trackbuf - mov cx,1 ; Load 1 sector - pm_call getfssec - - cmp word [trackbuf+510],0aa55h ; Boot signature - jne .bad_image ; Image not bootable - - mov cx,4 ; 4 partition entries - mov di,trackbuf+446 ; Start of partition table - - xor ax,ax ; Highest sector(al) head(ah) - -.part_scan: - cmp byte [di+4], 0 - jz .part_loop - lea si,[di+1] - call .hs_check - add si,byte 4 - call .hs_check -.part_loop: - add di,byte 16 - loop .part_scan - - push eax ; H/S - push edx ; File size - mov bl,ah - xor bh,bh - inc bx ; # of heads in BX - xor ah,ah ; # of sectors in AX - cwde ; EAX[31:16] <- 0 - mul bx - shl eax,9 ; Convert to bytes - ; Now eax contains the number of bytes per cylinder - pop ebx ; File size - xor edx,edx - div ebx - and edx,edx - jz .no_remainder - inc eax ; Fractional cylinder... - ; Now (e)ax contains the number of cylinders -.no_remainder: cmp eax,1024 - jna .ok_cyl - mov ax,1024 ; Max possible # -.ok_cyl: dec ax ; Convert to max cylinder no - pop ebx ; S(bl) H(bh) - shl ah,6 - or bl,ah - xchg ax,bx - shl eax,16 - mov ah,bl - mov al,4 ; Hard disk boot - mov byte [dsp_drive], 80h ; Drive 80h = hard disk - -.type_found: - TRACER 'T' - mov bl,[sp_media] - and bl,0F0h ; Copy controller info bits - or al,bl - mov [dsp_media],al ; Emulation type - shr eax,8 - mov [dsp_chs],eax ; C/H/S geometry - mov ax,[sp_devspec] ; Copy device spec - mov [dsp_devspec],ax - mov al,[sp_controller] ; Copy controller index - mov [dsp_controller],al - - TRACER 'V' - call vgaclearmode ; Reset video - - mov ax,4C00h ; Enable emulation and boot - mov si,dspec_packet - mov dl,[DriveNumber] - lss sp,[InitStack] - TRACER 'X' - - call int13 - - ; If this returns, we have problems -.bad_image: - mov si,err_disk_image - call writestr - jmp enter_command - -; -; Look for the highest seen H/S geometry -; We compute cylinders separately -; -.hs_check: - mov bl,[si] ; Head # - cmp bl,ah - jna .done_track - mov ah,bl ; New highest head # -.done_track: mov bl,[si+1] - and bl,3Fh ; Sector # - cmp bl,al - jna .done_sector - mov al,bl -.done_sector: ret - - - ; ----------------------------------------------------------------------------- ; Common modules ; ----------------------------------------------------------------------------- @@ -1352,33 +1204,8 @@ err_disk_image db 'Cannot load disk image (invalid file)?', CR, LF, 0 ; alignz 4 exten_table: db '.cbt' ; COMBOOT (specific) - db '.img' ; Disk image db '.bin' ; CD boot sector db '.com' ; COMBOOT (same as DOS) db '.c32' ; COM32 exten_table_end: dd 0, 0 ; Need 8 null bytes here - -; -; Floppy image table -; - alignz 4 -img_table_count equ 3 -img_table: - dd 1200*1024 ; 1200K floppy - db 1 ; Emulation type - db 80-1 ; Max cylinder - db 15 ; Max sector - db 2-1 ; Max head - - dd 1440*1024 ; 1440K floppy - db 2 ; Emulation type - db 80-1 ; Max cylinder - db 18 ; Max sector - db 2-1 ; Max head - - dd 2880*1024 ; 2880K floppy - db 3 ; Emulation type - db 80-1 ; Max cylinder - db 36 ; Max sector - db 2-1 ; Max head diff --git a/core/ldlinux.asm b/core/ldlinux.asm index f62f55b2..a2f859d0 100644 --- a/core/ldlinux.asm +++ b/core/ldlinux.asm @@ -37,6 +37,8 @@ ROOT_FS_OPS: dd vfat_fs_ops extern ext2_fs_ops dd ext2_fs_ops + extern ntfs_fs_ops + dd ntfs_fs_ops extern btrfs_fs_ops dd btrfs_fs_ops dd 0 diff --git a/core/syslinux.ld b/core/syslinux.ld index 40a01394..11adbcb8 100644 --- a/core/syslinux.ld +++ b/core/syslinux.ld @@ -280,8 +280,9 @@ SECTIONS __ctors_lma = __ctors_vma + __text_lma - __text_vma; .ctors : AT(__ctors_lma) { __ctors_start = .; - KEEP (*(SORT(.ctors.*))) - KEEP (*(.ctors)) + KEEP (*(SORT(.preinit_array*))) + KEEP (*(SORT(.init_array*))) + KEEP (*(SORT(.ctors*))) __ctors_end = .; } @@ -289,8 +290,8 @@ SECTIONS __dtors_lma = __dtors_vma + __text_lma - __text_vma; .dtors : AT(__dtors_lma) { __dtors_start = .; - KEEP (*(SORT(.dtors.*))) - KEEP (*(.dtors)) + KEEP (*(SORT(.fini_array*))) + KEEP (*(SORT(.dtors*))) __dtors_end = .; } diff --git a/core/ui.inc b/core/ui.inc index 0a4bb569..631860f8 100644 --- a/core/ui.inc +++ b/core/ui.inc @@ -681,11 +681,8 @@ is_bad_image: %else is_bss_sector equ is_bad_image %endif -%if IS_ISOLINUX - ; ok -%else -is_disk_image equ is_bad_image -%endif + +is_disk_image equ is_bad_image ; No longer supported section .data16 boot_prompt db 'boot: ', 0 |