Index: src/blob.c ================================================================== --- src/blob.c +++ src/blob.c @@ -818,32 +818,35 @@ } return got; } /* -** Reads symlink destination path and puts int into blob. +** Reads symlink destination path and puts it into blob. ** Any prior content of the blob is discarded, not freed. ** ** Returns length of destination path. ** -** On windows, zeros blob and returns 0. +** On windows, zeros blob and returns 0 if symlinks are not supported. */ int blob_read_link(Blob *pBlob, const char *zFilename){ -#if !defined(_WIN32) char zBuf[1024]; +#if !defined(_WIN32) ssize_t len = readlink(zFilename, zBuf, 1023); if( len < 0 ){ fossil_fatal("cannot read symbolic link %s", zFilename); } +#else + ssize_t len = win32_readlink(zFilename, zBuf, 1023); + if( len < 0 ){ + blob_zero(pBlob); + return 0; + } +#endif zBuf[len] = 0; /* null-terminate */ blob_zero(pBlob); blob_appendf(pBlob, "%s", zBuf); return len; -#else - blob_zero(pBlob); - return 0; -#endif } /* ** Write the content of a blob into a file. Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -1551,24 +1551,27 @@ int cmp; blob_resize(&filename, nBasename); blob_append(&filename, zName, -1); -#if !defined(_WIN32) - /* For unix, extract the "executable" and "symlink" permissions - ** directly from the filesystem. On windows, permissions are - ** unchanged from the original. However, only do this if the file + /* Extract the "executable" and "symlink" permissions + ** directly from the filesystem. However, only do this if the file ** itself is actually selected to be part of this check-in. */ 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(blob_str(&filename))) #endif + isLink = ( mPerm==PERM_LNK ); + } + if( isExe ){ zPerm = " x"; }else if( isLink ){ zPerm = " l"; /* note: symlinks don't have executable bit on unix */ }else{ Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -71,16 +71,26 @@ #if !defined(_WIN32) || !(defined(__MSVCRT__) || defined(_MSC_VER)) # define fossilStat stat #endif +#if defined(_WIN32) /* -** On Windows S_ISLNK always returns FALSE. +** On Windows S_ISLNK can be true or false. */ +/* the S_ISLNK provided by dirent.h for windows is inadequate, so fix it */ +#if defined(S_ISLNK) +# undef S_ISLNK +#endif +#if !defined(S_IFLNK) +# define S_IFLNK 0120000 +#endif #if !defined(S_ISLNK) -# define S_ISLNK(x) (0) +# define S_ISLNK(x) ((x)==S_IFLNK) +#endif #endif + static int fileStatValid = 0; static struct fossilStat fileStat; /* ** Fill stat buf with information received from stat() or lstat(). @@ -100,11 +110,15 @@ rc = lstat(zMbcs, buf); }else{ rc = stat(zMbcs, buf); } #else - rc = win32_stat(zMbcs, buf, isWd); + if( isWd && g.allowSymlinks ){ + rc = win32_lstat(zMbcs, buf); + }else{ + rc = win32_stat(zMbcs, buf); + } #endif fossil_path_free(zMbcs); return rc; } @@ -220,11 +234,15 @@ ** ** Arguments: target file (symlink will point to it), link file **/ void symlink_create(const char *zTargetFile, const char *zLinkFile){ #if !defined(_WIN32) - if( db_allow_symlinks(0) ){ + int symlinks_supported = 1; +#else + int symlinks_supported = win32_symlinks_supported(zLinkFile); +#endif + if( symlinks_supported && db_allow_symlinks(0) ){ int i, nName; char *zName, zBuf[1000]; nName = strlen(zLinkFile); if( nName>=sizeof(zBuf) ){ @@ -235,30 +253,65 @@ } nName = file_simplify_name(zName, nName, 0); for(i=1; id_name); zSubpath = mprintf("%s/%s", zPath, zUtf8Name); fossil_path_free(zUtf8Name); -#ifdef _DIRENT_HAVE_D_TYPE +#if defined(_DIRENT_HAVE_D_TYPE) && !defined(_WIN32) if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) ? (file_isdir(zSubpath)==1) : (pEntry->d_type==DT_DIR) ) #else if( file_isdir(zSubpath)==1 ) #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 @@ -79,18 +79,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 @@ -752,11 +752,11 @@ ManifestFile *pCoFile; /* File within current checkout manifest */ ManifestFile *pRvFile; /* File within revert version manifest */ const char *zFile; /* Filename relative to checkout root */ const char *zRevision; /* Selected revert version, NULL if current */ Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */ - int i; + int i, mayNeedDelete, mayBeLink; Stmt q; undo_capture_command_line(); zRevision = find_option("revision", "r", 1); verify_all_options(); @@ -851,18 +851,13 @@ /* Get contents of reverted-to file. */ content_get(fast_uuid_to_rid(pRvFile->zUuid), &record); undo_save(zFile); - if( file_wd_size(zFull)>=0 && (rvPerm==PERM_LNK || file_wd_islink(0)) ){ - file_delete(zFull); - } - if( rvPerm==PERM_LNK ){ - symlink_create(blob_str(&record), zFull); - }else{ - blob_write_to_file(&record, zFull); - } + mayNeedDelete = file_wd_size(zFull)>=0; + mayBeLink = file_wd_islink(0); + create_symlink_or_file(mayNeedDelete, rvPerm==PERM_LNK, mayBeLink, &record, zFull); file_wd_setexe(zFull, rvPerm==PERM_EXE); fossil_print("REVERT %s\n", zFile); mtime = file_wd_mtime(zFull); db_multi_exec( "UPDATE vfile" Index: src/vfile.c ================================================================== --- src/vfile.c +++ src/vfile.c @@ -186,14 +186,12 @@ while( db_step(&q)==SQLITE_ROW ){ int id, rid, isDeleted; const char *zName; int chnged = 0; int oldChnged; -#ifndef _WIN32 int origPerm; int currentPerm; -#endif i64 oldMtime; i64 currentMtime; i64 origSize; i64 currentSize; @@ -204,13 +202,21 @@ oldChnged = chnged = db_column_int(&q, 4); oldMtime = db_column_int64(&q, 7); origSize = db_column_int64(&q, 6); currentSize = file_wd_size(zName); currentMtime = file_wd_mtime(0); -#ifndef _WIN32 origPerm = db_column_int(&q, 8); currentPerm = file_wd_perm(zName); +#if defined(_WIN32) + /* + ** Windows doesn't have an execute bit, but it does support symlinks; + ** if the current permission is not a symlink, make it the original + ** permission (EXE or REG); if it is a symlink, leave it alone + */ + if( currentPerm != PERM_LNK ){ + currentPerm = origPerm; + } #endif if( chnged==0 && (isDeleted || rid==0) ){ /* "fossil rm" or "fossil add" always change the file */ chnged = 1; }else if( !file_wd_isfile_or_link(0) && currentSize>=0 ){ @@ -218,10 +224,17 @@ fossil_warning("not an ordinary file: %s", zName); nErr++; } chnged = 1; } +#if defined(_WIN32) + if (win32_check_symlink_type_changed(zName)){ + if( chnged!=1 ){ + chnged = 1; + } + }else /* make the following if an else if */ +#endif if( origSize!=currentSize ){ if( chnged!=1 ){ /* A file size change is definitive - the file has changed. No ** need to check the mtime or hash */ chnged = 1; @@ -251,11 +264,20 @@ file_set_mtime(zName, desiredMtime); currentMtime = file_wd_mtime(zName); } } } -#ifndef _WIN32 +#if !defined(_WIN32) || 1 + /* + ** as written this block was only designed to work on posix systems; + ** I can see no reason why it shouldn't work on Windows as well + ** in the winsymlink branch (after making a couple of other minor + ** tweak), so I've modified the condition to be "not windows or true"; + ** the conditional can be removed when / if this is ever merged + ** to trunk, but I think it serves as a handy reminder for now + ** that this code might be "special" + */ if( origPerm!=PERM_LNK && currentPerm==PERM_LNK ){ /* Changing to a symlink takes priority over all other change types. */ chnged = 7; }else if( chnged==0 || chnged==6 || chnged==7 || chnged==8 || chnged==9 ){ /* Confirm metadata change types. */ @@ -291,10 +313,11 @@ int promptFlag /* Prompt user to confirm overwrites */ ){ Stmt q; Blob content; int nRepos = strlen(g.zLocalRoot); + int mayNeedDelete, mayBeLink; if( vid>0 && id==0 ){ db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe, islink" " FROM vfile" " WHERE vid=%d AND mrid>0", @@ -343,18 +366,13 @@ 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", zName); } - if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(0)) ){ - file_delete(zName); - } - if( isLink ){ - symlink_create(blob_str(&content), zName); - }else{ - blob_write_to_file(&content, zName); - } + mayNeedDelete = file_wd_size(zName)>=0; + mayBeLink = file_wd_islink(0); + create_symlink_or_file(mayNeedDelete, isLink, mayBeLink, &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); } @@ -520,20 +538,20 @@ blob_appendf(pPath, "/%s", zUtf8); zPath = blob_str(pPath); if( glob_match(pIgnore1, &zPath[nPrefix+1]) || glob_match(pIgnore2, &zPath[nPrefix+1]) ){ /* do nothing */ -#ifdef _DIRENT_HAVE_D_TYPE +#if defined(_DIRENT_HAVE_D_TYPE) && !defined(_WIN32) }else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) ? (file_wd_isdir(zPath)==1) : (pEntry->d_type==DT_DIR) ){ #else }else if( file_wd_isdir(zPath)==1 ){ #endif if( !vfile_top_of_checkout(zPath) ){ vfile_scan(pPath, nPrefix, scanFlags, pIgnore1, pIgnore2); } -#ifdef _DIRENT_HAVE_D_TYPE +#if defined(_DIRENT_HAVE_D_TYPE) && !defined(_WIN32) }else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) ? (file_wd_isfile_or_link(zPath)) : (pEntry->d_type==DT_REG) ){ #else }else if( file_wd_isfile_or_link(zPath) ){ #endif @@ -640,11 +658,11 @@ blob_appendf(pPath, "/%s", zUtf8); zPath = blob_str(pPath); if( glob_match(pIgnore1, &zPath[nPrefix+1]) || glob_match(pIgnore2, &zPath[nPrefix+1]) ){ /* do nothing */ -#ifdef _DIRENT_HAVE_D_TYPE +#if defined(_DIRENT_HAVE_D_TYPE) && !defined(_WIN32) }else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) ? (file_wd_isdir(zPath)==1) : (pEntry->d_type==DT_DIR) ){ #else }else if( file_wd_isdir(zPath)==1 ){ #endif @@ -657,11 +675,11 @@ db_step(&ins); db_reset(&ins); fossil_free(zSavePath); result += count; /* found X normal files? */ } -#ifdef _DIRENT_HAVE_D_TYPE +#if defined(_DIRENT_HAVE_D_TYPE) && !defined(_WIN32) }else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) ? (file_wd_isfile_or_link(zPath)) : (pEntry->d_type==DT_REG) ){ #else }else if( file_wd_isfile_or_link(zPath) ){ #endif Index: src/winfile.c ================================================================== --- src/winfile.c +++ src/winfile.c @@ -22,34 +22,405 @@ #ifdef _WIN32 /* This code is for win32 only */ #include #include #include "winfile.h" + +#if !defined(S_IFLNK) +# define S_IFLNK 0120000 +#endif +#if !defined(SYMBOLIC_LINK_FLAG_DIRECTORY) +# define SYMBOLIC_LINK_FLAG_DIRECTORY (0x1) +#endif #ifndef LABEL_SECURITY_INFORMATION -# define LABEL_SECURITY_INFORMATION (0x00000010L) +# define LABEL_SECURITY_INFORMATION (0x00000010L) +#endif + +#ifndef FSCTL_GET_REPARSE_POINT +# define FSCTL_GET_REPARSE_POINT (((0x00000009) << 16) | ((0x00000000) << 14) | ((42) << 2) | (0)) +#endif + +static HANDLE dllhandle = NULL; +static DWORD (WINAPI *getFinalPathNameByHandleW) (HANDLE, LPWSTR, DWORD, DWORD) = NULL; +static BOOLEAN (APIENTRY *createSymbolicLinkW) (LPCWSTR, LPCWSTR, DWORD) = NULL; + +/* a couple defines to make the borrowed struct below compile */ +#ifndef _ANONYMOUS_UNION +# define _ANONYMOUS_UNION #endif +#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 { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + _ANONYMOUS_UNION union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + } DUMMYUNIONNAME; +} FOSSIL_REPARSE_DATA_BUFFER; + +#define LINK_BUFFER_SIZE 1024 + +static int isVistaOrLater(){ + if( !dllhandle ){ + HANDLE h = LoadLibraryW(L"KERNEL32"); + createSymbolicLinkW = (BOOLEAN (APIENTRY *) (LPCWSTR, LPCWSTR, DWORD)) GetProcAddress(h, "CreateSymbolicLinkW"); + getFinalPathNameByHandleW = (DWORD (WINAPI *) (HANDLE, LPWSTR, DWORD, DWORD)) GetProcAddress(h, "GetFinalPathNameByHandleW"); + dllhandle = h; + } + return createSymbolicLinkW != NULL; +} /* -** 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 GetFileAttributesExW(). +** Does not follow symbolic links, returning instead information about +** the link itself. +** Returns 0 on success, 1 on failure. */ -int win32_stat(const wchar_t *zFilename, struct fossilStat *buf, int isWd){ +int win32_lstat(const wchar_t *zFilename, struct fossilStat *buf){ WIN32_FILE_ATTRIBUTE_DATA attr; int rc = GetFileAttributesExW(zFilename, GetFileExInfoStandard, &attr); if( rc ){ + 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_path_to_utf8(zFilename); + char tlink[LINK_BUFFER_SIZE]; + tlen = win32_readlink(tname, tlink, sizeof(tlink)); + fossil_path_free(tname); + } + ULARGE_INTEGER ull; + + /* if a link was retrieved, it is a symlink, otherwise a dir or file */ + if( tlen == 0 ){ + buf->st_mode = ((attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? + S_IFDIR : S_IFREG); + + buf->st_size = (((i64)attr.nFileSizeHigh)<<32) | attr.nFileSizeLow; + }else{ + buf->st_mode = S_IFLNK; + buf->st_size = tlen; + } + ull.LowPart = attr.ftLastWriteTime.dwLowDateTime; ull.HighPart = attr.ftLastWriteTime.dwHighDateTime; - buf->st_mode = (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? - S_IFDIR : S_IFREG; - buf->st_size = (((i64)attr.nFileSizeHigh)<<32) | attr.nFileSizeLow; buf->st_mtime = ull.QuadPart / 10000000ULL - 11644473600ULL; } return !rc; } + +/* +** 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 (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, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if( (file == NULL) || (file == INVALID_HANDLE_VALUE) ){ + rc = 1; + break; + } + + /* get the final path name and close the handle */ + if( isVistaOrLater() ){ + len = getFinalPathNameByHandleW(file, nextFilename, LINK_BUFFER_SIZE - 1, 0); + }else{ + len = -1; + } + CloseHandle(file); + + /* if any problems getting the final path name error so exit */ + if( (len <= 0) || (len > LINK_BUFFER_SIZE - 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 */ + zFilename = nextFilename; + } + + 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? */ + WIN32_FILE_ATTRIBUTE_DATA attr; + int rc = GetFileAttributesEx(path, GetFileExInfoStandard, &attr); + if( rc && (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ){ + + /* since it is a reparse point, open it */ + HANDLE file = CreateFile(path, FILE_READ_EA, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if( (file != NULL) && (file != INVALID_HANDLE_VALUE) ){ + + /* use DeviceIoControl to get the reparse point data */ + + int data_size = sizeof(FOSSIL_REPARSE_DATA_BUFFER) + LINK_BUFFER_SIZE * sizeof(wchar_t); + FOSSIL_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, + data, data_size, &data_used, NULL); + + /* did the reparse point data fit into the desired buffer? */ + if( rc && (data_used < data_size) ){ + /* it fit, so setup the print name for further processing */ + USHORT + offset = data->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t), + length = data->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(wchar_t); + char *temp; + data->SymbolicLinkReparseBuffer.PathBuffer[offset + length] = 0; + + /* convert the filename to utf8, copy it, and discard the converted copy */ + temp = fossil_path_to_utf8(data->SymbolicLinkReparseBuffer.PathBuffer + offset); + rv = strlen(temp); + if( rv >= bufsiz ) + rv = bufsiz; + memcpy(buf, temp, rv); + fossil_path_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; + WIN32_FILE_ATTRIBUTE_DATA attr; + if( GetFileAttributesExW(zFilename, GetFileExInfoStandard, &attr) ){ + if( (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY ) + 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 = 0x2; /*SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE*/ + wchar_t *zMbcs, *zMbcsOld; + + /* does oldpath exist? is it a dir or a file? */ + zMbcsOld = fossil_utf8_to_path(oldpath, 0); + if( win32_stat(zMbcsOld, &stat) == 0 ){ + if( stat.st_mode == S_IFDIR ){ + flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; + } + } + + /* remove newpath before creating the symlink */ + zMbcs = fossil_utf8_to_path(newpath, 0); + win32_unlink_rmdir(zMbcs); + if( isVistaOrLater() ){ + created = createSymbolicLinkW(zMbcs, zMbcsOld, flags); + } + fossil_path_free(zMbcs); + fossil_path_free(zMbcsOld); + + /* if the symlink was not created, create a plain text file */ + if( !created ){ + Blob content; + blob_set(&content, oldpath); + blob_write_to_file(&content, newpath); + blob_reset(&content); + created = 1; + } + + return !created; +} + +/* +** Given a pathname to a file, return true if: +** 1. the file exists +** 2. the file is a symbolic link +** 3. the symbolic link's attributes can be acquired +** 4. the symbolic link type is different than the target type +*/ +int win32_check_symlink_type_changed(const char* zName){ + int changed = 0; + wchar_t* zMbcs; + fossilStat lstat_buf, stat_buf; + WIN32_FILE_ATTRIBUTE_DATA lstat_attr; + zMbcs = fossil_utf8_to_path(zName, 0); + if( win32_stat(zMbcs, &stat_buf) != 0 ){ + stat_buf.st_mode = S_IFREG; + } + changed = + (win32_lstat(zMbcs, &lstat_buf) == 0) && + (lstat_buf.st_mode == S_IFLNK) && + GetFileAttributesExW(zMbcs, GetFileExInfoStandard, &lstat_attr) && + ((stat_buf.st_mode == S_IFDIR) != ((lstat_attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)); + fossil_path_free(zMbcs); + return changed; +} + +/* +** Check if symlinks are potentially supported on the current OS for the given file. +** 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(const char* zFilename){ + TOKEN_PRIVILEGES tp; + LUID luid; + HANDLE process, token; + DWORD status; + int success; + wchar_t *pFilename; + wchar_t fullName[MAX_PATH+1]; + DWORD fullLength; + wchar_t volName[MAX_PATH+1]; + DWORD fsFlags; + + /* symlinks only supported on vista or greater */ + if( !isVistaOrLater() ){ + return 0; + } + + /* next we need to check to see if the privilege is available */ + + /* can't check privilege if we can't lookup its value */ + if( !LookupPrivilegeValue(NULL, SE_CREATE_SYMBOLIC_LINK_NAME, &luid) ){ + return 0; + } + + /* can't check privilege if we can't open the process token */ + process = GetCurrentProcess(); + if( !OpenProcessToken(process, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &token) ){ + return 0; + } + + /* by this point, we have a process token and the privilege value */ + /* try to enable the privilege then close the token */ + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); + status = GetLastError(); + + CloseHandle(token); + + /* any error means we failed to enable the privilege, symlinks not supported */ + if( status != ERROR_SUCCESS ){ + return 0; + } + + /* assume no support for symlinks */ + success = 0; + + pFilename = fossil_utf8_to_path(zFilename, 0); + + /* given the filename we're interested in, symlinks are supported if */ + /* 1. we can get the full name of the path from the given path */ + fullLength = GetFullPathNameW(pFilename, sizeof(fullName), fullName, NULL); + if( (fullLength > 0) && (fullLength < sizeof(fullName)) ){ + /* 2. we can get the volume path name from the full name */ + if( GetVolumePathNameW(fullName, volName, sizeof(volName)) ){ + /* 3. we can get volume information from the volume path name */ + if( GetVolumeInformationW(volName, NULL, 0, NULL, NULL, &fsFlags, NULL, 0) ){ + /* 4. the given volume support reparse points */ + if( fsFlags & FILE_SUPPORTS_REPARSE_POINTS ){ + /* all four conditions were true, so we support symlinks; success! */ + success = 1; + } + } + } + } + + fossil_path_free(pFilename); + + return success; +} /* ** Wrapper around the access() system call. This code was copied from Tcl ** 8.6 and then modified. */