/* ** Copyright (c) 2006 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License version 2 as published by the Free Software Foundation. ** ** 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. See the GNU ** General Public License for more details. ** ** You should have received a copy of the GNU General Public ** License along with this library; if not, write to the ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, ** Boston, MA 02111-1307, USA. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** File utilities */ #include "config.h" #include #include #include #include "file.h" /* ** The file status information from the most recent stat() call. */ static struct stat fileStat; static int fileStatValid = 0; /* ** Fill in the fileStat variable for the file named zFilename. ** If zFilename==0, then use the previous value of fileStat if ** there is a previous value. ** ** Return the number of errors. No error messages are generated. */ static int getStat(const char *zFilename){ if( zFilename==0 ){ if( fileStatValid==0 ) return 1; }else{ if( stat(zFilename, &fileStat)!=0 ) return 1; fileStatValid = 1; } return 0; } /* ** Return the size of a file in bytes. Return -1 if the file does not ** exist. If zFilename is NULL, return the size of the most recently ** stat-ed file. */ i64 file_size(const char *zFilename){ return getStat(zFilename) ? -1 : fileStat.st_size; } /* ** Return the modification time for a file. Return -1 if the file ** does not exist. If zFilename is NULL return the size of the most ** recently stat-ed file. */ i64 file_mtime(const char *zFilename){ return getStat(zFilename) ? -1 : fileStat.st_mtime; } /* ** Return TRUE if the named file is an ordinary file. Return false ** for directories, devices, fifos, symlinks, etc. */ int file_isfile(const char *zFilename){ return getStat(zFilename) ? 0 : S_ISREG(fileStat.st_mode); } /* ** Return TRUE if the named file is an executable. Return false ** for directories, devices, fifos, symlinks, etc. */ int file_isexe(const char *zFilename){ if( getStat(zFilename) || !S_ISREG(fileStat.st_mode) ) return 0; #ifdef __MINGW32__ return ((S_IXUSR)&fileStat.st_mode)!=0; #else return ((S_IXUSR|S_IXGRP|S_IXOTH)&fileStat.st_mode)!=0; #endif } /* ** Return 1 if zFilename is a directory. Return 0 if zFilename ** does not exist. Return 2 if zFilename exists but is something ** other than a directory. */ int file_isdir(const char *zFilename){ int rc; if( zFilename ){ char *zFN = mprintf("%s", zFilename); file_simplify_name(zFN, strlen(zFN)); rc = getStat(zFN); free(zFN); }else{ rc = getStat(0); } return rc ? 0 : (S_ISDIR(fileStat.st_mode) ? 1 : 2); } /* ** Return the tail of a file pathname. The tail is the last component ** of the path. For example, the tail of "/a/b/c.d" is "c.d". */ const char *file_tail(const char *z){ const char *zTail = z; while( z[0] ){ if( z[0]=='/' ) zTail = &z[1]; z++; } return zTail; } /* ** Copy the content of a file from one place to another. */ void file_copy(const char *zFrom, const char *zTo){ FILE *in, *out; int got; char zBuf[8192]; in = fopen(zFrom, "rb"); if( in==0 ) fossil_fatal("cannot open \"%s\" for reading", zFrom); out = fopen(zTo, "wb"); if( out==0 ) fossil_fatal("cannot open \"%s\" for writing", zTo); while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){ fwrite(zBuf, 1, got, out); } fclose(in); fclose(out); } /* ** Set or clear the execute bit on a file. */ void file_setexe(const char *zFilename, int onoff){ #ifndef __MINGW32__ struct stat buf; if( stat(zFilename, &buf)!=0 ) return; if( onoff ){ if( (buf.st_mode & 0111)!=0111 ){ chmod(zFilename, buf.st_mode | 0111); } }else{ if( (buf.st_mode & 0111)!=0 ){ chmod(zFilename, buf.st_mode & ~0111); } } #endif } /* ** Create the directory named in the argument, if it does not already ** exist. If forceFlag is 1, delete any prior non-directory object ** with the same name. ** ** Return the number of errors. */ int file_mkdir(const char *zName, int forceFlag){ int rc = file_isdir(zName); if( rc==2 ){ if( !forceFlag ) return 1; unlink(zName); } if( rc!=1 ){ #ifdef __MINGW32__ return mkdir(zName); #else return mkdir(zName, 0755); #endif } return 0; } /* ** Return true if the filename given is a valid filename for ** a file in a repository. Valid filenames follow all of the ** following rules: ** ** * Does not begin with "/" ** * Does not contain any path element named "." or ".." ** * Does not contain any of these characters in the path: "\*[]?" ** * Does not end with "/". ** * Does not contain two or more "/" characters in a row. ** * Contains at least one character */ int file_is_simple_pathname(const char *z){ int i; if( *z=='/' || *z==0 ) return 0; if( *z=='.' ){ if( z[1]=='/' || z[1]==0 ) return 0; if( z[1]=='.' && (z[2]=='/' || z[2]==0) ) return 0; } for(i=0; z[i]; i++){ if( z[i]=='\\' || z[i]=='*' || z[i]=='[' || z[i]==']' || z[i]=='?' ){ return 0; } if( z[i]=='/' ){ 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; } /* ** Simplify a filename by ** ** * removing any trailing and duplicate / ** * removing /./ ** * removing /A/../ ** ** Changes are made in-place. Return the new name length. */ int file_simplify_name(char *z, int n){ int i, j; #ifdef __MINGW32__ for(i=0; i1 && z[n-1]=='/' ){ n--; } for(i=j=0; i0 && z[j-1]!='/' ){ j--; } if( j>0 ){ j--; } i += 2; continue; } } z[j++] = z[i]; } z[j] = 0; return j; } /* ** Compute a canonical pathname for a file or directory. ** Make the name absolute if it is relative. ** Remove redundant / characters ** Remove all /./ path elements. ** Convert /A/../ to just / */ void file_canonical_name(const char *zOrigName, Blob *pOut){ if( zOrigName[0]=='/' #ifdef __MINGW32__ || zOrigName[0]=='\\' || (strlen(zOrigName)>3 && zOrigName[1]==':' && (zOrigName[2]=='\\' || zOrigName[2]=='/')) #endif ){ blob_set(pOut, zOrigName); blob_materialize(pOut); }else{ char zPwd[2000]; if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){ fprintf(stderr, "pwd too big: max %d\n", (int)sizeof(zPwd)-20); exit(1); } blob_zero(pOut); blob_appendf(pOut, "%//%/", zPwd, zOrigName); } blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut))); } /* ** COMMAND: test-canonical-name ** ** Test the operation of the canonical name generator. */ void cmd_test_canonical_name(void){ int i; Blob x; blob_zero(&x); for(i=2; i='a' && zUri[i]<='z'; i++){} if( zUri[i]!=':' ){ blob_zero(pScheme); blob_zero(pHost); blob_set(pPath, zUri); return; } blob_init(pScheme, zUri, i); i++; if( zUri[i]=='/' && zUri[i+1]=='/' ){ i += 2; j = i; while( zUri[i] && zUri[i]!='/' && zUri[i]!=':' ){ i++; } blob_init(pHost, &zUri[j], i-j); if( zUri[i]==':' ){ i++; *pPort = atoi(&zUri[i]); while( zUri[i] && zUri[i]!='/' ){ i++; } } }else{ blob_zero(pHost); } if( zUri[i]=='/' ){ blob_set(pPath, &zUri[i]); }else{ blob_set(pPath, "/"); } } /* ** Construct a random temporary filename into zBuf[]. */ void file_tempname(int nBuf, char *zBuf){ static const char *azDirs[] = { "/var/tmp", "/usr/tmp", "/tmp", "/temp", ".", }; static const unsigned char zChars[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; unsigned int i, j; struct stat buf; const char *zDir = "."; for(i=0; i= (size_t)nBuf ){ fossil_fatal("insufficient space for temporary filename"); } do{ sqlite3_snprintf(nBuf-17, zBuf, "%s/", zDir); j = (int)strlen(zBuf); sqlite3_randomness(15, &zBuf[j]); for(i=0; i<15; i++, j++){ zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; } zBuf[j] = 0; }while( access(zBuf,0)==0 ); }