/**
 * 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 <stdarg.h>
#include "types.h"

#ifdef HAVE_LIBNTFS
#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_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_list_entry
 * FIXME: Should we print errors as we go along? (AIA)
 */
static int ntfs_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_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));
      memcpy(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;
}

/**
 * ntfs_mount - open ntfs volume
 * @name:	name of device/file to open
 * @rwflag:	optional mount flags
 *
 * This function mounts an ntfs volume. @name should contain the name of the
 * device/file to mount as the ntfs volume.
 *
 * @rwflags is an optional second parameter. The same flags are used as for
 * the mount system call (man 2 mount). Currently only the following flag
 * is implemented:
 *	MS_RDONLY	- mount volume read-only
 *
 * The function opens the device or file @name and verifies that it contains a
 * valid bootsector. Then, it allocates an ntfs_volume structure and initializes
 * some of the values inside the structure from the information stored in the
 * bootsector. It proceeds to load the necessary system files and completes
 * setting up the structure.
 *
 * Return the allocated volume structure on success and NULL on error with
 * errno set to the error code.
 *
 * Note, that a copy is made of @name, and hence it can be discarded as
 * soon as the function returns.
 */

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_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;
    new_file=MALLOC(strlen(dir_data->local_dir)+strlen(dir_data->current_directory)+1);
    strcpy(new_file,dir_data->local_dir);
    strcat(new_file,dir_data->current_directory);
    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 debug)
{
#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. */
    vol = ntfs_device_mount(dev, MS_RDONLY);
#ifdef HAVE_NTFS_VOLUME_STARTUP
    if(!vol) {
      vol = ntfs_volume_startup(dev, MS_RDONLY);
      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->debug=debug;
    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
}


syntax highlighted by Code2HTML, v. 0.9.1