summaryrefslogtreecommitdiffstats
path: root/src/ntfs_dir.c
diff options
context:
space:
mode:
authorChristophe Grenier <grenier@cgsecurity.org>2007-10-29 22:38:52 +0100
committerChristophe Grenier <grenier@cgsecurity.org>2007-10-29 22:38:52 +0100
commit9928d99936105b4653d2d1b8ca74dc3ffba5c71e (patch)
tree06aa4f5e9f0055027c6fb54dd47a8414cf2fba32 /src/ntfs_dir.c
First version in git
Diffstat (limited to 'src/ntfs_dir.c')
-rw-r--r--src/ntfs_dir.c468
1 files changed, 468 insertions, 0 deletions
diff --git a/src/ntfs_dir.c b/src/ntfs_dir.c
new file mode 100644
index 0000000..58b6f90
--- /dev/null
+++ b/src/ntfs_dir.c
@@ -0,0 +1,468 @@
+/**
+ * ntfs_dir.c - Part of the TestDisk project.
+ *
+ * Copyright (c) 2004-2007 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 <config.h>
+#endif
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <stdio.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_TIME_H
+#include <time.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#ifdef HAVE_MACHINE_ENDIAN_H
+#include <machine/endian.h>
+#endif
+#include <ctype.h> /* isalpha */
+#include <stdarg.h>
+#include "types.h"
+
+#ifdef HAVE_LIBNTFS
+#include <ntfs/volume.h>
+#include <ntfs/attrib.h>
+#ifdef HAVE_NTFS_VERSION_H
+#include <ntfs/version.h>
+#endif
+#endif
+
+#include "common.h"
+#include "intrf.h"
+#include "ntfs.h"
+#include "dir.h"
+#include "ntfs_dir.h"
+#include "io_redir.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)
+
+/*
+ * 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;
+}
+
+/**
+ * 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;
+ }
+
+ if (ntfs_ucstombs (name, name_len, &filename, MAX_PATH) < 0) {
+ log_error("Cannot represent filename in current locale.\n");
+ goto free;
+ }
+
+ result = 0; /* These are successful */
+ if (filename[0] == '$') /* system */
+ 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));
+ filename_cpy(new_file->name,filename,(MAX_PATH<sizeof(new_file->name)?MAX_PATH:sizeof(new_file->name)));
+ 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);
+ memcpy(new_file,dir_data->local_dir,l1);
+ 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);
+ if(partition->boot_sector!=0)
+ io_redir_del_redir(disk_car,partition->part_offset);
+ 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;
+ if(partition->boot_sector!=0)
+ io_redir_add_redir(disk_car,partition->part_offset,DEFAULT_SECTOR_SIZE,partition->part_offset+(uint64_t)partition->boot_sector*disk_car->sector_size,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) {
+ if(partition->boot_sector!=0)
+ io_redir_del_redir(disk_car,partition->part_offset);
+ 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;
+ strncpy(dir_data->current_directory,"/",sizeof(dir_data->current_directory));
+ dir_data->current_inode=FILE_root;
+ dir_data->verbose=verbose;
+ 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
+}