2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
8 #include "../fileops.h"
12 #include "repository.h"
19 #ifndef FILE_NAME_NORMALIZED
20 # define FILE_NAME_NORMALIZED 0
23 #ifndef IO_REPARSE_TAG_SYMLINK
24 #define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
27 /* Options which we always provide to _wopen.
29 * _O_BINARY - Raw access; no translation of CR or LF characters
30 * _O_NOINHERIT - Do not mark the created handle as inheritable by child processes.
31 * The Windows default is 'not inheritable', but the CRT's default (following
32 * POSIX convention) is 'inheritable'. We have no desire for our handles to be
33 * inheritable on Windows, so specify the flag to get default behavior back. */
34 #define STANDARD_OPEN_FLAGS (_O_BINARY | _O_NOINHERIT)
36 /* Allowable mode bits on Win32. Using mode bits that are not supported on
37 * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it
38 * so we simply remove them.
40 #define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE)
42 /* GetFinalPathNameByHandleW signature */
43 typedef DWORD(WINAPI
*PFGetFinalPathNameByHandleW
)(HANDLE
, LPWSTR
, DWORD
, DWORD
);
45 int p_ftruncate(int fd
, long size
)
47 #if defined(_MSC_VER) && _MSC_VER >= 1500
48 return _chsize_s(fd
, size
);
50 return _chsize(fd
, size
);
54 int p_mkdir(const char *path
, mode_t mode
)
60 if (git_win32_path_from_utf8(buf
, path
) < 0)
66 int p_link(const char *old
, const char *new)
74 int p_unlink(const char *path
)
79 if (git_win32_path_from_utf8(buf
, path
) < 0)
82 error
= _wunlink(buf
);
84 /* If the file could not be deleted because it was
85 * read-only, clear the bit and try again */
86 if (error
== -1 && errno
== EACCES
) {
88 error
= _wunlink(buf
);
96 HANDLE fh
= (HANDLE
)_get_osfhandle(fd
);
98 if (fh
== INVALID_HANDLE_VALUE
) {
103 if (!FlushFileBuffers(fh
)) {
104 DWORD code
= GetLastError();
106 if (code
== ERROR_INVALID_HANDLE
)
117 GIT_INLINE(time_t) filetime_to_time_t(const FILETIME
*ft
)
119 long long winTime
= ((long long)ft
->dwHighDateTime
<< 32) + ft
->dwLowDateTime
;
120 winTime
-= 116444736000000000LL; /* Windows to Unix Epoch conversion */
121 winTime
/= 10000000; /* Nano to seconds resolution */
122 return (time_t)winTime
;
125 static bool path_is_volume(wchar_t *target
, size_t target_len
)
127 return (target_len
&& wcsncmp(target
, L
"\\??\\Volume{", 11) == 0);
130 /* On success, returns the length, in characters, of the path stored in dest.
131 * On failure, returns a negative value. */
132 static int readlink_w(
134 const git_win32_path path
)
136 BYTE buf
[MAXIMUM_REPARSE_DATA_BUFFER_SIZE
];
137 GIT_REPARSE_DATA_BUFFER
*reparse_buf
= (GIT_REPARSE_DATA_BUFFER
*)buf
;
138 HANDLE handle
= NULL
;
145 handle
= CreateFileW(path
, GENERIC_READ
,
146 FILE_SHARE_READ
| FILE_SHARE_DELETE
, NULL
, OPEN_EXISTING
,
147 FILE_FLAG_OPEN_REPARSE_POINT
| FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
149 if (handle
== INVALID_HANDLE_VALUE
) {
154 if (!DeviceIoControl(handle
, FSCTL_GET_REPARSE_POINT
, NULL
, 0,
155 reparse_buf
, sizeof(buf
), &ioctl_ret
, NULL
)) {
160 switch (reparse_buf
->ReparseTag
) {
161 case IO_REPARSE_TAG_SYMLINK
:
162 target
= reparse_buf
->SymbolicLinkReparseBuffer
.PathBuffer
+
163 (reparse_buf
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
));
164 target_len
= reparse_buf
->SymbolicLinkReparseBuffer
.SubstituteNameLength
/ sizeof(WCHAR
);
166 case IO_REPARSE_TAG_MOUNT_POINT
:
167 target
= reparse_buf
->MountPointReparseBuffer
.PathBuffer
+
168 (reparse_buf
->MountPointReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
));
169 target_len
= reparse_buf
->MountPointReparseBuffer
.SubstituteNameLength
/ sizeof(WCHAR
);
176 if (path_is_volume(target
, target_len
)) {
177 /* This path is a reparse point that represents another volume mounted
178 * at this location, it is not a symbolic link our input was canonical.
182 } else if (target_len
) {
183 /* The path may need to have a prefix removed. */
184 target_len
= git_win32__canonicalize_path(target
, target_len
);
186 /* Need one additional character in the target buffer
187 * for the terminating NULL. */
188 if (GIT_WIN_PATH_UTF16
> target_len
) {
189 wcscpy(dest
, target
);
190 error
= (int)target_len
;
199 #define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
206 WIN32_FILE_ATTRIBUTE_DATA fdata
;
208 if (GetFileAttributesExW(path
, GetFileExInfoStandard
, &fdata
)) {
214 if (fdata
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
219 if (!(fdata
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
))
226 buf
->st_mode
= (mode_t
)fMode
;
227 buf
->st_size
= ((git_off_t
)fdata
.nFileSizeHigh
<< 32) + fdata
.nFileSizeLow
;
228 buf
->st_dev
= buf
->st_rdev
= (_getdrive() - 1);
229 buf
->st_atime
= filetime_to_time_t(&(fdata
.ftLastAccessTime
));
230 buf
->st_mtime
= filetime_to_time_t(&(fdata
.ftLastWriteTime
));
231 buf
->st_ctime
= filetime_to_time_t(&(fdata
.ftCreationTime
));
233 if (fdata
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) {
234 git_win32_path target
;
236 if (readlink_w(target
, path
) >= 0) {
237 buf
->st_mode
= (buf
->st_mode
& ~S_IFMT
) | S_IFLNK
;
239 /* st_size gets the UTF-8 length of the target name, in bytes,
240 * not counting the NULL terminator */
241 if ((buf
->st_size
= git__utf16_to_8(NULL
, 0, target
)) < 0)
251 /* To match POSIX behavior, set ENOTDIR when any of the folders in the
252 * file path is a regular file, otherwise set ENOENT.
255 size_t path_len
= wcslen(path
);
257 /* scan up path until we find an existing item */
261 /* remove last directory component */
262 for (path_len
--; path_len
> 0 && !WIN32_IS_WSEP(path
[path_len
]); path_len
--);
267 path
[path_len
] = L
'\0';
268 attrs
= GetFileAttributesW(path
);
270 if (attrs
!= INVALID_FILE_ATTRIBUTES
) {
271 if (!(attrs
& FILE_ATTRIBUTE_DIRECTORY
))
281 static int do_lstat(const char *path
, struct stat
*buf
, bool posixly_correct
)
283 git_win32_path path_w
;
286 if ((len
= git_win32_path_from_utf8(path_w
, path
)) < 0)
289 git_win32__path_trim_end(path_w
, len
);
291 return lstat_w(path_w
, buf
, posixly_correct
);
294 int p_lstat(const char *filename
, struct stat
*buf
)
296 return do_lstat(filename
, buf
, false);
299 int p_lstat_posixly(const char *filename
, struct stat
*buf
)
301 return do_lstat(filename
, buf
, true);
304 int p_readlink(const char *path
, char *buf
, size_t bufsiz
)
306 git_win32_path path_w
, target_w
;
307 git_win32_utf8_path target
;
310 /* readlink(2) does not NULL-terminate the string written
311 * to the target buffer. Furthermore, the target buffer need
312 * not be large enough to hold the entire result. A truncated
313 * result should be written in this case. Since this truncation
314 * could occur in the middle of the encoding of a code point,
315 * we need to buffer the result on the stack. */
317 if (git_win32_path_from_utf8(path_w
, path
) < 0 ||
318 readlink_w(target_w
, path_w
) < 0 ||
319 (len
= git_win32_path_to_utf8(target
, target_w
)) < 0)
322 bufsiz
= min((size_t)len
, bufsiz
);
323 memcpy(buf
, target
, bufsiz
);
328 int p_symlink(const char *old
, const char *new)
330 /* Real symlinks on NTFS require admin privileges. Until this changes,
331 * libgit2 just creates a text file with the link target in the contents.
333 return git_futils_fake_symlink(old
, new);
336 int p_open(const char *path
, int flags
, ...)
341 if (git_win32_path_from_utf8(buf
, path
) < 0)
344 if (flags
& O_CREAT
) {
347 va_start(arg_list
, flags
);
348 mode
= (mode_t
)va_arg(arg_list
, int);
352 return _wopen(buf
, flags
| STANDARD_OPEN_FLAGS
, mode
& WIN32_MODE_MASK
);
355 int p_creat(const char *path
, mode_t mode
)
359 if (git_win32_path_from_utf8(buf
, path
) < 0)
363 _O_WRONLY
| _O_CREAT
| _O_TRUNC
| STANDARD_OPEN_FLAGS
,
364 mode
& WIN32_MODE_MASK
);
367 int p_getcwd(char *buffer_out
, size_t size
)
370 wchar_t *cwd
= _wgetcwd(buf
, GIT_WIN_PATH_UTF16
);
375 /* Convert the working directory back to UTF-8 */
376 if (git__utf16_to_8(buffer_out
, size
, cwd
) < 0) {
377 DWORD code
= GetLastError();
379 if (code
== ERROR_INSUFFICIENT_BUFFER
)
391 * Returns the address of the GetFinalPathNameByHandleW function.
392 * This function is available on Windows Vista and higher.
394 static PFGetFinalPathNameByHandleW
get_fpnbyhandle(void)
396 static PFGetFinalPathNameByHandleW pFunc
= NULL
;
397 PFGetFinalPathNameByHandleW toReturn
= pFunc
;
400 HMODULE hModule
= GetModuleHandleW(L
"kernel32");
403 toReturn
= (PFGetFinalPathNameByHandleW
)GetProcAddress(hModule
, "GetFinalPathNameByHandleW");
413 static int getfinalpath_w(
417 PFGetFinalPathNameByHandleW pgfp
= get_fpnbyhandle();
424 /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
425 * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
426 * target of the link. */
427 hFile
= CreateFileW(path
, GENERIC_READ
, FILE_SHARE_READ
| FILE_SHARE_DELETE
,
428 NULL
, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS
, NULL
);
430 if (INVALID_HANDLE_VALUE
== hFile
)
433 /* Call GetFinalPathNameByHandle */
434 dwChars
= pgfp(hFile
, dest
, GIT_WIN_PATH_UTF16
, FILE_NAME_NORMALIZED
);
437 if (!dwChars
|| dwChars
>= GIT_WIN_PATH_UTF16
)
440 /* The path may be delivered to us with a prefix; canonicalize */
441 return (int)git_win32__canonicalize_path(dest
, dwChars
);
444 static int follow_and_lstat_link(git_win32_path path
, struct stat
* buf
)
446 git_win32_path target_w
;
448 if (getfinalpath_w(target_w
, path
) < 0)
451 return lstat_w(target_w
, buf
, false);
454 int p_stat(const char* path
, struct stat
* buf
)
456 git_win32_path path_w
;
459 if ((len
= git_win32_path_from_utf8(path_w
, path
)) < 0 ||
460 lstat_w(path_w
, buf
, false) < 0)
463 /* The item is a symbolic link or mount point. No need to iterate
464 * to follow multiple links; use GetFinalPathNameFromHandle. */
465 if (S_ISLNK(buf
->st_mode
))
466 return follow_and_lstat_link(path_w
, buf
);
471 int p_chdir(const char* path
)
475 if (git_win32_path_from_utf8(buf
, path
) < 0)
481 int p_chmod(const char* path
, mode_t mode
)
485 if (git_win32_path_from_utf8(buf
, path
) < 0)
488 return _wchmod(buf
, mode
);
491 int p_rmdir(const char* path
)
496 if (git_win32_path_from_utf8(buf
, path
) < 0)
499 error
= _wrmdir(buf
);
502 switch (GetLastError()) {
503 /* _wrmdir() is documented to return EACCES if "A program has an open
504 * handle to the directory." This sounds like what everybody else calls
505 * EBUSY. Let's convert appropriate error codes.
507 case ERROR_SHARING_VIOLATION
:
511 /* This error can be returned when trying to rmdir an extant file. */
512 case ERROR_DIRECTORY
:
521 char *p_realpath(const char *orig_path
, char *buffer
)
523 git_win32_path orig_path_w
, buffer_w
;
525 if (git_win32_path_from_utf8(orig_path_w
, orig_path
) < 0)
528 /* Note that if the path provided is a relative path, then the current directory
529 * is used to resolve the path -- which is a concurrency issue because the current
530 * directory is a process-wide variable. */
531 if (!GetFullPathNameW(orig_path_w
, GIT_WIN_PATH_UTF16
, buffer_w
, NULL
)) {
532 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER
)
533 errno
= ENAMETOOLONG
;
540 /* The path must exist. */
541 if (GetFileAttributesW(buffer_w
) == INVALID_FILE_ATTRIBUTES
) {
546 if (!buffer
&& !(buffer
= git__malloc(GIT_WIN_PATH_UTF8
))) {
551 /* Convert the path to UTF-8. If the caller provided a buffer, then it
552 * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't,
553 * then we may overflow. */
554 if (git_win32_path_to_utf8(buffer
, buffer_w
) < 0)
557 git_path_mkposix(buffer
);
562 int p_vsnprintf(char *buffer
, size_t count
, const char *format
, va_list argptr
)
564 #if defined(_MSC_VER)
568 return _vscprintf(format
, argptr
);
571 len
= _vsnprintf_s(buffer
, count
, _TRUNCATE
, format
, argptr
);
573 len
= _vsnprintf(buffer
, count
, format
, argptr
);
577 return _vscprintf(format
, argptr
);
581 return vsnprintf(buffer
, count
, format
, argptr
);
585 int p_snprintf(char *buffer
, size_t count
, const char *format
, ...)
590 va_start(va
, format
);
591 r
= p_vsnprintf(buffer
, count
, format
, va
);
598 int p_mkstemp(char *tmp_path
)
600 #if defined(_MSC_VER) && _MSC_VER >= 1500
601 if (_mktemp_s(tmp_path
, strlen(tmp_path
) + 1) != 0)
604 if (_mktemp(tmp_path
) == NULL
)
608 return p_open(tmp_path
, O_RDWR
| O_CREAT
| O_EXCL
, 0744); //-V536
611 int p_access(const char* path
, mode_t mode
)
615 if (git_win32_path_from_utf8(buf
, path
) < 0)
618 return _waccess(buf
, mode
& WIN32_MODE_MASK
);
621 static int ensure_writable(wchar_t *fpath
)
625 attrs
= GetFileAttributesW(fpath
);
626 if (attrs
== INVALID_FILE_ATTRIBUTES
) {
627 if (GetLastError() == ERROR_FILE_NOT_FOUND
)
630 giterr_set(GITERR_OS
, "failed to get attributes");
634 if (!(attrs
& FILE_ATTRIBUTE_READONLY
))
637 attrs
&= ~FILE_ATTRIBUTE_READONLY
;
638 if (!SetFileAttributesW(fpath
, attrs
)) {
639 giterr_set(GITERR_OS
, "failed to set attributes");
646 int p_rename(const char *from
, const char *to
)
648 git_win32_path wfrom
;
651 int rename_succeeded
;
654 if (git_win32_path_from_utf8(wfrom
, from
) < 0 ||
655 git_win32_path_from_utf8(wto
, to
) < 0)
658 /* wait up to 50ms if file is locked by another thread or process */
660 rename_succeeded
= 0;
661 while (rename_tries
< 10) {
662 if (ensure_writable(wto
) == 0 &&
663 MoveFileExW(wfrom
, wto
, MOVEFILE_REPLACE_EXISTING
| MOVEFILE_COPY_ALLOWED
) != 0) {
664 rename_succeeded
= 1;
668 error
= GetLastError();
669 if (error
== ERROR_SHARING_VIOLATION
|| error
== ERROR_ACCESS_DENIED
) {
676 return rename_succeeded
? 0 : -1;
679 int p_recv(GIT_SOCKET socket
, void *buffer
, size_t length
, int flags
)
681 if ((size_t)((int)length
) != length
)
682 return -1; /* giterr_set will be done by caller */
684 return recv(socket
, buffer
, (int)length
, flags
);
687 int p_send(GIT_SOCKET socket
, const void *buffer
, size_t length
, int flags
)
689 if ((size_t)((int)length
) != length
)
690 return -1; /* giterr_set will be done by caller */
692 return send(socket
, buffer
, (int)length
, flags
);
696 * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html
697 * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that
700 p_localtime_r (const time_t *timer
, struct tm
*result
)
702 struct tm
*local_result
;
703 local_result
= localtime (timer
);
705 if (local_result
== NULL
|| result
== NULL
)
708 memcpy (result
, local_result
, sizeof (struct tm
));
712 p_gmtime_r (const time_t *timer
, struct tm
*result
)
714 struct tm
*local_result
;
715 local_result
= gmtime (timer
);
717 if (local_result
== NULL
|| result
== NULL
)
720 memcpy (result
, local_result
, sizeof (struct tm
));
724 int p_inet_pton(int af
, const char *src
, void *dst
)
726 struct sockaddr_storage sin
;
728 int sin_len
= sizeof(struct sockaddr_storage
), addr_len
;
732 addr
= &((struct sockaddr_in
*)&sin
)->sin_addr
;
733 addr_len
= sizeof(struct in_addr
);
734 } else if (af
== AF_INET6
) {
735 addr
= &((struct sockaddr_in6
*)&sin
)->sin6_addr
;
736 addr_len
= sizeof(struct in6_addr
);
738 errno
= EAFNOSUPPORT
;
742 if ((error
= WSAStringToAddressA((LPSTR
)src
, af
, NULL
, (LPSOCKADDR
)&sin
, &sin_len
)) == 0) {
743 memcpy(dst
, addr
, addr_len
);
747 switch(WSAGetLastError()) {
753 case WSA_NOT_ENOUGH_MEMORY
: