Login
Artifact [ae8ce12862]
Login

Artifact ae8ce12862718490849e213d36e4c744675f7485:


/* -*- 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.
*/

#include "fossil-scm/fossil-internal.h"

#include <assert.h>
#include <string.h> /* strlen() */
#include <stddef.h> /* NULL on linux */
#include <ctype.h>
#ifdef _WIN32
# include <direct.h>
# include <windows.h>
# include <sys/utime.h>
#else
# include <unistd.h> /* access(2) */
# include <sys/types.h>
# include <sys/stat.h>
# include <sys/time.h>
#endif
#include <errno.h>

/* Only for debugging */
#include <stdio.h>
#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)



FILE *fsl_fopen(const char *zName, const char *zMode){
  FILE *f;
  if(zName && ('-'==*zName && !zName[1])){
    f = (strchr(zMode, 'w') || strchr(zMode,'+'))
      ? stdout
      : stdin
      ;
  }else{
#ifdef _WIN32
    wchar_t *uMode;
    wchar_t *uName;
    f = _wfopen(uName, uMode);
    fsl_filename_free(uName);
    fsl_unicode_free(uMode);
#else
    f = fopen(zName, zMode);
#endif
  }
  return f;
}


void fsl_fclose( FILE * f ){
  if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){
    fclose(f);
  }
}

/*
** 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;
  else if(!nBuf) return FSL_RC_RANGE;
  /*
    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(!nBuf) return FSL_RC_RANGE;
  else if( getcwd(zBuf,
                  nBuf /*-1 not necessary: getcwd() NUL-terminates*/)==0 ){
    return fsl_errno_to_rc(errno, 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

/* Reminder: the semmantics of the 3rd parameter are
   reversed from v1's fossil_stat().
*/
int fsl_stat(const char *zFilename, fsl_fstat * fst,
              char derefSymlinks){
  if(!zFilename || !fst) return FSL_RC_MISUSE;
  else if(!*zFilename) return FSL_RC_RANGE;
  else{
    int rc;
    struct stat buf;
#if !defined(_WIN32)
    char *zMbcs = (char *)fsl_utf8_to_filename(zFilename);
    if(!zMbcs) rc = FSL_RC_OOM;
    else{
      if( derefSymlinks ){
        rc = stat(zMbcs, &buf);
      }else{
        rc = lstat(zMbcs, &buf);
      }
    }
#else
    wchar_t *zMbcs = (wchar_t *)fsl_utf8_to_filename(zFilename);
    rc = zMbcs ? _wstati64(zMbcs, &buf) : FSL_RC_OOM;
#endif
    if(zMbcs) fsl_filename_free(zMbcs);
    if(0==rc){
      *fst = fsl_fstat_empty;
      fst->ctime = (fsl_time_t)buf.st_ctime;
      fst->mtime = (fsl_time_t)buf.st_mtime;
      fst->size = (fsl_size_t)buf.st_size;
      if(S_ISDIR(buf.st_mode)) fst->type = FSL_FSTAT_TYPE_DIR;
#if !defined(_WIN32)
      else if(S_ISLNK(buf.st_mode)) fst->type = FSL_FSTAT_TYPE_LINK;
#endif
      else /* if(S_ISREG(buf.st_mode)) */{
        fst->type = FSL_FSTAT_TYPE_FILE;
#if defined(_WIN32)
#  ifndef S_IXUSR
#    define S_IXUSR  _S_IEXEC
#  endif
        if(((S_IXUSR)&buf.st_mode)!=0){
          fst->perms |= FSL_FSTAT_PERM_EXE;
        }
#else
        if( ((S_IXUSR|S_IXGRP|S_IXOTH)&buf.st_mode)!=0 ){
          fst->perms |= FSL_FSTAT_PERM_EXE;
        }
#if 0
        /* Porting artifact: something to consider... */
        else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
          return PERM_LNK;
#endif
#endif
      }
    }
    else{
      rc = fsl_errno_to_rc(errno, FSL_RC_IO);
    }
    return rc;
  }
}

fsl_size_t fsl_file_size(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 1) )
    ? -1
    : fst.size;
}

/*
  The family of 'wd' functions is historical in nature and not really
  needed(???) at the library level. 'wd' == 'working directory'
  (i.e. checkout).  Ideally the library won't have to do any _direct_
  manipulation of directory trees, e.g. checkouts. That is essentially
  app-level logic, though we'll need some level of infrastructure for
  the apps to build off of.  When that comes, the "wd" family of
  functions (or something similar) might come back into play.
*/

fsl_time_t fsl_file_mtime(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 1) )
    ? -1
    : (fsl_time_t)fst.mtime;
}


char fsl_is_file(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 1) )
    ? 0
    : (FSL_FSTAT_TYPE_FILE == fst.type)
    ;
}

/*
** Return true if zPath is an absolute pathname.  Return false
** if it is relative.
*/
char fsl_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;
  }
}

char fsl_is_simple_pathname(const char *z, char bStrictUtf8){
  int i;
  unsigned char c = (unsigned char) z[0];
  char maskNonAscii = bStrictUtf8 ? 0x80 : 0x00;
  if( c=='/' || c==0 ) return 0;
  if( c=='.' ){
    if( z[1]=='/' || z[1]==0 ) return 0;
    if( z[1]=='.' && (z[2]=='/' || z[2]==0) ) return 0;
  }
  for(i=0; (c=(unsigned char)z[i])!=0; i++){
    if( c & maskNonAscii ){
      if( (z[++i]&0xc0)!=0x80 ){
        /* Invalid first continuation byte */
        return 0;
      }
      if( c<0xc2 ){
        /* Invalid 1-byte UTF-8 sequence, or 2-byte overlong form. */
        return 0;
      }else if( (c&0xe0)==0xe0 ){
        /* 3-byte or more */
        int unicode;
        if( c&0x10 ){
          /* Unicode characters > U+FFFF are not supported.
           * Windows XP and earlier cannot handle them.
           */
          return 0;
        }
        /* This is a 3-byte UTF-8 character */
        unicode = ((c&0x0f)<<12) + ((z[i]&0x3f)<<6) + (z[i+1]&0x3f);
        if( unicode <= 0x07ff ){
          /* overlong form */
          return 0;
        }else if( unicode>=0xe000 ){
          /* U+E000..U+FFFF */
          if( (unicode<=0xf8ff) || (unicode>=0xfffe) ){
            /* U+E000..U+F8FF are for private use.
             * U+FFFE..U+FFFF are noncharacters. */
            return 0;
          } else if( (unicode>=0xfdd0) && (unicode<=0xfdef) ){
            /* U+FDD0..U+FDEF are noncharacters. */
            return 0;
          }
        }else if( (unicode>=0xd800) && (unicode<=0xdfff) ){
          /* U+D800..U+DFFF are for surrogate pairs. */
          return 0;
        }
        if( (z[++i]&0xc0)!=0x80 ){
          /* Invalid second continuation byte */
          return 0;
        }
      }
    }else if( bStrictUtf8 && (c=='\\') ){
      return 0;
    }
    if( c=='/' ){
      if( z[i+1]=='/' ) return 0;
      if( z[i+1]=='.' ){
        if( z[i+2]=='/' || z[i+2]==0 ) return 0;
        if( z[i+2]=='.' && (z[i+3]=='/' || z[i+3]==0) ) return 0;
      }
    }
  }
  if( z[i-1]=='/' ) return 0;
  return 1;
}


/*
** 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 char fsl_backup_dir(const char *z, fsl_int_t *pJ){
  fsl_int_t j = *pJ;
  fsl_int_t i;
  if( !j ) 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;
}



fsl_size_t fsl_file_simplify_name(char *z, fsl_int_t n_, char slash){
  fsl_size_t i;
  fsl_size_t n = (n_<0) ? fsl_strlen(z) : (fsl_size_t)n_;
  fsl_int_t j;
  /* 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 (fsl_size_t)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_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] = fsl_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_file_dirpart(char const * zFilename,
                     fsl_int_t nLen,
                     fsl_buffer * pOut,
                     char leaveSlash){
  if(!zFilename || !*zFilename || !pOut) return FSL_RC_MISUSE;
  else if(!nLen) return FSL_RC_RANGE;
  else{
    fsl_size_t n = (nLen>0) ? (fsl_size_t)nLen : fsl_strlen(zFilename);
    char const * z = zFilename + n;
    char doBreak = 0;
    if(!n) return FSL_RC_RANGE;
    else while( !doBreak && (--z >= zFilename) ){
      switch(*z){
#if defined(_WIN32)
        case '\\':
#endif
        case '/':
          if(!leaveSlash) --z;
          doBreak = 1;
          break;
      }
    }
    if(z<=zFilename){
      return (doBreak && leaveSlash)
        ? fsl_buffer_append(pOut, zFilename, 1)
        : fsl_buffer_append(pOut, "", 0) /* ensure a NUL terminator */;
    }else{
      return fsl_buffer_append(pOut, zFilename, z-zFilename + 1);
    }
  }

}

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_dir_check(zHome)<1 ){
    /* assert(0==tgt->used); */
    fsl_buffer tmp = fsl_buffer_empty;
    rc = fsl_buffer_appendf(&tmp,
                            "Invalid home directory: %s",
                            zHome);
    fsl_buffer_swap_free(&tmp, tgt, -1);
    return rc ? rc : FSL_RC_TYPE;
  }

#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

  return rc;
}

int fsl_errno_to_rc(int errNo, int dflt){
  switch(errNo){
    /* Plese expand on this as tests/use cases call for it... */
    case EINVAL:
      return FSL_RC_MISUSE;
    case ENOMEM:
      return FSL_RC_OOM;
    case EROFS:
    case EACCES:
    case EBUSY:
    case EPERM:
    /* case EISDIR: this conversion is context-dependent :/ */
      return FSL_RC_ACCESS;
    case ENAMETOOLONG:
    case ELOOP:
      return FSL_RC_RANGE;
    case ENOENT:
      return FSL_RC_NOT_FOUND;
    case EEXIST:
      return FSL_RC_ALREADY_EXISTS;
    case EIO:
      return FSL_RC_IO;
    default:
      return dflt;
  }
}

int fsl_file_unlink(const char *zFilename){
  int rc;
#ifdef _WIN32
  wchar_t *z = (wchar_t*)fsl_utf8_to_filename(zFilename);
  rc = _wunlink(z);
#else
  char *z = (char *)fsl_utf8_to_filename(zFilename);
  rc = unlink(zFilename);
#endif
  fsl_filename_free(z);
  if(rc){
    rc = fsl_errno_to_rc(errno, FSL_RC_IO);
  }
  return rc;
}

int fsl_mkdir(const char *zName, char forceFlag){
  int rc =
    /*file_wd_dir_check(zName)*/
    fsl_dir_check(zName)
    ;
  if( rc<0 ){
    if( !forceFlag ) return FSL_RC_TYPE;
    rc = fsl_file_unlink(zName);
    if(rc) return rc;
  }
  if( 0==rc ){
#if defined(_WIN32)
    typedef wchar_t char_t;
#define mkdir(F,P) _wmkdir(F)
#else
    typedef char char_t;
#endif
    char_t *zMbcs = (char_t*)fsl_utf8_to_filename(zName);
    if(!zMbcs) return FSL_RC_OOM;
    rc = mkdir(zMbcs, 0755);
    fsl_filename_free(zMbcs);
    if(rc){
      rc = fsl_errno_to_rc(errno, FSL_RC_IO);
    }
    return rc;
#if defined(_WIN32)
#undef mkdir
#endif
  }
  return 0;
}

int fsl_mkdir2(char const *zName, char forceFlag){
  int rc;
  fsl_buffer b = fsl_buffer_empty /* we copy zName to
                                     simplify traversal */;
  fsl_size_t n = fsl_strlen(zName);
  fsl_size_t i;
  char * zCan;
  if(n<2) return FSL_RC_RANGE;
#if 1
  /* This variant does more work (checks dirs we know already
     exist) but transforms the path into something platform-neutral.
     If we use fsl_file_simplify_name() instead then we end up
     having to do the trailing-slash logic here.
  */
  rc = fsl_file_canonical_name(zName, &b, 1);
  if(rc) goto end;
#else
  rc = fsl_buffer_append(&b, zName, n);
  if(rc) goto end;
#endif
  zCan = fsl_buffer_str(&b);
  n = b.used;
  for( i = 1; i < n; ++i ){
    if( '/'==zCan[i] ){
      zCan[i] = 0;
#if defined(_WIN32) || defined(__CYGWIN__)
      /*
      ** On Windows, local path looks like: C:/develop/project/file.txt
      ** The if stops us from trying to create a directory of a drive letter
      ** C: in this example.
      */
      if( !(i==2 && zCan[1]==':') ){
#endif
        rc = fsl_dir_check(zCan);
#if 0
        if(rc<0){
          if(forceFlag) rc = fsl_file_unlink(zCan);
          else rc = FSL_RC_TYPE;
          if(rc) goto end;
        }
#endif
        /* MARKER(("dir_check rc=%d, zCan=%s\n", rc, zCan)); */
        if(0>=rc){
          rc = fsl_mkdir(zCan, forceFlag);
          /* MARKER(("mkdir(%s) rc=%s\n", zCan, fsl_rc_cstr(rc))); */
          if( 0!=rc ) goto end;

        }else{
          rc = 0;
          /* Nothing to do. */
        }
#if defined(_WIN32) || defined(__CYGWIN__)
      }
#endif
      zCan[i] = '/';
    }
  }
  end:
  fsl_buffer_clear(&b);
  return rc;
}

char fsl_dir_check(const char *zFilename){
  fsl_fstat fst;
  int rc;
  if( zFilename ){
    char *zFN = fsl_mprintf("%s", zFilename);
    if(!zFN) rc = FSL_RC_OOM;
    else{
      fsl_file_simplify_name(zFN, -1, 0);
      rc = fsl_stat(zFN, &fst, 1);
      fsl_free(zFN);
    }
  }else{
    rc = -1 /*fsl_stat(zFilename, &fst, 1) historic: used static stat cache*/;
  }
  return rc ? 0 : ((FSL_FSTAT_TYPE_DIR == fst.type) ? 1 : -1);
}



#if 0
/*
** Same as dir_check(), but takes into account symlinks.
*/
int file_wd_dir_check(const char *zFilename){
  if(!zFilename || !*zFilename) return FSL_RC_MISUSE;
  else{
    int rc;
    fsl_fstat fst = fsl_fstat_empty;
    char *zFN = fsl_strdup(zFilename);
    if(!zFN) rc = FSL_RC_OOM;
    else{
      fsl_file_simplify_name(zFN, -1, 0);
      rc = fsl_stat(zFN, &fst, 0);
      fsl_free(zFN);
    }
    return rc ? 0 : ((FSL_FSTAT_TYPE_DIR == fst.type) ? 1 : 2);
  }
}
#endif

#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_wd_isexe(const char *zFilename){
  return fsl_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;
}
#endif

#if 0
/**
 ** Same as fsl_is_file(), but takes into account symlinks.
 */
char fsl_wd_isfile(const char *zFilename);
char fsl_wd_isfile(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? 0
    : (FSL_FSTAT_TYPE_FILE == fst.type);
}
#endif
#if 0
/**
 ** Same as fsl_file_mtime(), but takes into account symlinks.
 */
fsl_time_t fsl_wd_mtime(const char *zFilename);
fsl_time_t fsl_wd_mtime(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? -1
    : (fsl_time_t)fst.mtime;
}

char fsl_wd_isfile_or_link(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? 0
    : ((FSL_FSTAT_TYPE_LINK == fst.type)
       || (FSL_FSTAT_TYPE_FILE == fst.type))
    ;
}
#endif

#if 0
/**
 ** Same as fsl_file_size(), but takes into account symlinks.
 */
fsl_size_t fsl_wd_size(const char *zFilename);
fsl_size_t fsl_wd_size(const char *zFilename){
  fsl_fstat fst;
  return ( 0 != fsl_stat(zFilename, &fst, 0) )
    ? -1
    : fst.size;
}
#endif

void fsl_pathfinder_clear(fsl_pathfinder * pf){
  if(pf){
    fsl_list_visit_free(&pf->ext, 1);
    fsl_list_visit_free(&pf->dirs, 1);
    fsl_buffer_clear(&pf->buf);
    *pf = fsl_pathfinder_empty;
  }
}

static int fsl_pathfinder_add(fsl_list * li, char const * str){
  char * cp = fsl_strdup(str);
  int rc;
  if(!cp) rc = FSL_RC_OOM;
  else{
    rc = fsl_list_append(li, cp);
    if(rc) fsl_free(cp);
  }
  return rc;
}

int fsl_pathfinder_dir_add(fsl_pathfinder * pf, char const * dir){
  return (pf && dir)
    ? fsl_pathfinder_add(&pf->dirs, dir)
    : FSL_RC_MISUSE;
}

int fsl_pathfinder_ext_add(fsl_pathfinder * pf, char const * ext){
  return (pf && ext)
    ? fsl_pathfinder_add(&pf->ext, ext)
    : FSL_RC_MISUSE;
}

int fsl_pathfinder_search(fsl_pathfinder * pf,
                          char const * base,
                          char const ** pOut,
                          fsl_size_t * outLen ){
  fsl_buffer * buf = pf ? &pf->buf : NULL;
  fsl_list * ext;
  fsl_list * dirs;
  int rc = 0;
  fsl_size_t d, x, nD, nX, resetLen = 0;
  fsl_size_t baseLen;
  static char const pathSep =
#if defined(_WIN32)
    '\\'
#else
    '/'
#endif
    ;
  if(!buf || !base || !*base) return FSL_RC_MISUSE;
  else if(!*base) return FSL_RC_RANGE;
  else if(0==fsl_file_access( base, 0 )){
    if(pOut) *pOut = base;
    if(outLen) *outLen = fsl_strlen(base);
    return 0;
  }
  baseLen = fsl_strlen(base);
  ext = &pf->ext;
  dirs = &pf->dirs;
  nD = dirs->used;
  nX = ext->used;
  for( d = 0; !rc && (d < nD); ++d ){
    char const * vD = (char const *)dirs->list[d];
    buf->used = 0;
    if(vD){
      fsl_size_t const used = buf->used;
      rc = fsl_buffer_append(buf, vD, -1);
      if(rc) return rc;
      if(used != buf->used){
        /* Only append separator if vD is non-empty. */
        rc = fsl_buffer_append(buf, &pathSep, 1);
        if(rc) return rc;
      }
    }
    rc = fsl_buffer_append(buf, base, (fsl_int_t)baseLen);
    if(rc) return rc;
    if(0==fsl_file_access( (char const *)buf->mem, 0 )) goto gotone;
    resetLen = buf->used;
    for( x = 0; !rc && (x < nX); ++x ){
      char const * vX = (char const *)ext->list[x];
      if(vX){
        buf->used = resetLen;
        rc = fsl_buffer_append(buf, vX, -1);
        if(rc) return rc;
      }
      assert(buf->used < buf->capacity);
      buf->mem[buf->used] = 0;
      if(0==fsl_file_access( (char const *)buf->mem, 0 )){
        goto gotone;
      }
    }
  }

  return FSL_RC_NOT_FOUND;

  gotone:
  if(outLen) *outLen = buf->used;
  if(pOut) *pOut = (char const *)buf->mem;
  return 0;
}


#undef MARKER