/* ** 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. ** ** Functions named file_* are generic functions that always follow symlinks. ** ** Functions named file_wd_* are to be used for files inside working ** directories. They follow symlinks depending on 'allow-symlinks' setting. */ #include "config.h" #include #include #include #include #include #include "file.h" /* ** On Windows, include the Platform SDK header file. */ #ifdef _WIN32 # include # include # include #else # include #endif #if INTERFACE #include #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)) struct fossilStat { i64 st_size; i64 st_mtime; int st_mode; }; #else # define fossilStat stat #endif #endif /* INTERFACE */ /* ** On Windows S_ISLNK always returns FALSE. */ #if !defined(S_ISLNK) # define S_ISLNK(x) (0) #endif static int fileStatValid = 0; static struct fossilStat fileStat; /* ** Fill stat buf with information received from stat() or lstat(). ** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on. ** */ static int fossil_stat(const char *zFilename, struct fossilStat *buf, int isWd){ #if !defined(_WIN32) int rc; char *zMbcs = fossil_utf8_to_filename(zFilename); if( isWd && g.allowSymlinks ){ rc = lstat(zMbcs, buf); }else{ rc = stat(zMbcs, buf); } fossil_filename_free(zMbcs); return rc; #else return win32_stat(zFilename, buf, isWd); #endif } /* ** Fill in the fileStat variable for the file named zFilename. ** If zFilename==0, then use the previous value of fileStat if ** there is a previous value. ** ** If isWd is TRUE, do lstat() instead of stat() if allow-symlinks is on. ** ** Return the number of errors. No error messages are generated. */ static int getStat(const char *zFilename, int isWd){ int rc = 0; if( zFilename==0 ){ if( fileStatValid==0 ) rc = 1; }else{ if( fossil_stat(zFilename, &fileStat, isWd)!=0 ){ fileStatValid = 0; rc = 1; }else{ 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){ return getStat(zFilename, 0) ? -1 : fileStat.st_size; } /* ** Same as file_size(), but takes into account symlinks. */ i64 file_wd_size(const char *zFilename){ return getStat(zFilename, 1) ? -1 : fileStat.st_size; } /* ** Return the modification time for a file. Return -1 if the file ** does not exist. If zFilename is NULL return the size of the most ** recently stat-ed file. */ i64 file_mtime(const char *zFilename){ return getStat(zFilename, 0) ? -1 : fileStat.st_mtime; } /* ** Same as file_mtime(), but takes into account symlinks. */ i64 file_wd_mtime(const char *zFilename){ return getStat(zFilename, 1) ? -1 : fileStat.st_mtime; } /* ** Return TRUE if the named file is an ordinary file or symlink ** and symlinks are allowed. ** Return false for directories, devices, fifos, etc. */ int file_wd_isfile_or_link(const char *zFilename){ return getStat(zFilename, 1) ? 0 : S_ISREG(fileStat.st_mode) || S_ISLNK(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){ return getStat(zFilename, 0) ? 0 : S_ISREG(fileStat.st_mode); } /* ** Same as file_isfile(), but takes into account symlinks. */ int file_wd_isfile(const char *zFilename){ return getStat(zFilename, 1) ? 0 : S_ISREG(fileStat.st_mode); } /* ** Create symlink to file on Unix, or plain-text file with ** symlink target if "allow-symlinks" is off or we're on Windows. ** ** Arguments: target file (symlink will point to it), link file **/ void symlink_create(const char *zTargetFile, const char *zLinkFile){ #if !defined(_WIN32) if( g.allowSymlinks ){ int i, nName; char *zName, zBuf[1000]; nName = strlen(zLinkFile); if( nName>=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=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; while( z[0] ){ if( z[0]=='/' ) zTail = &z[1]; z++; } return zTail; } /* ** Copy the content of a file from one place to another. */ void file_copy(const char *zFrom, const char *zTo){ FILE *in, *out; int got; char zBuf[8192]; in = fossil_fopen(zFrom, "rb"); if( in==0 ) fossil_fatal("cannot open \"%s\" for reading", zFrom); 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); } /* ** Set or clear the execute bit on a file. Return true if a change ** occurred and false if this routine is a no-op. */ int file_wd_setexe(const char *zFilename, int onoff){ int rc = 0; #if !defined(_WIN32) struct stat buf; if( fossil_stat(zFilename, &buf, 1)!=0 || S_ISLNK(buf.st_mode) ) return 0; if( onoff ){ int targetMode = (buf.st_mode & 0444)>>2; if( (buf.st_mode & 0111)!=targetMode ){ chmod(zFilename, buf.st_mode | targetMode); rc = 1; } }else{ if( (buf.st_mode & 0111)!=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_filename(zFilename); utimes(zMbcs, tv); #else struct _utimbuf tb; wchar_t *zMbcs = fossil_utf8_to_filename(zFilename); tb.actime = newMTime; tb.modtime = newMTime; _wutime(zMbcs, &tb); #endif fossil_filename_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("test-set-mtime FILENAME DATE/TIME"); } db_open_or_attach(":memory:", "mem", 0); iMTime = db_int64(0, "SELECT strftime('%%s',%Q)", g.argv[3]); zFile = g.argv[2]; file_set_mtime(zFile, iMTime); iMTime = file_wd_mtime(zFile); zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')", iMTime); fossil_print("Set mtime of \"%s\" to %s (%lld)\n", zFile, zDate, iMTime); } /* ** Delete a file. ** ** Returns zero upon success. */ int file_delete(const char *zFilename){ int rc; #ifdef _WIN32 wchar_t *z = fossil_utf8_to_filename(zFilename); rc = _wunlink(z); #else char *z = fossil_utf8_to_filename(zFilename); rc = unlink(zFilename); #endif fossil_filename_free(z); return rc; } /* ** Create the directory named in the argument, if it does not already ** exist. If forceFlag is 1, delete any prior non-directory object ** with the same name. ** ** Return the number of errors. */ int file_mkdir(const char *zName, int forceFlag){ int rc = file_wd_isdir(zName); if( rc==2 ){ if( !forceFlag ) return 1; file_delete(zName); } if( rc!=1 ){ #if defined(_WIN32) wchar_t *zMbcs = fossil_utf8_to_filename(zName); rc = _wmkdir(zMbcs); #else char *zMbcs = fossil_utf8_to_filename(zName); rc = mkdir(zName, 0755); #endif fossil_filename_free(zMbcs); return rc; } return 0; } /* ** 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_wd_isdir(zName); if( rc==2 ) return 1; /* cannot remove normal file */ if( rc==1 ){ #if defined(_WIN32) wchar_t *zMbcs = fossil_utf8_to_filename(zName); rc = _wrmdir(zMbcs); #else char *zMbcs = fossil_utf8_to_filename(zName); rc = rmdir(zName); #endif fossil_filename_free(zMbcs); return rc; } return 0; } /* ** Return true if the filename given is a valid filename for ** a file in a repository. Valid filenames follow all of the ** following rules: ** ** * Does not begin with "/" ** * Does not contain any path element named "." or ".." ** * Does not contain any of these characters in the path: "\" ** * Does not end with "/". ** * Does not contain two or more "/" characters in a row. ** * Contains at least one character ** ** 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; } /* ** 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 ** ** * 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, j; if( n<0 ) n = strlen(z); /* On windows and cygwin convert all \ characters to / */ #if defined(_WIN32) || defined(__CYGWIN__) for(i=0; i1 && z[n-1]=='/' ){ n--; } } /* Remove duplicate '/' characters. Except, two // at the beginning ** of a pathname is allowed since this is important on windows. */ for(i=j=1; i=0 ) z[j] = z[i]; j++; } if( j==0 ) z[j++] = '.'; z[j] = 0; return j; } /* ** COMMAND: test-simplify-name ** ** %fossil test-simplify-name FILENAME... ** ** Print the simplified versions of each FILENAME. */ void cmd_test_simplify_name(void){ int i; char *z; for(i=2; i ", 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. */ void file_getcwd(char *zBuf, int nBuf){ #ifdef _WIN32 win32_getcwd(zBuf, nBuf); #else if( getcwd(zBuf, nBuf-1)==0 ){ if( errno==ERANGE ){ fossil_fatal("pwd too big: max %d\n", nBuf-1); }else{ fossil_fatal("cannot find current working directory; %s", strerror(errno)); } } #endif } /* ** Return true if zPath is an absolute pathname. Return false ** if it is relative. */ int file_is_absolute_path(const char *zPath){ if( zPath[0]=='/' #if defined(_WIN32) || defined(__CYGWIN__) || zPath[0]=='\\' || (fossil_isalpha(zPath[0]) && zPath[1]==':' && (zPath[2]=='\\' || zPath[2]=='/')) #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. */ void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){ if( file_is_absolute_path(zOrigName) ){ #if defined(_WIN32) || defined(__CYGWIN__) char *zOut; #endif blob_set(pOut, zOrigName); blob_materialize(pOut); #if defined(_WIN32) || defined(__CYGWIN__) /* ** On Windows/cygwin, normalize the drive letter to upper case. */ zOut = blob_str(pOut); if( fossil_islower(zOut[0]) && zOut[1]==':' ){ zOut[0] = fossil_toupper(zOut[0]); } #endif }else{ char zPwd[2000]; file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); #if defined(_WIN32) /* ** On Windows, normalize the drive letter to upper case. */ if( fossil_islower(zPwd[0]) && zPwd[1]==':' ){ zPwd[0] = fossil_toupper(zPwd[0]); } #endif blob_zero(pOut); blob_appendf(pOut, "%//%/", zPwd, zOrigName); } blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut), slash)); } /* ** 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 [%s]\n", zName, blob_buffer(&x)); blob_reset(&x); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_wd_size(zName)); fossil_print(" file_size = %s\n", zBuf); sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_wd_mtime(zName)); fossil_print(" file_mtime = %s\n", zBuf); fossil_print(" file_isfile = %d\n", file_wd_isfile(zName)); fossil_print(" file_isfile_or_link = %d\n",file_wd_isfile_or_link(zName)); fossil_print(" file_islink = %d\n", file_wd_islink(zName)); fossil_print(" file_isexe = %d\n", file_wd_isexe(zName)); fossil_print(" file_isdir = %d\n", file_wd_isdir(zName)); } } /* ** 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); } } } 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--; } 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; i0 && 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 ){ 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 checkout tree: %s", zOrigName); } return 0; } 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: ** --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; blob_zero(&x); capture_case_sensitive_option(); for(i=2; i='a' && zUri[i]<='z'; i++){} if( zUri[i]!=':' ){ blob_zero(pScheme); blob_zero(pHost); blob_set(pPath, zUri); return; } blob_init(pScheme, zUri, i); i++; if( zUri[i]=='/' && zUri[i+1]=='/' ){ i += 2; j = i; while( zUri[i] && zUri[i]!='/' && zUri[i]!=':' ){ i++; } blob_init(pHost, &zUri[j], i-j); if( zUri[i]==':' ){ i++; *pPort = atoi(&zUri[i]); while( zUri[i] && zUri[i]!='/' ){ i++; } } }else{ blob_zero(pHost); } if( zUri[i]=='/' ){ blob_set(pPath, &zUri[i]); }else{ blob_set(pPath, "/"); } } /* ** Construct a random temporary filename into zBuf[]. */ void file_tempname(int nBuf, char *zBuf){ #if defined(_WIN32) const char *azDirs[] = { 0, /* GetTempPath */ 0, /* TEMP */ 0, /* TMP */ ".", }; #else static const char *const azDirs[] = { "/var/tmp", "/usr/tmp", "/tmp", "/temp", ".", }; #endif static const unsigned char zChars[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; unsigned int i, j; const char *zDir = "."; int cnt = 0; #if defined(_WIN32) wchar_t zTmpPath[MAX_PATH]; if( GetTempPathW(MAX_PATH, zTmpPath) ){ azDirs[0] = fossil_filename_to_utf8(zTmpPath); } azDirs[1] = fossil_getenv("TEMP"); azDirs[2] = fossil_getenv("TMP"); #endif for(i=0; i= (size_t)nBuf ){ fossil_fatal("insufficient space for temporary filename"); } do{ if( cnt++>20 ) fossil_panic("cannot generate a temporary filename"); sqlite3_snprintf(nBuf-17, zBuf, "%s/", zDir); j = (int)strlen(zBuf); sqlite3_randomness(15, &zBuf[j]); for(i=0; i<15; i++, j++){ zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; } zBuf[j] = 0; }while( file_size(zBuf)>=0 ); #if defined(_WIN32) fossil_filename_free((char *)azDirs[0]); fossil_filename_free((char *)azDirs[1]); fossil_filename_free((char *)azDirs[2]); #endif } /* ** 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. */ int file_is_the_same(Blob *pContent, const char *zName){ i64 iSize; int rc; Blob onDisk; iSize = file_wd_size(zName); if( iSize<0 ) return 0; if( iSize!=blob_size(pContent) ) return 0; if( file_wd_islink(zName) ){ blob_read_link(&onDisk, zName); }else{ blob_read_from_file(&onDisk, zName); } rc = blob_compare(&onDisk, pContent); blob_reset(&onDisk); return rc==0; } /* ** Return the value of an environment variable as UTF8. ** Use fossil_filename_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_filename_to_utf8(zValue); return zValue; } /* ** Like fopen() but always takes a UTF8 argument. */ 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_filename(zName); FILE *f = _wfopen(uName, uMode); fossil_filename_free(uName); fossil_unicode_free(uMode); #else FILE *f = fopen(zName, zMode); #endif return f; }