|
|
|
|
@@ -2,14 +2,14 @@
|
|
|
|
|
#include "win32.h"
|
|
|
|
|
#include <conio.h>
|
|
|
|
|
#include <wchar.h>
|
|
|
|
|
#include <winioctl.h>
|
|
|
|
|
#include "../strbuf.h"
|
|
|
|
|
#include "../run-command.h"
|
|
|
|
|
#include "../cache.h"
|
|
|
|
|
#include "win32/lazyload.h"
|
|
|
|
|
|
|
|
|
|
#define HCAST(type, handle) ((type)(intptr_t)handle)
|
|
|
|
|
|
|
|
|
|
static const int delay[] = { 0, 1, 10, 20, 40 };
|
|
|
|
|
|
|
|
|
|
int err_win_to_posix(DWORD winerr)
|
|
|
|
|
{
|
|
|
|
|
int error = ENOSYS;
|
|
|
|
|
@@ -72,6 +72,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;
|
|
|
|
|
@@ -86,6 +87,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;
|
|
|
|
|
@@ -106,6 +108,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;
|
|
|
|
|
@@ -172,15 +177,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[] = { NULL, NULL, NULL };
|
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
|
|
va_start(args, format);
|
|
|
|
|
vsnprintf(question, sizeof(question), format, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
|
|
if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) {
|
|
|
|
|
retry_hook[1] = question;
|
|
|
|
|
@@ -202,6 +204,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,
|
|
|
|
|
@@ -243,33 +270,182 @@ int mingw_core_config(const char *var, const char *value, void *cb)
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DECLARE_PROC_ADDR(kernel32.dll, BOOLEAN, CreateSymbolicLinkW, LPCWSTR, LPCWSTR, DWORD);
|
|
|
|
|
|
|
|
|
|
enum phantom_symlink_result {
|
|
|
|
|
PHANTOM_SYMLINK_RETRY,
|
|
|
|
|
PHANTOM_SYMLINK_DONE,
|
|
|
|
|
PHANTOM_SYMLINK_DIRECTORY
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 '%S' relative to '%S' (too large)",
|
|
|
|
|
path, relative_to);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memcpy(out, relative_to, i * sizeof(wchar_t));
|
|
|
|
|
wcscpy(out + i, path);
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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_LONG_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, 1))
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
/* fix absolute path prefixes */
|
|
|
|
|
if (wbuf[0] == '\\') {
|
|
|
|
|
/* strip NT namespace prefixes */
|
|
|
|
|
if (!wcsncmp(wbuf, L"\\??\\", 4) ||
|
|
|
|
|
!wcsncmp(wbuf, L"\\\\?\\", 4))
|
|
|
|
|
wbuf += 4;
|
|
|
|
|
else if (!wcsnicmp(wbuf, L"\\DosDevices\\", 12))
|
|
|
|
|
wbuf += 12;
|
|
|
|
|
/* replace remaining '...UNC\' with '\\' */
|
|
|
|
|
if (!wcsnicmp(wbuf, L"UNC\\", 4)) {
|
|
|
|
|
wbuf += 2;
|
|
|
|
|
*wbuf = '\\';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* convert backslashes to slashes */
|
|
|
|
|
for (i = 0; wbuf[i]; i++)
|
|
|
|
|
if (wbuf[i] == '\\')
|
|
|
|
|
wbuf[i] = '/';
|
|
|
|
|
return wbuf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int mingw_unlink(const char *pathname)
|
|
|
|
|
{
|
|
|
|
|
int ret, tries = 0;
|
|
|
|
|
int tries = 0;
|
|
|
|
|
wchar_t wpathname[MAX_LONG_PATH];
|
|
|
|
|
if (xutftowcs_long_path(wpathname, pathname) < 0)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
} 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)
|
|
|
|
|
@@ -296,12 +472,14 @@ 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_LONG_PATH];
|
|
|
|
|
if (xutftowcs_long_path(wpathname, pathname) < 0)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
|
|
|
|
|
do {
|
|
|
|
|
if (!_wrmdir(wpathname))
|
|
|
|
|
return 0;
|
|
|
|
|
if (!is_file_in_use_error(GetLastError()))
|
|
|
|
|
errno = err_win_to_posix(GetLastError());
|
|
|
|
|
if (errno != EACCES)
|
|
|
|
|
@@ -310,21 +488,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);
|
|
|
|
|
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)
|
|
|
|
|
@@ -380,6 +546,8 @@ int mingw_mkdir(const char *path, int mode)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
ret = _wmkdir(wpath);
|
|
|
|
|
if (!ret)
|
|
|
|
|
process_phantom_symlinks();
|
|
|
|
|
if (!ret && needs_hiding(path))
|
|
|
|
|
return set_hidden_flag(wpath, 1);
|
|
|
|
|
return ret;
|
|
|
|
|
@@ -585,10 +753,29 @@ static int current_directory_len = 0;
|
|
|
|
|
int mingw_chdir(const char *dirname)
|
|
|
|
|
{
|
|
|
|
|
int result;
|
|
|
|
|
DECLARE_PROC_ADDR(kernel32.dll, DWORD, GetFinalPathNameByHandleW,
|
|
|
|
|
HANDLE, LPWSTR, DWORD, DWORD);
|
|
|
|
|
wchar_t wdirname[MAX_LONG_PATH];
|
|
|
|
|
if (xutftowcs_long_path(wdirname, dirname) < 0)
|
|
|
|
|
return -1;
|
|
|
|
|
result = _wchdir(wdirname);
|
|
|
|
|
|
|
|
|
|
if (has_symlinks && INIT_PROC_ADDR(GetFinalPathNameByHandleW)) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = _wchdir(normalize_ntpath(wdirname));
|
|
|
|
|
current_directory_len = GetCurrentDirectoryW(0, NULL);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
@@ -634,53 +821,46 @@ 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.
|
|
|
|
|
*/
|
|
|
|
|
static int do_lstat(int follow, const char *file_name, struct stat *buf)
|
|
|
|
|
int mingw_lstat(const char *file_name, struct stat *buf)
|
|
|
|
|
{
|
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA fdata;
|
|
|
|
|
WIN32_FIND_DATAW findbuf;
|
|
|
|
|
wchar_t wfilename[MAX_LONG_PATH];
|
|
|
|
|
if (xutftowcs_long_path(wfilename, file_name) < 0)
|
|
|
|
|
int wlen = xutftowcs_long_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, use FindFirstFile to get the reparse tag */
|
|
|
|
|
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
|
|
|
|
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
|
|
|
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
|
|
|
goto error;
|
|
|
|
|
FindClose(handle);
|
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
|
findbuf.dwReserved0);
|
|
|
|
|
buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
|
|
|
|
|
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
|
|
|
|
|
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
|
|
|
|
|
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
|
|
|
|
|
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
|
|
|
|
|
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
error:
|
|
|
|
|
switch (GetLastError()) {
|
|
|
|
|
case ERROR_ACCESS_DENIED:
|
|
|
|
|
case ERROR_SHARING_VIOLATION:
|
|
|
|
|
@@ -707,55 +887,51 @@ 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)
|
|
|
|
|
{
|
|
|
|
|
int namelen;
|
|
|
|
|
char alt_name[MAX_LONG_PATH];
|
|
|
|
|
|
|
|
|
|
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 >= MAX_LONG_PATH)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
memcpy(alt_name, file_name, namelen);
|
|
|
|
|
alt_name[namelen] = 0;
|
|
|
|
|
return do_lstat(follow, alt_name, buf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int (*lstat)(const char *file_name, struct stat *buf) = mingw_lstat;
|
|
|
|
|
|
|
|
|
|
int mingw_lstat(const char *file_name, struct stat *buf)
|
|
|
|
|
static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
|
|
|
|
|
{
|
|
|
|
|
return do_stat_internal(0, file_name, buf);
|
|
|
|
|
BY_HANDLE_FILE_INFORMATION fdata;
|
|
|
|
|
if (!GetFileInformationByHandle(hnd, &fdata)) {
|
|
|
|
|
errno = err_win_to_posix(GetLastError());
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
buf->st_ino = 0;
|
|
|
|
|
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
|
|
|
|
|
buf->st_gid = buf->st_uid = 0;
|
|
|
|
|
buf->st_nlink = 1;
|
|
|
|
|
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, 0);
|
|
|
|
|
buf->st_size = fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
|
|
|
|
|
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
|
|
|
|
|
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
|
|
|
|
|
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int mingw_stat(const char *file_name, struct stat *buf)
|
|
|
|
|
{
|
|
|
|
|
return do_stat_internal(1, file_name, buf);
|
|
|
|
|
wchar_t wfile_name[MAX_LONG_PATH];
|
|
|
|
|
HANDLE hnd;
|
|
|
|
|
int result;
|
|
|
|
|
|
|
|
|
|
/* open the file and let Windows resolve the links */
|
|
|
|
|
if (xutftowcs_long_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) {
|
|
|
|
|
errno = err_win_to_posix(GetLastError());
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
result = get_file_info_by_handle(hnd, buf);
|
|
|
|
|
CloseHandle(hnd);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int mingw_fstat(int fd, struct stat *buf)
|
|
|
|
|
{
|
|
|
|
|
HANDLE fh = (HANDLE)_get_osfhandle(fd);
|
|
|
|
|
BY_HANDLE_FILE_INFORMATION fdata;
|
|
|
|
|
|
|
|
|
|
if (fh == INVALID_HANDLE_VALUE) {
|
|
|
|
|
errno = EBADF;
|
|
|
|
|
return -1;
|
|
|
|
|
@@ -764,20 +940,8 @@ int mingw_fstat(int fd, struct stat *buf)
|
|
|
|
|
if (GetFileType(fh) != FILE_TYPE_DISK)
|
|
|
|
|
return _fstati64(fd, buf);
|
|
|
|
|
|
|
|
|
|
if (GetFileInformationByHandle(fh, &fdata)) {
|
|
|
|
|
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_dev = buf->st_rdev = 0; /* not used by Git */
|
|
|
|
|
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
|
|
|
|
|
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
|
|
|
|
|
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
|
|
|
|
|
if (!get_file_info_by_handle(fh, buf))
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
errno = EBADF;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
@@ -1843,28 +2007,29 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
|
|
|
|
|
#undef rename
|
|
|
|
|
int mingw_rename(const char *pold, const char *pnew)
|
|
|
|
|
{
|
|
|
|
|
DWORD attrs, gle;
|
|
|
|
|
DWORD attrs = INVALID_FILE_ATTRIBUTES, gle;
|
|
|
|
|
int tries = 0;
|
|
|
|
|
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
|
|
|
|
|
if (xutftowcs_long_path(wpold, pold) < 0 ||
|
|
|
|
|
xutftowcs_long_path(wpnew, pnew) < 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 (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
|
|
|
|
|
if (MoveFileExW(wpold, wpnew,
|
|
|
|
|
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
|
|
|
|
|
return 0;
|
|
|
|
|
/* TODO: translate more errors */
|
|
|
|
|
gle = GetLastError();
|
|
|
|
|
if (gle == ERROR_ACCESS_DENIED &&
|
|
|
|
|
(attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
|
|
|
|
|
|
|
|
|
|
/* 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 = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
|
|
|
|
|
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
|
|
|
|
|
DWORD attrsold = GetFileAttributesW(wpold);
|
|
|
|
|
if (attrsold == INVALID_FILE_ATTRIBUTES ||
|
|
|
|
|
@@ -1875,28 +2040,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;
|
|
|
|
|
|
|
|
|
|
@@ -1916,18 +2063,63 @@ int mingw_getpagesize(void)
|
|
|
|
|
return si.dwAllocationGranularity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724435.aspx */
|
|
|
|
|
enum EXTENDED_NAME_FORMAT {
|
|
|
|
|
NameDisplay = 3,
|
|
|
|
|
NameUserPrincipal = 8
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static char *get_extended_user_info(enum EXTENDED_NAME_FORMAT type)
|
|
|
|
|
{
|
|
|
|
|
DECLARE_PROC_ADDR(secur32.dll, BOOL, GetUserNameExW,
|
|
|
|
|
enum EXTENDED_NAME_FORMAT, LPCWSTR, PULONG);
|
|
|
|
|
static wchar_t wbuffer[1024];
|
|
|
|
|
DWORD len;
|
|
|
|
|
|
|
|
|
|
if (!INIT_PROC_ADDR(GetUserNameExW))
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
len = ARRAY_SIZE(wbuffer);
|
|
|
|
|
if (GetUserNameExW(type, wbuffer, &len)) {
|
|
|
|
|
char *converted = xmalloc((len *= 3));
|
|
|
|
|
if (xwcstoutf(converted, wbuffer, len) >= 0)
|
|
|
|
|
return converted;
|
|
|
|
|
free(converted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *mingw_query_user_email(void)
|
|
|
|
|
{
|
|
|
|
|
return get_extended_user_info(NameUserPrincipal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct passwd *getpwuid(int uid)
|
|
|
|
|
{
|
|
|
|
|
static unsigned initialized;
|
|
|
|
|
static char user_name[100];
|
|
|
|
|
static struct passwd p;
|
|
|
|
|
static struct passwd *p;
|
|
|
|
|
DWORD len;
|
|
|
|
|
|
|
|
|
|
DWORD len = sizeof(user_name);
|
|
|
|
|
if (!GetUserName(user_name, &len))
|
|
|
|
|
if (initialized)
|
|
|
|
|
return p;
|
|
|
|
|
|
|
|
|
|
len = sizeof(user_name);
|
|
|
|
|
if (!GetUserName(user_name, &len)) {
|
|
|
|
|
initialized = 1;
|
|
|
|
|
return NULL;
|
|
|
|
|
p.pw_name = user_name;
|
|
|
|
|
p.pw_gecos = "unknown";
|
|
|
|
|
p.pw_dir = NULL;
|
|
|
|
|
return &p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p = xmalloc(sizeof(*p));
|
|
|
|
|
p->pw_name = user_name;
|
|
|
|
|
p->pw_gecos = get_extended_user_info(NameDisplay);
|
|
|
|
|
if (!p->pw_gecos)
|
|
|
|
|
p->pw_gecos = "unknown";
|
|
|
|
|
p->pw_dir = NULL;
|
|
|
|
|
|
|
|
|
|
initialized = 1;
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static HANDLE timer_event;
|
|
|
|
|
@@ -2085,30 +2277,188 @@ int mingw_raise(int sig)
|
|
|
|
|
|
|
|
|
|
int link(const char *oldpath, const char *newpath)
|
|
|
|
|
{
|
|
|
|
|
typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
|
|
|
|
|
static T create_hard_link = NULL;
|
|
|
|
|
DECLARE_PROC_ADDR(kernel32.dll, BOOL, CreateHardLinkW,
|
|
|
|
|
LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
|
|
|
|
|
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
|
|
|
|
|
|
|
|
|
|
if (!INIT_PROC_ADDR(CreateHardLinkW))
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
|
|
|
|
|
xutftowcs_long_path(wnewpath, newpath) < 0)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
if (!create_hard_link) {
|
|
|
|
|
create_hard_link = (T) GetProcAddress(
|
|
|
|
|
GetModuleHandle("kernel32.dll"), "CreateHardLinkW");
|
|
|
|
|
if (!create_hard_link)
|
|
|
|
|
create_hard_link = (T)-1;
|
|
|
|
|
}
|
|
|
|
|
if (create_hard_link == (T)-1) {
|
|
|
|
|
errno = ENOSYS;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if (!create_hard_link(wnewpath, woldpath, NULL)) {
|
|
|
|
|
if (!CreateHardLinkW(wnewpath, woldpath, NULL)) {
|
|
|
|
|
errno = err_win_to_posix(GetLastError());
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int symlink(const char *target, const char *link)
|
|
|
|
|
{
|
|
|
|
|
wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
|
|
|
|
|
int len;
|
|
|
|
|
|
|
|
|
|
/* fail if symlinks are disabled or API is not supported (WinXP) */
|
|
|
|
|
if (!has_symlinks || !INIT_PROC_ADDR(CreateSymbolicLinkW)) {
|
|
|
|
|
errno = ENOSYS;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((len = xutftowcs_long_path(wtarget, target)) < 0
|
|
|
|
|
|| xutftowcs_long_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, 0)) {
|
|
|
|
|
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_LONG_PATH];
|
|
|
|
|
struct phantom_symlink_info *psi;
|
|
|
|
|
|
|
|
|
|
/* convert to absolute path to be independent of cwd */
|
|
|
|
|
len = GetFullPathNameW(wlink, MAX_LONG_PATH, wfullpath, NULL);
|
|
|
|
|
if (!len || len >= MAX_LONG_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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#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_).
|
|
|
|
|
*/
|
|
|
|
|
typedef struct _REPARSE_DATA_BUFFER {
|
|
|
|
|
DWORD ReparseTag;
|
|
|
|
|
WORD ReparseDataLength;
|
|
|
|
|
WORD Reserved;
|
|
|
|
|
_ANONYMOUS_UNION 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
|
|
|
|
|
|
|
|
|
|
int readlink(const char *path, char *buf, size_t bufsiz)
|
|
|
|
|
{
|
|
|
|
|
HANDLE handle;
|
|
|
|
|
WCHAR wpath[MAX_LONG_PATH], *wbuf;
|
|
|
|
|
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
|
|
|
DWORD dummy;
|
|
|
|
|
char tmpbuf[MAX_LONG_PATH];
|
|
|
|
|
int len;
|
|
|
|
|
|
|
|
|
|
/* fail if symlinks are disabled */
|
|
|
|
|
if (!has_symlinks) {
|
|
|
|
|
errno = ENOSYS;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (xutftowcs_long_path(wpath, path) < 0)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
/* 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 (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:
|
|
|
|
|
errno = EINVAL;
|
|
|
|
|
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).
|
|
|
|
|
*/
|
|
|
|
|
if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
|
|
|
|
|
return -1;
|
|
|
|
|
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,
|
|
|
|
|
@@ -2324,6 +2674,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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int handle_long_path(wchar_t *path, int len, int max_path, int expand)
|
|
|
|
|
@@ -2540,6 +2899,7 @@ void mingw_startup(void)
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|