mirror of
https://github.com/git/git.git
synced 2026-01-11 21:33:13 +09:00
Merge branch 'js/symlink-windows' into jch
Upstream symbolic link support on Windows from Git-for-Windows. * js/symlink-windows: mingw: special-case index entries for symlinks with buggy size mingw: emulate `stat()` a little more faithfully mingw: try to create symlinks without elevated permissions mingw: add support for symlinks to directories mingw: implement basic `symlink()` functionality (file symlinks only) mingw: implement `readlink()` mingw: allow `mingw_chdir()` to change to symlink-resolved directories mingw: support renaming symlinks mingw: handle symlinks to directories in `mingw_unlink()` mingw: add symlink-specific error codes mingw: change default of `core.symlinks` to false mingw: factor out the retry logic mingw: compute the correct size for symlinks in `mingw_lstat()` mingw: teach dirent about symlinks mingw: let `mingw_lstat()` error early upon problems with reparse points mingw: drop the separate `do_lstat()` function mingw: implement `stat()` with symlink support mingw: don't call `GetFileAttributes()` twice in `mingw_lstat()`
This commit is contained in:
commit
93764017bc
@ -121,10 +121,6 @@ struct utsname {
|
||||
* trivial stubs
|
||||
*/
|
||||
|
||||
static inline int readlink(const char *path UNUSED, char *buf UNUSED, size_t bufsiz UNUSED)
|
||||
{ errno = ENOSYS; return -1; }
|
||||
static inline int symlink(const char *oldpath UNUSED, const char *newpath UNUSED)
|
||||
{ errno = ENOSYS; return -1; }
|
||||
static inline int fchmod(int fildes UNUSED, mode_t mode UNUSED)
|
||||
{ errno = ENOSYS; return -1; }
|
||||
#ifndef __MINGW64_VERSION_MAJOR
|
||||
@ -197,6 +193,8 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out);
|
||||
int sigaction(int sig, struct sigaction *in, struct sigaction *out);
|
||||
int link(const char *oldpath, const char *newpath);
|
||||
int uname(struct utsname *buf);
|
||||
int symlink(const char *target, const char *link);
|
||||
int readlink(const char *path, char *buf, size_t bufsiz);
|
||||
|
||||
/*
|
||||
* replacements of existing functions
|
||||
|
||||
635
compat/mingw.c
635
compat/mingw.c
@ -21,14 +21,13 @@
|
||||
#define SECURITY_WIN32
|
||||
#include <sspi.h>
|
||||
#include <wchar.h>
|
||||
#include <winioctl.h>
|
||||
#include <winternl.h>
|
||||
|
||||
#define STATUS_DELETE_PENDING ((NTSTATUS) 0xC0000056)
|
||||
|
||||
#define HCAST(type, handle) ((type)(intptr_t)handle)
|
||||
|
||||
static const int delay[] = { 0, 1, 10, 20, 40 };
|
||||
|
||||
void open_in_gdb(void)
|
||||
{
|
||||
static struct child_process cp = CHILD_PROCESS_INIT;
|
||||
@ -103,6 +102,7 @@ int err_win_to_posix(DWORD winerr)
|
||||
case ERROR_INVALID_PARAMETER: error = EINVAL; break;
|
||||
case ERROR_INVALID_PASSWORD: error = EPERM; break;
|
||||
case ERROR_INVALID_PRIMARY_GROUP: error = EINVAL; break;
|
||||
case ERROR_INVALID_REPARSE_DATA: error = EINVAL; break;
|
||||
case ERROR_INVALID_SIGNAL_NUMBER: error = EINVAL; break;
|
||||
case ERROR_INVALID_TARGET_HANDLE: error = EIO; break;
|
||||
case ERROR_INVALID_WORKSTATION: error = EACCES; break;
|
||||
@ -117,6 +117,7 @@ int err_win_to_posix(DWORD winerr)
|
||||
case ERROR_NEGATIVE_SEEK: error = ESPIPE; break;
|
||||
case ERROR_NOACCESS: error = EFAULT; break;
|
||||
case ERROR_NONE_MAPPED: error = EINVAL; break;
|
||||
case ERROR_NOT_A_REPARSE_POINT: error = EINVAL; break;
|
||||
case ERROR_NOT_ENOUGH_MEMORY: error = ENOMEM; break;
|
||||
case ERROR_NOT_READY: error = EAGAIN; break;
|
||||
case ERROR_NOT_SAME_DEVICE: error = EXDEV; break;
|
||||
@ -137,6 +138,9 @@ int err_win_to_posix(DWORD winerr)
|
||||
case ERROR_PIPE_NOT_CONNECTED: error = EPIPE; break;
|
||||
case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break;
|
||||
case ERROR_READ_FAULT: error = EIO; break;
|
||||
case ERROR_REPARSE_ATTRIBUTE_CONFLICT: error = EINVAL; break;
|
||||
case ERROR_REPARSE_TAG_INVALID: error = EINVAL; break;
|
||||
case ERROR_REPARSE_TAG_MISMATCH: error = EINVAL; break;
|
||||
case ERROR_SEEK: error = EIO; break;
|
||||
case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break;
|
||||
case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break;
|
||||
@ -204,15 +208,12 @@ static int read_yes_no_answer(void)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ask_yes_no_if_possible(const char *format, ...)
|
||||
static int ask_yes_no_if_possible(const char *format, va_list args)
|
||||
{
|
||||
char question[4096];
|
||||
const char *retry_hook;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
vsnprintf(question, sizeof(question), format, args);
|
||||
va_end(args);
|
||||
|
||||
retry_hook = mingw_getenv("GIT_ASK_YESNO");
|
||||
if (retry_hook) {
|
||||
@ -237,6 +238,31 @@ static int ask_yes_no_if_possible(const char *format, ...)
|
||||
}
|
||||
}
|
||||
|
||||
static int retry_ask_yes_no(int *tries, const char *format, ...)
|
||||
{
|
||||
static const int delay[] = { 0, 1, 10, 20, 40 };
|
||||
va_list args;
|
||||
int result, saved_errno = errno;
|
||||
|
||||
if ((*tries) < ARRAY_SIZE(delay)) {
|
||||
/*
|
||||
* We assume that some other process had the file open at the wrong
|
||||
* moment and retry. In order to give the other process a higher
|
||||
* chance to complete its operation, we give up our time slice now.
|
||||
* If we have to retry again, we do sleep a bit.
|
||||
*/
|
||||
Sleep(delay[*tries]);
|
||||
(*tries)++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
va_start(args, format);
|
||||
result = ask_yes_no_if_possible(format, args);
|
||||
va_end(args);
|
||||
errno = saved_errno;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Windows only */
|
||||
enum hide_dotfiles_type {
|
||||
HIDE_DOTFILES_FALSE = 0,
|
||||
@ -270,6 +296,134 @@ int mingw_core_config(const char *var, const char *value,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int is_wdir_sep(wchar_t wchar)
|
||||
{
|
||||
return wchar == L'/' || wchar == L'\\';
|
||||
}
|
||||
|
||||
static const wchar_t *make_relative_to(const wchar_t *path,
|
||||
const wchar_t *relative_to, wchar_t *out,
|
||||
size_t size)
|
||||
{
|
||||
size_t i = wcslen(relative_to), len;
|
||||
|
||||
/* Is `path` already absolute? */
|
||||
if (is_wdir_sep(path[0]) ||
|
||||
(iswalpha(path[0]) && path[1] == L':' && is_wdir_sep(path[2])))
|
||||
return path;
|
||||
|
||||
while (i > 0 && !is_wdir_sep(relative_to[i - 1]))
|
||||
i--;
|
||||
|
||||
/* Is `relative_to` in the current directory? */
|
||||
if (!i)
|
||||
return path;
|
||||
|
||||
len = wcslen(path);
|
||||
if (i + len + 1 > size) {
|
||||
error("Could not make '%ls' relative to '%ls' (too large)",
|
||||
path, relative_to);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(out, relative_to, i * sizeof(wchar_t));
|
||||
wcscpy(out + i, path);
|
||||
return out;
|
||||
}
|
||||
|
||||
static DWORD symlink_file_flags = 0, symlink_directory_flags = 1;
|
||||
|
||||
enum phantom_symlink_result {
|
||||
PHANTOM_SYMLINK_RETRY,
|
||||
PHANTOM_SYMLINK_DONE,
|
||||
PHANTOM_SYMLINK_DIRECTORY
|
||||
};
|
||||
|
||||
/*
|
||||
* Changes a file symlink to a directory symlink if the target exists and is a
|
||||
* directory.
|
||||
*/
|
||||
static enum phantom_symlink_result
|
||||
process_phantom_symlink(const wchar_t *wtarget, const wchar_t *wlink)
|
||||
{
|
||||
HANDLE hnd;
|
||||
BY_HANDLE_FILE_INFORMATION fdata;
|
||||
wchar_t relative[MAX_PATH];
|
||||
const wchar_t *rel;
|
||||
|
||||
/* check that wlink is still a file symlink */
|
||||
if ((GetFileAttributesW(wlink)
|
||||
& (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
|
||||
!= FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
return PHANTOM_SYMLINK_DONE;
|
||||
|
||||
/* make it relative, if necessary */
|
||||
rel = make_relative_to(wtarget, wlink, relative, ARRAY_SIZE(relative));
|
||||
if (!rel)
|
||||
return PHANTOM_SYMLINK_DONE;
|
||||
|
||||
/* let Windows resolve the link by opening it */
|
||||
hnd = CreateFileW(rel, 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||
if (hnd == INVALID_HANDLE_VALUE) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
return PHANTOM_SYMLINK_RETRY;
|
||||
}
|
||||
|
||||
if (!GetFileInformationByHandle(hnd, &fdata)) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
CloseHandle(hnd);
|
||||
return PHANTOM_SYMLINK_RETRY;
|
||||
}
|
||||
CloseHandle(hnd);
|
||||
|
||||
/* if target exists and is a file, we're done */
|
||||
if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
||||
return PHANTOM_SYMLINK_DONE;
|
||||
|
||||
/* otherwise recreate the symlink with directory flag */
|
||||
if (DeleteFileW(wlink) &&
|
||||
CreateSymbolicLinkW(wlink, wtarget, symlink_directory_flags))
|
||||
return PHANTOM_SYMLINK_DIRECTORY;
|
||||
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
return PHANTOM_SYMLINK_RETRY;
|
||||
}
|
||||
|
||||
/* keep track of newly created symlinks to non-existing targets */
|
||||
struct phantom_symlink_info {
|
||||
struct phantom_symlink_info *next;
|
||||
wchar_t *wlink;
|
||||
wchar_t *wtarget;
|
||||
};
|
||||
|
||||
static struct phantom_symlink_info *phantom_symlinks = NULL;
|
||||
static CRITICAL_SECTION phantom_symlinks_cs;
|
||||
|
||||
static void process_phantom_symlinks(void)
|
||||
{
|
||||
struct phantom_symlink_info *current, **psi;
|
||||
EnterCriticalSection(&phantom_symlinks_cs);
|
||||
/* process phantom symlinks list */
|
||||
psi = &phantom_symlinks;
|
||||
while ((current = *psi)) {
|
||||
enum phantom_symlink_result result = process_phantom_symlink(
|
||||
current->wtarget, current->wlink);
|
||||
if (result == PHANTOM_SYMLINK_RETRY) {
|
||||
psi = ¤t->next;
|
||||
} else {
|
||||
/* symlink was processed, remove from list */
|
||||
*psi = current->next;
|
||||
free(current);
|
||||
/* if symlink was a directory, start over */
|
||||
if (result == PHANTOM_SYMLINK_DIRECTORY)
|
||||
psi = &phantom_symlinks;
|
||||
}
|
||||
}
|
||||
LeaveCriticalSection(&phantom_symlinks_cs);
|
||||
}
|
||||
|
||||
/* Normalizes NT paths as returned by some low-level APIs. */
|
||||
static wchar_t *normalize_ntpath(wchar_t *wbuf)
|
||||
{
|
||||
@ -297,7 +451,7 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)
|
||||
|
||||
int mingw_unlink(const char *pathname, int handle_in_use_error)
|
||||
{
|
||||
int ret, tries = 0;
|
||||
int tries = 0;
|
||||
wchar_t wpathname[MAX_PATH];
|
||||
if (xutftowcs_path(wpathname, pathname) < 0)
|
||||
return -1;
|
||||
@ -305,29 +459,26 @@ int mingw_unlink(const char *pathname, int handle_in_use_error)
|
||||
if (DeleteFileW(wpathname))
|
||||
return 0;
|
||||
|
||||
/* read-only files cannot be removed */
|
||||
_wchmod(wpathname, 0666);
|
||||
while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
|
||||
do {
|
||||
/* read-only files cannot be removed */
|
||||
_wchmod(wpathname, 0666);
|
||||
if (!_wunlink(wpathname))
|
||||
return 0;
|
||||
if (!is_file_in_use_error(GetLastError()))
|
||||
break;
|
||||
if (!handle_in_use_error)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* We assume that some other process had the source or
|
||||
* destination file open at the wrong moment and retry.
|
||||
* In order to give the other process a higher chance to
|
||||
* complete its operation, we give up our time slice now.
|
||||
* If we have to retry again, we do sleep a bit.
|
||||
* _wunlink() / DeleteFileW() for directory symlinks fails with
|
||||
* ERROR_ACCESS_DENIED (EACCES), so try _wrmdir() as well. This is the
|
||||
* same error we get if a file is in use (already checked above).
|
||||
*/
|
||||
Sleep(delay[tries]);
|
||||
tries++;
|
||||
}
|
||||
while (ret == -1 && is_file_in_use_error(GetLastError()) &&
|
||||
ask_yes_no_if_possible("Unlink of file '%s' failed. "
|
||||
"Should I try again?", pathname))
|
||||
ret = _wunlink(wpathname);
|
||||
return ret;
|
||||
if (!_wrmdir(wpathname))
|
||||
return 0;
|
||||
|
||||
if (!handle_in_use_error)
|
||||
return -1;
|
||||
} while (retry_ask_yes_no(&tries, "Unlink of file '%s' failed. "
|
||||
"Should I try again?", pathname));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int is_dir_empty(const wchar_t *wpath)
|
||||
@ -354,7 +505,7 @@ static int is_dir_empty(const wchar_t *wpath)
|
||||
|
||||
int mingw_rmdir(const char *pathname)
|
||||
{
|
||||
int ret, tries = 0;
|
||||
int tries = 0;
|
||||
wchar_t wpathname[MAX_PATH];
|
||||
struct stat st;
|
||||
|
||||
@ -380,7 +531,11 @@ int mingw_rmdir(const char *pathname)
|
||||
if (xutftowcs_path(wpathname, pathname) < 0)
|
||||
return -1;
|
||||
|
||||
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
|
||||
do {
|
||||
if (!_wrmdir(wpathname)) {
|
||||
invalidate_lstat_cache();
|
||||
return 0;
|
||||
}
|
||||
if (!is_file_in_use_error(GetLastError()))
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
if (errno != EACCES)
|
||||
@ -389,23 +544,9 @@ int mingw_rmdir(const char *pathname)
|
||||
errno = ENOTEMPTY;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* We assume that some other process had the source or
|
||||
* destination file open at the wrong moment and retry.
|
||||
* In order to give the other process a higher chance to
|
||||
* complete its operation, we give up our time slice now.
|
||||
* If we have to retry again, we do sleep a bit.
|
||||
*/
|
||||
Sleep(delay[tries]);
|
||||
tries++;
|
||||
}
|
||||
while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) &&
|
||||
ask_yes_no_if_possible("Deletion of directory '%s' failed. "
|
||||
"Should I try again?", pathname))
|
||||
ret = _wrmdir(wpathname);
|
||||
if (!ret)
|
||||
invalidate_lstat_cache();
|
||||
return ret;
|
||||
} while (retry_ask_yes_no(&tries, "Deletion of directory '%s' failed. "
|
||||
"Should I try again?", pathname));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline int needs_hiding(const char *path)
|
||||
@ -466,6 +607,8 @@ int mingw_mkdir(const char *path, int mode UNUSED)
|
||||
if (xutftowcs_path(wpath, path) < 0)
|
||||
return -1;
|
||||
ret = _wmkdir(wpath);
|
||||
if (!ret)
|
||||
process_phantom_symlinks();
|
||||
if (!ret && needs_hiding(path))
|
||||
return set_hidden_flag(wpath, 1);
|
||||
return ret;
|
||||
@ -853,9 +996,27 @@ int mingw_access(const char *filename, int mode)
|
||||
int mingw_chdir(const char *dirname)
|
||||
{
|
||||
wchar_t wdirname[MAX_PATH];
|
||||
|
||||
if (xutftowcs_path(wdirname, dirname) < 0)
|
||||
return -1;
|
||||
return _wchdir(wdirname);
|
||||
|
||||
if (has_symlinks) {
|
||||
HANDLE hnd = CreateFileW(wdirname, 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||
if (hnd == INVALID_HANDLE_VALUE) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
if (!GetFinalPathNameByHandleW(hnd, wdirname, ARRAY_SIZE(wdirname), 0)) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
CloseHandle(hnd);
|
||||
return -1;
|
||||
}
|
||||
CloseHandle(hnd);
|
||||
}
|
||||
|
||||
return _wchdir(normalize_ntpath(wdirname));
|
||||
}
|
||||
|
||||
int mingw_chmod(const char *filename, int mode)
|
||||
@ -917,53 +1078,139 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* We keep the do_lstat code in a separate function to avoid recursion.
|
||||
* When a path ends with a slash, the stat will fail with ENOENT. In
|
||||
* this case, we strip the trailing slashes and stat again.
|
||||
*
|
||||
* If follow is true then act like stat() and report on the link
|
||||
* target. Otherwise report on the link itself.
|
||||
#ifndef _WINNT_H
|
||||
/*
|
||||
* The REPARSE_DATA_BUFFER structure is defined in the Windows DDK (in
|
||||
* ntifs.h) and in MSYS1's winnt.h (which defines _WINNT_H). So define
|
||||
* it ourselves if we are on MSYS2 (whose winnt.h defines _WINNT_).
|
||||
*/
|
||||
static int do_lstat(int follow, const char *file_name, struct stat *buf)
|
||||
typedef struct _REPARSE_DATA_BUFFER {
|
||||
DWORD ReparseTag;
|
||||
WORD ReparseDataLength;
|
||||
WORD Reserved;
|
||||
#ifndef _MSC_VER
|
||||
_ANONYMOUS_UNION
|
||||
#endif
|
||||
union {
|
||||
struct {
|
||||
WORD SubstituteNameOffset;
|
||||
WORD SubstituteNameLength;
|
||||
WORD PrintNameOffset;
|
||||
WORD PrintNameLength;
|
||||
ULONG Flags;
|
||||
WCHAR PathBuffer[1];
|
||||
} SymbolicLinkReparseBuffer;
|
||||
struct {
|
||||
WORD SubstituteNameOffset;
|
||||
WORD SubstituteNameLength;
|
||||
WORD PrintNameOffset;
|
||||
WORD PrintNameLength;
|
||||
WCHAR PathBuffer[1];
|
||||
} MountPointReparseBuffer;
|
||||
struct {
|
||||
BYTE DataBuffer[1];
|
||||
} GenericReparseBuffer;
|
||||
} DUMMYUNIONNAME;
|
||||
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
|
||||
#endif
|
||||
|
||||
static int read_reparse_point(const WCHAR *wpath, BOOL fail_on_unknown_tag,
|
||||
char *tmpbuf, int *plen, DWORD *ptag)
|
||||
{
|
||||
HANDLE handle;
|
||||
WCHAR *wbuf;
|
||||
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
||||
DWORD dummy;
|
||||
|
||||
/* read reparse point data */
|
||||
handle = CreateFileW(wpath, 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, b,
|
||||
MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dummy, NULL)) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
CloseHandle(handle);
|
||||
return -1;
|
||||
}
|
||||
CloseHandle(handle);
|
||||
|
||||
/* get target path for symlinks or mount points (aka 'junctions') */
|
||||
switch ((*ptag = b->ReparseTag)) {
|
||||
case IO_REPARSE_TAG_SYMLINK:
|
||||
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
|
||||
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
|
||||
*(WCHAR*) (((char*) wbuf)
|
||||
+ b->SymbolicLinkReparseBuffer.SubstituteNameLength) = 0;
|
||||
break;
|
||||
case IO_REPARSE_TAG_MOUNT_POINT:
|
||||
wbuf = (WCHAR*) (((char*) b->MountPointReparseBuffer.PathBuffer)
|
||||
+ b->MountPointReparseBuffer.SubstituteNameOffset);
|
||||
*(WCHAR*) (((char*) wbuf)
|
||||
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
|
||||
break;
|
||||
default:
|
||||
if (fail_on_unknown_tag) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
} else {
|
||||
*plen = MAX_PATH;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((*plen =
|
||||
xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_PATH)) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mingw_lstat(const char *file_name, struct stat *buf)
|
||||
{
|
||||
WIN32_FILE_ATTRIBUTE_DATA fdata;
|
||||
DWORD reparse_tag = 0;
|
||||
int link_len = 0;
|
||||
wchar_t wfilename[MAX_PATH];
|
||||
if (xutftowcs_path(wfilename, file_name) < 0)
|
||||
int wlen = xutftowcs_path(wfilename, file_name);
|
||||
if (wlen < 0)
|
||||
return -1;
|
||||
|
||||
/* strip trailing '/', or GetFileAttributes will fail */
|
||||
while (wlen && is_dir_sep(wfilename[wlen - 1]))
|
||||
wfilename[--wlen] = 0;
|
||||
if (!wlen) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
|
||||
/* for reparse points, get the link tag and length */
|
||||
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
char tmpbuf[MAX_PATH];
|
||||
|
||||
if (read_reparse_point(wfilename, FALSE, tmpbuf,
|
||||
&link_len, &reparse_tag) < 0)
|
||||
return -1;
|
||||
}
|
||||
buf->st_ino = 0;
|
||||
buf->st_gid = 0;
|
||||
buf->st_uid = 0;
|
||||
buf->st_nlink = 1;
|
||||
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
|
||||
buf->st_size = fdata.nFileSizeLow |
|
||||
(((off_t)fdata.nFileSizeHigh)<<32);
|
||||
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
|
||||
reparse_tag);
|
||||
buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
|
||||
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
|
||||
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
|
||||
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
|
||||
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
|
||||
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
|
||||
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
WIN32_FIND_DATAW findbuf;
|
||||
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
|
||||
(findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
|
||||
if (follow) {
|
||||
char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
||||
} else {
|
||||
buf->st_mode = S_IFLNK;
|
||||
}
|
||||
buf->st_mode |= S_IREAD;
|
||||
if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
|
||||
buf->st_mode |= S_IWRITE;
|
||||
}
|
||||
FindClose(handle);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (GetLastError()) {
|
||||
case ERROR_ACCESS_DENIED:
|
||||
case ERROR_SHARING_VIOLATION:
|
||||
@ -990,39 +1237,6 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* We provide our own lstat/fstat functions, since the provided
|
||||
* lstat/fstat functions are so slow. These stat functions are
|
||||
* tailored for Git's usage (read: fast), and are not meant to be
|
||||
* complete. Note that Git stat()s are redirected to mingw_lstat()
|
||||
* too, since Windows doesn't really handle symlinks that well.
|
||||
*/
|
||||
static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
|
||||
{
|
||||
size_t namelen;
|
||||
char alt_name[PATH_MAX];
|
||||
|
||||
if (!do_lstat(follow, file_name, buf))
|
||||
return 0;
|
||||
|
||||
/* if file_name ended in a '/', Windows returned ENOENT;
|
||||
* try again without trailing slashes
|
||||
*/
|
||||
if (errno != ENOENT)
|
||||
return -1;
|
||||
|
||||
namelen = strlen(file_name);
|
||||
if (namelen && file_name[namelen-1] != '/')
|
||||
return -1;
|
||||
while (namelen && file_name[namelen-1] == '/')
|
||||
--namelen;
|
||||
if (!namelen || namelen >= PATH_MAX)
|
||||
return -1;
|
||||
|
||||
memcpy(alt_name, file_name, namelen);
|
||||
alt_name[namelen] = 0;
|
||||
return do_lstat(follow, alt_name, buf);
|
||||
}
|
||||
|
||||
static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
|
||||
{
|
||||
BY_HANDLE_FILE_INFORMATION fdata;
|
||||
@ -1036,7 +1250,7 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
|
||||
buf->st_gid = 0;
|
||||
buf->st_uid = 0;
|
||||
buf->st_nlink = 1;
|
||||
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
|
||||
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, 0);
|
||||
buf->st_size = fdata.nFileSizeLow |
|
||||
(((off_t)fdata.nFileSizeHigh)<<32);
|
||||
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
|
||||
@ -1046,13 +1260,37 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mingw_lstat(const char *file_name, struct stat *buf)
|
||||
{
|
||||
return do_stat_internal(0, file_name, buf);
|
||||
}
|
||||
int mingw_stat(const char *file_name, struct stat *buf)
|
||||
{
|
||||
return do_stat_internal(1, file_name, buf);
|
||||
wchar_t wfile_name[MAX_PATH];
|
||||
HANDLE hnd;
|
||||
int result;
|
||||
|
||||
/* open the file and let Windows resolve the links */
|
||||
if (xutftowcs_path(wfile_name, file_name) < 0)
|
||||
return -1;
|
||||
hnd = CreateFileW(wfile_name, 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||
if (hnd == INVALID_HANDLE_VALUE) {
|
||||
DWORD err = GetLastError();
|
||||
|
||||
if (err == ERROR_ACCESS_DENIED &&
|
||||
!mingw_lstat(file_name, buf) &&
|
||||
!S_ISLNK(buf->st_mode))
|
||||
/*
|
||||
* POSIX semantics state to still try to fill
|
||||
* information, even if permission is denied to create
|
||||
* a file handle.
|
||||
*/
|
||||
return 0;
|
||||
|
||||
errno = err_win_to_posix(err);
|
||||
return -1;
|
||||
}
|
||||
result = get_file_info_by_handle(hnd, buf);
|
||||
CloseHandle(hnd);
|
||||
return result;
|
||||
}
|
||||
|
||||
int mingw_fstat(int fd, struct stat *buf)
|
||||
@ -2197,7 +2435,7 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
|
||||
int mingw_rename(const char *pold, const char *pnew)
|
||||
{
|
||||
static int supports_file_rename_info_ex = 1;
|
||||
DWORD attrs, gle;
|
||||
DWORD attrs = INVALID_FILE_ATTRIBUTES, gle;
|
||||
int tries = 0;
|
||||
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
|
||||
int wpnew_len;
|
||||
@ -2208,15 +2446,6 @@ int mingw_rename(const char *pold, const char *pnew)
|
||||
if (wpnew_len < 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* Try native rename() first to get errno right.
|
||||
* It is based on MoveFile(), which cannot overwrite existing files.
|
||||
*/
|
||||
if (!_wrename(wpold, wpnew))
|
||||
return 0;
|
||||
if (errno != EEXIST)
|
||||
return -1;
|
||||
|
||||
repeat:
|
||||
if (supports_file_rename_info_ex) {
|
||||
/*
|
||||
@ -2292,13 +2521,22 @@ repeat:
|
||||
* to retry.
|
||||
*/
|
||||
} else {
|
||||
if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
|
||||
if (MoveFileExW(wpold, wpnew,
|
||||
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
|
||||
return 0;
|
||||
gle = GetLastError();
|
||||
}
|
||||
|
||||
/* TODO: translate more errors */
|
||||
if (gle == ERROR_ACCESS_DENIED &&
|
||||
/* revert file attributes on failure */
|
||||
if (attrs != INVALID_FILE_ATTRIBUTES)
|
||||
SetFileAttributesW(wpnew, attrs);
|
||||
|
||||
if (!is_file_in_use_error(gle)) {
|
||||
errno = err_win_to_posix(gle);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (attrs == INVALID_FILE_ATTRIBUTES &&
|
||||
(attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
|
||||
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
DWORD attrsold = GetFileAttributesW(wpold);
|
||||
@ -2310,28 +2548,10 @@ repeat:
|
||||
return -1;
|
||||
}
|
||||
if ((attrs & FILE_ATTRIBUTE_READONLY) &&
|
||||
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
|
||||
if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
|
||||
return 0;
|
||||
gle = GetLastError();
|
||||
/* revert file attributes on failure */
|
||||
SetFileAttributesW(wpnew, attrs);
|
||||
}
|
||||
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY))
|
||||
goto repeat;
|
||||
}
|
||||
if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
|
||||
/*
|
||||
* We assume that some other process had the source or
|
||||
* destination file open at the wrong moment and retry.
|
||||
* In order to give the other process a higher chance to
|
||||
* complete its operation, we give up our time slice now.
|
||||
* If we have to retry again, we do sleep a bit.
|
||||
*/
|
||||
Sleep(delay[tries]);
|
||||
tries++;
|
||||
goto repeat;
|
||||
}
|
||||
if (gle == ERROR_ACCESS_DENIED &&
|
||||
ask_yes_no_if_possible("Rename from '%s' to '%s' failed. "
|
||||
if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
|
||||
"Should I try again?", pold, pnew))
|
||||
goto repeat;
|
||||
|
||||
@ -2620,6 +2840,94 @@ int link(const char *oldpath, const char *newpath)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int symlink(const char *target, const char *link)
|
||||
{
|
||||
wchar_t wtarget[MAX_PATH], wlink[MAX_PATH];
|
||||
int len;
|
||||
|
||||
/* fail if symlinks are disabled or API is not supported (WinXP) */
|
||||
if (!has_symlinks) {
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((len = xutftowcs_path(wtarget, target)) < 0
|
||||
|| xutftowcs_path(wlink, link) < 0)
|
||||
return -1;
|
||||
|
||||
/* convert target dir separators to backslashes */
|
||||
while (len--)
|
||||
if (wtarget[len] == '/')
|
||||
wtarget[len] = '\\';
|
||||
|
||||
/* create file symlink */
|
||||
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* convert to directory symlink if target exists */
|
||||
switch (process_phantom_symlink(wtarget, wlink)) {
|
||||
case PHANTOM_SYMLINK_RETRY: {
|
||||
/* if target doesn't exist, add to phantom symlinks list */
|
||||
wchar_t wfullpath[MAX_PATH];
|
||||
struct phantom_symlink_info *psi;
|
||||
|
||||
/* convert to absolute path to be independent of cwd */
|
||||
len = GetFullPathNameW(wlink, MAX_PATH, wfullpath, NULL);
|
||||
if (!len || len >= MAX_PATH) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* over-allocate and fill phantom_symlink_info structure */
|
||||
psi = xmalloc(sizeof(struct phantom_symlink_info)
|
||||
+ sizeof(wchar_t) * (len + wcslen(wtarget) + 2));
|
||||
psi->wlink = (wchar_t *)(psi + 1);
|
||||
wcscpy(psi->wlink, wfullpath);
|
||||
psi->wtarget = psi->wlink + len + 1;
|
||||
wcscpy(psi->wtarget, wtarget);
|
||||
|
||||
EnterCriticalSection(&phantom_symlinks_cs);
|
||||
psi->next = phantom_symlinks;
|
||||
phantom_symlinks = psi;
|
||||
LeaveCriticalSection(&phantom_symlinks_cs);
|
||||
break;
|
||||
}
|
||||
case PHANTOM_SYMLINK_DIRECTORY:
|
||||
/* if we created a dir symlink, process other phantom symlinks */
|
||||
process_phantom_symlinks();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readlink(const char *path, char *buf, size_t bufsiz)
|
||||
{
|
||||
WCHAR wpath[MAX_PATH];
|
||||
char tmpbuf[MAX_PATH];
|
||||
int len;
|
||||
DWORD tag;
|
||||
|
||||
if (xutftowcs_path(wpath, path) < 0)
|
||||
return -1;
|
||||
|
||||
if (read_reparse_point(wpath, TRUE, tmpbuf, &len, &tag) < 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
|
||||
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
|
||||
* condition. There is no conversion function that produces invalid UTF-8,
|
||||
* so convert to a (hopefully large enough) temporary buffer, then memcpy
|
||||
* the requested number of bytes (including '\0' for robustness).
|
||||
*/
|
||||
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
|
||||
return min(bufsiz, len);
|
||||
}
|
||||
|
||||
pid_t waitpid(pid_t pid, int *status, int options)
|
||||
{
|
||||
HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
|
||||
@ -2808,6 +3116,15 @@ static void setup_windows_environment(void)
|
||||
if (!tmp && (tmp = getenv("USERPROFILE")))
|
||||
setenv("HOME", tmp, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Change 'core.symlinks' default to false, unless native symlinks are
|
||||
* enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can
|
||||
* run the test suite (which doesn't obey config files) with or without
|
||||
* symlink support.
|
||||
*/
|
||||
if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict"))
|
||||
has_symlinks = 0;
|
||||
}
|
||||
|
||||
static void get_current_user_sid(PSID *sid, HANDLE *linked_token)
|
||||
@ -3221,6 +3538,24 @@ static void maybe_redirect_std_handles(void)
|
||||
GENERIC_WRITE, FILE_FLAG_NO_BUFFERING);
|
||||
}
|
||||
|
||||
static void adjust_symlink_flags(void)
|
||||
{
|
||||
/*
|
||||
* Starting with Windows 10 Build 14972, symbolic links can be created
|
||||
* using CreateSymbolicLink() without elevation by passing the flag
|
||||
* SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE (0x02) as last
|
||||
* parameter, provided the Developer Mode has been enabled. Some
|
||||
* earlier Windows versions complain about this flag with an
|
||||
* ERROR_INVALID_PARAMETER, hence we have to test the build number
|
||||
* specifically.
|
||||
*/
|
||||
if (GetVersion() >= 14972 << 16) {
|
||||
symlink_file_flags |= 2;
|
||||
symlink_directory_flags |= 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifdef _DEBUG
|
||||
#include <crtdbg.h>
|
||||
@ -3256,6 +3591,7 @@ int wmain(int argc, const wchar_t **wargv)
|
||||
#endif
|
||||
|
||||
maybe_redirect_std_handles();
|
||||
adjust_symlink_flags();
|
||||
|
||||
/* determine size of argv and environ conversion buffer */
|
||||
maxlen = wcslen(wargv[0]);
|
||||
@ -3285,6 +3621,7 @@ int wmain(int argc, const wchar_t **wargv)
|
||||
|
||||
/* initialize critical section for waitpid pinfo_t list */
|
||||
InitializeCriticalSection(&pinfo_cs);
|
||||
InitializeCriticalSection(&phantom_symlinks_cs);
|
||||
|
||||
/* set up default file mode and file modes for stdin/out/err */
|
||||
_fmode = _O_BINARY;
|
||||
|
||||
@ -6,10 +6,12 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
static inline int file_attr_to_st_mode (DWORD attr)
|
||||
static inline int file_attr_to_st_mode (DWORD attr, DWORD tag)
|
||||
{
|
||||
int fMode = S_IREAD;
|
||||
if (attr & FILE_ATTRIBUTE_DIRECTORY)
|
||||
if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) && tag == IO_REPARSE_TAG_SYMLINK)
|
||||
fMode |= S_IFLNK;
|
||||
else if (attr & FILE_ATTRIBUTE_DIRECTORY)
|
||||
fMode |= S_IFDIR;
|
||||
else
|
||||
fMode |= S_IFREG;
|
||||
|
||||
@ -12,7 +12,10 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
|
||||
xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name));
|
||||
|
||||
/* Set file type, based on WIN32_FIND_DATA */
|
||||
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
if ((fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
&& fdata->dwReserved0 == IO_REPARSE_TAG_SYMLINK)
|
||||
ent->d_type = DT_LNK;
|
||||
else if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
ent->d_type = DT_DIR;
|
||||
else
|
||||
ent->d_type = DT_REG;
|
||||
|
||||
11
read-cache.c
11
read-cache.c
@ -470,6 +470,17 @@ int ie_modified(struct index_state *istate,
|
||||
* then we know it is.
|
||||
*/
|
||||
if ((changed & DATA_CHANGED) &&
|
||||
#ifdef GIT_WINDOWS_NATIVE
|
||||
/*
|
||||
* Work around Git for Windows v2.27.0 fixing a bug where symlinks'
|
||||
* target path lengths were not read at all, and instead recorded
|
||||
* as 4096: now, all symlinks would appear as modified.
|
||||
*
|
||||
* So let's just special-case symlinks with a target path length
|
||||
* (i.e. `sd_size`) of 4096 and force them to be re-checked.
|
||||
*/
|
||||
(!S_ISLNK(st->st_mode) || ce->ce_stat_data.sd_size != MAX_PATH) &&
|
||||
#endif
|
||||
(S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
|
||||
return changed;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user