зеркало из https://github.com/microsoft/git.git
Merge branch 'kblees/kb/symlinks'
This commit is contained in:
Коммит
c567092707
633
compat/mingw.c
633
compat/mingw.c
|
@ -4,6 +4,7 @@
|
|||
#include <sddl.h>
|
||||
#include <conio.h>
|
||||
#include <wchar.h>
|
||||
#include <winioctl.h>
|
||||
#include "../strbuf.h"
|
||||
#include "../run-command.h"
|
||||
#include "../cache.h"
|
||||
|
@ -16,8 +17,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;
|
||||
|
@ -93,6 +92,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;
|
||||
|
@ -107,6 +107,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;
|
||||
|
@ -127,6 +128,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;
|
||||
|
@ -194,15 +198,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;
|
||||
|
@ -224,6 +225,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,
|
||||
|
@ -275,6 +301,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 '%ls' relative to '%ls' (too large)",
|
||||
path, relative_to);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(out, relative_to, i * sizeof(wchar_t));
|
||||
wcscpy(out + i, path);
|
||||
return out;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
|
@ -302,7 +456,7 @@ 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;
|
||||
|
@ -310,26 +464,23 @@ int mingw_unlink(const char *pathname)
|
|||
if (DeleteFileW(wpathname))
|
||||
return 0;
|
||||
|
||||
/* read-only files cannot be removed */
|
||||
_wchmod(wpathname, 0666);
|
||||
while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
|
||||
do {
|
||||
/* read-only files cannot be removed */
|
||||
_wchmod(wpathname, 0666);
|
||||
if (!_wunlink(wpathname))
|
||||
return 0;
|
||||
if (!is_file_in_use_error(GetLastError()))
|
||||
break;
|
||||
/*
|
||||
* 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)
|
||||
|
@ -356,7 +507,7 @@ static int is_dir_empty(const wchar_t *wpath)
|
|||
|
||||
int mingw_rmdir(const char *pathname)
|
||||
{
|
||||
int ret, tries = 0;
|
||||
int tries = 0;
|
||||
wchar_t wpathname[MAX_LONG_PATH];
|
||||
struct stat st;
|
||||
|
||||
|
@ -382,7 +533,11 @@ int mingw_rmdir(const char *pathname)
|
|||
if (xutftowcs_long_path(wpathname, pathname) < 0)
|
||||
return -1;
|
||||
|
||||
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
|
||||
do {
|
||||
if (!_wrmdir(wpathname)) {
|
||||
invalidate_lstat_cache();
|
||||
return 0;
|
||||
}
|
||||
if (!is_file_in_use_error(GetLastError()))
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
if (errno != EACCES)
|
||||
|
@ -391,23 +546,9 @@ int mingw_rmdir(const char *pathname)
|
|||
errno = ENOTEMPTY;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* We assume that some other process had the source or
|
||||
* destination file open at the wrong moment and retry.
|
||||
* In order to give the other process a higher chance to
|
||||
* complete its operation, we give up our time slice now.
|
||||
* If we have to retry again, we do sleep a bit.
|
||||
*/
|
||||
Sleep(delay[tries]);
|
||||
tries++;
|
||||
}
|
||||
while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) &&
|
||||
ask_yes_no_if_possible("Deletion of directory '%s' failed. "
|
||||
"Should I try again?", pathname))
|
||||
ret = _wrmdir(wpathname);
|
||||
if (!ret)
|
||||
invalidate_lstat_cache();
|
||||
return ret;
|
||||
} while (retry_ask_yes_no(&tries, "Deletion of directory '%s' failed. "
|
||||
"Should I try again?", pathname));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline int needs_hiding(const char *path)
|
||||
|
@ -471,6 +612,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;
|
||||
|
@ -743,7 +886,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;
|
||||
}
|
||||
|
@ -789,53 +949,51 @@ 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)
|
||||
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
|
||||
char *tmpbuf, int *plen, DWORD *ptag);
|
||||
|
||||
int mingw_lstat(const char *file_name, struct stat *buf)
|
||||
{
|
||||
WIN32_FILE_ATTRIBUTE_DATA fdata;
|
||||
DWORD reparse_tag = 0;
|
||||
int link_len = 0;
|
||||
wchar_t wfilename[MAX_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, get the link tag and length */
|
||||
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
char tmpbuf[MAX_LONG_PATH];
|
||||
|
||||
if (readlink_1(wfilename, FALSE, tmpbuf, &link_len,
|
||||
&reparse_tag) < 0)
|
||||
return -1;
|
||||
}
|
||||
buf->st_ino = 0;
|
||||
buf->st_gid = 0;
|
||||
buf->st_uid = 0;
|
||||
buf->st_nlink = 1;
|
||||
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
|
||||
buf->st_size = fdata.nFileSizeLow |
|
||||
(((off_t)fdata.nFileSizeHigh)<<32);
|
||||
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
|
||||
reparse_tag);
|
||||
buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
|
||||
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
|
||||
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
|
||||
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
|
||||
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
|
||||
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
|
||||
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
WIN32_FIND_DATAW findbuf;
|
||||
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
|
||||
(findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
|
||||
if (follow) {
|
||||
char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
||||
} else {
|
||||
buf->st_mode = S_IFLNK;
|
||||
}
|
||||
buf->st_mode |= S_IREAD;
|
||||
if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
|
||||
buf->st_mode |= S_IWRITE;
|
||||
}
|
||||
FindClose(handle);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (GetLastError()) {
|
||||
case ERROR_ACCESS_DENIED:
|
||||
case ERROR_SHARING_VIOLATION:
|
||||
|
@ -862,39 +1020,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)
|
||||
|
@ -910,7 +1035,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 */
|
||||
|
@ -920,13 +1045,37 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int mingw_lstat(const char *file_name, struct stat *buf)
|
||||
{
|
||||
return do_stat_internal(0, file_name, buf);
|
||||
}
|
||||
int mingw_stat(const char *file_name, struct stat *buf)
|
||||
{
|
||||
return do_stat_internal(1, file_name, buf);
|
||||
wchar_t wfile_name[MAX_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) {
|
||||
DWORD err = GetLastError();
|
||||
|
||||
if (err == ERROR_ACCESS_DENIED &&
|
||||
!mingw_lstat(file_name, buf) &&
|
||||
!S_ISLNK(buf->st_mode))
|
||||
/*
|
||||
* POSIX semantics state to still try to fill
|
||||
* information, even if permission is denied to create
|
||||
* a file handle.
|
||||
*/
|
||||
return 0;
|
||||
|
||||
errno = err_win_to_posix(err);
|
||||
return -1;
|
||||
}
|
||||
result = get_file_info_by_handle(hnd, buf);
|
||||
CloseHandle(hnd);
|
||||
return result;
|
||||
}
|
||||
|
||||
int mingw_fstat(int fd, struct stat *buf)
|
||||
|
@ -2348,27 +2497,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);
|
||||
|
@ -2380,28 +2531,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;
|
||||
|
||||
|
@ -2678,6 +2811,185 @@ 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
|
||||
|
||||
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
|
||||
char *tmpbuf, int *plen, DWORD *ptag)
|
||||
{
|
||||
HANDLE handle;
|
||||
WCHAR *wbuf;
|
||||
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
||||
DWORD dummy;
|
||||
|
||||
/* read reparse point data */
|
||||
handle = CreateFileW(wpath, 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, b,
|
||||
MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dummy, NULL)) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
CloseHandle(handle);
|
||||
return -1;
|
||||
}
|
||||
CloseHandle(handle);
|
||||
|
||||
/* get target path for symlinks or mount points (aka 'junctions') */
|
||||
switch ((*ptag = b->ReparseTag)) {
|
||||
case IO_REPARSE_TAG_SYMLINK:
|
||||
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
|
||||
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
|
||||
*(WCHAR*) (((char*) wbuf)
|
||||
+ b->SymbolicLinkReparseBuffer.SubstituteNameLength) = 0;
|
||||
break;
|
||||
case IO_REPARSE_TAG_MOUNT_POINT:
|
||||
wbuf = (WCHAR*) (((char*) b->MountPointReparseBuffer.PathBuffer)
|
||||
+ b->MountPointReparseBuffer.SubstituteNameOffset);
|
||||
*(WCHAR*) (((char*) wbuf)
|
||||
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
|
||||
break;
|
||||
default:
|
||||
if (fail_on_unknown_tag) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
} else {
|
||||
*plen = MAX_LONG_PATH;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((*plen =
|
||||
xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readlink(const char *path, char *buf, size_t bufsiz)
|
||||
{
|
||||
WCHAR wpath[MAX_LONG_PATH];
|
||||
char tmpbuf[MAX_LONG_PATH];
|
||||
int len;
|
||||
DWORD tag;
|
||||
|
||||
if (xutftowcs_long_path(wpath, path) < 0)
|
||||
return -1;
|
||||
|
||||
if (readlink_1(wpath, TRUE, tmpbuf, &len, &tag) < 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
|
||||
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
|
||||
* condition. There is no conversion function that produces invalid UTF-8,
|
||||
* so convert to a (hopefully large enough) temporary buffer, then memcpy
|
||||
* the requested number of bytes (including '\0' for robustness).
|
||||
*/
|
||||
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
|
||||
return min(bufsiz, len);
|
||||
}
|
||||
|
||||
pid_t waitpid(pid_t pid, int *status, int options)
|
||||
{
|
||||
HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
|
||||
|
@ -2986,6 +3298,15 @@ static void setup_windows_environment(void)
|
|||
|
||||
if (!getenv("LC_ALL") && !getenv("LC_CTYPE") && !getenv("LANG"))
|
||||
setenv("LC_CTYPE", "C.UTF-8", 1);
|
||||
|
||||
/*
|
||||
* Change 'core.symlinks' default to false, unless native symlinks are
|
||||
* enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can
|
||||
* run the test suite (which doesn't obey config files) with or without
|
||||
* symlink support.
|
||||
*/
|
||||
if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict"))
|
||||
has_symlinks = 0;
|
||||
}
|
||||
|
||||
static PSID get_current_user_sid(void)
|
||||
|
@ -3372,6 +3693,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>
|
||||
|
@ -3406,6 +3745,7 @@ int wmain(int argc, const wchar_t **wargv)
|
|||
#endif
|
||||
|
||||
maybe_redirect_std_handles();
|
||||
adjust_symlink_flags();
|
||||
fsync_object_files = 1;
|
||||
|
||||
/* determine size of argv and environ conversion buffer */
|
||||
|
@ -3436,6 +3776,7 @@ int wmain(int argc, const wchar_t **wargv)
|
|||
|
||||
/* initialize critical section for waitpid pinfo_t list */
|
||||
InitializeCriticalSection(&pinfo_cs);
|
||||
InitializeCriticalSection(&phantom_symlinks_cs);
|
||||
|
||||
/* initialize critical section for fscache */
|
||||
InitializeCriticalSection(&fscache_cs);
|
||||
|
|
|
@ -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
|
||||
|
@ -217,6 +213,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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -18,7 +18,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;
|
||||
|
|
|
@ -200,10 +200,13 @@ static struct fsentry *fseentry_create_entry(struct fscache *cache,
|
|||
fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ?
|
||||
fdata->EaSize : 0;
|
||||
|
||||
fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes);
|
||||
fse->dirent.d_type = S_ISDIR(fse->st_mode) ? DT_DIR : DT_REG;
|
||||
fse->u.s.st_size = fdata->EndOfFile.LowPart |
|
||||
(((off_t)fdata->EndOfFile.HighPart) << 32);
|
||||
fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes,
|
||||
fdata->EaSize);
|
||||
fse->dirent.d_type = S_ISREG(fse->st_mode) ? DT_REG :
|
||||
S_ISDIR(fse->st_mode) ? DT_DIR : DT_LNK;
|
||||
fse->u.s.st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH :
|
||||
fdata->EndOfFile.LowPart |
|
||||
(((off_t)fdata->EndOfFile.HighPart) << 32);
|
||||
filetime_to_timespec((FILETIME *)&(fdata->LastAccessTime),
|
||||
&(fse->u.s.st_atim));
|
||||
filetime_to_timespec((FILETIME *)&(fdata->LastWriteTime),
|
||||
|
@ -579,6 +582,18 @@ int fscache_lstat(const char *filename, struct stat *st)
|
|||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Special case symbolic links: FindFirstFile()/FindNextFile() did not
|
||||
* provide us with the length of the target path.
|
||||
*/
|
||||
if (fse->u.s.st_size == MAX_LONG_PATH && S_ISLNK(fse->st_mode)) {
|
||||
char buf[MAX_LONG_PATH];
|
||||
int len = readlink(filename, buf, sizeof(buf) - 1);
|
||||
|
||||
if (len > 0)
|
||||
fse->u.s.st_size = len;
|
||||
}
|
||||
|
||||
/* copy stat data */
|
||||
st->st_ino = 0;
|
||||
st->st_gid = 0;
|
||||
|
|
|
@ -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);
|
||||
|
|
11
read-cache.c
11
read-cache.c
|
@ -479,6 +479,17 @@ int ie_modified(struct index_state *istate,
|
|||
* then we know it is.
|
||||
*/
|
||||
if ((changed & DATA_CHANGED) &&
|
||||
#ifdef GIT_WINDOWS_NATIVE
|
||||
/*
|
||||
* Work around Git for Windows v2.27.0 fixing a bug where symlinks'
|
||||
* target path lengths were not read at all, and instead recorded
|
||||
* as 4096: now, all symlinks would appear as modified.
|
||||
*
|
||||
* So let's just special-case symlinks with a target path length
|
||||
* (i.e. `sd_size`) of 4096 and force them to be re-checked.
|
||||
*/
|
||||
(!S_ISLNK(st->st_mode) || ce->ce_stat_data.sd_size != MAX_LONG_PATH) &&
|
||||
#endif
|
||||
(S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0))
|
||||
return changed;
|
||||
|
||||
|
|
10
strbuf.c
10
strbuf.c
|
@ -559,8 +559,6 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f)
|
|||
return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0;
|
||||
}
|
||||
|
||||
#define STRBUF_MAXLINK (2*PATH_MAX)
|
||||
|
||||
int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)
|
||||
{
|
||||
size_t oldalloc = sb->alloc;
|
||||
|
@ -568,15 +566,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;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче