summaryrefslogtreecommitdiff
path: root/src/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/file.c')
-rw-r--r--src/file.c3166
1 files changed, 3166 insertions, 0 deletions
diff --git a/src/file.c b/src/file.c
new file mode 100644
index 0000000..c4fb2bb
--- /dev/null
+++ b/src/file.c
@@ -0,0 +1,3166 @@
+/*
+** 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 be 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 = fossil_strdup(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 = fossil_strdup(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 tail of a command: the basename of the putative executable (which
+** could be quoted when containing spaces) and the following arguments.
+*/
+const char *command_tail(const char *z){
+ const char *zTail = z;
+ char chQuote = 0;
+ if( !zTail ) return 0;
+ while( z[0] && (!fossil_isspace(z[0]) || chQuote) ){
+ if( z[0]=='"' || z[0]=='\'' ){
+ chQuote = (chQuote==z[0]) ? 0 : 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;
+ }
+}
+
+/*
+** Return the basename of the putative executable in a command (w/o arguments).
+** The returned memory should be freed via fossil_free().
+*/
+char *command_basename(const char *z){
+ const char *zTail = command_tail(z);
+ const char *zEnd = zTail;
+ while( zEnd[0] && !fossil_isspace(zEnd[0]) && zEnd[0]!='"' && zEnd[0]!='\'' ){
+ zEnd++;
+ }
+ if( zEnd ){
+ return mprintf("%.*s", (int)(zEnd-zTail), zTail);
+ }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 = fossil_strdup(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 = fossil_strdup(zTail);
+ }else{
+ z = fossil_strdup(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 /
+** * On windows, add the drive letter prefix.
+**
+** 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){
+ char zPwd[2000];
+ blob_zero(pOut);
+ if( file_is_absolute_path(zOrigName) ){
+#if defined(_WIN32)
+ if( fossil_isdirsep(zOrigName[0]) ){
+ /* Add the drive letter to the full pathname */
+ file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
+ blob_appendf(pOut, "%.2s%/", zPwd, zOrigName);
+ }else
+#endif
+ {
+ blob_appendf(pOut, "%/", zOrigName);
+ }
+ }else{
+ 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.
+**
+** See also the "which" command (without the "test-" prefix). The plain
+** "which" command is more convenient to use since it provides the -a/-all
+** option, and because it is shorter. The "fossil which" command without
+** the "test-" prefix is recommended for day-to-day use. This command is
+** retained because it tests the internal file_fullexename() function
+** whereas plain "which" does not.
+*/
+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);
+ if( g.db==0 ) sqlite3_open(":memory:", &g.db);
+ sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
+ file_inode_sql_func, 0, 0);
+ 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);
+ z = db_text(0, "SELECT inode(%Q)", g.argv[i]);
+ fossil_print(" file_inode_sql_func = \"%s\"\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(
+#if defined(_WIN32) || defined(__CYGWIN__)
+ !fossil_isupper(z[0]) || z[1]!=':' || !fossil_isdirsep(z[2])
+#else
+ z[0]!='/'
+#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;
+}
+
+/*
+** Find the name of all objects (files and subdirectories) in a given
+** directory that match a GLOB pattern. If zGlob is NULL, then return
+** all objects. The list is written into *pazList and the number of
+** entries is returned. If pazList is NULL, then only the count is
+** returned.
+**
+** If zDir is not a directory, *pazList is unchanged and -1 is returned.
+**
+** Memory used to old *pazList should be freed using a subsequent call
+** to file_directory_list_free().
+**
+** This routine never counts the two "." and ".." special directory
+** entries, even if the provided glob would match them.
+*/
+int file_directory_list(
+ const char *zDir, /* Directory to get a listing of */
+ const char *zGlob, /* Only list objects matching this pattern */
+ int omitDotFiles, /* 0: skip "." and "..", 1: no .-files, 2: keep all */
+ int nLimit, /* Find at most this many files. 0 means "all" */
+ char ***pazList /* OUT: Write the list here, if not NULL */
+){
+ void *zNative;
+ DIR *d;
+ int n = -1;
+ int nAlloc = 0;
+ if( pazList ) *pazList = 0;
+ zNative = fossil_utf8_to_path(zDir,1);
+ d = opendir(zNative);
+ if( d ){
+ struct dirent *pEntry;
+ n = 0;
+ while( (pEntry=readdir(d))!=0 ){
+ char *zUtf8 = 0;
+ if( pEntry->d_name[0]==0 ) continue;
+ if( pEntry->d_name[0]=='.'
+ && omitDotFiles<2
+ && (omitDotFiles==1
+ /* Skip the special "." and ".." entries unless omitDotFiles>=2 */
+ || pEntry->d_name[1]==0
+ || (pEntry->d_name[1]=='.' && pEntry->d_name[2]==0)
+ )
+ ){
+ continue;
+ }
+ if( zGlob ){
+ int rc;
+ zUtf8 = fossil_path_to_utf8(pEntry->d_name);
+ rc = sqlite3_strglob(zGlob, zUtf8);
+ if( rc ){
+ fossil_path_free(zUtf8);
+ continue;
+ }
+ }
+ if( pazList ){
+ if( n+1 >= nAlloc ){
+ nAlloc = 100 + n;
+ *pazList = fossil_realloc(*pazList, nAlloc*sizeof(char*));
+ }
+ if( zUtf8==0 ){
+ zUtf8 = fossil_path_to_utf8(pEntry->d_name);
+ }
+ (*pazList)[n] = fossil_strdup(zUtf8);
+ }
+ n++;
+ if( zUtf8 ) fossil_path_free(zUtf8);
+ if( nLimit>0 && n>=nLimit ) break;
+ }
+ closedir(d);
+ }
+ fossil_path_free(zNative);
+ if( pazList ) (*pazList)[n] = 0;
+ return n;
+}
+void file_directory_list_free(char **azList){
+ char **az;
+ if( azList==0 ) return;
+ az = azList;
+ while( az[0] ){
+ fossil_free(az[0]);
+ az++;
+ }
+ fossil_free(azList);
+}
+
+/*
+** COMMAND: test-dir-list
+**
+** Usage: %fossil test-dir-list NAME [GLOB] [OPTIONS]
+**
+** Return the names of up to N objects in the directory NAME. If GLOB is
+** provided, then only show objects that match the GLOB pattern.
+**
+** This command is intended for testing the file_directory_list() function.
+**
+** Options:
+**
+** --count Only count files, do not list them.
+** --limit N Only show the first N files seen
+** --nodots Do not show or count files that start with '.'
+*/
+void test_dir_list_cmd(void){
+ int omitDotFiles = find_option("nodots",0,0)!=0;
+ const char *zLimit = find_option("limit",0,1);
+ int countOnly = find_option("count",0,0)!=0;
+ const char *zGlob;
+ const char *zDir;
+ char **azList = 0;
+ int nList;
+
+ 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;
+ nList = file_directory_list(zDir, zGlob, omitDotFiles,
+ zLimit ? atoi(zLimit) : 0,
+ countOnly ? 0 : &azList);
+ if( countOnly ){
+ fossil_print("%d\n", nList);
+ }else{
+ int i;
+ for(i=0; i<nList; i++){
+ fossil_print(" %s\n", azList[i]);
+ }
+ }
+ file_directory_list_free(azList);
+}
+
+/*
+** 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 = 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;
+ i64 sz;
+ 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;
+ }
+ }
+ if( rc && ((sz = file_size(zCkoutDb, ExtFILE))<1024 || (sz%512)!=0) ){
+ rc = 0;
+ }
+ fossil_free(zCkoutDb);
+ return rc;
+}
+
+/*
+** This is the implementation of inode(FILENAME) SQL function.
+**
+** dev_inode(FILENAME) returns a string. If FILENAME exists and is
+** a regular file, then the return string is of the form:
+**
+** DEV/INODE
+**
+** Where DEV and INODE are the device number and inode number for
+** the file. On Windows, the volume serial number (DEV) and file
+** identifier (INODE) are used to compute the value, see comments
+** on the win32_file_id() function.
+**
+** If FILENAME does not exist, then the return is an empty string.
+**
+** The value of inode() can be used to eliminate files from a list
+** that have duplicates because they have differing names due to links.
+**
+** Code that wants to use this SQL function needs to first register
+** it using a call such as the following:
+**
+** sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
+** file_inode_sql_func, 0, 0);
+*/
+void file_inode_sql_func(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *zFilename;
+ assert( argc==1 );
+ zFilename = (const char*)sqlite3_value_text(argv[0]);
+ if( zFilename==0 || zFilename[0]==0 || file_access(zFilename,F_OK) ){
+ sqlite3_result_text(context, "", 0, SQLITE_STATIC);
+ return;
+ }
+#if defined(_WIN32)
+ {
+ char *zFileId = win32_file_id(zFilename);
+ if( zFileId ){
+ sqlite3_result_text(context, zFileId, -1, fossil_free);
+ }else{
+ sqlite3_result_text(context, "", 0, SQLITE_STATIC);
+ }
+ }
+#else
+ {
+ struct stat buf;
+ int rc;
+ memset(&buf, 0, sizeof(buf));
+ rc = stat(zFilename, &buf);
+ if( rc ){
+ sqlite3_result_text(context, "", 0, SQLITE_STATIC);
+ }else{
+ sqlite3_result_text(context,
+ mprintf("%lld/%lld", (i64)buf.st_dev, (i64)buf.st_ino), -1,
+ fossil_free);
+ }
+ }
+#endif
+}