|
|
|
|
@@ -2,6 +2,7 @@
|
|
|
|
|
#include "win32.h"
|
|
|
|
|
#include <conio.h>
|
|
|
|
|
#include <wchar.h>
|
|
|
|
|
#include <winioctl.h>
|
|
|
|
|
#include "../strbuf.h"
|
|
|
|
|
#include "../run-command.h"
|
|
|
|
|
#include "../cache.h"
|
|
|
|
|
@@ -11,8 +12,6 @@
|
|
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
@@ -88,6 +87,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;
|
|
|
|
|
@@ -102,6 +102,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;
|
|
|
|
|
@@ -122,6 +123,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;
|
|
|
|
|
@@ -188,15 +192,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;
|
|
|
|
|
@@ -218,6 +219,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,
|
|
|
|
|
@@ -259,6 +285,134 @@ int mingw_core_config(const char *var, const char *value, void *cb)
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static DWORD symlink_file_flags = 0, symlink_directory_flags = 1;
|
|
|
|
|
|
|
|
|
|
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, 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)
|
|
|
|
|
{
|
|
|
|
|
@@ -286,31 +440,28 @@ static wchar_t *normalize_ntpath(wchar_t *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)
|
|
|
|
|
@@ -337,12 +488,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)
|
|
|
|
|
@@ -351,21 +504,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)
|
|
|
|
|
@@ -421,6 +562,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;
|
|
|
|
|
@@ -659,7 +802,24 @@ int mingw_chdir(const char *dirname)
|
|
|
|
|
wchar_t wdirname[MAX_LONG_PATH];
|
|
|
|
|
if (xutftowcs_long_path(wdirname, dirname) < 0)
|
|
|
|
|
return -1;
|
|
|
|
|
result = _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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = _wchdir(normalize_ntpath(wdirname));
|
|
|
|
|
current_directory_len = GetCurrentDirectoryW(0, NULL);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
@@ -705,53 +865,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 = { 0 };
|
|
|
|
|
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 */
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
error:
|
|
|
|
|
switch (GetLastError()) {
|
|
|
|
|
case ERROR_ACCESS_DENIED:
|
|
|
|
|
case ERROR_SHARING_VIOLATION:
|
|
|
|
|
@@ -778,39 +931,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)
|
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
|
|
|
|
|
@@ -826,7 +946,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 */
|
|
|
|
|
@@ -836,13 +956,25 @@ 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_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)
|
|
|
|
|
@@ -1892,27 +2024,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 &&
|
|
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
@@ -1924,28 +2058,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;
|
|
|
|
|
|
|
|
|
|
@@ -2217,6 +2333,167 @@ int link(const char *oldpath, const char *newpath)
|
|
|
|
|
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) {
|
|
|
|
|
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, 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_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;
|
|
|
|
|
#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
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
@@ -2406,6 +2683,15 @@ static void setup_windows_environment(void)
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
if (!getenv("LC_ALL") && !getenv("LC_CTYPE") && !getenv("LANG"))
|
|
|
|
|
setenv("LC_CTYPE", "C", 1);
|
|
|
|
|
}
|
|
|
|
|
@@ -2556,6 +2842,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>
|
|
|
|
|
@@ -2588,6 +2892,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]);
|
|
|
|
|
@@ -2617,6 +2922,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;
|
|
|
|
|
|