/* ** Copyright (c) 2006 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/ ** ******************************************************************************* ** ** File utilities. */ #include "config.h" #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <time.h> #include "file.h" /* ** On Windows, include the Platform SDK header file. */ #ifdef _WIN32 # include <direct.h> # include <windows.h> # include <sys/utime.h> #else # include <sys/time.h> # include <pwd.h> # include <grp.h> #endif #if INTERFACE /* Many APIs take an eFType argument which must be one of ExtFILE, RepoFILE, ** or SymFILE. ** ** The difference is in the handling of symbolic links. RepoFILE should be ** used for files that are under management by a Fossil repository. ExtFILE ** should be used for files that are not under management. SymFILE is for ** a few special cases such as the "fossil test-tarball" command when we never ** want to follow symlinks. ** ** ExtFILE Symbolic links always refer to the object to which the ** link points. Symlinks are never recognized as symlinks but ** instead always appear to the the target object. ** ** SymFILE Symbolic links always appear to be files whose name is ** the target pathname of the symbolic link. ** ** RepoFILE Like SymFILE if allow-symlinks is true, or like ** ExtFILE if allow-symlinks is false. In other words, ** symbolic links are only recognized as something different ** from files or directories if allow-symlinks is true. */ #include <stdlib.h> #define ExtFILE 0 /* Always follow symlinks */ #define RepoFILE 1 /* Follow symlinks if and only if allow-symlinks is OFF */ #define SymFILE 2 /* Never follow symlinks */ #include <dirent.h> #if defined(_WIN32) # define DIR _WDIR # define dirent _wdirent # define opendir _wopendir # define readdir _wreaddir # define closedir _wclosedir #endif /* _WIN32 */ #if defined(_WIN32) && (defined(__MSVCRT__) || defined(_MSC_VER)) /* ** File status information for windows systems. */ struct fossilStat { i64 st_size; i64 st_mtime; int st_mode; }; #endif #if defined(_WIN32) || defined(__CYGWIN__) # define fossil_isdirsep(a) (((a) == '/') || ((a) == '\\')) #else # define fossil_isdirsep(a) ((a) == '/') #endif #endif /* INTERFACE */ #if !defined(_WIN32) || !(defined(__MSVCRT__) || defined(_MSC_VER)) /* ** File status information for unix systems */ # define fossilStat stat #endif /* ** On Windows S_ISLNK always returns FALSE. */ #if !defined(S_ISLNK) # define S_ISLNK(x) (0) #endif /* ** Local state information for the file status routines */ static struct { struct fossilStat fileStat; /* File status from last fossil_stat() */ int fileStatValid; /* True if fileStat is valid */ } fx; /* ** Fill *buf with information about zFilename. ** ** If zFilename refers to a symbolic link: ** ** (A) If allow-symlinks is on and eFType is RepoFILE, then fill ** *buf with information about the symbolic link itself. ** ** (B) If allow-symlinks is off or eFType is ExtFILE, then fill ** *buf with information about the object that the symbolic link ** points to. */ static int fossil_stat( const char *zFilename, /* name of file or directory to inspect. */ struct fossilStat *buf, /* pointer to buffer where info should go. */ int eFType /* Look at symlink itself if RepoFILE and enabled. */ ){ int rc; void *zMbcs = fossil_utf8_to_path(zFilename, 0); #if !defined(_WIN32) if( (eFType==RepoFILE && db_allow_symlinks()) || eFType==SymFILE ){ /* Symlinks look like files whose content is the name of the target */ rc = lstat(zMbcs, buf); }else{ /* Symlinks look like the object to which they point */ rc = stat(zMbcs, buf); } #else rc = win32_stat(zMbcs, buf, eFType); #endif fossil_path_free(zMbcs); return rc; } /* ** Clears the fx.fileStat variable and its associated validity flag. */ static void resetStat(){ fx.fileStatValid = 0; memset(&fx.fileStat, 0, sizeof(struct fossilStat)); } /* ** Fill in the fx.fileStat variable for the file named zFilename. ** If zFilename==0, then use the previous value of fx.fileStat if ** there is a previous value. ** ** Return the number of errors. No error messages are generated. */ static int getStat(const char *zFilename, int eFType){ int rc = 0; if( zFilename==0 ){ if( fx.fileStatValid==0 ) rc = 1; }else{ if( fossil_stat(zFilename, &fx.fileStat, eFType)!=0 ){ fx.fileStatValid = 0; rc = 1; }else{ fx.fileStatValid = 1; rc = 0; } } return rc; } /* ** 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, int eFType){ return getStat(zFilename, eFType) ? -1 : fx.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, int eFType){ return getStat(zFilename, eFType) ? -1 : fx.fileStat.st_mtime; } /* ** Return the mode bits 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. */ int file_mode(const char *zFilename, int eFType){ return getStat(zFilename, eFType) ? -1 : (int)(fx.fileStat.st_mode); } /* ** Return TRUE if either of the following are true: ** ** (1) zFilename is an ordinary file ** ** (2) allow_symlinks is on and zFilename is a symbolic link to ** a file, directory, or other object */ int file_isfile_or_link(const char *zFilename){ if( getStat(zFilename, RepoFILE) ){ return 0; /* stat() failed. Return false. */ } return S_ISREG(fx.fileStat.st_mode) || S_ISLNK(fx.fileStat.st_mode); } /* ** Return TRUE if the named file is an ordinary file. Return false ** for directories, devices, fifos, symlinks, etc. */ int file_isfile(const char *zFilename, int eFType){ return getStat(zFilename, eFType) ? 0 : S_ISREG(fx.fileStat.st_mode); } /* ** Return TRUE if zFilename is a socket. */ int file_issocket(const char *zFilename){ #ifdef _WIN32 return 0; #else if( getStat(zFilename, ExtFILE) ){ return 0; /* stat() failed. Return false. */ } return S_ISSOCK(fx.fileStat.st_mode); #endif } /* ** Create a symbolic link named zLinkFile that points to zTargetFile. ** ** If allow-symlinks is off, create an ordinary file named zLinkFile ** with the name of zTargetFile as its content. **/ void symlink_create(const char *zTargetFile, const char *zLinkFile){ #if !defined(_WIN32) if( db_allow_symlinks() ){ int i, nName; char *zName, zBuf[1000]; nName = strlen(zLinkFile); if( nName>=(int)sizeof(zBuf) ){ zName = mprintf("%s", zLinkFile); }else{ zName = zBuf; memcpy(zName, zLinkFile, nName+1); } nName = file_simplify_name(zName, nName, 0); for(i=1; i<nName; i++){ if( zName[i]=='/' ){ zName[i] = 0; if( file_mkdir(zName, ExtFILE, 1) ){ fossil_fatal_recursive("unable to create directory %s", zName); return; } zName[i] = '/'; } } if( symlink(zTargetFile, zName)!=0 ){ fossil_fatal_recursive("unable to create symlink \"%s\"", zName); } if( zName!=zBuf ) free(zName); }else #endif { Blob content; blob_set(&content, zTargetFile); blob_write_to_file(&content, zLinkFile); blob_reset(&content); } } /* ** Copy symbolic link from zFrom to zTo. */ void symlink_copy(const char *zFrom, const char *zTo){ Blob content; blob_read_link(&content, zFrom); symlink_create(blob_str(&content), zTo); blob_reset(&content); } /* ** Return file permissions (normal, executable, or symlink): ** - PERM_EXE on Unix if file is executable; ** - PERM_LNK on Unix if file is symlink and allow-symlinks option is on; ** - PERM_REG for all other cases (regular file, directory, fifo, etc). ** ** If eFType is ExtFile then symbolic links are followed and so this ** routine can only return PERM_EXE and PERM_REG. ** ** On windows, this routine returns only PERM_REG. */ int file_perm(const char *zFilename, int eFType){ #if !defined(_WIN32) if( !getStat(zFilename, eFType) ){ if( S_ISREG(fx.fileStat.st_mode) && ((S_IXUSR)&fx.fileStat.st_mode)!=0 ) return PERM_EXE; else if( db_allow_symlinks() && S_ISLNK(fx.fileStat.st_mode) ) return PERM_LNK; } #endif return PERM_REG; } /* ** Return TRUE if the named file is an executable. Return false ** for directories, devices, fifos, symlinks, etc. */ int file_isexe(const char *zFilename, int eFType){ return file_perm(zFilename, eFType)==PERM_EXE; } /* ** Return TRUE if the named file is a symlink and symlinks are allowed. ** Return false for all other cases. ** ** This routines assumes RepoFILE - that zFilename is always a file ** under management. ** ** On Windows, always return False. */ int file_islink(const char *zFilename){ return file_perm(zFilename, RepoFILE)==PERM_LNK; } /* ** Check every sub-directory of zRoot along the path to zFile. ** If any sub-directory is really an ordinary file or a symbolic link, ** return an integer which is the length of the prefix of zFile which ** is the name of that object. Return 0 if all no non-directory ** objects are found along the path. ** ** Example: Given inputs ** ** zRoot = /home/alice/project1 ** zFile = /home/alice/project1/main/src/js/fileA.js ** ** Look for objects in the following order: ** ** /home/alice/project/main ** /home/alice/project/main/src ** /home/alice/project/main/src/js ** ** If any of those objects exist and are something other than a directory ** then return the length of the name of the first non-directory object ** seen. */ int file_nondir_objects_on_path(const char *zRoot, const char *zFile){ int i = (int)strlen(zRoot); char *z = fossil_strdup(zFile); assert( fossil_strnicmp(zRoot, z, i)==0 ); if( i && zRoot[i-1]=='/' ) i--; while( z[i]=='/' ){ int j, rc; for(j=i+1; z[j] && z[j]!='/'; j++){} if( z[j]!='/' ) break; z[j] = 0; rc = file_isdir(z, SymFILE); if( rc!=1 ){ if( rc==2 ){ fossil_free(z); return j; } break; } z[j] = '/'; i = j; } fossil_free(z); return 0; } /* ** The file named zFile is suppose to be an in-tree file. Check to ** ensure that it will be safe to write to this file by verifying that ** there are no symlinks or other non-directory objects in between the ** root of the check-out and zFile. ** ** If a problem is found, print a warning message (using fossil_warning()) ** and return non-zero. If everything is ok, return zero. */ int file_unsafe_in_tree_path(const char *zFile){ int n; if( !file_is_absolute_path(zFile) ){ fossil_fatal("%s is not an absolute pathname",zFile); } if( fossil_strnicmp(g.zLocalRoot, zFile, (int)strlen(g.zLocalRoot)) ){ fossil_fatal("%s is not a prefix of %s", g.zLocalRoot, zFile); } n = file_nondir_objects_on_path(g.zLocalRoot, zFile); if( n ){ fossil_warning("cannot write to %s because non-directory object %.*s" " is in the way", zFile, n, zFile); } return n; } /* ** 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 eFType){ int rc; char *zFN; zFN = mprintf("%s", zFilename); file_simplify_name(zFN, -1, 0); rc = getStat(zFN, eFType); if( rc ){ rc = 0; /* It does not exist at all. */ }else if( S_ISDIR(fx.fileStat.st_mode) ){ rc = 1; /* It exists and is a real directory. */ }else{ rc = 2; /* It exists and is something else. */ } free(zFN); return rc; } /* ** Return true (1) if zFilename seems like it seems like a valid ** repository database. */ int file_is_repository(const char *zFilename){ i64 sz; sqlite3 *db = 0; sqlite3_stmt *pStmt = 0; int rc; int i; static const char *azReqTab[] = { "blob", "delta", "rcvfrom", "user", "config" }; if( !file_isfile(zFilename, ExtFILE) ) return 0; sz = file_size(zFilename, ExtFILE); if( sz<35328 ) return 0; if( sz%512!=0 ) return 0; rc = sqlite3_open_v2(zFilename, &db, SQLITE_OPEN_READWRITE, 0); if( rc!=0 ) goto not_a_repo; for(i=0; i<count(azReqTab); i++){ if( sqlite3_table_column_metadata(db, "main", azReqTab[i],0,0,0,0,0,0) ){ goto not_a_repo; } } rc = sqlite3_prepare_v2(db, "SELECT 1 FROM config WHERE name='project-code'", -1, &pStmt, 0); if( rc ) goto not_a_repo; rc = sqlite3_step(pStmt); if( rc!=SQLITE_ROW ) goto not_a_repo; sqlite3_finalize(pStmt); sqlite3_close(db); return 1; not_a_repo: sqlite3_finalize(pStmt); sqlite3_close(db); return 0; } /* ** Wrapper around the access() system call. */ int file_access(const char *zFilename, int flags){ int rc; void *zMbcs = fossil_utf8_to_path(zFilename, 0); #ifdef _WIN32 rc = win32_access(zMbcs, flags); #else rc = access(zMbcs, flags); #endif fossil_path_free(zMbcs); return rc; } /* ** Wrapper around the chdir() system call. ** If bChroot=1, do a chroot to this dir as well ** (UNIX only) */ int file_chdir(const char *zChDir, int bChroot){ int rc; void *zPath = fossil_utf8_to_path(zChDir, 1); #ifdef _WIN32 rc = win32_chdir(zPath, bChroot); #else rc = chdir(zPath); if( !rc && bChroot ){ rc = chroot(zPath); if( !rc ) rc = chdir("/"); g.fJail = 1; } #endif fossil_path_free(zPath); return rc; } /* ** Find an unused filename similar to zBase with zSuffix appended. ** ** Make the name relative to the working directory if relFlag is true. ** ** Space to hold the new filename is obtained form mprintf() and should ** be freed by the caller. */ char *file_newname(const char *zBase, const char *zSuffix, int relFlag){ char *z = 0; int cnt = 0; z = mprintf("%s-%s", zBase, zSuffix); while( file_size(z, ExtFILE)>=0 ){ fossil_free(z); z = mprintf("%s-%s-%d", zBase, zSuffix, cnt++); } if( relFlag ){ Blob x; file_relative_name(z, &x, 0); fossil_free(z); z = blob_str(&x); } return z; } /* ** 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; if( !zTail ) return 0; while( z[0] ){ if( fossil_isdirsep(z[0]) ) zTail = &z[1]; z++; } return zTail; } /* ** Return the directory of a file path name. The directory is all components ** except the last one. For example, the directory of "/a/b/c.d" is "/a/b". ** If there is no directory, NULL is returned; otherwise, the returned memory ** should be freed via fossil_free(). */ char *file_dirname(const char *z){ const char *zTail = file_tail(z); if( zTail && zTail!=z ){ return mprintf("%.*s", (int)(zTail-z-1), z); }else{ return 0; } } /* SQL Function: file_dirname(NAME) ** ** Return the directory for NAME */ void file_dirname_sql_function( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zName = (const char*)sqlite3_value_text(argv[0]); char *zDir; if( zName==0 ) return; zDir = file_dirname(zName); if( zDir ){ sqlite3_result_text(context,zDir,-1,fossil_free); } } /* ** Rename a file or directory. ** Returns zero upon success. */ int file_rename( const char *zFrom, const char *zTo, int isFromDir, int isToDir ){ int rc; #if defined(_WIN32) wchar_t *zMbcsFrom = fossil_utf8_to_path(zFrom, isFromDir); wchar_t *zMbcsTo = fossil_utf8_to_path(zTo, isToDir); rc = _wrename(zMbcsFrom, zMbcsTo); #else char *zMbcsFrom = fossil_utf8_to_path(zFrom, isFromDir); char *zMbcsTo = fossil_utf8_to_path(zTo, isToDir); rc = rename(zMbcsFrom, zMbcsTo); #endif fossil_path_free(zMbcsTo); fossil_path_free(zMbcsFrom); return rc; } /* ** 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 = fossil_fopen(zFrom, "rb"); if( in==0 ) fossil_fatal("cannot open \"%s\" for reading", zFrom); file_mkfolder(zTo, ExtFILE, 0, 0); out = fossil_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); if( file_isexe(zFrom, ExtFILE) ) file_setexe(zTo, 1); } /* ** COMMAND: test-file-copy ** ** Usage: %fossil test-file-copy SOURCE DESTINATION ** ** Make a copy of the file at SOURCE into a new name DESTINATION. Any ** directories in the path leading up to DESTINATION that do not already ** exist are created automatically. */ void test_file_copy(void){ if( g.argc!=4 ){ fossil_fatal("Usage: %s test-file-copy SOURCE DESTINATION", g.argv[0]); } file_copy(g.argv[2], g.argv[3]); } /* ** Set or clear the execute bit on a file. Return true if a change ** occurred and false if this routine is a no-op. ** ** This routine assumes RepoFILE as the eFType. In other words, if ** zFilename is a symbolic link, it is the object that zFilename points ** to that is modified. */ int file_setexe(const char *zFilename, int onoff){ int rc = 0; #if !defined(_WIN32) struct stat buf; if( fossil_stat(zFilename, &buf, RepoFILE)!=0 || S_ISLNK(buf.st_mode) || S_ISDIR(buf.st_mode) ){ return 0; } if( onoff ){ int targetMode = (buf.st_mode & 0444)>>2; if( (buf.st_mode & 0100)==0 ){ chmod(zFilename, buf.st_mode | targetMode); rc = 1; } }else{ if( (buf.st_mode & 0100)!=0 ){ chmod(zFilename, buf.st_mode & ~0111); rc = 1; } } #endif /* _WIN32 */ return rc; } /* ** Set the mtime for a file. */ void file_set_mtime(const char *zFilename, i64 newMTime){ #if !defined(_WIN32) char *zMbcs; struct timeval tv[2]; memset(tv, 0, sizeof(tv[0])*2); tv[0].tv_sec = newMTime; tv[1].tv_sec = newMTime; zMbcs = fossil_utf8_to_path(zFilename, 0); utimes(zMbcs, tv); #else struct _utimbuf tb; wchar_t *zMbcs = fossil_utf8_to_path(zFilename, 0); tb.actime = newMTime; tb.modtime = newMTime; _wutime(zMbcs, &tb); #endif fossil_path_free(zMbcs); } /* ** COMMAND: test-set-mtime ** ** Usage: %fossil test-set-mtime FILENAME DATE/TIME ** ** Sets the mtime of the named file to the date/time shown. */ void test_set_mtime(void){ const char *zFile; char *zDate; i64 iMTime; if( g.argc!=4 ){ usage("FILENAME DATE/TIME"); } db_open_or_attach(":memory:", "mem"); iMTime = db_int64(0, "SELECT strftime('%%s',%Q)", g.argv[3]); zFile = g.argv[2]; file_set_mtime(zFile, iMTime); iMTime = file_mtime(zFile, RepoFILE); zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')", iMTime); fossil_print("Set mtime of \"%s\" to %s (%lld)\n", zFile, zDate, iMTime); } /* ** Change access permissions on a file. */ void file_set_mode(const char *zFN, int fd, const char *zMode, int bNoErr){ #if !defined(_WIN32) mode_t m; char *zEnd = 0; m = strtol(zMode, &zEnd, 0); if( (zEnd[0] || fchmod(fd, m)) && !bNoErr ){ fossil_fatal("cannot change permissions on %s to \"%s\"", zFN, zMode); } #endif } /* Change the owner of a file to zOwner. zOwner can be of the form ** USER:GROUP. */ void file_set_owner(const char *zFN, int fd, const char *zOwner){ #if !defined(_WIN32) const char *zGrp; const char *zUsr = zOwner; struct passwd *pw; struct group *grp; uid_t uid = -1; gid_t gid = -1; zGrp = strchr(zUsr, ':'); if( zGrp ){ int n = (int)(zGrp - zUsr); zUsr = fossil_strndup(zUsr, n); zGrp++; } pw = getpwnam(zUsr); if( pw==0 ){ fossil_fatal("no such user: \"%s\"", zUsr); } uid = pw->pw_uid; if( zGrp ){ grp = getgrnam(zGrp); if( grp==0 ){ fossil_fatal("no such group: \"%s\"", zGrp); } gid = grp->gr_gid; } if( chown(zFN, uid, gid) ){ fossil_fatal("cannot change ownership of %s to %s",zFN, zOwner); } if( zOwner!=zUsr ){ fossil_free((char*)zUsr); } #endif } /* ** Delete a file. ** ** If zFilename is a symbolic link, then it is the link itself that is ** removed, not the object that zFilename points to. ** ** Returns zero upon success. */ int file_delete(const char *zFilename){ int rc; #ifdef _WIN32 wchar_t *z = fossil_utf8_to_path(zFilename, 0); rc = _wunlink(z); #else char *z = fossil_utf8_to_path(zFilename, 0); rc = unlink(zFilename); #endif fossil_path_free(z); return rc; } /* SQL Function: file_delete(NAME) ** ** Remove file NAME. Return zero on success and non-zero if anything goes ** wrong. */ void file_delete_sql_function( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zName = (const char*)sqlite3_value_text(argv[0]); int rc; if( zName==0 ){ rc = 1; }else{ rc = file_delete(zName); } sqlite3_result_int(context, rc); } /* ** Create a directory called zName, 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 eFType, int forceFlag){ int rc = file_isdir(zName, eFType); if( rc==2 ){ if( !forceFlag ) return 1; file_delete(zName); } if( rc!=1 ){ #if defined(_WIN32) wchar_t *zMbcs = fossil_utf8_to_path(zName, 1); rc = _wmkdir(zMbcs); #else char *zMbcs = fossil_utf8_to_path(zName, 1); rc = mkdir(zMbcs, 0755); #endif fossil_path_free(zMbcs); return rc; } return 0; } /* ** Create the tree of directories in which zFilename belongs, if that sequence ** of directories does not already exist. ** ** On success, return zero. On error, return errorReturn if positive, otherwise ** print an error message and abort. */ int file_mkfolder( const char *zFilename, /* Pathname showing directories to be created */ int eFType, /* Follow symlinks if ExtFILE */ int forceFlag, /* Delete non-directory objects in the way */ int errorReturn /* What to do when an error is seen */ ){ int nName, rc = 0; char *zName; nName = strlen(zFilename); zName = mprintf("%s", zFilename); nName = file_simplify_name(zName, nName, 0); while( nName>0 && zName[nName-1]!='/' ){ nName--; } if( nName>1 ){ zName[nName-1] = 0; if( file_isdir(zName, eFType)!=1 ){ rc = file_mkfolder(zName, eFType, forceFlag, errorReturn); if( rc==0 ){ if( file_mkdir(zName, eFType, forceFlag) && file_isdir(zName, eFType)!=1 ){ if( errorReturn <= 0 ){ fossil_fatal_recursive("unable to create directory %s", zName); } rc = errorReturn; } } } } free(zName); return rc; } #if defined(_WIN32) /* ** Returns non-zero if the specified name represents a real directory, i.e. ** not a junction or symbolic link. This is important for some operations, ** e.g. removing directories via _wrmdir(), because its detection of empty ** directories will (apparently) not work right for junctions and symbolic ** links, etc. */ int file_is_normal_dir(wchar_t *zName){ /* ** Mask off attributes, applicable to directories, that are harmless for ** our purposes. This may need to be updated if other attributes should ** be ignored by this function. */ DWORD dwAttributes = GetFileAttributesW(zName); if( dwAttributes==INVALID_FILE_ATTRIBUTES ) return 0; dwAttributes &= ~( FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED ); return dwAttributes==FILE_ATTRIBUTE_DIRECTORY; } /* ** COMMAND: test-is-normal-dir ** ** Usage: %fossil test-is-normal-dir NAME... ** ** Returns non-zero if the specified names represent real directories, i.e. ** not junctions, symbolic links, etc. */ void test_is_normal_dir(void){ int i; for(i=2; i<g.argc; i++){ wchar_t *zMbcs = fossil_utf8_to_path(g.argv[i], 1); fossil_print("ATTRS \"%s\" -> %lx\n", g.argv[i], GetFileAttributesW(zMbcs)); fossil_print("ISDIR \"%s\" -> %d\n", g.argv[i], file_is_normal_dir(zMbcs)); fossil_path_free(zMbcs); } } #endif /* ** Removes the directory named in the argument, if it exists. The directory ** must be empty and cannot be the current directory or the root directory. ** ** Returns zero upon success. */ int file_rmdir(const char *zName){ int rc = file_isdir(zName, RepoFILE); if( rc==2 ) return 1; /* cannot remove normal file */ if( rc==1 ){ #if defined(_WIN32) wchar_t *zMbcs = fossil_utf8_to_path(zName, 1); if( file_is_normal_dir(zMbcs) ){ rc = _wrmdir(zMbcs); }else{ rc = ENOTDIR; /* junction, symbolic link, etc. */ } #else char *zMbcs = fossil_utf8_to_path(zName, 1); rc = rmdir(zName); #endif fossil_path_free(zMbcs); return rc; } return 0; } /* SQL Function: rmdir(NAME) ** ** Try to remove the directory NAME. Return zero on success and non-zero ** for failure. */ void file_rmdir_sql_function( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zName = (const char*)sqlite3_value_text(argv[0]); int rc; if( zName==0 ){ rc = 1; }else{ rc = file_rmdir(zName); } sqlite3_result_int(context, rc); } /* ** Check the input argument to see if it looks like it has an prefix that ** indicates a remote file. If so, return the tail of the specification, ** which is the name of the file on the remote system. ** ** If the input argument does not have a prefix that makes it look like ** a remote file reference, then return NULL. ** ** Remote files look like: "HOST:PATH" or "USER@HOST:PATH". Host must ** be a valid hostname, meaning it must follow these rules: ** ** * Only characters [-.a-zA-Z0-9]. No spaces or other punctuation ** * Does not begin or end with - ** * Name is two or more characters long (otherwise it might be ** confused with a drive-letter on Windows). ** ** The USER section, if it exists, must not contain the '@' character. */ const char *file_skip_userhost(const char *zIn){ const char *zTail; int n, i; if( zIn[0]==':' ) return 0; zTail = strchr(zIn, ':'); if( zTail==0 ) return 0; if( zTail - zIn > 10000 ) return 0; n = (int)(zTail - zIn); if( n<2 ) return 0; if( zIn[n-1]=='-' || zIn[n-1]=='.' ) return 0; for(i=n-1; i>0 && zIn[i-1]!='@'; i--){ if( !fossil_isalnum(zIn[i]) && zIn[i]!='-' && zIn[i]!='.' ) return 0; } if( zIn[i]=='-' || zIn[i]=='.' || i==1 ) return 0; if( i>1 ){ i -= 2; while( i>=0 ){ if( zIn[i]=='@' ) return 0; i--; } } return zTail+1; } /* ** 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 ** ** Invalid UTF8 characters result in a false return if bStrictUtf8 is ** true. If bStrictUtf8 is false, invalid UTF8 characters are silently ** ignored. See http://en.wikipedia.org/wiki/UTF-8#Invalid_byte_sequences ** and http://en.wikipedia.org/wiki/Unicode (for the noncharacters) ** ** The bStrictUtf8 flag is true for new inputs, but is false when parsing ** legacy manifests, for backwards compatibility. */ int file_is_simple_pathname(const char *z, int 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; } int file_is_simple_pathname_nonstrict(const char *z){ unsigned char c = (unsigned char) z[0]; 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; } while( (z = strchr(z+1, '/'))!=0 ){ if( z[1]=='/' ) return 0; if( z[1]==0 ) return 0; if( z[1]=='.' ){ if( z[2]=='/' || z[2]==0 ) return 0; if( z[2]=='.' && (z[3]=='/' || z[3]==0) ) 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 int 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 ** ** * Remove extended path prefix on windows and cygwin ** * 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 file_simplify_name(char *z, int n, int slash){ int i = 1, j; assert( z!=0 ); if( n<0 ) n = strlen(z); if( n==0 ) return 0; /* On windows and cygwin convert all \ characters to / * and remove extended path prefix if present */ #if defined(_WIN32) || defined(__CYGWIN__) for(j=0; j<n; j++){ if( z[j]=='\\' ) z[j] = '/'; } if( n>3 && !memcmp(z, "//?/", 4) ){ if( fossil_strnicmp(z+4,"UNC", 3) ){ i += 4; z[0] = z[4]; }else{ i += 6; z[0] = '/'; } } #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(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]=='/') && 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; } /* ** COMMAND: test-simplify-name ** ** Usage: %fossil test-simplify-name FILENAME... ** ** Print the simplified versions of each FILENAME. This is used to test ** the file_simplify_name() routine. ** ** If FILENAME is of the form "HOST:PATH" or "USER@HOST:PATH", then remove ** and print the remote host prefix first. This is used to test the ** file_skip_userhost() interface. */ void cmd_test_simplify_name(void){ int i; char *z; const char *zTail; for(i=2; i<g.argc; i++){ zTail = file_skip_userhost(g.argv[i]); if( zTail ){ fossil_print("... ON REMOTE: %.*s\n", (int)(zTail-g.argv[i]), g.argv[i]); z = mprintf("%s", zTail); }else{ z = mprintf("%s", g.argv[i]); } fossil_print("[%s] -> ", z); file_simplify_name(z, -1, 0); fossil_print("[%s]\n", z); fossil_free(z); } } /* ** Get the current working directory. ** ** On windows, the name is converted from unicode to UTF8 and all '\\' ** characters are converted to '/'. No conversions are needed on ** unix. ** ** Store the value of the CWD in zBuf which is nBuf bytes in size. ** or if zBuf==0, allocate space to hold the result using fossil_malloc(). */ char *file_getcwd(char *zBuf, int nBuf){ if( zBuf==0 ){ char zTemp[2000]; return fossil_strdup(file_getcwd(zTemp, sizeof(zTemp))); } #ifdef _WIN32 win32_getcwd(zBuf, nBuf); #else if( getcwd(zBuf, nBuf-1)==0 ){ if( errno==ERANGE ){ fossil_fatal("pwd too big: max %d", nBuf-1); }else{ fossil_fatal("cannot find current working directory; %s", strerror(errno)); } } #endif return zBuf; } /* ** Return true if zPath is an absolute pathname. Return false ** if it is relative. */ int file_is_absolute_path(const char *zPath){ if( fossil_isdirsep(zPath[0]) #if defined(_WIN32) || defined(__CYGWIN__) || (fossil_isalpha(zPath[0]) && zPath[1]==':' && (fossil_isdirsep(zPath[2]) || zPath[2]=='\0')) #endif ){ return 1; }else{ return 0; } } /* ** 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 / ** If the slash parameter is non-zero, the trailing slash, if any, ** is retained. ** ** See also: file_canonical_name_dup() */ void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){ blob_zero(pOut); if( file_is_absolute_path(zOrigName) ){ blob_appendf(pOut, "%/", zOrigName); }else{ char zPwd[2000]; file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); if( zPwd[0]=='/' && strlen(zPwd)==1 ){ /* when on '/', don't add an extra '/' */ if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){ /* '.' when on '/' mean '/' */ blob_appendf(pOut, "%/", zPwd); }else{ blob_appendf(pOut, "%/%/", zPwd, zOrigName); } }else{ blob_appendf(pOut, "%//%/", zPwd, zOrigName); } } #if defined(_WIN32) || defined(__CYGWIN__) { char *zOut; /* ** On Windows/cygwin, normalize the drive letter to upper case. */ zOut = blob_str(pOut); if( fossil_islower(zOut[0]) && zOut[1]==':' && zOut[2]=='/' ){ zOut[0] = fossil_toupper(zOut[0]); } } #endif blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut), slash)); } /* ** Compute the canonical name of a file. Store that name in ** memory obtained from fossil_malloc() and return a pointer to the ** name. ** ** See also: file_canonical_name() */ char *file_canonical_name_dup(const char *zOrigName){ Blob x; if( zOrigName==0 ) return 0; blob_init(&x, 0, 0); file_canonical_name(zOrigName, &x, 0); return blob_str(&x); } /* ** Convert zPath, which is a relative pathname rooted at zDir, into the ** case preferred by the underlying filesystem. Return the a copy ** of the converted path in memory obtained from fossil_malloc(). ** ** For case-sensitive filesystems, such as on Linux, this routine is ** just fossil_strdup(). But for case-insenstiive but "case preserving" ** filesystems, such as on MacOS or Windows, we want the filename to be ** in the preserved casing. That's what this routine does. */ char *file_case_preferred_name(const char *zDir, const char *zPath){ #ifndef _WIN32 /* Call win32_file_case_preferred_name() on Windows. */ DIR *d; int i; char *zResult = 0; void *zNative = 0; if( filenames_are_case_sensitive() ){ return fossil_strdup(zPath); } for(i=0; zPath[i] && zPath[i]!='/' && zPath[i]!='\\'; i++){} zNative = fossil_utf8_to_path(zDir, 1); d = opendir(zNative); if( d ){ struct dirent *pEntry; while( (pEntry = readdir(d))!=0 ){ char *zUtf8 = fossil_path_to_utf8(pEntry->d_name); if( fossil_strnicmp(zUtf8, zPath, i)==0 && zUtf8[i]==0 ){ if( zPath[i]==0 ){ zResult = fossil_strdup(zUtf8); }else{ char *zSubDir = mprintf("%s/%s", zDir, zUtf8); char *zSubPath = file_case_preferred_name(zSubDir, &zPath[i+1]); zResult = mprintf("%s/%s", zUtf8, zSubPath); fossil_free(zSubPath); fossil_free(zSubDir); } fossil_path_free(zUtf8); break; } fossil_path_free(zUtf8); } closedir(d); } fossil_path_free(zNative); if( zResult==0 ) zResult = fossil_strdup(zPath); return zResult; #else /* !_WIN32 */ return win32_file_case_preferred_name(zDir,zPath); #endif /* !_WIN32 */ } /* ** COMMAND: test-case-filename ** ** Usage: fossil test-case-filename DIRECTORY PATH PATH PATH .... ** ** All the PATH arguments (there must be one at least one) are pathnames ** relative to DIRECTORY. This test command prints the OS-preferred name ** for each PATH in filesystems where case is not significant. */ void test_preferred_fn(void){ int i; if( g.argc<4 ){ usage("DIRECTORY PATH ..."); } for(i=3; i<g.argc; i++){ char *z = file_case_preferred_name(g.argv[2], g.argv[i]); fossil_print("%s -> %s\n", g.argv[i], z); fossil_free(z); } } /* ** The input is the name of an executable, such as one might ** type on a command-line. This routine resolves that name into ** a full pathname. The result is obtained from fossil_malloc() ** and should be freed by the caller. */ char *file_fullexename(const char *zCmd){ #ifdef _WIN32 char *zPath; char *z = 0; const char *zExe = ""; if( sqlite3_strlike("%.exe",zCmd,0)!=0 ) zExe = ".exe"; if( file_is_absolute_path(zCmd) ){ return mprintf("%s%s", zCmd, zExe); } if( strchr(zCmd,'\\')!=0 && strchr(zCmd,'/')!=0 ){ int i; Blob out = BLOB_INITIALIZER; file_canonical_name(zCmd, &out, 0); blob_append(&out, zExe, -1); z = fossil_strdup(blob_str(&out)); blob_reset(&out); for(i=0; z[i]; i++){ if( z[i]=='/' ) z[i] = '\\'; } return z; } z = mprintf(".\\%s%s", zCmd, zExe); if( file_isfile(z, ExtFILE) ){ int i; Blob out = BLOB_INITIALIZER; file_canonical_name(zCmd, &out, 0); blob_append(&out, zExe, -1); z = fossil_strdup(blob_str(&out)); blob_reset(&out); for(i=0; z[i]; i++){ if( z[i]=='/' ) z[i] = '\\'; } return z; } fossil_free(z); zPath = fossil_getenv("PATH"); while( zPath && zPath[0] ){ int n; char *zColon; zColon = strchr(zPath, ';'); n = zColon ? (int)(zColon-zPath) : (int)strlen(zPath); while( n>0 && zPath[n-1]=='\\' ){ n--; } z = mprintf("%.*s\\%s%s", n, zPath, zCmd, zExe); if( file_isfile(z, ExtFILE) ){ return z; } fossil_free(z); if( zColon==0 ) break; zPath = zColon+1; } return fossil_strdup(zCmd); #else char *zPath; char *z; if( zCmd[0]=='/' ){ return fossil_strdup(zCmd); } if( strchr(zCmd,'/')!=0 ){ Blob out = BLOB_INITIALIZER; file_canonical_name(zCmd, &out, 0); z = fossil_strdup(blob_str(&out)); blob_reset(&out); return z; } zPath = fossil_getenv("PATH"); while( zPath && zPath[0] ){ int n; char *zColon; zColon = strchr(zPath, ':'); n = zColon ? (int)(zColon-zPath) : (int)strlen(zPath); z = mprintf("%.*s/%s", n, zPath, zCmd); if( file_isexe(z, ExtFILE) ){ return z; } fossil_free(z); if( zColon==0 ) break; zPath = zColon+1; } return fossil_strdup(zCmd); #endif } /* ** COMMAND: test-which ** ** Usage: %fossil test-which ARGS... ** ** For each argument, search the PATH for the executable with the name ** and print its full pathname. */ void test_which_cmd(void){ int i; for(i=2; i<g.argc; i++){ char *z = file_fullexename(g.argv[i]); fossil_print("%z\n", z); } } /* ** Emits the effective or raw stat() information for the specified ** file or directory, optionally preserving the trailing slash and ** resetting the cached stat() information. */ static void emitFileStat( const char *zPath, int slash, int reset ){ char zBuf[200]; char *z; Blob x; char *zFull; int rc; sqlite3_int64 iMtime; struct fossilStat testFileStat; memset(zBuf, 0, sizeof(zBuf)); blob_zero(&x); file_canonical_name(zPath, &x, slash); zFull = blob_str(&x); fossil_print("[%s] -> [%s]\n", zPath, zFull); memset(&testFileStat, 0, sizeof(struct fossilStat)); rc = fossil_stat(zPath, &testFileStat, 0); fossil_print(" stat_rc = %d\n", rc); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size); fossil_print(" stat_size = %s\n", zBuf); if( g.db==0 ) sqlite3_open(":memory:", &g.db); z = db_text(0, "SELECT datetime(%lld, 'unixepoch')", testFileStat.st_mtime); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld (%s)", testFileStat.st_mtime, z); fossil_free(z); fossil_print(" stat_mtime = %s\n", zBuf); fossil_print(" stat_mode = 0%o\n", testFileStat.st_mode); memset(&testFileStat, 0, sizeof(struct fossilStat)); rc = fossil_stat(zPath, &testFileStat, 1); fossil_print(" l_stat_rc = %d\n", rc); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size); fossil_print(" l_stat_size = %s\n", zBuf); z = db_text(0, "SELECT datetime(%lld, 'unixepoch')", testFileStat.st_mtime); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld (%s)", testFileStat.st_mtime, z); fossil_free(z); fossil_print(" l_stat_mtime = %s\n", zBuf); fossil_print(" l_stat_mode = 0%o\n", testFileStat.st_mode); if( reset ) resetStat(); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_size(zPath,ExtFILE)); fossil_print(" file_size(ExtFILE) = %s\n", zBuf); iMtime = file_mtime(zPath, ExtFILE); z = db_text(0, "SELECT datetime(%lld, 'unixepoch')", iMtime); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld (%s)", iMtime, z); fossil_free(z); fossil_print(" file_mtime(ExtFILE) = %s\n", zBuf); fossil_print(" file_mode(ExtFILE) = 0%o\n", file_mode(zPath,ExtFILE)); fossil_print(" file_isfile(ExtFILE) = %d\n", file_isfile(zPath,ExtFILE)); fossil_print(" file_isdir(ExtFILE) = %d\n", file_isdir(zPath,ExtFILE)); fossil_print(" file_issocket() = %d\n", file_issocket(zPath)); if( reset ) resetStat(); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_size(zPath,RepoFILE)); fossil_print(" file_size(RepoFILE) = %s\n", zBuf); iMtime = file_mtime(zPath,RepoFILE); z = db_text(0, "SELECT datetime(%lld, 'unixepoch')", iMtime); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld (%s)", iMtime, z); fossil_free(z); fossil_print(" file_mtime(RepoFILE) = %s\n", zBuf); fossil_print(" file_mode(RepoFILE) = 0%o\n", file_mode(zPath,RepoFILE)); fossil_print(" file_isfile(RepoFILE) = %d\n", file_isfile(zPath,RepoFILE)); fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath)); fossil_print(" file_islink = %d\n", file_islink(zPath)); fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE)); fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE)); fossil_print(" file_is_repository = %d\n", file_is_repository(zPath)); fossil_print(" file_is_reserved_name = %d\n", file_is_reserved_name(zFull,-1)); fossil_print(" file_in_cwd = %d\n", file_in_cwd(zPath)); blob_reset(&x); if( reset ) resetStat(); } /* ** COMMAND: test-file-environment ** ** Usage: %fossil test-file-environment FILENAME... ** ** Display the effective file handling subsystem "settings" and then ** display file system information about the files specified, if any. ** ** Options: ** --allow-symlinks BOOLEAN Temporarily turn allow-symlinks on/off ** --open-config Open the configuration database first ** --reset Reset cached stat() info for each file ** --root ROOT Use ROOT as the root of the check-out ** --slash Trailing slashes, if any, are retained */ void cmd_test_file_environment(void){ int i; int slashFlag = find_option("slash",0,0)!=0; int resetFlag = find_option("reset",0,0)!=0; const char *zRoot = find_option("root",0,1); const char *zAllow = find_option("allow-symlinks",0,1); if( find_option("open-config", 0, 0)!=0 ){ Th_OpenConfig(1); } db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0); fossil_print("filenames_are_case_sensitive() = %d\n", filenames_are_case_sensitive()); if( zAllow ){ g.allowSymlinks = !is_false(zAllow); } if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot; fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks()); fossil_print("local-root = [%s]\n", zRoot); for(i=2; i<g.argc; i++){ char *z; emitFileStat(g.argv[i], slashFlag, resetFlag); z = file_canonical_name_dup(g.argv[i]); fossil_print(" file_canonical_name = %s\n", z); fossil_print(" file_nondir_path = "); if( fossil_strnicmp(zRoot,z,(int)strlen(zRoot))!=0 ){ fossil_print("(--root is not a prefix of this file)\n"); }else{ int n = file_nondir_objects_on_path(zRoot, z); fossil_print("%.*s\n", n, z); } fossil_free(z); } } /* ** COMMAND: test-canonical-name ** ** Usage: %fossil test-canonical-name FILENAME... ** ** Test the operation of the canonical name generator. ** Also test Fossil's ability to measure attributes of a file. */ void cmd_test_canonical_name(void){ int i; Blob x; int slashFlag = find_option("slash",0,0)!=0; blob_zero(&x); for(i=2; i<g.argc; i++){ char zBuf[100]; const char *zName = g.argv[i]; file_canonical_name(zName, &x, slashFlag); fossil_print("[%s] -> [%s]\n", zName, blob_buffer(&x)); blob_reset(&x); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_size(zName,RepoFILE)); fossil_print(" file_size = %s\n", zBuf); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_mtime(zName,RepoFILE)); fossil_print(" file_mtime = %s\n", zBuf); fossil_print(" file_isfile = %d\n", file_isfile(zName,RepoFILE)); fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zName)); fossil_print(" file_islink = %d\n", file_islink(zName)); fossil_print(" file_isexe = %d\n", file_isexe(zName,RepoFILE)); fossil_print(" file_isdir = %d\n", file_isdir(zName,RepoFILE)); } } /* ** Return TRUE if the given filename is canonical. ** ** Canonical names are full pathnames using "/" not "\" and which ** contain no "/./" or "/../" terms. */ int file_is_canonical(const char *z){ int i; if( z[0]!='/' #if defined(_WIN32) || defined(__CYGWIN__) && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/') #endif ) return 0; for(i=0; z[i]; i++){ if( z[i]=='\\' ) return 0; if( z[i]=='/' ){ 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; } } } return 1; } /* ** Return a pointer to the first character in a pathname past the ** drive letter. This routine is a no-op on unix. */ char *file_without_drive_letter(char *zIn){ #ifdef _WIN32 if( fossil_isalpha(zIn[0]) && zIn[1]==':' ) zIn += 2; #endif return zIn; } /* ** Compute a pathname for a file or directory that is relative ** to the current directory. If the slash parameter is non-zero, ** the trailing slash, if any, is retained. */ void file_relative_name(const char *zOrigName, Blob *pOut, int slash){ char *zPath; blob_set(pOut, zOrigName); blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut), slash)); zPath = file_without_drive_letter(blob_buffer(pOut)); if( zPath[0]=='/' ){ int i, j; Blob tmp; char *zPwd; char zBuf[2000]; zPwd = zBuf; file_getcwd(zBuf, sizeof(zBuf)-20); zPwd = file_without_drive_letter(zBuf); i = 1; #if defined(_WIN32) || defined(__CYGWIN__) while( zPath[i] && fossil_tolower(zPwd[i])==fossil_tolower(zPath[i]) ) i++; #else while( zPath[i] && zPwd[i]==zPath[i] ) i++; #endif if( zPath[i]==0 ){ memcpy(&tmp, pOut, sizeof(tmp)); if( zPwd[i]==0 ){ blob_set(pOut, "."); }else{ blob_set(pOut, ".."); for(j=i+1; zPwd[j]; j++){ if( zPwd[j]=='/' ){ blob_append(pOut, "/..", 3); } } while( i>0 && (zPwd[i]!='/')) --i; blob_append(pOut, zPath+i, j-i); } if( slash && i>0 && zPath[strlen(zPath)-1]=='/'){ blob_append(pOut, "/", 1); } blob_reset(&tmp); return; } if( zPwd[i]==0 && zPath[i]=='/' ){ memcpy(&tmp, pOut, sizeof(tmp)); blob_set(pOut, "./"); blob_append(pOut, &zPath[i+1], -1); blob_reset(&tmp); return; } while( zPath[i-1]!='/' ){ i--; } if( zPwd[0]=='/' && strlen(zPwd)==1 ){ /* If on '/', don't go to higher level */ blob_zero(&tmp); }else{ blob_set(&tmp, "../"); } for(j=i; zPwd[j]; j++){ if( zPwd[j]=='/' ){ blob_append(&tmp, "../", 3); } } blob_append(&tmp, &zPath[i], -1); blob_reset(pOut); memcpy(pOut, &tmp, sizeof(tmp)); } } /* ** COMMAND: test-relative-name ** ** Test the operation of the relative name generator. */ void cmd_test_relative_name(void){ int i; Blob x; int slashFlag = find_option("slash",0,0)!=0; blob_zero(&x); for(i=2; i<g.argc; i++){ file_relative_name(g.argv[i], &x, slashFlag); fossil_print("%s\n", blob_buffer(&x)); blob_reset(&x); } } /* ** Compute a full path name for a file in the local tree. If ** the absolute flag is non-zero, the computed path will be ** absolute, starting with the root path of the local tree; ** otherwise, it will be relative to the root of the local ** tree. In both cases, the root of the local tree is defined ** by the g.zLocalRoot variable. Return TRUE on success. On ** failure, print and error message and quit if the errFatal ** flag is true. If errFatal is false, then simply return 0. */ int file_tree_name( const char *zOrigName, Blob *pOut, int absolute, int errFatal ){ Blob localRoot; int nLocalRoot; char *zLocalRoot; Blob full; int nFull; char *zFull; int (*xCmp)(const char*,const char*,int); blob_zero(pOut); if( !g.localOpen ){ if( absolute && !file_is_absolute_path(zOrigName) ){ if( errFatal ){ fossil_fatal("relative to absolute needs open check-out tree: %s", zOrigName); } return 0; }else{ /* ** The original path may be relative or absolute; however, without ** an open check-out tree, the only things we can do at this point ** is return it verbatim or generate a fatal error. The caller is ** probably expecting a tree-relative path name will be returned; ** however, most places where this function is called already check ** if the local check-out tree is open, either directly or indirectly, ** which would make this situation impossible. Alternatively, they ** could check the returned path using the file_is_absolute_path() ** function. */ blob_appendf(pOut, "%s", zOrigName); return 1; } } file_canonical_name(g.zLocalRoot, &localRoot, 1); nLocalRoot = blob_size(&localRoot); zLocalRoot = blob_buffer(&localRoot); assert( nLocalRoot>0 && zLocalRoot[nLocalRoot-1]=='/' ); file_canonical_name(zOrigName, &full, 0); nFull = blob_size(&full); zFull = blob_buffer(&full); if( filenames_are_case_sensitive() ){ xCmp = fossil_strncmp; }else{ xCmp = fossil_strnicmp; } /* Special case. zOrigName refers to g.zLocalRoot directory. */ if( (nFull==nLocalRoot-1 && xCmp(zLocalRoot, zFull, nFull)==0) || (nFull==1 && zFull[0]=='/' && nLocalRoot==1 && zLocalRoot[0]=='/') ){ if( absolute ){ blob_append(pOut, zLocalRoot, nLocalRoot); }else{ blob_append(pOut, ".", 1); } blob_reset(&localRoot); blob_reset(&full); return 1; } if( nFull<=nLocalRoot || xCmp(zLocalRoot, zFull, nLocalRoot) ){ blob_reset(&localRoot); blob_reset(&full); if( errFatal ){ fossil_fatal("file outside of check-out tree: %s", zOrigName); } return 0; } if( absolute ){ if( !file_is_absolute_path(zOrigName) ){ blob_append(pOut, zLocalRoot, nLocalRoot); } blob_append(pOut, zOrigName, -1); blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut), 0)); }else{ blob_append(pOut, &zFull[nLocalRoot], nFull-nLocalRoot); } blob_reset(&localRoot); blob_reset(&full); return 1; } /* ** COMMAND: test-tree-name ** ** Test the operation of the tree name generator. ** ** Options: ** --absolute Return an absolute path instead of a relative one ** --case-sensitive B Enable or disable case-sensitive filenames. B is ** a boolean: "yes", "no", "true", "false", etc. */ void cmd_test_tree_name(void){ int i; Blob x; int absoluteFlag = find_option("absolute",0,0)!=0; db_find_and_open_repository(0,0); blob_zero(&x); for(i=2; i<g.argc; i++){ if( file_tree_name(g.argv[i], &x, absoluteFlag, 1) ){ fossil_print("%s\n", blob_buffer(&x)); blob_reset(&x); } } } /* ** zFile is the name of a file. Return true if that file is in the ** current working directory (the "pwd" or file_getcwd() directory). ** Return false if the file is someplace else. */ int file_in_cwd(const char *zFile){ char *zFull = file_canonical_name_dup(zFile); char *zCwd = file_getcwd(0,0); size_t nCwd = strlen(zCwd); size_t nFull = strlen(zFull); int rc = 1; int (*xCmp)(const char*,const char*,int); if( filenames_are_case_sensitive() ){ xCmp = fossil_strncmp; }else{ xCmp = fossil_strnicmp; } if( nFull>nCwd+1 && xCmp(zFull,zCwd,nCwd)==0 && zFull[nCwd]=='/' && strchr(zFull+nCwd+1, '/')==0 ){ rc = 1; }else{ rc = 0; } fossil_free(zFull); fossil_free(zCwd); return rc; } /* ** Parse a URI into scheme, host, port, and path. */ void file_parse_uri( const char *zUri, Blob *pScheme, Blob *pHost, int *pPort, Blob *pPath ){ int i, j; for(i=0; zUri[i] && zUri[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 pBuf where the name of ** the temporary file is derived from zBasis. The suffix on the temp ** file is the same as the suffix on zBasis, and the temp file has ** the root of zBasis in its name. ** ** If zTag is not NULL, then try to create the temp-file using zTag ** as a differentiator. If that fails, or if zTag is NULL, then use ** a bunch of random characters as the tag. ** ** Dangerous characters in zBasis are changed. ** ** See also fossil_temp_filename() and file_time_tempname(); */ void file_tempname(Blob *pBuf, const char *zBasis, const char *zTag){ #if defined(_WIN32) const char *azDirs[] = { 0, /* GetTempPath */ 0, /* TEMP */ 0, /* TMP */ ".", }; #else static const char *azDirs[] = { 0, /* TMPDIR */ "/var/tmp", "/usr/tmp", "/tmp", "/temp", ".", }; #endif static const unsigned char zChars[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; unsigned int i; const char *zDir = "."; int cnt = 0; char zRand[16]; int nBasis; const char *zSuffix; char *z; #if defined(_WIN32) wchar_t zTmpPath[MAX_PATH]; if( GetTempPathW(MAX_PATH, zTmpPath) ){ azDirs[0] = fossil_path_to_utf8(zTmpPath); /* Removing trailing \ from the temp path */ z = (char*)azDirs[0]; i = (int)strlen(z)-1; if( i>0 && z[i]=='\\' ) z[i] = 0; } azDirs[1] = fossil_getenv("TEMP"); azDirs[2] = fossil_getenv("TMP"); #else azDirs[0] = fossil_getenv("TMPDIR"); #endif for(i=0; i<count(azDirs); i++){ if( azDirs[i]==0 ) continue; if( !file_isdir(azDirs[i], ExtFILE) ) continue; zDir = azDirs[i]; break; } assert( zBasis!=0 ); zSuffix = 0; for(i=0; zBasis[i]; i++){ if( zBasis[i]=='/' || zBasis[i]=='\\' ){ zBasis += i+1; i = -1; }else if( zBasis[i]=='.' ){ zSuffix = zBasis + i; } } if( zSuffix==0 || zSuffix<=zBasis ){ zSuffix = ""; nBasis = i; }else{ nBasis = (int)(zSuffix - zBasis); } if( nBasis==0 ){ nBasis = 6; zBasis = "fossil"; } do{ blob_zero(pBuf); if( cnt++>20 ) fossil_fatal("cannot generate a temporary filename"); if( zTag==0 ){ const int nRand = sizeof(zRand)-1; sqlite3_randomness(nRand, zRand); for(i=0; i<nRand; i++){ zRand[i] = (char)zChars[ ((unsigned char)zRand[i])%(sizeof(zChars)-1) ]; } zRand[nRand] = 0; zTag = zRand; } blob_appendf(pBuf, "%s/%.*s~%s%s", zDir, nBasis, zBasis, zTag, zSuffix); zTag = 0; for(z=blob_str(pBuf); z!=0 && (z=strpbrk(z,"'\"`;|$&"))!=0; z++){ z[0] = '_'; } }while( file_size(blob_str(pBuf), ExtFILE)>=0 ); #if defined(_WIN32) fossil_path_free((char *)azDirs[0]); fossil_path_free((char *)azDirs[1]); fossil_path_free((char *)azDirs[2]); /* Change all \ characters in the windows path into / so that they can ** be safely passed to a subcommand, such as by gdiff */ z = blob_buffer(pBuf); for(i=0; z[i]; i++) if( z[i]=='\\' ) z[i] = '/'; #else fossil_path_free((char *)azDirs[0]); #endif } /* ** Compute a temporary filename in zDir. The filename is based on ** the current time. ** ** See also fossil_temp_filename() and file_tempname(); */ char *file_time_tempname(const char *zDir, const char *zSuffix){ struct tm *tm; unsigned int r; static unsigned int cnt = 0; time_t t; t = time(0); tm = gmtime(&t); sqlite3_randomness(sizeof(r), &r); return mprintf("%s/%04d%02d%02d%02d%02d%02d%04d%06d%s", zDir, tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, cnt++, r%1000000, zSuffix); } /* ** COMMAND: test-tempname ** Usage: fossil test-name [--time SUFFIX] [--tag NAME] BASENAME ... ** ** Generate temporary filenames derived from BASENAME. Use the --time ** option to generate temp names based on the time of day. If --tag NAME ** is specified, try to use NAME as the differentiator in the temp file. ** ** If --time is used, file_time_tempname() generates the filename. ** If BASENAME is present, file_tempname() generates the filename. ** Without --time or BASENAME, fossil_temp_filename() generates the filename. */ void file_test_tempname(void){ int i; const char *zSuffix = find_option("time",0,1); Blob x = BLOB_INITIALIZER; char *z; const char *zTag = find_option("tag",0,1); verify_all_options(); if( g.argc<=2 ){ z = fossil_temp_filename(); fossil_print("%s\n", z); sqlite3_free(z); } for(i=2; i<g.argc; i++){ if( zSuffix ){ z = file_time_tempname(g.argv[i], zSuffix); fossil_print("%s\n", z); fossil_free(z); }else{ file_tempname(&x, g.argv[i], zTag); fossil_print("%s\n", blob_str(&x)); blob_reset(&x); } } } /* ** Return true if a file named zName exists and has identical content ** to the blob pContent. If zName does not exist or if the content is ** different in any way, then return false. ** ** This routine assumes RepoFILE */ int file_is_the_same(Blob *pContent, const char *zName){ i64 iSize; int rc; Blob onDisk; iSize = file_size(zName, RepoFILE); if( iSize<0 ) return 0; if( iSize!=blob_size(pContent) ) return 0; blob_read_from_file(&onDisk, zName, RepoFILE); rc = blob_compare(&onDisk, pContent); blob_reset(&onDisk); return rc==0; } /* ** Return the value of an environment variable as UTF8. ** Use fossil_path_free() to release resources. */ char *fossil_getenv(const char *zName){ #ifdef _WIN32 wchar_t *uName = fossil_utf8_to_unicode(zName); void *zValue = _wgetenv(uName); fossil_unicode_free(uName); #else char *zValue = getenv(zName); #endif if( zValue ) zValue = fossil_path_to_utf8(zValue); return zValue; } /* ** Sets the value of an environment variable as UTF8. */ int fossil_setenv(const char *zName, const char *zValue){ int rc; char *zString = mprintf("%s=%s", zName, zValue); #ifdef _WIN32 wchar_t *uString = fossil_utf8_to_unicode(zString); rc = _wputenv(uString); fossil_unicode_free(uString); fossil_free(zString); #else rc = putenv(zString); /* NOTE: Cannot free the string on POSIX. */ /* fossil_free(zString); */ #endif return rc; } /* ** Clear all environment variables */ int fossil_clearenv(void){ #ifdef _WIN32 int rc = 0; LPWCH zzEnv = GetEnvironmentStringsW(); if( zzEnv ){ LPCWSTR zEnv = zzEnv; /* read-only */ while( 1 ){ LPWSTR zNewEnv = _wcsdup(zEnv); /* writable */ if( zNewEnv ){ LPWSTR zEquals = wcsstr(zNewEnv, L"="); if( zEquals ){ zEquals[1] = 0; /* no value */ if( zNewEnv==zEquals || _wputenv(zNewEnv)==0 ){ /* via CRT */ /* do nothing */ }else{ zEquals[0] = 0; /* name only */ if( !SetEnvironmentVariableW(zNewEnv, NULL) ){ /* via Win32 */ rc = 1; } } if( rc==0 ){ zEnv += (lstrlenW(zEnv) + 1); /* double NUL term? */ if( zEnv[0]==0 ){ free(zNewEnv); break; /* no more vars */ } } }else{ rc = 1; } }else{ rc = 1; } free(zNewEnv); if( rc!=0 ) break; } if( !FreeEnvironmentStringsW(zzEnv) ){ rc = 2; } }else{ rc = 1; } return rc; #else extern char **environ; environ[0] = 0; return 0; #endif } /* ** Like fopen() but always takes a UTF8 argument. ** ** This function assumes ExtFILE. In other words, symbolic links ** are always followed. */ FILE *fossil_fopen(const char *zName, const char *zMode){ #ifdef _WIN32 wchar_t *uMode = fossil_utf8_to_unicode(zMode); wchar_t *uName = fossil_utf8_to_path(zName, 0); FILE *f = _wfopen(uName, uMode); fossil_path_free(uName); fossil_unicode_free(uMode); #else FILE *f = fopen(zName, zMode); #endif return f; } /* ** Wrapper for freopen() that understands UTF8 arguments. */ FILE *fossil_freopen(const char *zName, const char *zMode, FILE *stream){ #ifdef _WIN32 wchar_t *uMode = fossil_utf8_to_unicode(zMode); wchar_t *uName = fossil_utf8_to_path(zName, 0); FILE *f = _wfreopen(uName, uMode, stream); fossil_path_free(uName); fossil_unicode_free(uMode); #else FILE *f = freopen(zName, zMode, stream); #endif return f; } /* ** Works like fclose() except that: ** ** 1) is a no-op if f is 0 or if it is stdin. ** ** 2) If f is one of (stdout, stderr), it is flushed but not closed. */ void fossil_fclose(FILE *f){ if(f!=0){ if(stdout==f || stderr==f){ fflush(f); }else if(stdin!=f){ fclose(f); } } } /* ** Works like fopen(zName,"wb") except that: ** ** 1) If zName is "-", the stdout handle is returned. ** ** 2) Else file_mkfolder() is used to create all directories ** which lead up to the file before opening it. ** ** 3) It fails fatally if the file cannot be opened. */ FILE *fossil_fopen_for_output(const char *zFilename){ if(zFilename[0]=='-' && zFilename[1]==0){ return stdout; }else{ FILE * p; file_mkfolder(zFilename, ExtFILE, 1, 0); p = fossil_fopen(zFilename, "wb"); if( p==0 ){ #if defined(_WIN32) const char *zReserved = file_is_win_reserved(zFilename); if( zReserved ){ fossil_fatal("cannot open \"%s\" because \"%s\" is " "a reserved name on Windows", zFilename, zReserved); } #endif fossil_fatal("unable to open file \"%s\" for writing", zFilename); } return p; } } /* ** Return non-NULL if zFilename contains pathname elements that ** are reserved on Windows. The returned string is the disallowed ** path element. */ const char *file_is_win_reserved(const char *zPath){ static const char *const azRes[] = { "CON","PRN","AUX","NUL","COM","LPT" }; static char zReturn[5]; int i; while( zPath[0] ){ for(i=0; i<count(azRes); i++){ if( sqlite3_strnicmp(zPath, azRes[i], 3)==0 && ((i>=4 && fossil_isdigit(zPath[3]) && (zPath[4]=='/' || zPath[4]=='.' || zPath[4]==0)) || (i<4 && (zPath[3]=='/' || zPath[3]=='.' || zPath[3]==0))) ){ sqlite3_snprintf(5,zReturn,"%.*s", i>=4 ? 4 : 3, zPath); return zReturn; } } while( zPath[0] && zPath[0]!='/' ) zPath++; while( zPath[0]=='/' ) zPath++; } return 0; } /* ** COMMAND: test-valid-for-windows ** Usage: fossil test-valid-for-windows FILENAME .... ** ** Show which filenames are not valid for Windows */ void file_test_valid_for_windows(void){ int i; for(i=2; i<g.argc; i++){ fossil_print("%s %s\n", file_is_win_reserved(g.argv[i]), g.argv[i]); } } /* ** Returns non-zero if the specified file extension belongs to a Fossil ** repository file. */ int file_is_repository_extension(const char *zPath){ if( fossil_strcmp(zPath, ".fossil")==0 ) return 1; #if USE_SEE if( fossil_strcmp(zPath, ".efossil")==0 ) return 1; #endif return 0; } /* ** Returns non-zero if the specified path appears to match a file extension ** that should belong to a Fossil repository file. */ int file_contains_repository_extension(const char *zPath){ if( sqlite3_strglob("*.fossil*",zPath)==0 ) return 1; #if USE_SEE if( sqlite3_strglob("*.efossil*",zPath)==0 ) return 1; #endif return 0; } /* ** Returns non-zero if the specified path ends with a file extension that ** should belong to a Fossil repository file. */ int file_ends_with_repository_extension(const char *zPath, int bQual){ if( bQual ){ if( sqlite3_strglob("*/*.fossil", zPath)==0 ) return 1; #if USE_SEE if( sqlite3_strglob("*/*.efossil", zPath)==0 ) return 1; #endif }else{ if( sqlite3_strglob("*.fossil", zPath)==0 ) return 1; #if USE_SEE if( sqlite3_strglob("*.efossil", zPath)==0 ) return 1; #endif } return 0; } /* ** Remove surplus "/" characters from the beginning of a full pathname. ** Extra leading "/" characters are benign on unix. But on Windows ** machines, they must be removed. Example: Convert "/C:/fossil/xyx.fossil" ** into "C:/fossil/xyz.fossil". Cygwin should behave as Windows here. */ const char *file_cleanup_fullpath(const char *z){ #if defined(_WIN32) || defined(__CYGWIN__) if( z[0]=='/' && fossil_isalpha(z[1]) && z[2]==':' && z[3]=='/' ) z++; #else while( z[0]=='/' && z[1]=='/' ) z++; #endif return z; } /* ** Count the number of objects (files and subdirectories) in a given ** directory. Return the count. Return -1 if the object is not a ** directory. ** ** This routine never counts the two "." and ".." special directory ** entries, even if the provided glob would match them. */ int file_directory_size(const char *zDir, const char *zGlob, int omitDotFiles){ void *zNative; DIR *d; int n = -1; zNative = fossil_utf8_to_path(zDir,1); d = opendir(zNative); if( d ){ struct dirent *pEntry; n = 0; while( (pEntry=readdir(d))!=0 ){ if( pEntry->d_name[0]==0 ) continue; if( pEntry->d_name[0]=='.' && (omitDotFiles /* Skip the special "." and ".." entries. */ || pEntry->d_name[1]==0 || (pEntry->d_name[1]=='.' && pEntry->d_name[2]==0))){ continue; } if( zGlob ){ char *zUtf8 = fossil_path_to_utf8(pEntry->d_name); int rc = sqlite3_strglob(zGlob, zUtf8); fossil_path_free(zUtf8); if( rc ) continue; } n++; } closedir(d); } fossil_path_free(zNative); return n; } /* ** COMMAND: test-dir-size ** ** Usage: %fossil test-dir-size NAME [GLOB] [--nodots] ** ** Return the number of objects in the directory NAME. If GLOB is ** provided, then only count objects that match the GLOB pattern. ** if --nodots is specified, omit files that begin with ".". */ void test_dir_size_cmd(void){ int omitDotFiles = find_option("nodots",0,0)!=0; const char *zGlob; const char *zDir; verify_all_options(); if( g.argc!=3 && g.argc!=4 ){ usage("NAME [GLOB] [-nodots]"); } zDir = g.argv[2]; zGlob = g.argc==4 ? g.argv[3] : 0; fossil_print("%d\n", file_directory_size(zDir, zGlob, omitDotFiles)); } /* ** Internal helper for touch_cmd(). zAbsName must be resolvable as-is ** to an existing file - this function does not expand/normalize ** it. i.e. it "really should" be an absolute path. zTreeName is ** strictly cosmetic: it is used when dryRunFlag, verboseFlag, or ** quietFlag generate output, and is assumed to be a repo-relative or ** or subdir-relative filename. ** ** newMTime is the file's new timestamp (Unix epoch). ** ** Returns 1 if it sets zAbsName's mtime, 0 if it does not (indicating ** that the file already has that timestamp or a warning was emitted ** or was not found). If dryRunFlag is true then it outputs the name ** of the file it would have timestamped but does not stamp the ** file. If verboseFlag is true, it outputs a message if the file's ** timestamp is actually modified. If quietFlag is true then the ** output of non-fatal warning messages is suppressed. ** ** As a special case, if newMTime is 0 then this function emits a ** warning (unless quietFlag is true), does NOT set the timestamp, and ** returns 0. The timestamp is known to be zero when ** mtime_of_manifest_file() is asked to provide the timestamp for a ** file which is currently undergoing an uncommitted merge (though ** this may depend on exactly where that merge is happening the ** history of the project). */ static int touch_cmd_stamp_one_file(char const *zAbsName, char const *zTreeName, i64 newMtime, int dryRunFlag, int verboseFlag, int quietFlag){ i64 currentMtime; if(newMtime==0){ if( quietFlag==0 ){ fossil_print("SKIPPING timestamp of 0: %s\n", zTreeName); } return 0; } currentMtime = file_mtime(zAbsName, 0); if(currentMtime<0){ fossil_print("SKIPPING: cannot stat file: %s\n", zAbsName); return 0; }else if(currentMtime==newMtime){ return 0; }else if( dryRunFlag!=0 ){ fossil_print( "dry-run: %s\n", zTreeName ); }else{ file_set_mtime(zAbsName, newMtime); if( verboseFlag!=0 ){ fossil_print( "touched %s\n", zTreeName ); } } return 1; } /* ** Internal helper for touch_cmd(). If the given file name is found in ** the given check-out version, which MUST be the check-out version ** currently populating the vfile table, the vfile.mrid value for the ** file is returned, else 0 is returned. zName must be resolvable ** as-is from the vfile table - this function neither expands nor ** normalizes it, though it does compare using the repo's ** filename_collation() preference. */ static int touch_cmd_vfile_mrid( int vid, char const *zName ){ int mrid = 0; static Stmt q = empty_Stmt_m; db_static_prepare(&q, "SELECT vfile.mrid " "FROM vfile LEFT JOIN blob ON vfile.mrid=blob.rid " "WHERE vid=:vid AND pathname=:pathname %s", filename_collation()); db_bind_int(&q, ":vid", vid); db_bind_text(&q, ":pathname", zName); if(SQLITE_ROW==db_step(&q)){ mrid = db_column_int(&q, 0); } db_reset(&q); return mrid; } /* ** COMMAND: touch* ** ** Usage: %fossil touch ?OPTIONS? ?FILENAME...? ** ** For each file in the current check-out matching one of the provided ** list of glob patterns and/or file names, the file's mtime is ** updated to a value specified by one of the flags --checkout, ** --checkin, or --now. ** ** If neither glob patterns nor filenames are provided, it operates on ** all files managed by the currently checked-out version. ** ** This command gets its name from the conventional Unix "touch" ** command. ** ** Options: ** --now Stamp each affected file with the current time. ** This is the default behavior. ** -c|--checkin Stamp each affected file with the time of the ** most recent check-in which modified that file ** -C|--checkout Stamp each affected file with the time of the ** currently checked-out version ** -g GLOBLIST Comma-separated list of glob patterns ** -G GLOBFILE Similar to -g but reads its globs from a ** fossil-conventional glob list file ** -v|--verbose Outputs extra information about its globs ** and each file it touches ** -n|--dry-run Outputs which files would require touching, ** but does not touch them ** -q|--quiet Suppress warnings, e.g. when skipping unmanaged ** or out-of-tree files ** ** Only one of --now, --checkin, and --checkout may be used. The ** default is --now. ** ** Only one of -g or -G may be used. If neither is provided and no ** additional filenames are provided, the effect is as if a glob of ** '*' were provided, i.e. all files belonging to the ** currently checked-out version. Note that all glob patterns provided ** via these flags are always evaluated as if they are relative to the ** top of the source tree, not the current working (sub)directory. ** Filenames provided without these flags, on the other hand, are ** treated as relative to the current directory. ** ** As a special case, files currently undergoing an uncommitted merge ** might not get timestamped with --checkin because it may be ** impossible for fossil to choose between multiple potential ** timestamps. A non-fatal warning is emitted for such cases. ** */ void touch_cmd(){ const char * zGlobList; /* -g List of glob patterns */ const char * zGlobFile; /* -G File of glob patterns */ Glob * pGlob = 0; /* List of glob patterns */ int verboseFlag; int dryRunFlag; int vid; /* Check-out version */ int changeCount = 0; /* Number of files touched */ int quietFlag = 0; /* -q|--quiet */ int timeFlag; /* -1==--checkin, 1==--checkout, 0==--now */ i64 nowTime = 0; /* Timestamp of --now or --checkout */ Stmt q; Blob absBuffer = empty_blob; /* Absolute filename buffer */ verboseFlag = find_option("verbose","v",0)!=0; quietFlag = find_option("quiet","q",0)!=0 || g.fQuiet; dryRunFlag = find_option("dry-run","n",0)!=0; zGlobList = find_option("glob", "g",1); zGlobFile = find_option("globfile", "G",1); if(zGlobList && zGlobFile){ fossil_fatal("Options -g and -G may not be used together."); } { int const ci = (find_option("checkin","c",0) || find_option("check-in",0,0)) ? 1 : 0; int const co = find_option("checkout","C",0) ? 1 : 0; int const now = find_option("now",0,0) ? 1 : 0; if(ci + co + now > 1){ fossil_fatal("Options --checkin, --checkout, and --now may " "not be used together."); }else if(co){ timeFlag = 1; if(verboseFlag){ fossil_print("Timestamp = current check-out version.\n"); } }else if(ci){ timeFlag = -1; if(verboseFlag){ fossil_print("Timestamp = check-in in which each file was " "most recently modified.\n"); } }else{ timeFlag = 0; if(verboseFlag){ fossil_print("Timestamp = current system time.\n"); } } } verify_all_options(); db_must_be_within_tree(); vid = db_lget_int("checkout", 0); if(vid==0){ fossil_fatal("Cannot determine check-out version."); } if(zGlobList){ pGlob = *zGlobList ? glob_create(zGlobList) : 0; }else if(zGlobFile){ Blob globs = empty_blob; blob_read_from_file(&globs, zGlobFile, ExtFILE); pGlob = glob_create( globs.aData ); blob_reset(&globs); } if( pGlob && verboseFlag!=0 ){ int i; for(i=0; i<pGlob->nPattern; ++i){ fossil_print("glob: %s\n", pGlob->azPattern[i]); } } db_begin_transaction(); if(timeFlag==0){/*--now*/ nowTime = time(0); }else if(timeFlag>0){/*--checkout: get the check-out manifest's timestamp*/ assert(vid>0); nowTime = db_int64(-1, "SELECT CAST(strftime('%%s'," "(SELECT mtime FROM event WHERE objid=%d)" ") AS INTEGER)", vid); if(nowTime<0){ fossil_fatal("Could not determine check-out version's time!"); } }else{ /* --checkin */ assert(0 == nowTime); } if((pGlob && pGlob->nPattern>0) || g.argc<3){ /* ** We have either (1) globs or (2) no trailing filenames. If there ** are neither globs nor filenames then we operate on all managed ** files. */ db_prepare(&q, "SELECT vfile.mrid, pathname " "FROM vfile LEFT JOIN blob ON vfile.mrid=blob.rid " "WHERE vid=%d", vid); while(SQLITE_ROW==db_step(&q)){ int const fid = db_column_int(&q, 0); const char * zName = db_column_text(&q, 1); i64 newMtime = nowTime; char const * zAbs = 0; /* absolute path */ absBuffer.nUsed = 0; assert(timeFlag<0 ? newMtime==0 : newMtime>0); if(pGlob){ if(glob_match(pGlob, zName)==0) continue; } blob_appendf( &absBuffer, "%s%s", g.zLocalRoot, zName ); zAbs = blob_str(&absBuffer); if( newMtime || mtime_of_manifest_file(vid, fid, &newMtime)==0 ){ changeCount += touch_cmd_stamp_one_file( zAbs, zName, newMtime, dryRunFlag, verboseFlag, quietFlag ); } } db_finalize(&q); } glob_free(pGlob); pGlob = 0; if(g.argc>2){ /* ** Trailing filenames on the command line. These require extra ** care to avoid modifying unmanaged or out-of-tree files and ** finding an associated --checkin timestamp. */ int i; Blob treeNameBuf = empty_blob; /* Buffer for file_tree_name(). */ for( i = 2; i < g.argc; ++i, blob_reset(&treeNameBuf) ){ char const * zArg = g.argv[i]; char const * zTreeFile; /* repo-relative filename */ char const * zAbs; /* absolute filename */ i64 newMtime = nowTime; int nameCheck; int fid; /* vfile.mrid of file */ nameCheck = file_tree_name( zArg, &treeNameBuf, 0, 0 ); if(nameCheck==0){ if(quietFlag==0){ fossil_print("SKIPPING out-of-tree file: %s\n", zArg); } continue; } zTreeFile = blob_str(&treeNameBuf); fid = touch_cmd_vfile_mrid( vid, zTreeFile ); if(fid==0){ if(quietFlag==0){ fossil_print("SKIPPING unmanaged file: %s\n", zArg); } continue; } absBuffer.nUsed = 0; blob_appendf(&absBuffer, "%s%s", g.zLocalRoot, zTreeFile); zAbs = blob_str(&absBuffer); if(timeFlag<0){/*--checkin*/ if(mtime_of_manifest_file( vid, fid, &newMtime )!=0){ fossil_fatal("Could not resolve --checkin mtime of %s", zTreeFile); } }else{ assert(newMtime>0); } changeCount += touch_cmd_stamp_one_file( zAbs, zArg, newMtime, dryRunFlag, verboseFlag, quietFlag ); } } db_end_transaction(0); blob_reset(&absBuffer); if( dryRunFlag!=0 ){ fossil_print("dry-run: would have touched %d file(s)\n", changeCount); }else{ fossil_print("Touched %d file(s)\n", changeCount); } } /* ** If zFileName is not NULL and contains a '.', this returns a pointer ** to the position after the final '.', else it returns NULL. As a ** special case, if it ends with a period then a pointer to the ** terminating NUL byte is returned. */ const char * file_extension(const char *zFileName){ const char * zExt = zFileName ? strrchr(zFileName, '.') : 0; return zExt ? &zExt[1] : 0; } /* ** Returns non-zero if the specified file name ends with any reserved name, ** e.g.: _FOSSIL_ or .fslckout. Specifically, it returns 1 for exact match ** or 2 for a tail match on a longer file name. ** ** For the sake of efficiency, zFilename must be a canonical name, e.g. an ** absolute path using only forward slash ('/') as a directory separator. ** ** nFilename must be the length of zFilename. When negative, strlen() will ** be used to calculate it. */ int file_is_reserved_name(const char *zFilename, int nFilename){ const char *zEnd; /* one-after-the-end of zFilename */ int gotSuffix = 0; /* length of suffix (-wal, -shm, -journal) */ assert( zFilename && "API misuse" ); if( nFilename<0 ) nFilename = (int)strlen(zFilename); if( nFilename<8 ) return 0; /* strlen("_FOSSIL_") */ zEnd = zFilename + nFilename; if( nFilename>=12 ){ /* strlen("_FOSSIL_-(shm|wal)") */ /* Check for (-wal, -shm, -journal) suffixes, with an eye towards ** runtime speed. */ if( zEnd[-4]=='-' ){ if( fossil_strnicmp("wal", &zEnd[-3], 3) && fossil_strnicmp("shm", &zEnd[-3], 3) ){ return 0; } gotSuffix = 4; }else if( nFilename>=16 && zEnd[-8]=='-' ){ /*strlen(_FOSSIL_-journal) */ if( fossil_strnicmp("journal", &zEnd[-7], 7) ) return 0; gotSuffix = 8; } if( gotSuffix ){ assert( 4==gotSuffix || 8==gotSuffix ); zEnd -= gotSuffix; nFilename -= gotSuffix; gotSuffix = 1; } assert( nFilename>=8 && "strlen(_FOSSIL_)" ); assert( gotSuffix==0 || gotSuffix==1 ); } switch( zEnd[-1] ){ case '_':{ if( fossil_strnicmp("_FOSSIL_", &zEnd[-8], 8) ) return 0; if( 8==nFilename ) return 1; return zEnd[-9]=='/' ? 2 : gotSuffix; } case 'T': case 't':{ if( nFilename<9 || zEnd[-9]!='.' || fossil_strnicmp(".fslckout", &zEnd[-9], 9) ){ return 0; } if( 9==nFilename ) return 1; return zEnd[-10]=='/' ? 2 : gotSuffix; } default:{ return 0; } } } /* ** COMMAND: test-is-reserved-name ** ** Usage: %fossil test-is-reserved-name FILENAMES... ** ** Passes each given name to file_is_reserved_name() and outputs one ** line per file: the result value of that function followed by the ** name. */ void test_is_reserved_name_cmd(void){ int i; if(g.argc<3){ usage("FILENAME_1 [...FILENAME_N]"); } for( i = 2; i < g.argc; ++i ){ const int check = file_is_reserved_name(g.argv[i], -1); fossil_print("%d %s\n", check, g.argv[i]); } } /* ** Returns 1 if the given directory contains a file named .fslckout, 2 ** if it contains a file named _FOSSIL_, else returns 0. */ int dir_has_ckout_db(const char *zDir){ int rc = 0; char * zCkoutDb = mprintf("%//.fslckout", zDir); if(file_isfile(zCkoutDb, ExtFILE)){ rc = 1; }else{ fossil_free(zCkoutDb); zCkoutDb = mprintf("%//_FOSSIL_", zDir); if(file_isfile(zCkoutDb, ExtFILE)){ rc = 2; } } fossil_free(zCkoutDb); return rc; }