* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
+
+#include "common.h"
+
#include "../posix.h"
-#include "../fileops.h"
+#include "../futils.h"
#include "path.h"
#include "path_w32.h"
#include "utf-conv.h"
#define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
#endif
-/* Options which we always provide to _wopen.
- *
- * _O_BINARY - Raw access; no translation of CR or LF characters
- * _O_NOINHERIT - Do not mark the created handle as inheritable by child processes.
- * The Windows default is 'not inheritable', but the CRT's default (following
- * POSIX convention) is 'inheritable'. We have no desire for our handles to be
- * inheritable on Windows, so specify the flag to get default behavior back. */
-#define STANDARD_OPEN_FLAGS (_O_BINARY | _O_NOINHERIT)
+#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
+# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02
+#endif
+
+#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY
+# define SYMBOLIC_LINK_FLAG_DIRECTORY 0x01
+#endif
/* Allowable mode bits on Win32. Using mode bits that are not supported on
* Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it
*/
#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE)
-/* GetFinalPathNameByHandleW signature */
-typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD);
+unsigned long git_win32__createfile_sharemode =
+ FILE_SHARE_READ | FILE_SHARE_WRITE;
+int git_win32__retries = 10;
GIT_INLINE(void) set_errno(void)
{
os_error == ERROR_ACCESS_DENIED);
}
-#define do_with_retries(fn, cleanup) \
+#define do_with_retries(fn, remediation) \
do { \
- int __tries, __ret; \
- for (__tries = 0; __tries < 10; __tries++) { \
- if (__tries && (__ret = (cleanup)) != 0) \
- return __ret; \
+ int __retry, __ret; \
+ for (__retry = git_win32__retries; __retry; __retry--) { \
if ((__ret = (fn)) != GIT_RETRY) \
return __ret; \
+ if (__retry > 1 && (__ret = (remediation)) != 0) { \
+ if (__ret == GIT_RETRY) \
+ continue; \
+ return __ret; \
+ } \
Sleep(5); \
} \
return -1; \
if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY)))
goto on_error;
- return 0;
+ return GIT_RETRY;
on_error:
set_errno();
* We now take a "git_off_t" rather than "long" because
* files may be longer than 2Gb.
*/
-int p_ftruncate(int fd, git_off_t size)
+int p_ftruncate(int fd, off64_t size)
{
if (size < 0) {
errno = EINVAL;
GIT_INLINE(int) unlink_once(const wchar_t *path)
{
+ DWORD error;
+
if (DeleteFileW(path))
return 0;
+ if ((error = GetLastError()) == ERROR_ACCESS_DENIED) {
+ WIN32_FILE_ATTRIBUTE_DATA fdata;
+ if (!GetFileAttributesExW(path, GetFileExInfoStandard, &fdata) ||
+ !(fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
+ !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ goto out;
+
+ if (RemoveDirectoryW(path))
+ return 0;
+ }
+
+out:
+ SetLastError(error);
+
if (last_error_retryable())
return GIT_RETRY;
if ((len = git_win32_path_from_utf8(path_w, path)) < 0)
return -1;
- git_win32__path_trim_end(path_w, len);
+ git_win32_path_trim_end(path_w, len);
return lstat_w(path_w, buf, posixly_correct);
}
return do_lstat(filename, buf, true);
}
-int p_utimes(const char *filename, const struct p_timeval times[2])
-{
- int fd, error;
-
- if ((fd = p_open(filename, O_RDWR)) < 0)
- return fd;
-
- error = p_futimes(fd, times);
-
- close(fd);
- return error;
-}
-
-int p_futimes(int fd, const struct p_timeval times[2])
-{
- HANDLE handle;
- FILETIME atime = {0}, mtime = {0};
-
- if (times == NULL) {
- SYSTEMTIME st;
-
- GetSystemTime(&st);
- SystemTimeToFileTime(&st, &atime);
- SystemTimeToFileTime(&st, &mtime);
- } else {
- git_win32__timeval_to_filetime(&atime, times[0]);
- git_win32__timeval_to_filetime(&mtime, times[1]);
- }
-
- if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE)
- return -1;
-
- if (SetFileTime(handle, NULL, &atime, &mtime) == 0)
- return -1;
-
- return 0;
-}
-
int p_readlink(const char *path, char *buf, size_t bufsiz)
{
git_win32_path path_w, target_w;
return (int)bufsiz;
}
-int p_symlink(const char *old, const char *new)
-{
- /* Real symlinks on NTFS require admin privileges. Until this changes,
- * libgit2 just creates a text file with the link target in the contents.
- */
- return git_futils_fake_symlink(old, new);
-}
-
-GIT_INLINE(int) open_once(
- const wchar_t *path,
- DWORD access,
- DWORD sharing,
- DWORD creation_disposition,
- DWORD attributes,
- int osf_flags)
+static bool target_is_dir(const char *target, const char *path)
{
- HANDLE handle = CreateFileW(path, access, sharing, NULL,
- creation_disposition, attributes, 0);
+ git_buf resolved = GIT_BUF_INIT;
+ git_win32_path resolved_w;
+ bool isdir = true;
- if (handle == INVALID_HANDLE_VALUE) {
- if (last_error_retryable())
- return GIT_RETRY;
+ if (git_path_is_absolute(target))
+ git_win32_path_from_utf8(resolved_w, target);
+ else if (git_path_dirname_r(&resolved, path) < 0 ||
+ git_path_apply_relative(&resolved, target) < 0 ||
+ git_win32_path_from_utf8(resolved_w, resolved.ptr) < 0)
+ goto out;
- set_errno();
- return -1;
- }
+ isdir = GetFileAttributesW(resolved_w) & FILE_ATTRIBUTE_DIRECTORY;
- return _open_osfhandle((intptr_t)handle, osf_flags);
+out:
+ git_buf_dispose(&resolved);
+ return isdir;
}
-int p_open(const char *path, int flags, ...)
+int p_symlink(const char *target, const char *path)
{
- git_win32_path wpath;
- mode_t mode = 0;
- DWORD access, sharing, creation, attributes, osf_flags;
+ git_win32_path target_w, path_w;
+ DWORD dwFlags;
- if (git_win32_path_from_utf8(wpath, path) < 0)
+ /*
+ * Convert both target and path to Windows-style paths. Note that we do
+ * not want to use `git_win32_path_from_utf8` for converting the target,
+ * as that function will automatically pre-pend the current working
+ * directory in case the path is not absolute. As Git will instead use
+ * relative symlinks, this is not someting we want.
+ */
+ if (git_win32_path_from_utf8(path_w, path) < 0 ||
+ git_win32_path_relative_from_utf8(target_w, target) < 0)
return -1;
- if (flags & O_CREAT) {
- va_list arg_list;
+ dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+ if (target_is_dir(target, path))
+ dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
- va_start(arg_list, flags);
- mode = (mode_t)va_arg(arg_list, int);
- va_end(arg_list);
- }
+ if (!CreateSymbolicLinkW(path_w, target_w, dwFlags))
+ return -1;
+
+ return 0;
+}
+
+struct open_opts {
+ DWORD access;
+ DWORD sharing;
+ SECURITY_ATTRIBUTES security;
+ DWORD creation_disposition;
+ DWORD attributes;
+ int osf_flags;
+};
+
+GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode)
+{
+ memset(opts, 0, sizeof(struct open_opts));
switch (flags & (O_WRONLY | O_RDWR)) {
case O_WRONLY:
- access = GENERIC_WRITE;
+ opts->access = GENERIC_WRITE;
break;
case O_RDWR:
- access = GENERIC_READ | GENERIC_WRITE;
+ opts->access = GENERIC_READ | GENERIC_WRITE;
break;
default:
- access = GENERIC_READ;
+ opts->access = GENERIC_READ;
break;
}
- sharing = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ opts->sharing = (DWORD)git_win32__createfile_sharemode;
switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) {
case O_CREAT | O_EXCL:
case O_CREAT | O_TRUNC | O_EXCL:
- creation = CREATE_NEW;
+ opts->creation_disposition = CREATE_NEW;
break;
case O_CREAT | O_TRUNC:
- creation = CREATE_ALWAYS;
+ opts->creation_disposition = CREATE_ALWAYS;
break;
case O_TRUNC:
- creation = TRUNCATE_EXISTING;
+ opts->creation_disposition = TRUNCATE_EXISTING;
break;
case O_CREAT:
- creation = OPEN_ALWAYS;
+ opts->creation_disposition = OPEN_ALWAYS;
break;
default:
- creation = OPEN_EXISTING;
+ opts->creation_disposition = OPEN_EXISTING;
break;
}
- attributes = (mode & S_IWRITE) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_READONLY;
- osf_flags = flags & (O_RDONLY | O_APPEND);
+ opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ?
+ FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL;
+ opts->osf_flags = flags & (O_RDONLY | O_APPEND);
+
+ opts->security.nLength = sizeof(SECURITY_ATTRIBUTES);
+ opts->security.lpSecurityDescriptor = NULL;
+ opts->security.bInheritHandle = 0;
+}
+
+GIT_INLINE(int) open_once(
+ const wchar_t *path,
+ struct open_opts *opts)
+{
+ int fd;
+
+ HANDLE handle = CreateFileW(path, opts->access, opts->sharing,
+ &opts->security, opts->creation_disposition, opts->attributes, 0);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ if (last_error_retryable())
+ return GIT_RETRY;
+
+ set_errno();
+ return -1;
+ }
+
+ if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0)
+ CloseHandle(handle);
+
+ return fd;
+}
+
+int p_open(const char *path, int flags, ...)
+{
+ git_win32_path wpath;
+ mode_t mode = 0;
+ struct open_opts opts = {0};
+
+ if (git_win32_path_from_utf8(wpath, path) < 0)
+ return -1;
+
+ if (flags & O_CREAT) {
+ va_list arg_list;
+
+ va_start(arg_list, flags);
+ mode = (mode_t)va_arg(arg_list, int);
+ va_end(arg_list);
+ }
+
+ open_opts_from_posix(&opts, flags, mode);
do_with_retries(
- open_once(wpath, access, sharing, creation, attributes, osf_flags),
+ open_once(wpath, &opts),
0);
}
return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
}
+int p_utimes(const char *path, const struct p_timeval times[2])
+{
+ git_win32_path wpath;
+ int fd, error;
+ DWORD attrs_orig, attrs_new = 0;
+ struct open_opts opts = { 0 };
+
+ if (git_win32_path_from_utf8(wpath, path) < 0)
+ return -1;
+
+ attrs_orig = GetFileAttributesW(wpath);
+
+ if (attrs_orig & FILE_ATTRIBUTE_READONLY) {
+ attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY;
+
+ if (!SetFileAttributesW(wpath, attrs_new)) {
+ git_error_set(GIT_ERROR_OS, "failed to set attributes");
+ return -1;
+ }
+ }
+
+ open_opts_from_posix(&opts, O_RDWR, 0);
+
+ if ((fd = open_once(wpath, &opts)) < 0) {
+ error = -1;
+ goto done;
+ }
+
+ error = p_futimes(fd, times);
+ close(fd);
+
+done:
+ if (attrs_orig != attrs_new) {
+ DWORD os_error = GetLastError();
+ SetFileAttributesW(wpath, attrs_orig);
+ SetLastError(os_error);
+ }
+
+ return error;
+}
+
+int p_futimes(int fd, const struct p_timeval times[2])
+{
+ HANDLE handle;
+ FILETIME atime = { 0 }, mtime = { 0 };
+
+ if (times == NULL) {
+ SYSTEMTIME st;
+
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &atime);
+ SystemTimeToFileTime(&st, &mtime);
+ }
+ else {
+ git_win32__timeval_to_filetime(&atime, times[0]);
+ git_win32__timeval_to_filetime(&mtime, times[1]);
+ }
+
+ if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE)
+ return -1;
+
+ if (SetFileTime(handle, NULL, &atime, &mtime) == 0)
+ return -1;
+
+ return 0;
+}
+
int p_getcwd(char *buffer_out, size_t size)
{
git_win32_path buf;
return 0;
}
-/*
- * Returns the address of the GetFinalPathNameByHandleW function.
- * This function is available on Windows Vista and higher.
- */
-static PFGetFinalPathNameByHandleW get_fpnbyhandle(void)
-{
- static PFGetFinalPathNameByHandleW pFunc = NULL;
- PFGetFinalPathNameByHandleW toReturn = pFunc;
-
- if (!toReturn) {
- HMODULE hModule = GetModuleHandleW(L"kernel32");
-
- if (hModule)
- toReturn = (PFGetFinalPathNameByHandleW)GetProcAddress(hModule, "GetFinalPathNameByHandleW");
-
- pFunc = toReturn;
- }
-
- assert(toReturn);
-
- return toReturn;
-}
-
static int getfinalpath_w(
git_win32_path dest,
const wchar_t *path)
{
- PFGetFinalPathNameByHandleW pgfp = get_fpnbyhandle();
HANDLE hFile;
DWORD dwChars;
- if (!pgfp)
- return -1;
-
/* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
* specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
* target of the link. */
return -1;
/* Call GetFinalPathNameByHandle */
- dwChars = pgfp(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
+ dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
CloseHandle(hFile);
if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)
return -1;
- /* The path may be delivered to us with a prefix; canonicalize */
- return (int)git_win32__canonicalize_path(dest, dwChars);
+ /* The path may be delivered to us with a namespace prefix; remove */
+ return (int)git_win32_path_remove_namespace(dest, dwChars);
}
static int follow_and_lstat_link(git_win32_path path, struct stat* buf)
return -1;
#endif
- return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); //-V536
+ return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); /* -V536 */
}
int p_access(const char* path, mode_t mode)
int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags)
{
if ((size_t)((int)length) != length)
- return -1; /* giterr_set will be done by caller */
+ return -1; /* git_error_set will be done by caller */
return recv(socket, buffer, (int)length, flags);
}
int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags)
{
if ((size_t)((int)length) != length)
- return -1; /* giterr_set will be done by caller */
+ return -1; /* git_error_set will be done by caller */
return send(socket, buffer, (int)length, flags);
}