/* * Copyright (C) the libgit2 contributors. All rights reserved. * * 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 "../posix.h" #include "../fileops.h" #include "path.h" #include "path_w32.h" #include "utf-conv.h" #include "repository.h" #include "reparse.h" #include "global.h" #include "buffer.h" #include #include #include #include #ifndef FILE_NAME_NORMALIZED # define FILE_NAME_NORMALIZED 0 #endif #ifndef IO_REPARSE_TAG_SYMLINK #define IO_REPARSE_TAG_SYMLINK (0xA000000CL) #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 * so we simply remove them. */ #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) { switch (GetLastError()) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: case ERROR_INVALID_DRIVE: case ERROR_NO_MORE_FILES: case ERROR_BAD_NETPATH: case ERROR_BAD_NET_NAME: case ERROR_BAD_PATHNAME: case ERROR_FILENAME_EXCED_RANGE: errno = ENOENT; break; case ERROR_BAD_ENVIRONMENT: errno = E2BIG; break; case ERROR_BAD_FORMAT: case ERROR_INVALID_STARTING_CODESEG: case ERROR_INVALID_STACKSEG: case ERROR_INVALID_MODULETYPE: case ERROR_INVALID_EXE_SIGNATURE: case ERROR_EXE_MARKED_INVALID: case ERROR_BAD_EXE_FORMAT: case ERROR_ITERATED_DATA_EXCEEDS_64k: case ERROR_INVALID_MINALLOCSIZE: case ERROR_DYNLINK_FROM_INVALID_RING: case ERROR_IOPL_NOT_ENABLED: case ERROR_INVALID_SEGDPL: case ERROR_AUTODATASEG_EXCEEDS_64k: case ERROR_RING2SEG_MUST_BE_MOVABLE: case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: case ERROR_INFLOOP_IN_RELOC_CHAIN: errno = ENOEXEC; break; case ERROR_INVALID_HANDLE: case ERROR_INVALID_TARGET_HANDLE: case ERROR_DIRECT_ACCESS_HANDLE: errno = EBADF; break; case ERROR_WAIT_NO_CHILDREN: case ERROR_CHILD_NOT_COMPLETE: errno = ECHILD; break; case ERROR_NO_PROC_SLOTS: case ERROR_MAX_THRDS_REACHED: case ERROR_NESTING_NOT_ALLOWED: errno = EAGAIN; break; case ERROR_ARENA_TRASHED: case ERROR_NOT_ENOUGH_MEMORY: case ERROR_INVALID_BLOCK: case ERROR_NOT_ENOUGH_QUOTA: errno = ENOMEM; break; case ERROR_ACCESS_DENIED: case ERROR_CURRENT_DIRECTORY: case ERROR_WRITE_PROTECT: case ERROR_BAD_UNIT: case ERROR_NOT_READY: case ERROR_BAD_COMMAND: case ERROR_CRC: case ERROR_BAD_LENGTH: case ERROR_SEEK: case ERROR_NOT_DOS_DISK: case ERROR_SECTOR_NOT_FOUND: case ERROR_OUT_OF_PAPER: case ERROR_WRITE_FAULT: case ERROR_READ_FAULT: case ERROR_GEN_FAILURE: case ERROR_SHARING_VIOLATION: case ERROR_LOCK_VIOLATION: case ERROR_WRONG_DISK: case ERROR_SHARING_BUFFER_EXCEEDED: case ERROR_NETWORK_ACCESS_DENIED: case ERROR_CANNOT_MAKE: case ERROR_FAIL_I24: case ERROR_DRIVE_LOCKED: case ERROR_SEEK_ON_DEVICE: case ERROR_NOT_LOCKED: case ERROR_LOCK_FAILED: errno = EACCES; break; case ERROR_FILE_EXISTS: case ERROR_ALREADY_EXISTS: errno = EEXIST; break; case ERROR_NOT_SAME_DEVICE: errno = EXDEV; break; case ERROR_INVALID_FUNCTION: case ERROR_INVALID_ACCESS: case ERROR_INVALID_DATA: case ERROR_INVALID_PARAMETER: case ERROR_NEGATIVE_SEEK: errno = EINVAL; break; case ERROR_TOO_MANY_OPEN_FILES: errno = EMFILE; break; case ERROR_DISK_FULL: errno = ENOSPC; break; case ERROR_BROKEN_PIPE: errno = EPIPE; break; case ERROR_DIR_NOT_EMPTY: errno = ENOTEMPTY; break; default: errno = EINVAL; } } GIT_INLINE(bool) last_error_retryable(void) { int os_error = GetLastError(); return (os_error == ERROR_SHARING_VIOLATION || os_error == ERROR_ACCESS_DENIED); } #define do_with_retries(fn, remediation) \ do { \ int __tries, __ret; \ for (__tries = 0; __tries < git_win32__retries; __tries++) { \ if (__tries && (__ret = (remediation)) != 0) \ return __ret; \ if ((__ret = (fn)) != GIT_RETRY) \ return __ret; \ Sleep(5); \ } \ return -1; \ } while (0) \ static int ensure_writable(wchar_t *path) { DWORD attrs; if ((attrs = GetFileAttributesW(path)) == INVALID_FILE_ATTRIBUTES) goto on_error; if ((attrs & FILE_ATTRIBUTE_READONLY) == 0) return 0; if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY))) goto on_error; return 0; on_error: set_errno(); return -1; } /** * Truncate or extend file. * * 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) { if (size < 0) { errno = EINVAL; return -1; } #if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API) return ((_chsize_s(fd, size) == 0) ? 0 : -1); #else /* TODO MINGW32 Find a replacement for _chsize() that handles big files. */ if (size > INT32_MAX) { errno = EFBIG; return -1; } return _chsize(fd, (long)size); #endif } int p_mkdir(const char *path, mode_t mode) { git_win32_path buf; GIT_UNUSED(mode); if (git_win32_path_from_utf8(buf, path) < 0) return -1; return _wmkdir(buf); } int p_link(const char *old, const char *new) { GIT_UNUSED(old); GIT_UNUSED(new); errno = ENOSYS; return -1; } GIT_INLINE(int) unlink_once(const wchar_t *path) { if (DeleteFileW(path)) return 0; if (last_error_retryable()) return GIT_RETRY; set_errno(); return -1; } int p_unlink(const char *path) { git_win32_path wpath; if (git_win32_path_from_utf8(wpath, path) < 0) return -1; do_with_retries(unlink_once(wpath), ensure_writable(wpath)); } int p_fsync(int fd) { HANDLE fh = (HANDLE)_get_osfhandle(fd); p_fsync__cnt++; if (fh == INVALID_HANDLE_VALUE) { errno = EBADF; return -1; } if (!FlushFileBuffers(fh)) { DWORD code = GetLastError(); if (code == ERROR_INVALID_HANDLE) errno = EINVAL; else errno = EIO; return -1; } return 0; } #define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\') static int lstat_w( wchar_t *path, struct stat *buf, bool posix_enotdir) { WIN32_FILE_ATTRIBUTE_DATA fdata; if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) { if (!buf) return 0; return git_win32__file_attribute_to_stat(buf, &fdata, path); } switch (GetLastError()) { case ERROR_ACCESS_DENIED: errno = EACCES; break; default: errno = ENOENT; break; } /* To match POSIX behavior, set ENOTDIR when any of the folders in the * file path is a regular file, otherwise set ENOENT. */ if (errno == ENOENT && posix_enotdir) { size_t path_len = wcslen(path); /* scan up path until we find an existing item */ while (1) { DWORD attrs; /* remove last directory component */ for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--); if (path_len <= 0) break; path[path_len] = L'\0'; attrs = GetFileAttributesW(path); if (attrs != INVALID_FILE_ATTRIBUTES) { if (!(attrs & FILE_ATTRIBUTE_DIRECTORY)) errno = ENOTDIR; break; } } } return -1; } static int do_lstat(const char *path, struct stat *buf, bool posixly_correct) { git_win32_path path_w; int len; if ((len = git_win32_path_from_utf8(path_w, path)) < 0) return -1; git_win32__path_trim_end(path_w, len); return lstat_w(path_w, buf, posixly_correct); } int p_lstat(const char *filename, struct stat *buf) { return do_lstat(filename, buf, false); } int p_lstat_posixly(const char *filename, struct stat *buf) { return do_lstat(filename, buf, true); } int p_readlink(const char *path, char *buf, size_t bufsiz) { git_win32_path path_w, target_w; git_win32_utf8_path target; int len; /* readlink(2) does not NULL-terminate the string written * to the target buffer. Furthermore, the target buffer need * not be large enough to hold the entire result. A truncated * result should be written in this case. Since this truncation * could occur in the middle of the encoding of a code point, * we need to buffer the result on the stack. */ if (git_win32_path_from_utf8(path_w, path) < 0 || git_win32_path_readlink_w(target_w, path_w) < 0 || (len = git_win32_path_to_utf8(target, target_w)) < 0) return -1; bufsiz = min((size_t)len, bufsiz); memcpy(buf, target, 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); } 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: opts->access = GENERIC_WRITE; break; case O_RDWR: opts->access = GENERIC_READ | GENERIC_WRITE; break; default: opts->access = GENERIC_READ; break; } 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: opts->creation_disposition = CREATE_NEW; break; case O_CREAT | O_TRUNC: opts->creation_disposition = CREATE_ALWAYS; break; case O_TRUNC: opts->creation_disposition = TRUNCATE_EXISTING; break; case O_CREAT: opts->creation_disposition = OPEN_ALWAYS; break; default: opts->creation_disposition = OPEN_EXISTING; break; } 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, &opts), 0); } 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)) { giterr_set(GITERR_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; wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16); if (!cwd) return -1; /* Convert the working directory back to UTF-8 */ if (git__utf16_to_8(buffer_out, size, cwd) < 0) { DWORD code = GetLastError(); if (code == ERROR_INSUFFICIENT_BUFFER) errno = ERANGE; else errno = EINVAL; return -1; } 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. */ hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (INVALID_HANDLE_VALUE == hFile) return -1; /* Call GetFinalPathNameByHandle */ dwChars = pgfp(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); } static int follow_and_lstat_link(git_win32_path path, struct stat* buf) { git_win32_path target_w; if (getfinalpath_w(target_w, path) < 0) return -1; return lstat_w(target_w, buf, false); } int p_fstat(int fd, struct stat *buf) { BY_HANDLE_FILE_INFORMATION fhInfo; HANDLE fh = (HANDLE)_get_osfhandle(fd); if (fh == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(fh, &fhInfo)) { errno = EBADF; return -1; } git_win32__file_information_to_stat(buf, &fhInfo); return 0; } int p_stat(const char* path, struct stat* buf) { git_win32_path path_w; int len; if ((len = git_win32_path_from_utf8(path_w, path)) < 0 || lstat_w(path_w, buf, false) < 0) return -1; /* The item is a symbolic link or mount point. No need to iterate * to follow multiple links; use GetFinalPathNameFromHandle. */ if (S_ISLNK(buf->st_mode)) return follow_and_lstat_link(path_w, buf); return 0; } int p_chdir(const char* path) { git_win32_path buf; if (git_win32_path_from_utf8(buf, path) < 0) return -1; return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) { git_win32_path buf; if (git_win32_path_from_utf8(buf, path) < 0) return -1; return _wchmod(buf, mode); } int p_rmdir(const char* path) { git_win32_path buf; int error; if (git_win32_path_from_utf8(buf, path) < 0) return -1; error = _wrmdir(buf); if (error == -1) { switch (GetLastError()) { /* _wrmdir() is documented to return EACCES if "A program has an open * handle to the directory." This sounds like what everybody else calls * EBUSY. Let's convert appropriate error codes. */ case ERROR_SHARING_VIOLATION: errno = EBUSY; break; /* This error can be returned when trying to rmdir an extant file. */ case ERROR_DIRECTORY: errno = ENOTDIR; break; } } return error; } char *p_realpath(const char *orig_path, char *buffer) { git_win32_path orig_path_w, buffer_w; if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0) return NULL; /* Note that if the path provided is a relative path, then the current directory * is used to resolve the path -- which is a concurrency issue because the current * directory is a process-wide variable. */ if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) errno = ENAMETOOLONG; else errno = EINVAL; return NULL; } /* The path must exist. */ if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { errno = ENOENT; return NULL; } if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) { errno = ENOMEM; return NULL; } /* Convert the path to UTF-8. If the caller provided a buffer, then it * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't, * then we may overflow. */ if (git_win32_path_to_utf8(buffer, buffer_w) < 0) return NULL; git_path_mkposix(buffer); return buffer; } int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) { #if defined(_MSC_VER) int len; if (count == 0) return _vscprintf(format, argptr); #if _MSC_VER >= 1500 len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr); #else len = _vsnprintf(buffer, count, format, argptr); #endif if (len < 0) return _vscprintf(format, argptr); return len; #else /* MinGW */ return vsnprintf(buffer, count, format, argptr); #endif } int p_snprintf(char *buffer, size_t count, const char *format, ...) { va_list va; int r; va_start(va, format); r = p_vsnprintf(buffer, count, format, va); va_end(va); return r; } /* TODO: wut? */ int p_mkstemp(char *tmp_path) { #if defined(_MSC_VER) && _MSC_VER >= 1500 if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0) return -1; #else if (_mktemp(tmp_path) == NULL) return -1; #endif return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); //-V536 } int p_access(const char* path, mode_t mode) { git_win32_path buf; if (git_win32_path_from_utf8(buf, path) < 0) return -1; return _waccess(buf, mode & WIN32_MODE_MASK); } GIT_INLINE(int) rename_once(const wchar_t *from, const wchar_t *to) { if (MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) return 0; if (last_error_retryable()) return GIT_RETRY; set_errno(); return -1; } int p_rename(const char *from, const char *to) { git_win32_path wfrom, wto; if (git_win32_path_from_utf8(wfrom, from) < 0 || git_win32_path_from_utf8(wto, to) < 0) return -1; do_with_retries(rename_once(wfrom, wto), ensure_writable(wto)); } 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 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 send(socket, buffer, (int)length, flags); } /** * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that */ struct tm * p_localtime_r (const time_t *timer, struct tm *result) { struct tm *local_result; local_result = localtime (timer); if (local_result == NULL || result == NULL) return NULL; memcpy (result, local_result, sizeof (struct tm)); return result; } struct tm * p_gmtime_r (const time_t *timer, struct tm *result) { struct tm *local_result; local_result = gmtime (timer); if (local_result == NULL || result == NULL) return NULL; memcpy (result, local_result, sizeof (struct tm)); return result; } int p_inet_pton(int af, const char *src, void *dst) { struct sockaddr_storage sin; void *addr; int sin_len = sizeof(struct sockaddr_storage), addr_len; int error = 0; if (af == AF_INET) { addr = &((struct sockaddr_in *)&sin)->sin_addr; addr_len = sizeof(struct in_addr); } else if (af == AF_INET6) { addr = &((struct sockaddr_in6 *)&sin)->sin6_addr; addr_len = sizeof(struct in6_addr); } else { errno = EAFNOSUPPORT; return -1; } if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) { memcpy(dst, addr, addr_len); return 1; } switch(WSAGetLastError()) { case WSAEINVAL: return 0; case WSAEFAULT: errno = ENOSPC; return -1; case WSA_NOT_ENOUGH_MEMORY: errno = ENOMEM; return -1; } errno = EINVAL; return -1; }