]> git.proxmox.com Git - libgit2.git/blobdiff - src/win32/posix_w32.c
New upstream version 1.0.0+dfsg.1
[libgit2.git] / src / win32 / posix_w32.c
index 28cd3e2e9ef9f61a0fc60a6b9ccd998000c2d145..cacf986e8b56755a4e7e1ac8bf73520ee28f6a12 100644 (file)
@@ -4,8 +4,11 @@
  * 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
@@ -41,8 +43,9 @@
  */
 #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)
 {
@@ -165,14 +168,17 @@ GIT_INLINE(bool) last_error_retryable(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;                                                   \
@@ -191,7 +197,7 @@ static int ensure_writable(wchar_t *path)
        if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY)))
                goto on_error;
 
-       return 0;
+       return GIT_RETRY;
 
 on_error:
        set_errno();
@@ -204,7 +210,7 @@ on_error:
  * 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;
@@ -245,9 +251,25 @@ int p_link(const char *old, const char *new)
 
 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;
 
@@ -353,7 +375,7 @@ static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
        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);
 }
@@ -368,44 +390,6 @@ int p_lstat_posixly(const char *filename, struct stat *buf)
        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;
@@ -430,91 +414,151 @@ int p_readlink(const char *path, char *buf, size_t bufsiz)
        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);
 }
 
@@ -523,6 +567,73 @@ int p_creat(const char *path, mode_t mode)
        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;
@@ -546,40 +657,13 @@ int p_getcwd(char *buffer_out, size_t size)
        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. */
@@ -590,14 +674,14 @@ static int getfinalpath_w(
                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)
@@ -780,7 +864,7 @@ int p_mkstemp(char *tmp_path)
                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)
@@ -819,7 +903,7 @@ int p_rename(const char *from, const char *to)
 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);
 }
@@ -827,7 +911,7 @@ int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int 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);
 }