| /* |
| * Copyright (C) 2011-2012 Paulo Alcantara <[email protected]> |
| * |
| * 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; |
| static inline enum dirent_type get_inode_mode(struct ntfs_mft_record *mrec); |
| 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); |
| static inline uint8_t *mapping_chunk_init(struct ntfs_attr_record *attr,struct mapping_chunk *chunk,uint32_t *offset); |
| static int parse_data_run(const void *stream, uint32_t *offset, uint8_t *attr_len, struct mapping_chunk *chunk); |
| |
| /*** 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; |
| } |
| |
| /* AndyAlex: read and validate single MFT record. Keep in mind that MFT itself can be fragmented */ |
| static struct ntfs_mft_record *ntfs_mft_record_lookup_any(struct fs_info *fs, |
| uint32_t file, block_t *out_blk, bool is_v31) |
| { |
| const uint64_t mft_record_size = NTFS_SB(fs)->mft_record_size; |
| uint8_t *buf = NULL; |
| const uint32_t mft_record_shift = ilog2(mft_record_size); |
| const uint32_t clust_byte_shift = NTFS_SB(fs)->clust_byte_shift; |
| uint64_t next_offset = 0; |
| uint64_t lcn = 0; |
| block_t blk = 0; |
| uint64_t offset = 0; |
| |
| struct ntfs_mft_record *mrec = NULL, *lmrec = NULL; |
| uint64_t start_blk = 0; |
| struct ntfs_attr_record *attr = NULL; |
| uint8_t *stream = NULL; |
| uint32_t attr_offset = 0; |
| uint8_t *attr_len = NULL; |
| struct mapping_chunk chunk; |
| |
| int err = 0; |
| |
| /* determine MFT record's LCN */ |
| uint64_t vcn = (file << mft_record_shift >> clust_byte_shift); |
| dprintf("in %s(%s)\n", __func__,(is_v31?"v3.1":"v3.0")); |
| if (0==vcn) { |
| lcn = NTFS_SB(fs)->mft_lcn; |
| } else do { |
| dprintf("%s: looking for VCN %u for MFT record %u\n", __func__,(unsigned)vcn,(unsigned)file); |
| mrec = NTFS_SB(fs)->mft_record_lookup(fs, 0, &start_blk); |
| if (!mrec) {dprintf("%s: read MFT(0) failed\n", __func__); break;} |
| lmrec = mrec; |
| if (get_inode_mode(mrec) != DT_REG) {dprintf("%s: $MFT is not a file\n", __func__); break;} |
| attr = ntfs_attr_lookup(fs, NTFS_AT_DATA, &mrec, lmrec); |
| if (!attr) {dprintf("%s: $MFT have no data attr\n", __func__); break;} |
| if (!attr->non_resident) {dprintf("%s: $MFT data attr is resident\n", __func__); break;} |
| attr_len = (uint8_t *)attr + attr->len; |
| stream = mapping_chunk_init(attr, &chunk, &attr_offset); |
| while (true) { |
| err = parse_data_run(stream, &attr_offset, attr_len, &chunk); |
| if (err) {dprintf("%s: $MFT data run parse failed with error %d\n", __func__,err); break;} |
| if (chunk.flags & MAP_UNALLOCATED) continue; |
| if (chunk.flags & MAP_END) break; |
| if (chunk.flags & MAP_ALLOCATED) { |
| dprintf("%s: Chunk: VCN=%u, LCN=%u, len=%u\n", __func__,(unsigned)chunk.vcn,(unsigned)chunk.lcn,(unsigned)chunk.len); |
| if ((vcn>=chunk.vcn)&&(vcn<chunk.vcn+chunk.len)) { |
| lcn=vcn-chunk.vcn+chunk.lcn; |
| dprintf("%s: VCN %u for MFT record %u maps to lcn %u\n", __func__,(unsigned)vcn,(unsigned)file,(unsigned)lcn); |
| break; |
| } |
| chunk.vcn += chunk.len; |
| } |
| } |
| } while(false); |
| if (mrec!=NULL) free(mrec); |
| mrec = NULL; |
| if (0==lcn) { |
| dprintf("%s: unable to map VCN %u for MFT record %u\n", __func__,(unsigned)vcn,(unsigned)file); |
| return NULL; |
| } |
| |
| /* determine MFT record's block number */ |
| blk = (lcn << clust_byte_shift >> BLOCK_SHIFT(fs)); |
| offset = (file << mft_record_shift) % BLOCK_SIZE(fs); |
| |
| /* Allocate buffer */ |
| buf = (uint8_t *)malloc(mft_record_size); |
| if (!buf) {malloc_error("uint8_t *");return 0;} |
| |
| /* Read block */ |
| err = ntfs_read(fs, buf, mft_record_size, mft_record_size, &blk, |
| &offset, &next_offset, &lcn); |
| if (err) { |
| dprintf("%s: error read block %u from cache\n", __func__, blk); |
| printf("Error while reading from cache.\n"); |
| free(buf); |
| return NULL; |
| } |
| |
| /* Process fixups and make structure pointer */ |
| ntfs_fixups_writeback(fs, (struct ntfs_record *)buf); |
| mrec = (struct ntfs_mft_record *)buf; |
| |
| /* check if it has a valid magic number and record number */ |
| if (mrec->magic != NTFS_MAGIC_FILE) mrec = NULL; |
| if (mrec && is_v31) if (mrec->mft_record_no != file) mrec = NULL; |
| if (mrec!=NULL) { |
| if (out_blk) { |
| *out_blk = (file << mft_record_shift >> BLOCK_SHIFT(fs)); /* update record starting block */ |
| } |
| return mrec; /* found MFT record */ |
| } |
| |
| /* Invalid record */ |
| dprintf("%s: MFT record %u is invalid\n", __func__, (unsigned)file); |
| free(buf); |
| return NULL; |
| } |
| |
| static struct ntfs_mft_record *ntfs_mft_record_lookup_3_0(struct fs_info *fs, |
| uint32_t file, block_t *blk) |
| { |
| return ntfs_mft_record_lookup_any(fs,file,blk,false); |
| } |
| |
| static struct ntfs_mft_record *ntfs_mft_record_lookup_3_1(struct fs_info *fs, |
| uint32_t file, block_t *blk) |
| { |
| return ntfs_mft_record_lookup_any(fs,file,blk,true); |
| } |
| |
| 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; |
| const int byte_shift = 8; |
| int mask; |
| 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--) |
| res = (res << byte_shift) | *byte--; |
| |
| 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 = ir->index.entries_offset; |
| |
| idx_root_next_entry: |
| if (readdir_state->in_idx_root) { |
| ie = (struct ntfs_idx_entry *)((uint8_t *)&ir->index + 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 += 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, |
| .open_config = generic_open_config, |
| .readdir = ntfs_readdir, |
| .iget_root = ntfs_iget_root, |
| .iget = ntfs_iget, |
| .next_extent = ntfs_next_extent, |
| .fs_uuid = NULL, |
| }; |