Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -473,11 +473,10 @@ Glob *pIgnore; Blob rewrittenPathname; const char *zPathname, *zDisplayName; if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP; - capture_case_sensitive_option(); db_must_be_within_tree(); cwdRelative = determine_cwd_relative_option(); /* We should be done with options.. */ verify_all_options(); @@ -596,11 +595,10 @@ if( find_option("allckouts",0,0)!=0 ) scanFlags |= SCAN_NESTED; zIgnoreFlag = find_option("ignore",0,1); verboseFlag = find_option("verbose","v",0)!=0; zKeepFlag = find_option("keep",0,1); zCleanFlag = find_option("clean",0,1); - capture_case_sensitive_option(); db_must_be_within_tree(); if( zIgnoreFlag==0 ){ zIgnoreFlag = db_get("ignore-glob", 0); } if( zKeepFlag==0 ){ @@ -1114,12 +1112,17 @@ */ if( isSelected ){ int mPerm; mPerm = file_wd_perm(blob_str(&filename)); +#if !defined(_WIN32) isExe = ( mPerm==PERM_EXE ); - isLink = ( mPerm==PERM_LNK ); +#endif +#if defined(_WIN32) + if (win32_symlinks_supported()) +#endif + isLink = ( mPerm==PERM_LNK ); } if( isExe ){ zPerm = " x"; }else if( isLink ){ Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -207,14 +207,25 @@ } nName = file_simplify_name(zName, nName, 0); for(i=1; i #include #include #include #include +#include + #if defined( __MINGW32__) || defined(__DMC__) || defined(_MSC_VER) || defined(__POCC__) # ifndef WIN32 # define WIN32 # endif -# include #else # include #endif /* Index: src/stash.c ================================================================== --- src/stash.c +++ src/stash.c @@ -241,18 +241,11 @@ blob_read_from_file(&disk, zOPath); } content_get(rid, &a); blob_delta_apply(&a, &delta, &b); if( isLink == isNewLink && blob_compare(&disk, &a)==0 ){ - if( isLink || isNewLink ){ - file_delete(zNPath); - } - if( isLink ){ - symlink_create(blob_str(&b), zNPath); - }else{ - blob_write_to_file(&b, zNPath); - } + create_symlink_or_file(1, isLink, isNewLink, &b, zNPath); file_wd_setexe(zNPath, isExec); fossil_print("UPDATE %s\n", zNew); }else{ int rc; if( isLink || isNewLink ){ Index: src/undo.c ================================================================== --- src/undo.c +++ src/undo.c @@ -70,18 +70,11 @@ if( new_exists ){ fossil_print("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname); }else{ fossil_print("NEW %s\n", zPathname); } - if( new_exists && (new_link || old_link) ){ - file_delete(zFullname); - } - if( old_link ){ - symlink_create(blob_str(&new), zFullname); - }else{ - blob_write_to_file(&new, zFullname); - } + create_symlink_or_file(new_exists, old_link, new_link, &new, zFullname); file_wd_setexe(zFullname, old_exe); }else{ fossil_print("DELETE %s\n", zPathname); file_delete(zFullname); } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -766,11 +766,11 @@ db_multi_exec( "DELETE FROM vmerge;" "INSERT OR IGNORE INTO torevert " " SELECT pathname" " FROM vfile " - " WHERE chnged OR deleted OR rid=0 OR pathname!=origname;" + " WHERE chnged OR deleted OR rid=0 OR pathname!=origname OR islink;" ); } db_multi_exec( "INSERT OR IGNORE INTO torevert" " SELECT origname" @@ -808,18 +808,11 @@ zFile, zFile ); }else{ sqlite3_int64 mtime; undo_save(zFile); - if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(zFull)) ){ - file_delete(zFull); - } - if( isLink ){ - symlink_create(blob_str(&record), zFull); - }else{ - blob_write_to_file(&record, zFull); - } + create_symlink_or_file(file_wd_size(zFull)>=0, isLink, file_wd_islink(zFull), &record, zFull); file_wd_setexe(zFull, isExe); fossil_print("REVERTED: %s\n", zFile); mtime = file_wd_mtime(zFull); db_multi_exec( "UPDATE vfile" Index: src/vfile.c ================================================================== --- src/vfile.c +++ src/vfile.c @@ -320,18 +320,11 @@ if( verbose ) fossil_print("%s\n", &zName[nRepos]); if( file_wd_isdir(zName) == 1 ){ /*TODO(dchest): remove directories? */ fossil_fatal("%s is directory, cannot overwrite\n", zName); } - if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){ - file_delete(zName); - } - if( isLink ){ - symlink_create(blob_str(&content), zName); - }else{ - blob_write_to_file(&content, zName); - } + create_symlink_or_file(file_wd_size(zName)>=0, isLink, file_wd_islink(zName), &content, zName); file_wd_setexe(zName, isExe); blob_reset(&content); db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", file_wd_mtime(zName), id); } Index: src/winfile.c ================================================================== --- src/winfile.c +++ src/winfile.c @@ -28,48 +28,72 @@ #ifndef LABEL_SECURITY_INFORMATION # define LABEL_SECURITY_INFORMATION (0x00000010L) #endif -/* copy & paste from ntifs.h */ +/* a couple defines to make the borrowed struct below compile */ +#define _ANONYMOUS_UNION +#define DUMMYUNIONNAME + +/* +** this structure copied on 20 Sept 2014 from +** https://reactos-mirror.googlecode.com/svn-history/r54752/branches/usb-bringup/include/ddk/ntifs.h +** which is a public domain file from the ReactOS DDK package. +*/ + typedef struct _REPARSE_DATA_BUFFER { - ULONG ReparseTag; + ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; - union { + _ANONYMOUS_UNION union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[1]; + ULONG Flags; + WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; - WCHAR PathBuffer[1]; + WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; - }; + } DUMMYUNIONNAME; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; #define LINK_BUFFER_SIZE 1024 +/* +** Fill stat buf with information received from GetFileAttributesExW(). +** Does not follow symbolic links, returning instead information about +** the link itself. +** Returns 0 on success, 1 on failure. +*/ int win32_lstat(const wchar_t *zFilename, struct fossilStat *buf){ WIN32_FILE_ATTRIBUTE_DATA attr; int rc = GetFileAttributesExW(zFilename, GetFileExInfoStandard, &attr); if( rc ){ - char *tname = fossil_filename_to_utf8(zFilename); - char tlink[LINK_BUFFER_SIZE]; - ssize_t tlen = win32_readlink(tname, tlink, sizeof(tlink)); + ssize_t tlen = 0; /* assume it is not a symbolic link */ + + /* if it is a reparse point it *might* be a symbolic link */ + /* so defer to win32_readlink to actually check */ + if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT){ + char *tname = fossil_filename_to_utf8(zFilename); + char tlink[LINK_BUFFER_SIZE]; + tlen = win32_readlink(tname, tlink, sizeof(tlink)); + fossil_filename_free(tname); + } + ULARGE_INTEGER ull; + /* if a link was retrieved, it is a symlink, otherwise a dir or file */ buf->st_mode = (tlen > 0) ? S_IFLNK : ((attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? S_IFDIR : S_IFREG); buf->st_size = (((i64)attr.nFileSizeHigh)<<32) | attr.nFileSizeLow; @@ -80,40 +104,47 @@ } return !rc; } /* -** 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. -** +** Fill stat buf with information received from win32_lstat(). +** If a symbolic link is found, follow it and return information about +** the target, repeating until an actual target is found. +** Limit the number of loop iterations so as to avoid an infinite loop +** due to circular links. This should never happen because +** GetFinalPathNameByHandleW() should always preclude that need, but being +** prepared to loop seems prudent, or at least not harmful. +** Returns 0 on success, 1 on failure. */ int win32_stat(const wchar_t *zFilename, struct fossilStat *buf){ int rc; HANDLE file; wchar_t nextFilename[LINK_BUFFER_SIZE]; DWORD len; + int iterationsRemaining = 8; /* 8 is arbitrary, can be modified as needed */ - while (1){ + while (iterationsRemaining-- > 0){ rc = win32_lstat(zFilename, buf); + /* exit on error or not link */ if ((rc != 0) || (buf->st_mode != S_IFLNK)) break; /* it is a link, so open the linked file */ file = CreateFileW(zFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); if ((file == NULL) || (file == INVALID_HANDLE_VALUE)){ - rc = -1; + rc = 1; break; } /* get the final path name and close the handle */ len = GetFinalPathNameByHandleW(file, nextFilename, LINK_BUFFER_SIZE - 1, 0); CloseHandle(file); /* if any problems getting the final path name error so exit */ if ((len <= 0) || (len > LINK_BUFFER_SIZE - 1)){ - rc = -1; + rc = 1; break; } /* prepare to try again just in case we have a chain to follow */ /* this shouldn't happen, but just trying to be safe */ @@ -121,10 +152,15 @@ } return rc; } +/* +** An implementation of a posix-like readlink function for win32. +** Copies the target of a symbolic link to buf if possible. +** Returns the length of the link copied to buf on success, -1 on failure. +*/ ssize_t win32_readlink(const char *path, char *buf, size_t bufsiz){ /* assume we're going to fail */ ssize_t rv = -1; /* does path reference a reparse point? */ @@ -137,49 +173,79 @@ FILE_FLAG_OPEN_REPARSE_POINT, NULL); if ((file != NULL) && (file != INVALID_HANDLE_VALUE)){ /* use DeviceIoControl to get the reparse point data */ - union { - REPARSE_DATA_BUFFER data; - char buffer[sizeof(REPARSE_DATA_BUFFER) + LINK_BUFFER_SIZE * sizeof(wchar_t)]; - } u; - DWORD bytes; - - u.data.ReparseTag = IO_REPARSE_TAG_SYMLINK; - u.data.ReparseDataLength = 0; - u.data.Reserved = 0; + int data_size = sizeof(REPARSE_DATA_BUFFER) + LINK_BUFFER_SIZE * sizeof(wchar_t); + REPARSE_DATA_BUFFER* data = fossil_malloc(data_size); + DWORD data_used; + + data->ReparseTag = IO_REPARSE_TAG_SYMLINK; + data->ReparseDataLength = 0; + data->Reserved = 0; int rc = DeviceIoControl(file, FSCTL_GET_REPARSE_POINT, NULL, 0, - &u, sizeof(u), &bytes, NULL); + data, data_size, &data_used, NULL); /* did the reparse point data fit into the desired buffer? */ - if (rc && (bytes < sizeof(u))){ + if (rc && (data_used < data_size)){ /* it fit, so setup the print name for further processing */ USHORT - offset = u.data.SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t), - length = u.data.SymbolicLinkReparseBuffer.PrintNameLength / sizeof(wchar_t); + offset = data->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t), + length = data->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(wchar_t); char *temp; - u.data.SymbolicLinkReparseBuffer.PathBuffer[offset + length] = 0; + data->SymbolicLinkReparseBuffer.PathBuffer[offset + length] = 0; /* convert the filename to utf8, copy it, and discard the converted copy */ - temp = fossil_filename_to_utf8(u.data.SymbolicLinkReparseBuffer.PathBuffer + offset); + temp = fossil_filename_to_utf8(data->SymbolicLinkReparseBuffer.PathBuffer + offset); rv = strlen(temp); if (rv >= bufsiz) rv = bufsiz; memcpy(buf, temp, rv); fossil_filename_free(temp); } + + fossil_free(data); /* all done, close the reparse point */ CloseHandle(file); } } return rv; } +/* +** Either unlink a file or remove a directory on win32 systems. +** To delete a symlink on a posix system, you simply unlink the entry. +** Unfortunately for our purposes, win32 differentiates between symlinks for +** files and for directories. Thus you must unlink a file symlink or rmdir a +** directory symlink. This is a convenience function used when we know we're +** deleting a symlink of some type. +** Returns 0 on success, 1 on failure. +*/ +int win32_unlink_rmdir(const wchar_t *zFilename){ + int rc = 0; + fossilStat stat; + if (win32_stat(zFilename, &stat) == 0){ + if (stat.st_mode == S_IFDIR) + rc = RemoveDirectoryW(zFilename); + else + rc = DeleteFileW(zFilename); + } + return !rc; +} + +/* +** An implementation of a posix-like symlink function for win32. +** Attempts to create a file or directory symlink based on the target. +** Defaults to a file symlink if the target does not exist / can't be checked. +** Finally, if the symlink cannot be created for whatever reason (perhaps +** newpath is on a network share or a FAT derived file system), default to +** creation of a text file with the context of the link. +** Returns 0 on success, 1 on failure. +*/ int win32_symlink(const char *oldpath, const char *newpath){ fossilStat stat; int created = 0; DWORD flags = 0; wchar_t *zMbcs; @@ -187,10 +253,12 @@ /* does oldpath exist? is it a dir or a file? */ zMbcs = fossil_utf8_to_filename(oldpath); if (win32_stat(zMbcs, &stat) == 0){ if (stat.st_mode == S_IFDIR) flags = SYMBOLIC_LINK_FLAG_DIRECTORY; - DeleteFile(newpath); - if (CreateSymbolicLink(newpath, oldpath, flags)) - created = 1; } fossil_filename_free(zMbcs); + + /* remove newpath before creating the symlink */ + zMbcs = fossil_utf8_to_filename(newpath); + win32_unlink_rmdir(zMbcs); + fossil_filename_free(zMbcs); @@ -197,5 +265,7 @@ + + created = CreateSymbolicLink(newpath, oldpath, flags); /* if the symlink was not created, create a plain text file */ if (!created){ Blob content; blob_set(&content, oldpath); @@ -202,13 +272,21 @@ blob_write_to_file(&content, newpath); blob_reset(&content); created = 1; } - return created ? 0 : -1; + return !created; } +/* +** Check if symlinks are potentially supported on the current OS. +** Theoretically this code should work on any NT based version of windows +** but I have no way of testing that. The initial check for +** IsWindowsVistaOrGreater() should in theory eliminate any system prior to +** Windows Vista, but I have no way to test that at this time. +** Return 1 if supported, 0 if not. +*/ int win32_symlinks_supported(){ TOKEN_PRIVILEGES tp; LUID luid; HANDLE process, token; DWORD status;