Merge pull request #156 from kblees/kb/symlinks

Symlink support
This commit is contained in:
Johannes Schindelin
2018-06-08 13:42:24 +02:00
committed by Jameson Miller
10 changed files with 573 additions and 205 deletions

View File

@@ -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 = &current->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;

View File

@@ -123,10 +123,6 @@ struct utsname {
* trivial stubs
*/
static inline int readlink(const char *path, char *buf, size_t bufsiz)
{ errno = ENOSYS; return -1; }
static inline int symlink(const char *oldpath, const char *newpath)
{ errno = ENOSYS; return -1; }
static inline int fchmod(int fildes, mode_t mode)
{ errno = ENOSYS; return -1; }
#ifndef __MINGW64_VERSION_MAJOR
@@ -218,6 +214,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
@@ -450,6 +448,8 @@ static inline void convert_slashes(char *path)
int mingw_offset_1st_component(const char *path);
#define offset_1st_component mingw_offset_1st_component
#define PATH_SEP ';'
extern char *mingw_query_user_email(void);
#define query_user_email mingw_query_user_email
#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
#define PRIuMAX "I64u"
#define PRId64 "I64d"

View File

@@ -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;

View File

@@ -16,7 +16,10 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3);
/* 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;

View File

@@ -149,9 +149,10 @@ static struct fsentry *fseentry_create_entry(struct fsentry *list,
fse = fsentry_alloc(list, buf, len);
fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes);
fse->st_size = (((off64_t) (fdata->nFileSizeHigh)) << 32)
| fdata->nFileSizeLow;
fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes,
fdata->dwReserved0);
fse->st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH :
fdata->nFileSizeLow | (((off_t) fdata->nFileSizeHigh) << 32);
fse->st_atime = filetime_to_time_t(&(fdata->ftLastAccessTime));
fse->st_mtime = filetime_to_time_t(&(fdata->ftLastWriteTime));
fse->st_ctime = filetime_to_time_t(&(fdata->ftCreationTime));
@@ -442,7 +443,8 @@ static struct dirent *fscache_readdir(DIR *base_dir)
if (!next)
return NULL;
dir->pfsentry = next;
dir->dirent.d_type = S_ISDIR(next->st_mode) ? DT_DIR : DT_REG;
dir->dirent.d_type = S_ISREG(next->st_mode) ? DT_REG :
S_ISDIR(next->st_mode) ? DT_DIR : DT_LNK;
dir->dirent.d_name = (char*) next->name;
return &(dir->dirent);
}

View File

@@ -7,6 +7,7 @@
#include <wingdi.h>
#include <winreg.h>
#include "win32.h"
#include "win32/lazyload.h"
static int fd_is_interactive[3] = { 0, 0, 0 };
#define FD_CONSOLE 0x1
@@ -41,26 +42,21 @@ typedef struct _CONSOLE_FONT_INFOEX {
#endif
#endif
typedef BOOL (WINAPI *PGETCURRENTCONSOLEFONTEX)(HANDLE, BOOL,
PCONSOLE_FONT_INFOEX);
static void warn_if_raster_font(void)
{
DWORD fontFamily = 0;
PGETCURRENTCONSOLEFONTEX pGetCurrentConsoleFontEx;
DECLARE_PROC_ADDR(kernel32.dll, BOOL, GetCurrentConsoleFontEx,
HANDLE, BOOL, PCONSOLE_FONT_INFOEX);
/* don't bother if output was ascii only */
if (!non_ascii_used)
return;
/* GetCurrentConsoleFontEx is available since Vista */
pGetCurrentConsoleFontEx = (PGETCURRENTCONSOLEFONTEX) GetProcAddress(
GetModuleHandle("kernel32.dll"),
"GetCurrentConsoleFontEx");
if (pGetCurrentConsoleFontEx) {
if (INIT_PROC_ADDR(GetCurrentConsoleFontEx)) {
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof(cfi);
if (pGetCurrentConsoleFontEx(console, 0, &cfi))
if (GetCurrentConsoleFontEx(console, 0, &cfi))
fontFamily = cfi.FontFamily;
} else {
/* pre-Vista: check default console font in registry */

View File

@@ -392,6 +392,10 @@ static inline char *git_find_last_dir_sep(const char *path)
#define find_last_dir_sep git_find_last_dir_sep
#endif
#ifndef query_user_email
#define query_user_email() NULL
#endif
#if defined(__HP_cc) && (__HP_cc >= 61000)
#define NORETURN __attribute__((noreturn))
#define NORETURN_PTR

View File

@@ -168,6 +168,9 @@ const char *ident_default_email(void)
strbuf_addstr(&git_default_email, email);
committer_ident_explicitly_given |= IDENT_MAIL_GIVEN;
author_ident_explicitly_given |= IDENT_MAIL_GIVEN;
} else if ((email = query_user_email()) && email[0]) {
strbuf_addstr(&git_default_email, email);
free((char *)email);
} else
copy_email(xgetpwuid_self(&default_email_is_bogus),
&git_default_email, &default_email_is_bogus);

View File

@@ -17,14 +17,14 @@ static void trim_last_path_component(struct strbuf *path)
int i = path->len;
/* back up past trailing slashes, if any */
while (i && path->buf[i - 1] == '/')
while (i && is_dir_sep(path->buf[i - 1]))
i--;
/*
* then go backwards until a slash, or the beginning of the
* string
*/
while (i && path->buf[i - 1] != '/')
while (i && !is_dir_sep(path->buf[i - 1]))
i--;
strbuf_setlen(path, i);

View File

@@ -459,8 +459,6 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f)
}
#define STRBUF_MAXLINK (2*PATH_MAX)
int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
{
size_t oldalloc = sb->alloc;
@@ -468,15 +466,15 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
if (hint < 32)
hint = 32;
while (hint < STRBUF_MAXLINK) {
for (;;) {
ssize_t len;
strbuf_grow(sb, hint);
len = readlink(path, sb->buf, hint);
strbuf_grow(sb, hint + 1);
len = readlink(path, sb->buf, hint + 1);
if (len < 0) {
if (errno != ERANGE)
break;
} else if (len < hint) {
} else if (len <= hint) {
strbuf_setlen(sb, len);
return 0;
}