/* pfs-fs.c:
 *
 ****************************************************************
 * Copyright (C) 2002, 2003 Scott Parish
 * Copyright (C) 2003 Tom Lord
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "config-options.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/machine/types.h"
#include "hackerlab/mem/alloc-limits.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fmt/cvt.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/vu/safe.h"
#include "tla/libfsutils/dir-as-cwd.h"
#include "tla/libfsutils/dir-listing.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libfsutils/copy-file.h"
#include "tla/libfsutils/file-contents.h"
#include "tla/libarch/archives.h"
#include "tla/libarch/exec.h"
#include "tla/libarch/pfs-fs.h"



struct arch_pfs_fs_session
{
  struct arch_pfs_session pfs;
  char * cwd;
};



/* __STDC__ prototypes for static functions */
static t_uchar * fs_abs_path (const t_uchar * cwd,
                              const t_uchar *path);
static int fs_get (struct arch_pfs_fs_session * p,
                   int data_fd,
                   const t_uchar * path,
                   int soft_errors);
static int pfs_get_file (struct arch_pfs_session * p,
                         int out_fd,
                         const t_uchar * path,
                         int soft_errors);
static t_uchar * pfs_file_contents (struct arch_pfs_session * p,
                                    const t_uchar * path,
                                    int soft_errors);
static rel_table pfs_directory_files (struct arch_pfs_session * p,
                                      const t_uchar * path,
                                      int soft_errors);
static int pfs_put_file (struct arch_pfs_session *p,
                         const t_uchar * path,
                         mode_t perms,
                         int data_fd,
                         int soft_errors);
static int pfs_mkdir (struct arch_pfs_session * p,
                      const t_uchar * path,
                      mode_t mode,
                      int soft_errors);
static int pfs_file_exists (struct arch_pfs_session * p,
                            const t_uchar * path);
static int pfs_is_dir (struct arch_pfs_session * p,
                       const t_uchar * path);
static int pfs_rename (struct arch_pfs_session * p,
                       t_uchar ** errstr,
                       const t_uchar * from_rel,
                       const t_uchar * to_rel,
                       int soft_errors);
static int pfs_rmdir (struct arch_pfs_session * p,
                      const t_uchar * path,
                      const int soft_errors);
static int pfs_rm (struct arch_pfs_session * p,
                   const t_uchar * path,
                   int soft_errors);
static t_uchar * dirfold (t_uchar *dir);



struct arch_pfs_vtable fs_pfs_fns =
  {
    pfs_file_exists,
    pfs_is_dir,

    pfs_file_contents,
    pfs_get_file,
    pfs_directory_files,

    pfs_put_file,

    pfs_mkdir,
    pfs_rename,

    pfs_rmdir,
    pfs_rm,
  };



struct arch_pfs_session *
arch_pfs_fs_connect (const t_uchar * uri)
{
  struct arch_pfs_fs_session * answer = 0;

  invariant (!!uri);

  answer = (struct arch_pfs_fs_session *)lim_malloc (0, sizeof (*answer));
  mem_set0 ((t_uchar *)answer, sizeof (*answer));

  answer->pfs.vtable = &fs_pfs_fns;
  answer->cwd = directory_as_cwd (uri);

  return &answer->pfs;
}



static t_uchar *
fs_abs_path (const t_uchar * cwd,
             const t_uchar *path)
{
  t_uchar * ap;
  if (path[0] != '/')
    ap = str_alloc_cat_many (0, cwd, "/", path, str_end);
  else
    ap = str_save (0, path);

  return dirfold (ap);
}



static int
fs_get (struct arch_pfs_fs_session * p,
        int data_fd,
        const t_uchar * path,
        int soft_errors)
{
  t_uchar * file = 0;
  int errn;
  int fd;

  file = fs_abs_path (p->cwd, path);
  fd = vu_open (&errn, file , O_RDONLY, 0444);

  if (fd < 0)
    {
      if (soft_errors)
        {
          lim_free (0, file);
	  return -1;
        }
      safe_printfmt (2, "Failed to open files: %s\n", file);
      exit (2);
    }

  safe_lseek (fd, (off_t)0, SEEK_SET);
  copy_fd (fd, data_fd);
  safe_close (fd);

  lim_free (0, file);

  return 0;
}



static int
pfs_get_file (struct arch_pfs_session * p,
              int out_fd,
              const t_uchar * path,
              int soft_errors)
{
  struct arch_pfs_fs_session * pfs = (struct arch_pfs_fs_session *)p;

  return fs_get (pfs, out_fd, path, soft_errors);
}


static t_uchar *
pfs_file_contents (struct arch_pfs_session * p,
                   const t_uchar * path,
                   int soft_errors)
{
  struct arch_pfs_fs_session * pfs = (struct arch_pfs_fs_session *)p;
  int errn;
  int fd;
  t_uchar * answer = 0;
  t_uchar * file = 0;

  file = fs_abs_path (pfs->cwd, path);
  fd = vu_open (&errn, file , O_RDONLY, 0444);

  if (fd == -1)
    {
      if (soft_errors)
        {
          lim_free (0, file);
	  return 0;
        }
      safe_printfmt (2, "Failed to open files: %s\n", fs_abs_path (pfs->cwd, path));
      exit (2);
    }

  safe_lseek (fd, (off_t)0, SEEK_SET);
  answer = fd_contents (fd);

  safe_close (fd);
  lim_free (0, file);

  return answer;
}


static rel_table
pfs_directory_files (struct arch_pfs_session * p,
                     const t_uchar * path,
                     int soft_errors)
{
  struct arch_pfs_fs_session * pfs = (struct arch_pfs_fs_session *)p;
  rel_table answer = rel_table_nil;
  t_uchar * dir = 0;

  dir = fs_abs_path (pfs->cwd, path);

  if (!safe_file_is_directory_following (dir))
    {
      if (soft_errors)
        return rel_table_nil;
      else
        {
          safe_printfmt (2, "not a directory (%s)\n", dir);
          exit (2);
        }
    }

  answer = directory_files (dir);

  lim_free (0, dir);

  return answer;
}



static int
pfs_put_file (struct arch_pfs_session *p,
              const t_uchar * path,
              mode_t perms,
              int data_fd,
              int soft_errors)
{
  struct arch_pfs_fs_session * pfs = (struct arch_pfs_fs_session *)p;
  t_uchar * file = 0;
  int errn;
  int fd;

  file = fs_abs_path (pfs->cwd, path);
  fd = vu_open (&errn, file , O_WRONLY | O_CREAT | O_EXCL, perms);

  if (fd == -1)
    { 
      if (soft_errors)
        {
          lim_free (0, file);
          return -1;
        }
      safe_printfmt (2, "Failed to open files: %s\n", file);
      exit (2);
    }


  copy_fd (data_fd, fd);

  safe_close (fd);
  lim_free (0, file);

  return 0;
}


static int
pfs_mkdir (struct arch_pfs_session * p,
           const t_uchar * path,
           mode_t mode,
           int soft_errors)
{
  struct arch_pfs_fs_session * pfs = (struct arch_pfs_fs_session *)p;
  t_uchar * dir = 0;
  int errn;
  int answer;

  dir = fs_abs_path (pfs->cwd, path);

  answer = vu_mkdir (&errn, dir , mode);

  if (answer && !soft_errors)
    {
      safe_printfmt (2, "Failed to create directory: %s\n", dir);
      exit (2);
    }

  lim_free (0, dir);

  return answer;
}


static int
pfs_file_exists (struct arch_pfs_session * p,
                 const t_uchar * path)
{
  struct arch_pfs_fs_session * pfs = (struct arch_pfs_fs_session *)p;
  t_uchar * abs_path = 0;
  int answer;

  abs_path = fs_abs_path (pfs->cwd, path);

  answer = !safe_access (abs_path, F_OK);

  lim_free (0, abs_path);

  return answer;
}


static int
pfs_is_dir (struct arch_pfs_session * p,
            const t_uchar * path)
{
  struct arch_pfs_fs_session * pfs = (struct arch_pfs_fs_session *)p;
  t_uchar * abs_path = 0;
  struct stat stat_buf;
  int errn;
  int answer;

  abs_path = fs_abs_path (pfs->cwd, path);

  if (vu_stat (&errn, abs_path, &stat_buf))
    answer =  -1;
  else
    answer = !!S_ISDIR (stat_buf.st_mode);

  lim_free (0, abs_path);

  return answer;
}


static int
pfs_rename (struct arch_pfs_session * p,
            t_uchar ** errstr,
            const t_uchar * from_rel,
            const t_uchar * to_rel,
            int soft_errors)
{
  struct arch_pfs_fs_session * pfs = (struct arch_pfs_fs_session *)p;
  int ret = 0;
  int errn;
  t_uchar * from = 0;
  t_uchar * to = 0;

  from = fs_abs_path (pfs->cwd, from_rel);
  to = fs_abs_path (pfs->cwd, to_rel);

  ret = vu_rename (&errn, from, to);

  if (ret)
    {
      if (soft_errors)
        *errstr = str_save (0, "could not rename file.");
      else
        {
          safe_printfmt (2, "Failed to rename file: %s => %s\n", from, to);
          exit (2);
        }
    }


  lim_free (0, from);
  lim_free (0, to);

  return ret;
}


static int
pfs_rmdir (struct arch_pfs_session * p,
           const t_uchar * path,
           const int soft_errors)
{
  struct arch_pfs_fs_session * pfs = (struct arch_pfs_fs_session *)p;
  int errn;
  int ret;
  t_uchar * dir = 0;

  dir = fs_abs_path (pfs->cwd, path);

  ret = vu_rmdir (&errn, dir);

  if (ret && !soft_errors)
    {
      safe_printfmt (2, "Failed to remove directory: %s\n", dir);
      exit (2);
    }

  lim_free (0, dir);

  return ret;
}


static int
pfs_rm (struct arch_pfs_session * p,
        const t_uchar * path,
        int soft_errors)
{
  struct arch_pfs_fs_session * pfs = (struct arch_pfs_fs_session *)p;
  int errn;
  int ret;
  char * file = 0;

  file = fs_abs_path (pfs->cwd, path);

  ret = vu_unlink (&errn, file);

  if (ret && !soft_errors)
    {
      safe_printfmt (2, "Failed to remove file: %s\n", file);
      exit (2);
    }

  lim_free (0, file);

  return ret;
}



static t_uchar *
dirfold (t_uchar *dir)
{
  t_uchar * buf;
  t_uchar * this;
  t_uchar * next;
  int dir_i = 0;

  this = next = buf = str_save (0, dir);
  while ((this = str_separate (&next, "/")) != NULL)
    {
      if (str_length (this) == 0 || (str_length (this) == 1 && this[0] == '.'))
        continue;
      else if (str_length (this) == 2 && *this == '.' && this[1] == '.')
        {
          if (dir_i > 0)
            dir_i = (int)((char *)strrchr (dir, '/') - (char *)dir);
          dir[dir_i] = 0;
        }
      else
        {
          dir[dir_i++] = '/';
          strcpy (dir + dir_i, this);
          dir_i += str_length (this);
        }
    }
  lim_free (0, buf);

  if (!str_length (dir))
      str_cpy (dir, "/");

  return dir;
}




/* tag: Tom Lord Thu Jun  5 15:23:06 2003 (pfs-fs.c)
 */
