/* -*- 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 #include #include /* strlen() */ #include /* NULL on linux */ #include #include "fossil/fossil.h" #include "fsl_internal.h" #ifdef _WIN32 # include # include # include #else # include # include /* access(2) */ # include # include #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; i1 && 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=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(0used); 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