/** * ntfs_dir.c - Part of the TestDisk project. * * Copyright (c) 2004-2008 Christophe Grenier * * Original version comes from the Linux-NTFS project. * Copyright (c) 2003 Lode Leroy * Copyright (c) 2003 Anton Altaparmakov * Copyright (c) 2003 Richard Russon * * 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 (in the main directory of the Linux-NTFS * distribution in the file COPYING); if not, write to the Free Software * Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #include #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_TIME_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_MACHINE_ENDIAN_H #include #endif #ifdef HAVE_ICONV_H #include #endif #include /* isalpha */ #include #include "types.h" #ifdef HAVE_LIBNTFS #include #include #ifdef HAVE_NTFS_VERSION_H #include #endif #endif #include "common.h" #include "intrf.h" #include "ntfs.h" #include "dir.h" #include "ntfs_dir.h" #include "ntfs_utl.h" #include "ntfs_inc.h" #include "log.h" #ifdef HAVE_LIBNTFS #define MAX_PATH 1024 #define PATH_SEP '/' #define NTFS_DT_DIR 4 #define NTFS_DT_REG 8 #define NTFS_TIME_OFFSET ((s64)(369 * 365 + 89) * 24 * 3600 * 10000000) #ifndef FILE_first_user #define FILE_first_user 16 #endif /* * This is the "ntfs_filldir" function type, used by ntfs_readdir() to let * the caller specify what kind of dirent layout it wants to have. * This allows the caller to read directories into their application or * to have different dirent layouts depending on the binary type. */ typedef int (*ntfs_filldir_t)(void *dirent, const ntfschar *name, const int name_len, const int name_type, const s64 pos, const MFT_REF mref, const unsigned dt_type); extern struct ntfs_device_operations ntfs_device_testdisk_io_ops; extern int ntfs_readdir(ntfs_inode *dir_ni, s64 *pos, void *dirent, ntfs_filldir_t filldir); extern u64 ntfs_inode_lookup_by_name(ntfs_inode *dir_ni, const ntfschar *uname, const int uname_len); static time_t ntfs2utc (s64 ntfstime); static int ntfs_td_list_entry( struct ntfs_dir_struct *ls, const ntfschar *name, const int name_len, const int name_type, const s64 pos, const MFT_REF mref, const unsigned dt_type); static file_data_t *ntfs_dir(disk_t *disk_car, const partition_t *partition, dir_data_t *dir_data, const unsigned long int cluster); static int ntfs_copy(disk_t *disk_car, const partition_t *partition, dir_data_t *dir_data, const file_data_t *file); static void dir_partition_ntfs_close(dir_data_t *dir_data); /** * index_get_size - Find the INDX block size from the index root * @inode: Inode of the directory to be checked * * Find the size of a directory's INDX block from the INDEX_ROOT attribute. * * Return: n Success, the INDX blocks are n bytes in size * 0 Error, not a directory */ static int index_get_size(ntfs_inode *inode) { ATTR_RECORD *attr90; INDEX_ROOT *iroot; attr90 = find_first_attribute(AT_INDEX_ROOT, inode->mrec); if (!attr90) return 0; // not a directory iroot = (INDEX_ROOT*)((u8*)attr90 + le16_to_cpu(attr90->value_offset)); return iroot->index_block_size; } /** * ntfs2utc - Convert an NTFS time to Unix time * @time: An NTFS time in 100ns units since 1601 * * NTFS stores times as the number of 100ns intervals since January 1st 1601 at * 00:00 UTC. This system will not suffer from Y2K problems until ~57000AD. * * Return: n A Unix time (number of seconds since 1970) */ static time_t ntfs2utc (s64 ntfstime) { return (ntfstime - (NTFS_TIME_OFFSET)) / 10000000; } #ifdef HAVE_ICONV static int ntfs_ucstoutf8(iconv_t cd, const ntfschar *ins, int ins_len, char **outs, int outs_len) { const char *inp; char *outp; size_t inb_left, outb_left; if (cd == (iconv_t)(-1)) return -1; outp = *outs; inp = (const char *) ins; inb_left = ins_len << 1; // ntfschar is 16-bit outb_left = outs_len - 1; // reserve 1 byte for NUL if (iconv(cd, (char**)&inp, &inb_left, &outp, &outb_left) == (size_t)(-1)) { // Regardless of the value of errno log_error("ntfs_ucstoutf8: iconv failed\n"); return -1; } *outp = '\0'; return 0; } #endif /** * ntfs_td_list_entry * FIXME: Should we print errors as we go along? (AIA) */ static int ntfs_td_list_entry( struct ntfs_dir_struct *ls, const ntfschar *name, const int name_len, const int name_type, const s64 pos, const MFT_REF mref, const unsigned dt_type) { char *filename = NULL; int result = 0; filename = calloc (1, MAX_PATH); if (!filename) { log_critical("ntfs_td_list_entry calloc failed\n"); return -1; } #ifdef HAVE_ICONV if (ntfs_ucstoutf8(ls->cd, name, name_len, &filename, MAX_PATH) < 0 && ntfs_ucstombs (name, name_len, &filename, MAX_PATH) < 0) { log_error("Cannot represent filename in current locale.\n"); goto free; } #else if (ntfs_ucstombs (name, name_len, &filename, MAX_PATH) < 0) { log_error("Cannot represent filename in current locale.\n"); goto free; } #endif result = 0; /* These are successful */ if (MREF(mref) < FILE_first_user && filename[0] == '$') /* Hide system file */ goto free; /* Keep FILE_NAME_WIN32 and FILE_NAME_POSIX */ if ((name_type & FILE_NAME_WIN32_AND_DOS) == FILE_NAME_DOS) goto free; { s64 filesize = 0; ntfs_inode *ni; ntfs_attr_search_ctx *ctx = NULL; FILE_NAME_ATTR *file_name_attr; ATTR_RECORD *attr; result = -1; /* Everything else is bad */ ni = ntfs_inode_open(ls->vol, mref); if (!ni) goto release; ctx = ntfs_attr_get_search_ctx(ni, ni->mrec); if (!ctx) goto release; if (ntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) goto release; attr = ctx->attr; file_name_attr = (FILE_NAME_ATTR *)((char *)attr + le16_to_cpu(attr->value_offset)); if (!file_name_attr) goto release; if (dt_type != NTFS_DT_DIR) { if (!ntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) filesize = ntfs_get_attribute_value_length( ctx->attr); } { file_data_t *new_file=MALLOC(sizeof(*new_file)); memcpy(new_file->name,filename,(MAX_PATHname)?MAX_PATH:sizeof(new_file->name))); new_file->status=0; new_file->prev=ls->current_file; new_file->next=NULL; new_file->filestat.st_dev=0; new_file->filestat.st_ino=MREF(mref); new_file->filestat.st_mode = (dt_type == NTFS_DT_DIR?LINUX_S_IFDIR| LINUX_S_IRUGO | LINUX_S_IXUGO:LINUX_S_IFREG | LINUX_S_IRUGO); new_file->filestat.st_nlink=1; new_file->filestat.st_uid=0; new_file->filestat.st_gid=0; new_file->filestat.st_rdev=0; new_file->filestat.st_size=filesize; new_file->filestat.st_blksize=DEFAULT_SECTOR_SIZE; #ifdef HAVE_STRUCT_STAT_ST_BLOCKS if(new_file->filestat.st_blksize!=0) { new_file->filestat.st_blocks=(new_file->filestat.st_size+new_file->filestat.st_blksize-1)/new_file->filestat.st_blksize; } #endif new_file->filestat.st_atime=ntfs2utc(sle64_to_cpu(file_name_attr->last_access_time)); new_file->filestat.st_ctime=ntfs2utc(sle64_to_cpu(file_name_attr->creation_time)); new_file->filestat.st_mtime=ntfs2utc(sle64_to_cpu(file_name_attr->last_data_change_time)); new_file->prev=ls->current_file; new_file->next=NULL; /* log_debug("fat: new file %s de=%p size=%u\n",new_file->name,de,de->size); */ if(ls->current_file!=NULL) ls->current_file->next=new_file; else ls->dir_list=new_file; ls->current_file=new_file; } result = 0; release: /* Release atrtibute search context and close the inode. */ if (ctx) ntfs_attr_put_search_ctx(ctx); if (ni) ntfs_inode_close(ni); } free: free (filename); return result; } static file_data_t *ntfs_dir(disk_t *disk_car, const partition_t *partition, dir_data_t *dir_data, const unsigned long int cluster) { ntfs_inode *inode; s64 pos; struct ntfs_dir_struct *ls=(struct ntfs_dir_struct*)dir_data->private_dir_data; ls->dir_list=NULL; ls->current_file=NULL; inode = ntfs_inode_open (ls->vol, cluster); if (!inode) { log_error("ntfs_dir: ntfs_inode_open failed\n"); return NULL; } /* * We now are at the final path component. If it is a file just * list it. If it is a directory, list its contents. */ pos = 0; if (inode->mrec->flags & MFT_RECORD_IS_DIRECTORY) { if(ntfs_readdir(inode, &pos, ls, (ntfs_filldir_t)ntfs_td_list_entry)<0) { log_error("ntfs_readdir failed\n"); } } else log_critical("ntfs_readdir BUG not MFT_RECORD_IS_DIRECTORY\n"); /* Finished with the inode; release it. */ ntfs_inode_close(inode); return ls->dir_list; } static int ntfs_copy(disk_t *disk_car, const partition_t *partition, dir_data_t *dir_data, const file_data_t *file) { const unsigned long int first_inode=file->filestat.st_ino; ntfs_inode *inode; struct ntfs_dir_struct *ls=(struct ntfs_dir_struct*)dir_data->private_dir_data; inode = ntfs_inode_open (ls->vol, first_inode); if (!inode) { log_error("ntfs_copy: ntfs_inode_open failed\n"); return -1; } { const int bufsize = 4096; char *buffer; char *new_file; ntfs_attr *attr; FILE *f_out; s64 bytes_read, written; s64 offset; u32 block_size; buffer = MALLOC(bufsize); if (!buffer) return -2; attr = ntfs_attr_open(inode, AT_DATA, NULL, 0); if (!attr) { log_error("Cannot find attribute type 0x%lx.\n", (long) AT_DATA); free(buffer); return -3; } if ((inode->mft_no < 2) && (attr->type == AT_DATA)) block_size = ls->vol->mft_record_size; else if (attr->type == AT_INDEX_ALLOCATION) block_size = index_get_size(inode); else block_size = 0; { int l1=strlen(dir_data->local_dir); int l2=strlen(dir_data->current_directory); new_file=MALLOC(l1+l2+1); l1=filename_convert(new_file,dir_data->local_dir,l1+1); filename_convert(new_file+l1,dir_data->current_directory,l2+1); } f_out=create_file(new_file); if(!f_out) { log_critical("Can't create file %s: %s\n",new_file, strerror(errno)); free(new_file); return -4; } offset = 0; for (;;) { if (block_size > 0) { // These types have fixup bytes_read = ntfs_attr_mst_pread(attr, offset, 1, block_size, buffer); bytes_read *= block_size; } else { bytes_read = ntfs_attr_pread(attr, offset, bufsize, buffer); } //ntfs_log_info("read %lld bytes\n", bytes_read); if (bytes_read == -1) { log_error("ERROR: Couldn't read file"); break; } if (!bytes_read) break; written = fwrite(buffer, 1, bytes_read, f_out); if (written != bytes_read) { log_error("ERROR: Couldn't output all data!"); break; } offset += bytes_read; } fclose(f_out); set_date(new_file, file->filestat.st_atime, file->filestat.st_mtime); ntfs_attr_close(attr); free(new_file); free(buffer); } /* Finished with the inode; release it. */ ntfs_inode_close(inode); return 0; } static void dir_partition_ntfs_close(dir_data_t *dir_data) { const partition_t *partition; disk_t *disk_car; struct ntfs_dir_struct *ls=(struct ntfs_dir_struct*)dir_data->private_dir_data; /* ntfs_umount() will invoke ntfs_device_free() for us. */ disk_car=ls->my_data->disk_car; partition=ls->my_data->partition; ntfs_umount(ls->vol, FALSE); free(ls->my_data); #ifdef HAVE_ICONV if (ls->cd != (iconv_t)(-1)) iconv_close(ls->cd); #endif free(ls); } #endif int dir_partition_ntfs_init(disk_t *disk_car, const partition_t *partition, dir_data_t *dir_data, const int verbose) { #ifdef HAVE_LIBNTFS struct ntfs_device *dev; my_data_t *my_data=NULL; ntfs_volume *vol=NULL; #ifdef NTFS_LOG_LEVEL_VERBOSE ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE); ntfs_log_set_handler(ntfs_log_handler_stderr); #endif dev = ntfs_device_alloc("/", 0, &ntfs_device_testdisk_io_ops, NULL); if (dev) { my_data=MALLOC(sizeof(*my_data)); my_data->partition=partition; my_data->disk_car=disk_car; my_data->offset=0; dev->d_private=my_data; /* Call ntfs_device_mount() to do the actual mount. */ #ifdef MS_RDONLY vol = ntfs_device_mount(dev, MS_RDONLY); #else vol = ntfs_device_mount(dev, NTFS_MNT_RDONLY); #endif #ifdef HAVE_NTFS_VOLUME_STARTUP if(!vol) { #ifdef MS_RDONLY vol = ntfs_volume_startup(dev, MS_RDONLY); #else vol = ntfs_volume_startup(dev, NTFS_MNT_RDONLY); #endif if(vol) { log_warning("NTFS filesystem need to be repaired.\n"); } } #endif } if (!vol) { free(my_data); ntfs_device_free(dev); return -1; } if (vol->flags & VOLUME_IS_DIRTY) { log_warning("NTFS Volume is dirty.\n"); } { struct ntfs_dir_struct *ls=(struct ntfs_dir_struct *)MALLOC(sizeof(*ls)); ls->dir_list=NULL; ls->current_file=NULL; ls->vol=vol; ls->my_data=my_data; #ifdef HAVE_ICONV if ((ls->cd = iconv_open("UTF-8", "UTF-16LE")) == (iconv_t)(-1)) { log_error("ntfs_ucstoutf8: iconv_open failed\n"); } #endif strncpy(dir_data->current_directory,"/",sizeof(dir_data->current_directory)); dir_data->current_inode=FILE_root; dir_data->verbose=verbose; dir_data->capabilities=0; dir_data->get_dir=ntfs_dir; dir_data->copy_file=ntfs_copy; dir_data->close=&dir_partition_ntfs_close; dir_data->local_dir=NULL; dir_data->private_dir_data=ls; } return 0; #else return -2; #endif } const char*td_ntfs_version(void) { #ifdef HAVE_LIBNTFS #ifdef HAVE_NTFS_LIBNTFS_VERSION return ntfs_libntfs_version(); #else return "available"; #endif #else return "none"; #endif }