/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2013 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** 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.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
** This file contains routines related to working with the filesystem.
*/

#if !defined _XOPEN_SOURCE
#  define _XOPEN_SOURCE 500 /* for lstat() on Linux */
#endif
#if !defined _BSD_SOURCE
# define _BSD_SOURCE /* alternate for lstat(), possibly more
                        portable than _XOPEN_SOURCE
                     */
#endif

#include <sqlite3.h>
#include <assert.h>
#include <string.h> /* strlen() */
#include <stddef.h> /* NULL on linux */
#include <ctype.h>

#include "fossil/fossil.h"
#include "fsl_internal.h"

#ifdef _WIN32
# include <direct.h>
# include <windows.h>
# include <sys/utime.h>
#else
# include <errno.h>
# include <unistd.h> /* access(2) */
# include <sys/time.h>
# include <sys/stat.h>
#endif




/*
** Wrapper around the access() system call.
*/
int fsl_file_access(const char *zFilename, int flags){
#ifdef _WIN32
  wchar_t *zMbcs = (wchar_t *)fsl_utf8_to_filename(zFilename);
#define ACC _waccess
#else
  char *zMbcs = (char*)fsl_utf8_to_filename(zFilename);
#define ACC access
#endif
  int rc = zMbcs ? ACC(zMbcs, flags) : FSL_RC_OOM;
  if(zMbcs) fsl_filename_free(zMbcs);
  return rc;
#undef ACC
}


int fsl_getcwd(char *zBuf, fsl_size_t nBuf, fsl_size_t * outLen){
#ifdef _WIN32
  char *zPwdUtf8;
  fsl_size_t nPwd;
  fsl_size_t i;
  wchar_t zPwd[2000];
  if(!zBuf) return FSL_RC_MISUSE;
  /*
    http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx

    It says:

    Note File I/O functions in the Windows API convert "/" to "\" as
    part of converting the name to an NT-style name, except when using
    the "\\?\" prefix as detailed in the following sections.

    So the path-demangling bits below might do more damage they
    fix?
  */
  else if( _wgetcwd(zPwd, sizeof(zPwd)/sizeof(zPwd[0])-1)==0 ){
    /* FIXME: how to determine if FSL_RC_RANGE is a better
       return value?
    */
    return FSL_RC_IO;
  }
  zPwdUtf8 = fsl_filename_to_utf8(zPwd);
  if(!zPwdUtf8) return FSL_RC_OOM;
  nPwd = strlen(zPwdUtf8);
  if( nPwd > nBuf-1 ){
    fsl_filename_free(zPwdUtf8);
    return FSL_RC_RANGE;
  }
  for(i=0; zPwdUtf8[i]; i++) if( zPwdUtf8[i]=='\\' ) zPwdUtf8[i] = '/';
  memcpy(zBuf, zPwdUtf8, nPwd+1);
  fsl_filename_free(zPwdUtf8);
  if(outLen) *outLen = nPwd;
  return 0;
#else
  if(!zBuf) return FSL_RC_MISUSE;
  else if( getcwd(zBuf,
                  nBuf /*-1 not necessary: getcwd() NUL-terminates*/)==0 ){
    switch(errno){
      case ERANGE: return FSL_RC_RANGE;
      case ENOENT: /* ??? return FSL_RC_ACCESS; */
      default:
        return FSL_RC_IO;
    }
  }else{
    if(outLen) *outLen = fsl_strlen(zBuf);
    return 0;
  }
#endif
}

/*
** The file status information from the most recent stat() call.
**
** Use _stati64 rather than stat on windows, in order to handle files
** larger than 2GB.
*/
#if defined(_WIN32) && (defined(__MSVCRT__) || defined(_MSC_VER))
# undef stat
# define stat _stati64
#endif
/*
** On Windows S_ISLNK always returns FALSE.
*/
#if !defined(S_ISLNK)
# define S_ISLNK(x) (0)
#endif

/*
** Fill stat buf with information received from stat() or lstat().
** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on.
**
*/
static int fsl_stat(const char *zFilename, struct stat *buf, int isWd){
  int rc;
#if !defined(_WIN32)
  char *zMbcs = fsl_utf8_to_filename(zFilename);
  if(!zMbcs) rc = FSL_RC_OOM;
  else{
    if( isWd /* && FIXME: need access to: f->cache.allowSymlinks */ ){
      rc = lstat(zMbcs, buf);
    }else{
      rc = stat(zMbcs, buf);
    }
  }
#else
  wchar_t *zMbcs = fsl_utf8_to_filename(zFilename);
  rc = zMbcs ? _wstati64(zMbcs, buf) : FSL_RC_OOM;
#endif
  if(zMbcs) fsl_filename_free(zMbcs);
  return rc;
}

fsl_int64_t fsl_file_size(const char *zFilename){
  static struct stat fileStat;
  return ( 0 != fsl_stat(zFilename, &fileStat, 0) )
    ? -1
    : fileStat.st_size;
}

fsl_int64_t fsl_file_wd_size(const char *zFilename){
  static struct stat fileStat;
  return ( 0 != fsl_stat(zFilename, &fileStat, 1) )
    ? -1
    : fileStat.st_size;
}

fsl_time_t fsl_file_mtime(const char *zFilename){
  static struct stat fileStat;
  return ( 0 != fsl_stat(zFilename, &fileStat, 0) )
    ? -1
    : (fsl_time_t)fileStat.st_mtime;
}

fsl_time_t fsl_file_wd_mtime(const char *zFilename){
  static struct stat fileStat;
  return ( 0 != fsl_stat(zFilename, &fileStat, 1) )
    ? -1
    : (fsl_time_t)fileStat.st_mtime;
}

int fsl_file_wd_isfile_or_link(const char *zFilename){
  static struct stat fileStat;
  return ( 0 != fsl_stat(zFilename, &fileStat, 1) )
    ? 0
    : (S_ISREG(fileStat.st_mode) || S_ISLNK(fileStat.st_mode))
    ;
}

int fsl_file_isfile(const char *zFilename){
  static struct stat fileStat;
  return ( 0 != fsl_stat(zFilename, &fileStat, 0) )
    ? 0
    : S_ISREG(fileStat.st_mode);
}

int fsl_file_wd_isfile(const char *zFilename){
  static struct stat fileStat;
  return ( 0 != fsl_stat(zFilename, &fileStat, 1) )
    ? 0
    : S_ISREG(fileStat.st_mode);
}

/*
** Return true if zPath is an absolute pathname.  Return false
** if it is relative.
*/
char fsl_file_is_absolute_path(const char *zPath){
  if( zPath && zPath[0]=='/'
#if defined(_WIN32) || defined(__CYGWIN__)
      || zPath[0]=='\\'
      || (fsl_isalpha(zPath[0]) && zPath[1]==':'
           && (zPath[2]=='\\' || zPath[2]=='/'))
#endif
  ){
    return 1;
  }else{
    return 0;
  }
}

/*
** If the last component of the pathname in z[0]..z[j-1] is something
** other than ".." then back it out and return true.  If the last
** component is empty or if it is ".." then return false.
*/
static int fsl_backup_dir(const char *z, int *pJ){
  int j = *pJ;
  int i;
  if( j<=0 ) return 0;
  for(i=j-1; i>0 && z[i-1]!='/'; i--){}
  if( z[i]=='.' && i==j-2 && z[i+1]=='.' ) return 0;
  *pJ = i-1;
  return 1;
}



/*
** Simplify a filename by
**
**  * Convert all \ into / on windows and cygwin
**  * removing any trailing and duplicate /
**  * removing /./
**  * removing /A/../
**
** Changes are made in-place.  Return the new name length.
** If the slash parameter is non-zero, the trailing slash, if any,
** is retained.
*/
int fsl_file_simplify_name(char *z, int n, int slash){
  int i, j;
  if( n<0 ) n = fsl_strlen(z);
  /* On windows and cygwin convert all \ characters to / */
#if defined(_WIN32) || defined(__CYGWIN__)
  for(i=0; i<n; i++){
    if( z[i]=='\\' ) z[i] = '/';
  }
#endif
  /* Removing trailing "/" characters */
  if( !slash ){
    while( n>1 && z[n-1]=='/' ){ n--; }
  }

  /* Remove duplicate '/' characters.  Except, two // at the beginning
  ** of a pathname is allowed since this is important on windows. */
  for(i=j=1; i<n; i++){
    z[j++] = z[i];
    while( z[i]=='/' && i<n-1 && z[i+1]=='/' ) i++;
  }
  n = j;
  /* Skip over zero or more initial "./" sequences */
  for(i=0; i<n-1 && z[i]=='.' && z[i+1]=='/'; i+=2){}

  /* Begin copying from z[i] back to z[j]... */
  for(j=0; i<n; i++){
    if( z[i]=='/' ){
      /* Skip over internal "/." directory components */
      if( z[i+1]=='.' && (i+2==n || z[i+2]=='/') ){
        i += 1;
        continue;
      }

      /* If this is a "/.." directory component then back out the
      ** previous term of the directory if it is something other than ".."
      ** or "."
      */
      if( z[i+1]=='.' && i+2<n && z[i+2]=='.' && (i+3==n || z[i+3]=='/')
       && fsl_backup_dir(z, &j)
      ){
        i += 2;
        continue;
      }
    }
    if( j>=0 ) z[j] = z[i];
    j++;
  }
  if( j==0 ) z[j++] = '.';
  z[j] = 0;
  return j;
}

int fsl_file_canonical_name(const char *zOrigName,
                            fsl_buffer *pOut, char slash){
  int rc;
  if(!zOrigName || !pOut) return FSL_RC_MISUSE;
  else if( fsl_file_is_absolute_path(zOrigName) ){
    pOut->used = 0;
    rc = fsl_buffer_append( pOut, zOrigName, -1 );
#if defined(_WIN32) || defined(__CYGWIN__)
    if(!rc){
      char *zOut;
      /*
      ** On Windows/cygwin, normalize the drive letter to upper case.
      */
      zOut = fsl_buffer_str(pOut);
      if( fsl_islower(zOut[0]) && zOut[1]==':' ){
        zOut[0] = fossil_toupper(zOut[0]);
      }
    }
#endif
  }else{
    char zPwd[2000];
    rc = fsl_getcwd(zPwd, sizeof(zPwd)-fsl_strlen(zOrigName), NULL);
    if(!rc){
#if defined(_WIN32)
      /*
      ** On Windows, normalize the drive letter to upper case.
      */
      if( !rc && fsl_islower(zPwd[0]) && zPwd[1]==':' ){
        zPwd[0] = fsl_toupper(zPwd[0]);
      }
#endif
      pOut->used = 0;
      rc = fsl_buffer_appendf(pOut, "%//%/", zPwd, zOrigName);
    }
  }
  if(!rc){
    fsl_size_t const newLen =
      (fsl_size_t)fsl_file_simplify_name(fsl_buffer_str(pOut),
                                         (int)pOut->used, slash);
    rc = fsl_buffer_resize(pOut, newLen);
  }
  return rc;
}


int fsl_find_home_dir( fsl_buffer * tgt, char requireWriteAccess ){
  char * zHome = NULL;
  int rc = 0;
  tgt->used = 0;
#if defined(_WIN32) || defined(__CYGWIN__)
  zHome = fsl_getenv("LOCALAPPDATA");
  if( zHome==0 ){
    zHome = fsl_getenv("APPDATA");
    if( zHome==0 ){
      char *zDrive = fsl_getenv("HOMEDRIVE");
      zHome = fsl_getenv("HOMEPATH");
      if( zDrive && zHome ){
        tgt->used = 0;
        rc = fsl_buffer_appendf(tgt, "%s", zDrive);
        fsl_filename_free(zDrive);
        if(rc){
          fsl_filename_free(zHome);
          return rc;
        }
      }
    }
  }
  if(NULL==zHome){
    rc = fsl_buffer_append(tgt,
                           "Cannot locate home directory - "
                           "please set the LOCALAPPDATA or "
                           "APPDATA or HOMEPATH "
                           "environment variables.",
                           -1);
    return rc ? rc : FSL_RC_NOT_FOUND;
  }
  rc = fsl_buffer_appendf( tgt, "%/", zHome );
#else
  /* Unix... */
  zHome = fsl_getenv("HOME");
  if( zHome==0 ){
    rc = fsl_buffer_append(tgt,
                           "Cannot locate home directory - "
                           "please set the HOME environment "
                           "variable.",
                           -1);
    return rc ? rc : FSL_RC_NOT_FOUND;
  }
  rc = fsl_buffer_appendf( tgt, "%s", zHome );
#endif


  fsl_filename_free(zHome);
  if(rc) return rc;
  assert(0<tgt->used);
  zHome = fsl_buffer_str(tgt);
  
  if( fsl_file_isdir(zHome)!=1 ){
    assert(0==tgt->used);
    rc = fsl_buffer_appendf(tgt,
                            "Invalid home directory: %s",
                            zHome);
    return rc ? rc : FSL_RC_TYPE;
  }

  assert(!rc);


#if !(defined(_WIN32) || defined(__CYGWIN__))
  /* Not sure why, but the is-writable check is historically only done
     on Unix platforms? */
  if( requireWriteAccess &&
      (0 != fsl_file_access(zHome, W_OK)) ){
    fsl_buffer tmp = fsl_buffer_empty;
    rc = fsl_buffer_appendf(&tmp,
                            "Home directory [%s] must "
                            "be writeable.",
                            zHome);
    fsl_buffer_swap_free(&tmp, tgt, -1);
    return rc ? rc : FSL_RC_ACCESS;
  }
#endif

#if 0
  /* Old stuff to get rid of soon. */
#if defined(_WIN32) || defined(__CYGWIN__)
  /* . filenames give some window systems problems and many apps problems */
  rc = fsl_buffer_append(tgt, "/_fossil", 8);
#else
  /* Unix ... */
  rc = fsl_buffer_append(tgt, "/.fossil", 8);
  if( fsl_file_access(zHome, W_OK) ){
    tgt->used = 0;
    rc = fsl_buffer_appendf(tgt, "Home directory [%s] must "
                            "be writeable.", zHome);
    fsl_filename_free(zHome);
    return rc ? rc : FSL_RC_ACCESS;
  }
  rc = fsl_buffer_append(tgt, "/.fossil", 8);
#endif
#endif

  return rc;
}

char fsl_file_isdir(const char *zFilename){
  static struct stat fileStat;
  int rc;
  if( zFilename ){
    char *zFN = fsl_mprintf("%s", zFilename);
    if(!zFN) rc = FSL_RC_OOM;
    else{
      rc = fsl_file_simplify_name(zFN, -1, 0);
      if(!rc) rc = fsl_stat(zFilename, &fileStat, 0);
      fsl_free(zFN);
    }
  }else{
    rc = fsl_stat(zFilename, &fileStat, 0);
  }
  return rc ? 0 : (S_ISDIR(fileStat.st_mode) ? 1 : 2);
}


#if 0
/* This block requires permissions flags from v1's manifest.c. */

/*
** Return TRUE if the named file is an executable.  Return false
** for directories, devices, fifos, symlinks, etc.
*/
int fsl_file_wd_isexe(const char *zFilename){
  return fsl_file_wd_perm(zFilename)==PERM_EXE;
}

/*
** Return TRUE if the named file is a symlink and symlinks are allowed.
** Return false for all other cases.
**
** On Windows, always return False.
*/
int file_wd_islink(const char *zFilename){
  return file_wd_perm(zFilename)==PERM_LNK;
}


/*
** Same as file_isdir(), but takes into account symlinks.
*/
int file_wd_isdir(const char *zFilename){
  int rc;

  if( zFilename ){
    char *zFN = mprintf("%s", zFilename);
    file_simplify_name(zFN, -1, 0);
    rc = getStat(zFN, 1);
    free(zFN);
  }else{
    rc = getStat(0, 1);
  }
  return rc ? 0 : (S_ISDIR(fileStat.st_mode) ? 1 : 2);
}

#endif
