mirror of
https://git.proxmox.com/git/libgit2
synced 2026-01-04 19:28:32 +00:00
Handle win32 reparse points properly
This commit is contained in:
parent
c2c8161541
commit
65477db166
@ -30,7 +30,7 @@ GIT_INLINE(int) p_link(const char *old, const char *new)
|
||||
extern int p_mkdir(const char *path, mode_t mode);
|
||||
extern int p_unlink(const char *path);
|
||||
extern int p_lstat(const char *file_name, struct stat *buf);
|
||||
extern int p_readlink(const char *link, char *target, size_t target_len);
|
||||
extern int p_readlink(const char *path, char *buf, size_t bufsiz);
|
||||
extern int p_symlink(const char *old, const char *new);
|
||||
extern char *p_realpath(const char *orig_path, char *buffer);
|
||||
extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr);
|
||||
|
||||
@ -9,12 +9,13 @@
|
||||
#include "path.h"
|
||||
#include "utf-conv.h"
|
||||
#include "repository.h"
|
||||
#include "reparse.h"
|
||||
#include <errno.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include <ws2tcpip.h>
|
||||
|
||||
#if defined(__MINGW32__)
|
||||
#ifndef FILE_NAME_NORMALIZED
|
||||
# define FILE_NAME_NORMALIZED 0
|
||||
#endif
|
||||
|
||||
@ -100,29 +101,79 @@ GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft)
|
||||
return (time_t)winTime;
|
||||
}
|
||||
|
||||
#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
|
||||
|
||||
static int do_lstat(
|
||||
const char *file_name, struct stat *buf, int posix_enotdir)
|
||||
/* On success, returns the length, in characters, of the path stored in dest.
|
||||
* On failure, returns a negative value. */
|
||||
static int readlink_w(
|
||||
git_win32_path dest,
|
||||
const git_win32_path path)
|
||||
{
|
||||
WIN32_FILE_ATTRIBUTE_DATA fdata;
|
||||
git_win32_path fbuf;
|
||||
wchar_t lastch;
|
||||
int flen;
|
||||
BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf;
|
||||
HANDLE handle = NULL;
|
||||
DWORD ioctl_ret;
|
||||
wchar_t *target;
|
||||
size_t target_len;
|
||||
|
||||
if ((flen = utf8_to_16_with_errno(fbuf, file_name)) < 0)
|
||||
int error = -1;
|
||||
|
||||
handle = CreateFileW(path, GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||
|
||||
if (INVALID_HANDLE_VALUE == handle) {
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
|
||||
/* truncate trailing slashes */
|
||||
for (; flen > 0; --flen) {
|
||||
lastch = fbuf[flen - 1];
|
||||
if (WIN32_IS_WSEP(lastch))
|
||||
fbuf[flen - 1] = L'\0';
|
||||
else if (lastch != L'\0')
|
||||
break;
|
||||
}
|
||||
|
||||
if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {
|
||||
if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
|
||||
reparse_buf, sizeof(buf), &ioctl_ret, NULL)) {
|
||||
errno = EINVAL;
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
switch (reparse_buf->ReparseTag) {
|
||||
case IO_REPARSE_TAG_SYMLINK:
|
||||
target = reparse_buf->SymbolicLinkReparseBuffer.PathBuffer +
|
||||
(reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
|
||||
target_len = reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
|
||||
break;
|
||||
case IO_REPARSE_TAG_MOUNT_POINT:
|
||||
target = reparse_buf->MountPointReparseBuffer.PathBuffer +
|
||||
(reparse_buf->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
|
||||
target_len = reparse_buf->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
|
||||
break;
|
||||
default:
|
||||
errno = EINVAL;
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
if (target_len) {
|
||||
/* The path may need to have a prefix removed. */
|
||||
target_len = git_win32__to_dos(target, target_len);
|
||||
|
||||
/* Need one additional character in the target buffer
|
||||
* for the terminating NULL. */
|
||||
if (GIT_WIN_PATH_UTF16 > target_len) {
|
||||
wcscpy(dest, target);
|
||||
error = (int)target_len;
|
||||
}
|
||||
}
|
||||
|
||||
on_error:
|
||||
CloseHandle(handle);
|
||||
return error;
|
||||
}
|
||||
|
||||
#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)) {
|
||||
int fMode = S_IREAD;
|
||||
|
||||
if (!buf)
|
||||
@ -136,12 +187,6 @@ static int do_lstat(
|
||||
if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
|
||||
fMode |= S_IWRITE;
|
||||
|
||||
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
fMode |= S_IFLNK;
|
||||
|
||||
if ((fMode & (S_IFDIR | S_IFLNK)) == (S_IFDIR | S_IFLNK)) // junction
|
||||
fMode ^= S_IFLNK;
|
||||
|
||||
buf->st_ino = 0;
|
||||
buf->st_gid = 0;
|
||||
buf->st_uid = 0;
|
||||
@ -153,16 +198,17 @@ static int do_lstat(
|
||||
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
|
||||
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
|
||||
|
||||
/* Windows symlinks have zero file size, call readlink to determine
|
||||
* the length of the path pointed to, which we expect everywhere else
|
||||
*/
|
||||
if (S_ISLNK(fMode)) {
|
||||
git_win32_utf8_path target;
|
||||
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
git_win32_path target;
|
||||
|
||||
if (p_readlink(file_name, target, GIT_WIN_PATH_UTF8) == -1)
|
||||
return -1;
|
||||
if (readlink_w(target, path) >= 0) {
|
||||
buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFLNK;
|
||||
|
||||
buf->st_size = strlen(target);
|
||||
/* st_size gets the UTF-8 length of the target name, in bytes,
|
||||
* not counting the NULL terminator */
|
||||
if ((buf->st_size = git__utf16_to_8(NULL, 0, target)) < 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -174,18 +220,23 @@ static int do_lstat(
|
||||
* file path is a regular file, otherwise set ENOENT.
|
||||
*/
|
||||
if (posix_enotdir) {
|
||||
size_t path_len = wcslen(path);
|
||||
|
||||
/* scan up path until we find an existing item */
|
||||
while (1) {
|
||||
/* remove last directory component */
|
||||
for (--flen; flen > 0 && !WIN32_IS_WSEP(fbuf[flen]); --flen);
|
||||
DWORD attrs;
|
||||
|
||||
if (flen <= 0)
|
||||
/* remove last directory component */
|
||||
for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--);
|
||||
|
||||
if (path_len <= 0)
|
||||
break;
|
||||
|
||||
fbuf[flen] = L'\0';
|
||||
path[path_len] = L'\0';
|
||||
attrs = GetFileAttributesW(path);
|
||||
|
||||
if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {
|
||||
if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
||||
if (INVALID_FILE_ATTRIBUTES != attrs) {
|
||||
if (!(attrs & FILE_ATTRIBUTE_DIRECTORY))
|
||||
errno = ENOTDIR;
|
||||
break;
|
||||
}
|
||||
@ -195,105 +246,51 @@ static int do_lstat(
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
|
||||
{
|
||||
git_win32_path path_w;
|
||||
int len;
|
||||
|
||||
if ((len = utf8_to_16_with_errno(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, 0);
|
||||
return do_lstat(filename, buf, false);
|
||||
}
|
||||
|
||||
int p_lstat_posixly(const char *filename, struct stat *buf)
|
||||
{
|
||||
return do_lstat(filename, buf, 1);
|
||||
return do_lstat(filename, buf, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the address of the GetFinalPathNameByHandleW function.
|
||||
* This function is available on Windows Vista and higher.
|
||||
*/
|
||||
static PFGetFinalPathNameByHandleW get_fpnbyhandle(void)
|
||||
int p_readlink(const char *path, char *buf, size_t bufsiz)
|
||||
{
|
||||
static PFGetFinalPathNameByHandleW pFunc = NULL;
|
||||
PFGetFinalPathNameByHandleW toReturn = pFunc;
|
||||
git_win32_path path_w, target_w;
|
||||
git_win32_utf8_path target;
|
||||
int len;
|
||||
|
||||
if (!toReturn) {
|
||||
HMODULE hModule = GetModuleHandleW(L"kernel32");
|
||||
/* 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 (hModule)
|
||||
toReturn = (PFGetFinalPathNameByHandleW)GetProcAddress(hModule, "GetFinalPathNameByHandleW");
|
||||
if (utf8_to_16_with_errno(path_w, path) < 0 ||
|
||||
readlink_w(target_w, path_w) < 0 ||
|
||||
(len = git_win32_path_to_utf8(target, target_w)) < 0)
|
||||
return -1;
|
||||
|
||||
pFunc = toReturn;
|
||||
}
|
||||
bufsiz = min((size_t)len, bufsiz);
|
||||
memcpy(buf, target, bufsiz);
|
||||
|
||||
assert(toReturn);
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parts of the The p_readlink function are heavily inspired by the php
|
||||
* readlink function in link_win32.c
|
||||
*
|
||||
* Copyright (c) 1999 - 2012 The PHP Group. All rights reserved.
|
||||
*
|
||||
* For details of the PHP license see http://www.php.net/license/3_01.txt
|
||||
*/
|
||||
int p_readlink(const char *link, char *target, size_t target_len)
|
||||
{
|
||||
static const wchar_t prefix[] = L"\\\\?\\";
|
||||
PFGetFinalPathNameByHandleW pgfp = get_fpnbyhandle();
|
||||
HANDLE hFile = NULL;
|
||||
wchar_t *target_w = NULL;
|
||||
bool trim_prefix;
|
||||
git_win32_path link_w;
|
||||
DWORD dwChars, dwLastError;
|
||||
int error = -1;
|
||||
|
||||
/* Check that we found the function, and convert to UTF-16 */
|
||||
if (!pgfp || utf8_to_16_with_errno(link_w, link) < 0)
|
||||
goto on_error;
|
||||
|
||||
/* 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(link_w, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
|
||||
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
goto on_error;
|
||||
|
||||
/* Find out how large the buffer should be to hold the result */
|
||||
if (!(dwChars = pgfp(hFile, NULL, 0, FILE_NAME_NORMALIZED)))
|
||||
goto on_error;
|
||||
|
||||
if (!(target_w = git__malloc(dwChars * sizeof(wchar_t))))
|
||||
goto on_error;
|
||||
|
||||
/* Call a second time */
|
||||
dwChars = pgfp(hFile, target_w, dwChars, FILE_NAME_NORMALIZED);
|
||||
|
||||
if (!dwChars)
|
||||
goto on_error;
|
||||
|
||||
/* Do we need to trim off a \\?\ from the start of the path? */
|
||||
trim_prefix = (dwChars >= ARRAY_SIZE(prefix)) &&
|
||||
!wcsncmp(prefix, target_w, ARRAY_SIZE(prefix));
|
||||
|
||||
/* Convert the result to UTF-8 */
|
||||
if (git__utf16_to_8(target, target_len, trim_prefix ? target_w + 4 : target_w) < 0)
|
||||
goto on_error;
|
||||
|
||||
error = 0;
|
||||
|
||||
on_error:
|
||||
dwLastError = GetLastError();
|
||||
|
||||
if (hFile && INVALID_HANDLE_VALUE != hFile)
|
||||
CloseHandle(hFile);
|
||||
|
||||
if (target_w)
|
||||
git__free(target_w);
|
||||
|
||||
SetLastError(dwLastError);
|
||||
return error;
|
||||
return (int)bufsiz;
|
||||
}
|
||||
|
||||
int p_symlink(const char *old, const char *new)
|
||||
@ -356,20 +353,94 @@ 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. */
|
||||
hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
|
||||
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
/* Call GetFinalPathNameByHandle */
|
||||
dwChars = pgfp(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
|
||||
|
||||
if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16) {
|
||||
DWORD error = GetLastError();
|
||||
CloseHandle(hFile);
|
||||
SetLastError(error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
CloseHandle(hFile);
|
||||
|
||||
/* The path may be delivered to us with a prefix; canonicalize */
|
||||
return (int)git_win32__to_dos(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_stat(const char* path, struct stat* buf)
|
||||
{
|
||||
git_win32_utf8_path target;
|
||||
int error = 0;
|
||||
git_win32_path path_w;
|
||||
int len;
|
||||
|
||||
error = do_lstat(path, buf, 0);
|
||||
if ((len = utf8_to_16_with_errno(path_w, path)) < 0)
|
||||
return -1;
|
||||
|
||||
/* We need not do this in a loop to unwind chains of symlinks since
|
||||
* p_readlink calls GetFinalPathNameByHandle which does it for us. */
|
||||
if (error >= 0 && S_ISLNK(buf->st_mode) &&
|
||||
(error = p_readlink(path, target, GIT_WIN_PATH_UTF8)) >= 0)
|
||||
error = do_lstat(target, buf, 0);
|
||||
git_win32__path_trim_end(path_w, len);
|
||||
|
||||
return error;
|
||||
if (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)
|
||||
|
||||
57
src/win32/reparse.h
Normal file
57
src/win32/reparse.h
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_git_win32_reparse_h__
|
||||
#define INCLUDE_git_win32_reparse_h__
|
||||
|
||||
/* This structure is defined on MSDN at
|
||||
* http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx
|
||||
*
|
||||
* It was formerly included in the Windows 2000 SDK and remains defined in
|
||||
* MinGW, so we must define it with a silly name to avoid conflicting.
|
||||
*/
|
||||
typedef struct _GIT_REPARSE_DATA_BUFFER {
|
||||
ULONG ReparseTag;
|
||||
USHORT ReparseDataLength;
|
||||
USHORT Reserved;
|
||||
union {
|
||||
struct {
|
||||
USHORT SubstituteNameOffset;
|
||||
USHORT SubstituteNameLength;
|
||||
USHORT PrintNameOffset;
|
||||
USHORT PrintNameLength;
|
||||
ULONG Flags;
|
||||
WCHAR PathBuffer[1];
|
||||
} SymbolicLinkReparseBuffer;
|
||||
struct {
|
||||
USHORT SubstituteNameOffset;
|
||||
USHORT SubstituteNameLength;
|
||||
USHORT PrintNameOffset;
|
||||
USHORT PrintNameLength;
|
||||
WCHAR PathBuffer[1];
|
||||
} MountPointReparseBuffer;
|
||||
struct {
|
||||
UCHAR DataBuffer[1];
|
||||
} GenericReparseBuffer;
|
||||
};
|
||||
} GIT_REPARSE_DATA_BUFFER;
|
||||
|
||||
#define REPARSE_DATA_HEADER_SIZE 8
|
||||
#define REPARSE_DATA_MOUNTPOINT_HEADER_SIZE 8
|
||||
#define REPARSE_DATA_UNION_SIZE 12
|
||||
|
||||
/* Missing in MinGW */
|
||||
#ifndef FSCTL_GET_REPARSE_POINT
|
||||
# define FSCTL_GET_REPARSE_POINT 0x000900a8
|
||||
#endif
|
||||
|
||||
/* Missing in MinGW */
|
||||
#ifndef FSCTL_SET_REPARSE_POINT
|
||||
# define FSCTL_SET_REPARSE_POINT 0x000900a4
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@ -7,6 +7,8 @@
|
||||
|
||||
#include "w32_util.h"
|
||||
|
||||
#define CONST_STRLEN(x) ((sizeof(x)/sizeof(x[0])) - 1)
|
||||
|
||||
/**
|
||||
* Creates a FindFirstFile(Ex) filter string from a UTF-8 path.
|
||||
* The filter string enumerates all items in the directory.
|
||||
@ -67,3 +69,71 @@ int git_win32__sethidden(const char *path)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any trailing backslashes from a path, except in the case of a drive
|
||||
* letter path (C:\, D:\, etc.). This function cannot fail.
|
||||
*
|
||||
* @param path The path which should be trimmed.
|
||||
* @return The length of the modified string (<= the input length)
|
||||
*/
|
||||
size_t git_win32__path_trim_end(wchar_t *str, size_t len)
|
||||
{
|
||||
while (1) {
|
||||
if (!len || str[len - 1] != L'\\')
|
||||
break;
|
||||
|
||||
/* Don't trim backslashes from drive letter paths, which
|
||||
* are 3 characters long and of the form C:\, D:\, etc. */
|
||||
if (3 == len && git_win32__isalpha(str[0]) && str[1] == ':')
|
||||
break;
|
||||
|
||||
len--;
|
||||
}
|
||||
|
||||
str[len] = L'\0';
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any of the following namespace prefixes from a path,
|
||||
* if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
|
||||
*
|
||||
* @param path The path which should be converted.
|
||||
* @return The length of the modified string (<= the input length)
|
||||
*/
|
||||
size_t git_win32__to_dos(wchar_t *str, size_t len)
|
||||
{
|
||||
static const wchar_t dosdevices_prefix[] = L"\\\?\?\\";
|
||||
static const wchar_t nt_prefix[] = L"\\\\?\\";
|
||||
static const wchar_t unc_prefix[] = L"UNC\\";
|
||||
size_t to_advance = 0;
|
||||
|
||||
/* "\??\" -- DOS Devices prefix */
|
||||
if (len >= CONST_STRLEN(dosdevices_prefix) &&
|
||||
!wcsncmp(str, dosdevices_prefix, CONST_STRLEN(dosdevices_prefix))) {
|
||||
to_advance += CONST_STRLEN(dosdevices_prefix);
|
||||
len -= CONST_STRLEN(dosdevices_prefix);
|
||||
}
|
||||
/* "\\?\" -- NT namespace prefix */
|
||||
else if (len >= CONST_STRLEN(nt_prefix) &&
|
||||
!wcsncmp(str, nt_prefix, CONST_STRLEN(nt_prefix))) {
|
||||
to_advance += CONST_STRLEN(nt_prefix);
|
||||
len -= CONST_STRLEN(nt_prefix);
|
||||
}
|
||||
|
||||
/* "\??\UNC\", "\\?\UNC\" -- UNC prefix */
|
||||
if (to_advance && len >= CONST_STRLEN(unc_prefix) &&
|
||||
!wcsncmp(str + to_advance, unc_prefix, CONST_STRLEN(unc_prefix))) {
|
||||
to_advance += CONST_STRLEN(unc_prefix);
|
||||
len -= CONST_STRLEN(unc_prefix);
|
||||
}
|
||||
|
||||
if (to_advance) {
|
||||
memmove(str, str + to_advance, len * sizeof(wchar_t));
|
||||
str[len] = L'\0';
|
||||
}
|
||||
|
||||
return git_win32__path_trim_end(str, len);
|
||||
}
|
||||
@ -10,6 +10,11 @@
|
||||
|
||||
#include "utf-conv.h"
|
||||
|
||||
GIT_INLINE(bool) git_win32__isalpha(wchar_t c)
|
||||
{
|
||||
return ((c >= L'A' && c <= L'Z') || (c >= L'a' && c <= L'z'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a FindFirstFile(Ex) filter string from a UTF-8 path.
|
||||
* The filter string enumerates all items in the directory.
|
||||
@ -28,4 +33,22 @@ bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src);
|
||||
*/
|
||||
int git_win32__sethidden(const char *path);
|
||||
|
||||
/**
|
||||
* Removes any trailing backslashes from a path, except in the case of a drive
|
||||
* letter path (C:\, D:\, etc.). This function cannot fail.
|
||||
*
|
||||
* @param path The path which should be trimmed.
|
||||
* @return The length of the modified string (<= the input length)
|
||||
*/
|
||||
size_t git_win32__path_trim_end(wchar_t *str, size_t len);
|
||||
|
||||
/**
|
||||
* Removes any of the following namespace prefixes from a path,
|
||||
* if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail.
|
||||
*
|
||||
* @param path The path which should be converted.
|
||||
* @return The length of the modified string (<= the input length)
|
||||
*/
|
||||
size_t git_win32__to_dos(wchar_t *str, size_t len);
|
||||
|
||||
#endif
|
||||
|
||||
@ -29,6 +29,17 @@
|
||||
|
||||
#define cl_git_fail_with(expr, error) cl_assert_equal_i(error,expr)
|
||||
|
||||
/**
|
||||
* Like cl_git_pass, only for Win32 error code conventions
|
||||
*/
|
||||
#define cl_win32_pass(expr) do { \
|
||||
int _win32_res; \
|
||||
if ((_win32_res = (expr)) == 0) { \
|
||||
giterr_set(GITERR_OS, "Returned: %d, system error code: %d", _win32_res, GetLastError()); \
|
||||
cl_git_report_failure(_win32_res, __FILE__, __LINE__, "System call failed: " #expr); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
void cl_git_report_failure(int, const char *, int, const char *);
|
||||
|
||||
#define cl_assert_at_line(expr,file,line) \
|
||||
|
||||
602
tests/core/link.c
Normal file
602
tests/core/link.c
Normal file
@ -0,0 +1,602 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "posix.h"
|
||||
#include "buffer.h"
|
||||
#include "path.h"
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
# include "win32/reparse.h"
|
||||
#endif
|
||||
|
||||
void test_core_link__cleanup(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
RemoveDirectory("lstat_junction");
|
||||
RemoveDirectory("lstat_dangling");
|
||||
RemoveDirectory("lstat_dangling_dir");
|
||||
RemoveDirectory("lstat_dangling_junction");
|
||||
|
||||
RemoveDirectory("stat_junction");
|
||||
RemoveDirectory("stat_dangling");
|
||||
RemoveDirectory("stat_dangling_dir");
|
||||
RemoveDirectory("stat_dangling_junction");
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
static bool is_administrator(void)
|
||||
{
|
||||
static SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY };
|
||||
PSID admin_sid;
|
||||
BOOL is_admin;
|
||||
|
||||
cl_win32_pass(AllocateAndInitializeSid(&authority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &admin_sid));
|
||||
cl_win32_pass(CheckTokenMembership(NULL, admin_sid, &is_admin));
|
||||
FreeSid(admin_sid);
|
||||
|
||||
return is_admin ? true : false;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void do_symlink(const char *old, const char *new, int is_dir)
|
||||
{
|
||||
#ifndef GIT_WIN32
|
||||
GIT_UNUSED(is_dir);
|
||||
|
||||
cl_must_pass(symlink(old, new));
|
||||
#else
|
||||
typedef DWORD (WINAPI *create_symlink_func)(LPCTSTR, LPCTSTR, DWORD);
|
||||
HMODULE module;
|
||||
create_symlink_func pCreateSymbolicLink;
|
||||
|
||||
if (!is_administrator())
|
||||
clar__skip();
|
||||
|
||||
cl_assert(module = GetModuleHandle("kernel32"));
|
||||
cl_assert(pCreateSymbolicLink = (create_symlink_func)GetProcAddress(module, "CreateSymbolicLinkA"));
|
||||
|
||||
cl_win32_pass(pCreateSymbolicLink(new, old, is_dir));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void do_hardlink(const char *old, const char *new)
|
||||
{
|
||||
#ifndef GIT_WIN32
|
||||
cl_must_pass(link(old, new));
|
||||
#else
|
||||
typedef DWORD (WINAPI *create_hardlink_func)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES);
|
||||
HMODULE module;
|
||||
create_hardlink_func pCreateHardLink;
|
||||
|
||||
if (!is_administrator())
|
||||
clar__skip();
|
||||
|
||||
cl_assert(module = GetModuleHandle("kernel32"));
|
||||
cl_assert(pCreateHardLink = (create_hardlink_func)GetProcAddress(module, "CreateHardLinkA"));
|
||||
|
||||
cl_win32_pass(pCreateHardLink(new, old, 0));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef GIT_WIN32
|
||||
|
||||
static void do_junction(const char *old, const char *new)
|
||||
{
|
||||
GIT_REPARSE_DATA_BUFFER *reparse_buf;
|
||||
HANDLE handle;
|
||||
git_buf unparsed_buf = GIT_BUF_INIT;
|
||||
wchar_t *subst_utf16, *print_utf16;
|
||||
DWORD ioctl_ret;
|
||||
int subst_utf16_len, subst_byte_len, print_utf16_len, print_byte_len, ret;
|
||||
USHORT reparse_buflen;
|
||||
size_t i;
|
||||
|
||||
/* Junction targets must be the unparsed name, starting with \??\, using
|
||||
* backslashes instead of forward, and end in a trailing backslash.
|
||||
* eg: \??\C:\Foo\
|
||||
*/
|
||||
git_buf_puts(&unparsed_buf, "\\??\\");
|
||||
|
||||
for (i = 0; i < strlen(old); i++)
|
||||
git_buf_putc(&unparsed_buf, old[i] == '/' ? '\\' : old[i]);
|
||||
|
||||
git_buf_putc(&unparsed_buf, '\\');
|
||||
|
||||
subst_utf16_len = git__utf8_to_16(NULL, 0, git_buf_cstr(&unparsed_buf));
|
||||
subst_byte_len = subst_utf16_len * sizeof(WCHAR);
|
||||
|
||||
print_utf16_len = subst_utf16_len - 4;
|
||||
print_byte_len = subst_byte_len - (4 * sizeof(WCHAR));
|
||||
|
||||
/* The junction must be an empty directory before the junction attribute
|
||||
* can be added.
|
||||
*/
|
||||
cl_win32_pass(CreateDirectoryA(new, NULL));
|
||||
|
||||
handle = CreateFileA(new, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||
cl_win32_pass(handle != INVALID_HANDLE_VALUE);
|
||||
|
||||
reparse_buflen = (USHORT)(REPARSE_DATA_HEADER_SIZE +
|
||||
REPARSE_DATA_MOUNTPOINT_HEADER_SIZE +
|
||||
subst_byte_len + sizeof(WCHAR) +
|
||||
print_byte_len + sizeof(WCHAR));
|
||||
|
||||
reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
|
||||
cl_assert(reparse_buf);
|
||||
|
||||
subst_utf16 = reparse_buf->MountPointReparseBuffer.PathBuffer;
|
||||
print_utf16 = subst_utf16 + subst_utf16_len + 1;
|
||||
|
||||
ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1,
|
||||
git_buf_cstr(&unparsed_buf));
|
||||
cl_assert_equal_i(subst_utf16_len, ret);
|
||||
|
||||
ret = git__utf8_to_16(print_utf16,
|
||||
print_utf16_len + 1, git_buf_cstr(&unparsed_buf) + 4);
|
||||
cl_assert_equal_i(print_utf16_len, ret);
|
||||
|
||||
reparse_buf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
|
||||
reparse_buf->MountPointReparseBuffer.SubstituteNameOffset = 0;
|
||||
reparse_buf->MountPointReparseBuffer.SubstituteNameLength = subst_byte_len;
|
||||
reparse_buf->MountPointReparseBuffer.PrintNameOffset = (USHORT)(subst_byte_len + sizeof(WCHAR));
|
||||
reparse_buf->MountPointReparseBuffer.PrintNameLength = print_byte_len;
|
||||
reparse_buf->ReparseDataLength = reparse_buflen - REPARSE_DATA_HEADER_SIZE;
|
||||
|
||||
cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
|
||||
reparse_buf, reparse_buflen, NULL, 0, &ioctl_ret, NULL));
|
||||
|
||||
CloseHandle(handle);
|
||||
LocalFree(reparse_buf);
|
||||
}
|
||||
|
||||
static void do_custom_reparse(const char *path)
|
||||
{
|
||||
REPARSE_GUID_DATA_BUFFER *reparse_buf;
|
||||
HANDLE handle;
|
||||
DWORD ioctl_ret;
|
||||
|
||||
const char *reparse_data = "Reparse points are silly.";
|
||||
size_t reparse_buflen = REPARSE_GUID_DATA_BUFFER_HEADER_SIZE +
|
||||
strlen(reparse_data) + 1;
|
||||
|
||||
reparse_buf = LocalAlloc(LMEM_FIXED|LMEM_ZEROINIT, reparse_buflen);
|
||||
cl_assert(reparse_buf);
|
||||
|
||||
reparse_buf->ReparseTag = 42;
|
||||
reparse_buf->ReparseDataLength = (WORD)(strlen(reparse_data) + 1);
|
||||
|
||||
reparse_buf->ReparseGuid.Data1 = 0xdeadbeef;
|
||||
reparse_buf->ReparseGuid.Data2 = 0xdead;
|
||||
reparse_buf->ReparseGuid.Data3 = 0xbeef;
|
||||
reparse_buf->ReparseGuid.Data4[0] = 42;
|
||||
reparse_buf->ReparseGuid.Data4[1] = 42;
|
||||
reparse_buf->ReparseGuid.Data4[2] = 42;
|
||||
reparse_buf->ReparseGuid.Data4[3] = 42;
|
||||
reparse_buf->ReparseGuid.Data4[4] = 42;
|
||||
reparse_buf->ReparseGuid.Data4[5] = 42;
|
||||
reparse_buf->ReparseGuid.Data4[6] = 42;
|
||||
reparse_buf->ReparseGuid.Data4[7] = 42;
|
||||
reparse_buf->ReparseGuid.Data4[8] = 42;
|
||||
|
||||
memcpy(reparse_buf->GenericReparseBuffer.DataBuffer,
|
||||
reparse_data, strlen(reparse_data) + 1);
|
||||
|
||||
handle = CreateFileA(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||
cl_win32_pass(handle != INVALID_HANDLE_VALUE);
|
||||
|
||||
cl_win32_pass(DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT,
|
||||
reparse_buf,
|
||||
reparse_buf->ReparseDataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE,
|
||||
NULL, 0, &ioctl_ret, NULL));
|
||||
|
||||
CloseHandle(handle);
|
||||
LocalFree(reparse_buf);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
git_buf *unslashify(git_buf *buf)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < buf->size; i++)
|
||||
if (buf->ptr[i] == '/')
|
||||
buf->ptr[i] = '\\';
|
||||
#endif
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
void test_core_link__stat_regular_file(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
cl_git_rewritefile("stat_regfile", "This is a regular file!\n");
|
||||
|
||||
cl_must_pass(p_stat("stat_regfile", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(24, st.st_size);
|
||||
}
|
||||
|
||||
void test_core_link__lstat_regular_file(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
cl_git_rewritefile("lstat_regfile", "This is a regular file!\n");
|
||||
|
||||
cl_must_pass(p_stat("lstat_regfile", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(24, st.st_size);
|
||||
}
|
||||
|
||||
void test_core_link__stat_symlink(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
cl_git_rewritefile("stat_target", "This is the target of a symbolic link.\n");
|
||||
do_symlink("stat_target", "stat_symlink", 0);
|
||||
|
||||
cl_must_pass(p_stat("stat_target", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(39, st.st_size);
|
||||
|
||||
cl_must_pass(p_stat("stat_symlink", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(39, st.st_size);
|
||||
}
|
||||
|
||||
void test_core_link__stat_symlink_directory(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
p_mkdir("stat_dirtarget", 0777);
|
||||
do_symlink("stat_dirtarget", "stat_dirlink", 1);
|
||||
|
||||
cl_must_pass(p_stat("stat_dirtarget", &st));
|
||||
cl_assert(S_ISDIR(st.st_mode));
|
||||
|
||||
cl_must_pass(p_stat("stat_dirlink", &st));
|
||||
cl_assert(S_ISDIR(st.st_mode));
|
||||
}
|
||||
|
||||
void test_core_link__stat_symlink_chain(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
cl_git_rewritefile("stat_final_target", "Final target of some symbolic links...\n");
|
||||
do_symlink("stat_final_target", "stat_chain_3", 0);
|
||||
do_symlink("stat_chain_3", "stat_chain_2", 0);
|
||||
do_symlink("stat_chain_2", "stat_chain_1", 0);
|
||||
|
||||
cl_must_pass(p_stat("stat_chain_1", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(39, st.st_size);
|
||||
}
|
||||
|
||||
void test_core_link__stat_dangling_symlink(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
do_symlink("stat_nonexistent", "stat_dangling", 0);
|
||||
|
||||
cl_must_fail(p_stat("stat_nonexistent", &st));
|
||||
cl_must_fail(p_stat("stat_dangling", &st));
|
||||
}
|
||||
|
||||
void test_core_link__stat_dangling_symlink_directory(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
do_symlink("stat_nonexistent", "stat_dangling_dir", 1);
|
||||
|
||||
cl_must_fail(p_stat("stat_nonexistent_dir", &st));
|
||||
cl_must_fail(p_stat("stat_dangling", &st));
|
||||
}
|
||||
|
||||
void test_core_link__lstat_symlink(void)
|
||||
{
|
||||
git_buf target_path = GIT_BUF_INIT;
|
||||
struct stat st;
|
||||
|
||||
/* Windows always writes the canonical path as the link target, so
|
||||
* write the full path on all platforms.
|
||||
*/
|
||||
git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_target");
|
||||
|
||||
cl_git_rewritefile("lstat_target", "This is the target of a symbolic link.\n");
|
||||
do_symlink(git_buf_cstr(&target_path), "lstat_symlink", 0);
|
||||
|
||||
cl_must_pass(p_lstat("lstat_target", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(39, st.st_size);
|
||||
|
||||
cl_must_pass(p_lstat("lstat_symlink", &st));
|
||||
cl_assert(S_ISLNK(st.st_mode));
|
||||
cl_assert_equal_i(git_buf_len(&target_path), st.st_size);
|
||||
|
||||
git_buf_free(&target_path);
|
||||
}
|
||||
|
||||
void test_core_link__lstat_symlink_directory(void)
|
||||
{
|
||||
git_buf target_path = GIT_BUF_INIT;
|
||||
struct stat st;
|
||||
|
||||
git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_dirtarget");
|
||||
|
||||
p_mkdir("lstat_dirtarget", 0777);
|
||||
do_symlink(git_buf_cstr(&target_path), "lstat_dirlink", 1);
|
||||
|
||||
cl_must_pass(p_lstat("lstat_dirtarget", &st));
|
||||
cl_assert(S_ISDIR(st.st_mode));
|
||||
|
||||
cl_must_pass(p_lstat("lstat_dirlink", &st));
|
||||
cl_assert(S_ISLNK(st.st_mode));
|
||||
cl_assert_equal_i(git_buf_len(&target_path), st.st_size);
|
||||
|
||||
git_buf_free(&target_path);
|
||||
}
|
||||
|
||||
void test_core_link__lstat_dangling_symlink(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
do_symlink("lstat_nonexistent", "lstat_dangling", 0);
|
||||
|
||||
cl_must_fail(p_lstat("lstat_nonexistent", &st));
|
||||
|
||||
cl_must_pass(p_lstat("lstat_dangling", &st));
|
||||
cl_assert(S_ISLNK(st.st_mode));
|
||||
cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size);
|
||||
}
|
||||
|
||||
void test_core_link__lstat_dangling_symlink_directory(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
do_symlink("lstat_nonexistent", "lstat_dangling_dir", 1);
|
||||
|
||||
cl_must_fail(p_lstat("lstat_nonexistent", &st));
|
||||
|
||||
cl_must_pass(p_lstat("lstat_dangling_dir", &st));
|
||||
cl_assert(S_ISLNK(st.st_mode));
|
||||
cl_assert_equal_i(strlen("lstat_nonexistent"), st.st_size);
|
||||
}
|
||||
|
||||
void test_core_link__stat_junction(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
git_buf target_path = GIT_BUF_INIT;
|
||||
struct stat st;
|
||||
|
||||
git_buf_join(&target_path, '/', clar_sandbox_path(), "stat_junctarget");
|
||||
|
||||
p_mkdir("stat_junctarget", 0777);
|
||||
do_junction(git_buf_cstr(&target_path), "stat_junction");
|
||||
|
||||
cl_must_pass(p_stat("stat_junctarget", &st));
|
||||
cl_assert(S_ISDIR(st.st_mode));
|
||||
|
||||
cl_must_pass(p_stat("stat_junction", &st));
|
||||
cl_assert(S_ISDIR(st.st_mode));
|
||||
|
||||
git_buf_free(&target_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_core_link__stat_dangling_junction(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
git_buf target_path = GIT_BUF_INIT;
|
||||
struct stat st;
|
||||
|
||||
git_buf_join(&target_path, '/', clar_sandbox_path(), "stat_nonexistent_junctarget");
|
||||
|
||||
p_mkdir("stat_nonexistent_junctarget", 0777);
|
||||
do_junction(git_buf_cstr(&target_path), "stat_dangling_junction");
|
||||
|
||||
RemoveDirectory("stat_nonexistent_junctarget");
|
||||
|
||||
cl_must_fail(p_stat("stat_nonexistent_junctarget", &st));
|
||||
cl_must_fail(p_stat("stat_dangling_junction", &st));
|
||||
|
||||
git_buf_free(&target_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_core_link__lstat_junction(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
git_buf target_path = GIT_BUF_INIT;
|
||||
struct stat st;
|
||||
|
||||
git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_junctarget");
|
||||
|
||||
p_mkdir("lstat_junctarget", 0777);
|
||||
do_junction(git_buf_cstr(&target_path), "lstat_junction");
|
||||
|
||||
cl_must_pass(p_lstat("lstat_junctarget", &st));
|
||||
cl_assert(S_ISDIR(st.st_mode));
|
||||
|
||||
cl_must_pass(p_lstat("lstat_junction", &st));
|
||||
cl_assert(S_ISLNK(st.st_mode));
|
||||
|
||||
git_buf_free(&target_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_core_link__lstat_dangling_junction(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
git_buf target_path = GIT_BUF_INIT;
|
||||
struct stat st;
|
||||
|
||||
git_buf_join(&target_path, '/', clar_sandbox_path(), "lstat_nonexistent_junctarget");
|
||||
|
||||
p_mkdir("lstat_nonexistent_junctarget", 0777);
|
||||
do_junction(git_buf_cstr(&target_path), "lstat_dangling_junction");
|
||||
|
||||
RemoveDirectory("lstat_nonexistent_junctarget");
|
||||
|
||||
cl_must_fail(p_lstat("lstat_nonexistent_junctarget", &st));
|
||||
|
||||
cl_must_pass(p_lstat("lstat_dangling_junction", &st));
|
||||
cl_assert(S_ISLNK(st.st_mode));
|
||||
cl_assert_equal_i(git_buf_len(&target_path), st.st_size);
|
||||
|
||||
git_buf_free(&target_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_core_link__stat_hardlink(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
cl_git_rewritefile("stat_hardlink1", "This file has many names!\n");
|
||||
do_hardlink("stat_hardlink1", "stat_hardlink2");
|
||||
|
||||
cl_must_pass(p_stat("stat_hardlink1", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(26, st.st_size);
|
||||
|
||||
cl_must_pass(p_stat("stat_hardlink2", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(26, st.st_size);
|
||||
}
|
||||
|
||||
void test_core_link__lstat_hardlink(void)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
cl_git_rewritefile("lstat_hardlink1", "This file has many names!\n");
|
||||
do_hardlink("lstat_hardlink1", "lstat_hardlink2");
|
||||
|
||||
cl_must_pass(p_lstat("lstat_hardlink1", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(26, st.st_size);
|
||||
|
||||
cl_must_pass(p_lstat("lstat_hardlink2", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(26, st.st_size);
|
||||
}
|
||||
|
||||
void test_core_link__stat_reparse_point(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
struct stat st;
|
||||
|
||||
/* Generic reparse points should be treated as regular files, only
|
||||
* symlinks and junctions should be treated as links.
|
||||
*/
|
||||
|
||||
cl_git_rewritefile("stat_reparse", "This is a reparse point!\n");
|
||||
do_custom_reparse("stat_reparse");
|
||||
|
||||
cl_must_pass(p_lstat("stat_reparse", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(25, st.st_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_core_link__lstat_reparse_point(void)
|
||||
{
|
||||
#ifdef GIT_WIN32
|
||||
struct stat st;
|
||||
|
||||
cl_git_rewritefile("lstat_reparse", "This is a reparse point!\n");
|
||||
do_custom_reparse("lstat_reparse");
|
||||
|
||||
cl_must_pass(p_lstat("lstat_reparse", &st));
|
||||
cl_assert(S_ISREG(st.st_mode));
|
||||
cl_assert_equal_i(25, st.st_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_core_link__readlink_nonexistent_file(void)
|
||||
{
|
||||
char buf[2048];
|
||||
|
||||
cl_must_fail(p_readlink("readlink_nonexistent", buf, 2048));
|
||||
cl_assert_equal_i(ENOENT, errno);
|
||||
}
|
||||
|
||||
void test_core_link__readlink_normal_file(void)
|
||||
{
|
||||
char buf[2048];
|
||||
|
||||
cl_git_rewritefile("readlink_regfile", "This is a regular file!\n");
|
||||
cl_must_fail(p_readlink("readlink_regfile", buf, 2048));
|
||||
cl_assert_equal_i(EINVAL, errno);
|
||||
}
|
||||
|
||||
void test_core_link__readlink_symlink(void)
|
||||
{
|
||||
git_buf target_path = GIT_BUF_INIT;
|
||||
int len;
|
||||
char buf[2048];
|
||||
|
||||
git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_target");
|
||||
|
||||
cl_git_rewritefile("readlink_target", "This is the target of a symlink\n");
|
||||
do_symlink(git_buf_cstr(&target_path), "readlink_link", 0);
|
||||
|
||||
len = p_readlink("readlink_link", buf, 2048);
|
||||
cl_must_pass(len);
|
||||
|
||||
buf[len] = 0;
|
||||
|
||||
cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf);
|
||||
|
||||
git_buf_free(&target_path);
|
||||
}
|
||||
|
||||
void test_core_link__readlink_dangling(void)
|
||||
{
|
||||
git_buf target_path = GIT_BUF_INIT;
|
||||
int len;
|
||||
char buf[2048];
|
||||
|
||||
git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_nonexistent");
|
||||
|
||||
do_symlink(git_buf_cstr(&target_path), "readlink_dangling", 0);
|
||||
|
||||
len = p_readlink("readlink_dangling", buf, 2048);
|
||||
cl_must_pass(len);
|
||||
|
||||
buf[len] = 0;
|
||||
|
||||
cl_assert_equal_s(git_buf_cstr(unslashify(&target_path)), buf);
|
||||
|
||||
git_buf_free(&target_path);
|
||||
}
|
||||
|
||||
void test_core_link__readlink_multiple(void)
|
||||
{
|
||||
git_buf target_path = GIT_BUF_INIT,
|
||||
path3 = GIT_BUF_INIT, path2 = GIT_BUF_INIT, path1 = GIT_BUF_INIT;
|
||||
int len;
|
||||
char buf[2048];
|
||||
|
||||
git_buf_join(&target_path, '/', clar_sandbox_path(), "readlink_final");
|
||||
git_buf_join(&path3, '/', clar_sandbox_path(), "readlink_3");
|
||||
git_buf_join(&path2, '/', clar_sandbox_path(), "readlink_2");
|
||||
git_buf_join(&path1, '/', clar_sandbox_path(), "readlink_1");
|
||||
|
||||
do_symlink(git_buf_cstr(&target_path), git_buf_cstr(&path3), 0);
|
||||
do_symlink(git_buf_cstr(&path3), git_buf_cstr(&path2), 0);
|
||||
do_symlink(git_buf_cstr(&path2), git_buf_cstr(&path1), 0);
|
||||
|
||||
len = p_readlink("readlink_1", buf, 2048);
|
||||
cl_must_pass(len);
|
||||
|
||||
buf[len] = 0;
|
||||
|
||||
cl_assert_equal_s(git_buf_cstr(unslashify(&path2)), buf);
|
||||
|
||||
git_buf_free(&path1);
|
||||
git_buf_free(&path2);
|
||||
git_buf_free(&path3);
|
||||
git_buf_free(&target_path);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user