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