1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
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.
6 */
7
8 #include "common.h"
9
10 #include "../posix.h"
11 #include "../futils.h"
12 #include "path.h"
13 #include "path_w32.h"
14 #include "utf-conv.h"
15 #include "repository.h"
16 #include "reparse.h"
17 #include "buffer.h"
18 #include <errno.h>
19 #include <io.h>
20 #include <fcntl.h>
21 #include <ws2tcpip.h>
22
23 #ifndef FILE_NAME_NORMALIZED
24 # define FILE_NAME_NORMALIZED 0
25 #endif
26
27 #ifndef IO_REPARSE_TAG_SYMLINK
28 #define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
29 #endif
30
31 #ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
32 # define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02
33 #endif
34
35 #ifndef SYMBOLIC_LINK_FLAG_DIRECTORY
36 # define SYMBOLIC_LINK_FLAG_DIRECTORY 0x01
37 #endif
38
39 /* Allowable mode bits on Win32. Using mode bits that are not supported on
40 * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it
41 * so we simply remove them.
42 */
43 #define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE)
44
45 unsigned long git_win32__createfile_sharemode =
46 FILE_SHARE_READ | FILE_SHARE_WRITE;
47 int git_win32__retries = 10;
48
set_errno(void)49 GIT_INLINE(void) set_errno(void)
50 {
51 switch (GetLastError()) {
52 case ERROR_FILE_NOT_FOUND:
53 case ERROR_PATH_NOT_FOUND:
54 case ERROR_INVALID_DRIVE:
55 case ERROR_NO_MORE_FILES:
56 case ERROR_BAD_NETPATH:
57 case ERROR_BAD_NET_NAME:
58 case ERROR_BAD_PATHNAME:
59 case ERROR_FILENAME_EXCED_RANGE:
60 errno = ENOENT;
61 break;
62 case ERROR_BAD_ENVIRONMENT:
63 errno = E2BIG;
64 break;
65 case ERROR_BAD_FORMAT:
66 case ERROR_INVALID_STARTING_CODESEG:
67 case ERROR_INVALID_STACKSEG:
68 case ERROR_INVALID_MODULETYPE:
69 case ERROR_INVALID_EXE_SIGNATURE:
70 case ERROR_EXE_MARKED_INVALID:
71 case ERROR_BAD_EXE_FORMAT:
72 case ERROR_ITERATED_DATA_EXCEEDS_64k:
73 case ERROR_INVALID_MINALLOCSIZE:
74 case ERROR_DYNLINK_FROM_INVALID_RING:
75 case ERROR_IOPL_NOT_ENABLED:
76 case ERROR_INVALID_SEGDPL:
77 case ERROR_AUTODATASEG_EXCEEDS_64k:
78 case ERROR_RING2SEG_MUST_BE_MOVABLE:
79 case ERROR_RELOC_CHAIN_XEEDS_SEGLIM:
80 case ERROR_INFLOOP_IN_RELOC_CHAIN:
81 errno = ENOEXEC;
82 break;
83 case ERROR_INVALID_HANDLE:
84 case ERROR_INVALID_TARGET_HANDLE:
85 case ERROR_DIRECT_ACCESS_HANDLE:
86 errno = EBADF;
87 break;
88 case ERROR_WAIT_NO_CHILDREN:
89 case ERROR_CHILD_NOT_COMPLETE:
90 errno = ECHILD;
91 break;
92 case ERROR_NO_PROC_SLOTS:
93 case ERROR_MAX_THRDS_REACHED:
94 case ERROR_NESTING_NOT_ALLOWED:
95 errno = EAGAIN;
96 break;
97 case ERROR_ARENA_TRASHED:
98 case ERROR_NOT_ENOUGH_MEMORY:
99 case ERROR_INVALID_BLOCK:
100 case ERROR_NOT_ENOUGH_QUOTA:
101 errno = ENOMEM;
102 break;
103 case ERROR_ACCESS_DENIED:
104 case ERROR_CURRENT_DIRECTORY:
105 case ERROR_WRITE_PROTECT:
106 case ERROR_BAD_UNIT:
107 case ERROR_NOT_READY:
108 case ERROR_BAD_COMMAND:
109 case ERROR_CRC:
110 case ERROR_BAD_LENGTH:
111 case ERROR_SEEK:
112 case ERROR_NOT_DOS_DISK:
113 case ERROR_SECTOR_NOT_FOUND:
114 case ERROR_OUT_OF_PAPER:
115 case ERROR_WRITE_FAULT:
116 case ERROR_READ_FAULT:
117 case ERROR_GEN_FAILURE:
118 case ERROR_SHARING_VIOLATION:
119 case ERROR_LOCK_VIOLATION:
120 case ERROR_WRONG_DISK:
121 case ERROR_SHARING_BUFFER_EXCEEDED:
122 case ERROR_NETWORK_ACCESS_DENIED:
123 case ERROR_CANNOT_MAKE:
124 case ERROR_FAIL_I24:
125 case ERROR_DRIVE_LOCKED:
126 case ERROR_SEEK_ON_DEVICE:
127 case ERROR_NOT_LOCKED:
128 case ERROR_LOCK_FAILED:
129 errno = EACCES;
130 break;
131 case ERROR_FILE_EXISTS:
132 case ERROR_ALREADY_EXISTS:
133 errno = EEXIST;
134 break;
135 case ERROR_NOT_SAME_DEVICE:
136 errno = EXDEV;
137 break;
138 case ERROR_INVALID_FUNCTION:
139 case ERROR_INVALID_ACCESS:
140 case ERROR_INVALID_DATA:
141 case ERROR_INVALID_PARAMETER:
142 case ERROR_NEGATIVE_SEEK:
143 errno = EINVAL;
144 break;
145 case ERROR_TOO_MANY_OPEN_FILES:
146 errno = EMFILE;
147 break;
148 case ERROR_DISK_FULL:
149 errno = ENOSPC;
150 break;
151 case ERROR_BROKEN_PIPE:
152 errno = EPIPE;
153 break;
154 case ERROR_DIR_NOT_EMPTY:
155 errno = ENOTEMPTY;
156 break;
157 default:
158 errno = EINVAL;
159 }
160 }
161
last_error_retryable(void)162 GIT_INLINE(bool) last_error_retryable(void)
163 {
164 int os_error = GetLastError();
165
166 return (os_error == ERROR_SHARING_VIOLATION ||
167 os_error == ERROR_ACCESS_DENIED);
168 }
169
170 #define do_with_retries(fn, remediation) \
171 do { \
172 int __retry, __ret; \
173 for (__retry = git_win32__retries; __retry; __retry--) { \
174 if ((__ret = (fn)) != GIT_RETRY) \
175 return __ret; \
176 if (__retry > 1 && (__ret = (remediation)) != 0) { \
177 if (__ret == GIT_RETRY) \
178 continue; \
179 return __ret; \
180 } \
181 Sleep(5); \
182 } \
183 return -1; \
184 } while (0) \
185
ensure_writable(wchar_t * path)186 static int ensure_writable(wchar_t *path)
187 {
188 DWORD attrs;
189
190 if ((attrs = GetFileAttributesW(path)) == INVALID_FILE_ATTRIBUTES)
191 goto on_error;
192
193 if ((attrs & FILE_ATTRIBUTE_READONLY) == 0)
194 return 0;
195
196 if (!SetFileAttributesW(path, (attrs & ~FILE_ATTRIBUTE_READONLY)))
197 goto on_error;
198
199 return GIT_RETRY;
200
201 on_error:
202 set_errno();
203 return -1;
204 }
205
206 /**
207 * Truncate or extend file.
208 *
209 * We now take a "git_off_t" rather than "long" because
210 * files may be longer than 2Gb.
211 */
p_ftruncate(int fd,off64_t size)212 int p_ftruncate(int fd, off64_t size)
213 {
214 if (size < 0) {
215 errno = EINVAL;
216 return -1;
217 }
218
219 #if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API)
220 return ((_chsize_s(fd, size) == 0) ? 0 : -1);
221 #else
222 /* TODO MINGW32 Find a replacement for _chsize() that handles big files. */
223 if (size > INT32_MAX) {
224 errno = EFBIG;
225 return -1;
226 }
227 return _chsize(fd, (long)size);
228 #endif
229 }
230
p_mkdir(const char * path,mode_t mode)231 int p_mkdir(const char *path, mode_t mode)
232 {
233 git_win32_path buf;
234
235 GIT_UNUSED(mode);
236
237 if (git_win32_path_from_utf8(buf, path) < 0)
238 return -1;
239
240 return _wmkdir(buf);
241 }
242
p_link(const char * old,const char * new)243 int p_link(const char *old, const char *new)
244 {
245 GIT_UNUSED(old);
246 GIT_UNUSED(new);
247 errno = ENOSYS;
248 return -1;
249 }
250
unlink_once(const wchar_t * path)251 GIT_INLINE(int) unlink_once(const wchar_t *path)
252 {
253 DWORD error;
254
255 if (DeleteFileW(path))
256 return 0;
257
258 if ((error = GetLastError()) == ERROR_ACCESS_DENIED) {
259 WIN32_FILE_ATTRIBUTE_DATA fdata;
260 if (!GetFileAttributesExW(path, GetFileExInfoStandard, &fdata) ||
261 !(fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
262 !(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
263 goto out;
264
265 if (RemoveDirectoryW(path))
266 return 0;
267 }
268
269 out:
270 SetLastError(error);
271
272 if (last_error_retryable())
273 return GIT_RETRY;
274
275 set_errno();
276 return -1;
277 }
278
p_unlink(const char * path)279 int p_unlink(const char *path)
280 {
281 git_win32_path wpath;
282
283 if (git_win32_path_from_utf8(wpath, path) < 0)
284 return -1;
285
286 do_with_retries(unlink_once(wpath), ensure_writable(wpath));
287 }
288
p_fsync(int fd)289 int p_fsync(int fd)
290 {
291 HANDLE fh = (HANDLE)_get_osfhandle(fd);
292
293 p_fsync__cnt++;
294
295 if (fh == INVALID_HANDLE_VALUE) {
296 errno = EBADF;
297 return -1;
298 }
299
300 if (!FlushFileBuffers(fh)) {
301 DWORD code = GetLastError();
302
303 if (code == ERROR_INVALID_HANDLE)
304 errno = EINVAL;
305 else
306 errno = EIO;
307
308 return -1;
309 }
310
311 return 0;
312 }
313
314 #define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
315
lstat_w(wchar_t * path,struct stat * buf,bool posix_enotdir)316 static int lstat_w(
317 wchar_t *path,
318 struct stat *buf,
319 bool posix_enotdir)
320 {
321 WIN32_FILE_ATTRIBUTE_DATA fdata;
322
323 if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) {
324 if (!buf)
325 return 0;
326
327 return git_win32__file_attribute_to_stat(buf, &fdata, path);
328 }
329
330 switch (GetLastError()) {
331 case ERROR_ACCESS_DENIED:
332 errno = EACCES;
333 break;
334 default:
335 errno = ENOENT;
336 break;
337 }
338
339 /* To match POSIX behavior, set ENOTDIR when any of the folders in the
340 * file path is a regular file, otherwise set ENOENT.
341 */
342 if (errno == ENOENT && posix_enotdir) {
343 size_t path_len = wcslen(path);
344
345 /* scan up path until we find an existing item */
346 while (1) {
347 DWORD attrs;
348
349 /* remove last directory component */
350 for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--);
351
352 if (path_len <= 0)
353 break;
354
355 path[path_len] = L'\0';
356 attrs = GetFileAttributesW(path);
357
358 if (attrs != INVALID_FILE_ATTRIBUTES) {
359 if (!(attrs & FILE_ATTRIBUTE_DIRECTORY))
360 errno = ENOTDIR;
361 break;
362 }
363 }
364 }
365
366 return -1;
367 }
368
do_lstat(const char * path,struct stat * buf,bool posixly_correct)369 static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
370 {
371 git_win32_path path_w;
372 int len;
373
374 if ((len = git_win32_path_from_utf8(path_w, path)) < 0)
375 return -1;
376
377 git_win32_path_trim_end(path_w, len);
378
379 return lstat_w(path_w, buf, posixly_correct);
380 }
381
p_lstat(const char * filename,struct stat * buf)382 int p_lstat(const char *filename, struct stat *buf)
383 {
384 return do_lstat(filename, buf, false);
385 }
386
p_lstat_posixly(const char * filename,struct stat * buf)387 int p_lstat_posixly(const char *filename, struct stat *buf)
388 {
389 return do_lstat(filename, buf, true);
390 }
391
p_readlink(const char * path,char * buf,size_t bufsiz)392 int p_readlink(const char *path, char *buf, size_t bufsiz)
393 {
394 git_win32_path path_w, target_w;
395 git_win32_utf8_path target;
396 int len;
397
398 /* readlink(2) does not NULL-terminate the string written
399 * to the target buffer. Furthermore, the target buffer need
400 * not be large enough to hold the entire result. A truncated
401 * result should be written in this case. Since this truncation
402 * could occur in the middle of the encoding of a code point,
403 * we need to buffer the result on the stack. */
404
405 if (git_win32_path_from_utf8(path_w, path) < 0 ||
406 git_win32_path_readlink_w(target_w, path_w) < 0 ||
407 (len = git_win32_path_to_utf8(target, target_w)) < 0)
408 return -1;
409
410 bufsiz = min((size_t)len, bufsiz);
411 memcpy(buf, target, bufsiz);
412
413 return (int)bufsiz;
414 }
415
target_is_dir(const char * target,const char * path)416 static bool target_is_dir(const char *target, const char *path)
417 {
418 git_buf resolved = GIT_BUF_INIT;
419 git_win32_path resolved_w;
420 bool isdir = true;
421
422 if (git_path_is_absolute(target))
423 git_win32_path_from_utf8(resolved_w, target);
424 else if (git_path_dirname_r(&resolved, path) < 0 ||
425 git_path_apply_relative(&resolved, target) < 0 ||
426 git_win32_path_from_utf8(resolved_w, resolved.ptr) < 0)
427 goto out;
428
429 isdir = GetFileAttributesW(resolved_w) & FILE_ATTRIBUTE_DIRECTORY;
430
431 out:
432 git_buf_dispose(&resolved);
433 return isdir;
434 }
435
p_symlink(const char * target,const char * path)436 int p_symlink(const char *target, const char *path)
437 {
438 git_win32_path target_w, path_w;
439 DWORD dwFlags;
440
441 /*
442 * Convert both target and path to Windows-style paths. Note that we do
443 * not want to use `git_win32_path_from_utf8` for converting the target,
444 * as that function will automatically pre-pend the current working
445 * directory in case the path is not absolute. As Git will instead use
446 * relative symlinks, this is not someting we want.
447 */
448 if (git_win32_path_from_utf8(path_w, path) < 0 ||
449 git_win32_path_relative_from_utf8(target_w, target) < 0)
450 return -1;
451
452 dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
453 if (target_is_dir(target, path))
454 dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
455
456 if (!CreateSymbolicLinkW(path_w, target_w, dwFlags))
457 return -1;
458
459 return 0;
460 }
461
462 struct open_opts {
463 DWORD access;
464 DWORD sharing;
465 SECURITY_ATTRIBUTES security;
466 DWORD creation_disposition;
467 DWORD attributes;
468 int osf_flags;
469 };
470
open_opts_from_posix(struct open_opts * opts,int flags,mode_t mode)471 GIT_INLINE(void) open_opts_from_posix(struct open_opts *opts, int flags, mode_t mode)
472 {
473 memset(opts, 0, sizeof(struct open_opts));
474
475 switch (flags & (O_WRONLY | O_RDWR)) {
476 case O_WRONLY:
477 opts->access = GENERIC_WRITE;
478 break;
479 case O_RDWR:
480 opts->access = GENERIC_READ | GENERIC_WRITE;
481 break;
482 default:
483 opts->access = GENERIC_READ;
484 break;
485 }
486
487 opts->sharing = (DWORD)git_win32__createfile_sharemode;
488
489 switch (flags & (O_CREAT | O_TRUNC | O_EXCL)) {
490 case O_CREAT | O_EXCL:
491 case O_CREAT | O_TRUNC | O_EXCL:
492 opts->creation_disposition = CREATE_NEW;
493 break;
494 case O_CREAT | O_TRUNC:
495 opts->creation_disposition = CREATE_ALWAYS;
496 break;
497 case O_TRUNC:
498 opts->creation_disposition = TRUNCATE_EXISTING;
499 break;
500 case O_CREAT:
501 opts->creation_disposition = OPEN_ALWAYS;
502 break;
503 default:
504 opts->creation_disposition = OPEN_EXISTING;
505 break;
506 }
507
508 opts->attributes = ((flags & O_CREAT) && !(mode & S_IWRITE)) ?
509 FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL;
510 opts->osf_flags = flags & (O_RDONLY | O_APPEND);
511
512 opts->security.nLength = sizeof(SECURITY_ATTRIBUTES);
513 opts->security.lpSecurityDescriptor = NULL;
514 opts->security.bInheritHandle = 0;
515 }
516
open_once(const wchar_t * path,struct open_opts * opts)517 GIT_INLINE(int) open_once(
518 const wchar_t *path,
519 struct open_opts *opts)
520 {
521 int fd;
522
523 HANDLE handle = CreateFileW(path, opts->access, opts->sharing,
524 &opts->security, opts->creation_disposition, opts->attributes, 0);
525
526 if (handle == INVALID_HANDLE_VALUE) {
527 if (last_error_retryable())
528 return GIT_RETRY;
529
530 set_errno();
531 return -1;
532 }
533
534 if ((fd = _open_osfhandle((intptr_t)handle, opts->osf_flags)) < 0)
535 CloseHandle(handle);
536
537 return fd;
538 }
539
p_open(const char * path,int flags,...)540 int p_open(const char *path, int flags, ...)
541 {
542 git_win32_path wpath;
543 mode_t mode = 0;
544 struct open_opts opts = {0};
545
546 #ifdef GIT_DEBUG_STRICT_OPEN
547 if (strstr(path, "//") != NULL) {
548 errno = EACCES;
549 return -1;
550 }
551 #endif
552
553 if (git_win32_path_from_utf8(wpath, path) < 0)
554 return -1;
555
556 if (flags & O_CREAT) {
557 va_list arg_list;
558
559 va_start(arg_list, flags);
560 mode = (mode_t)va_arg(arg_list, int);
561 va_end(arg_list);
562 }
563
564 open_opts_from_posix(&opts, flags, mode);
565
566 do_with_retries(
567 open_once(wpath, &opts),
568 0);
569 }
570
p_creat(const char * path,mode_t mode)571 int p_creat(const char *path, mode_t mode)
572 {
573 return p_open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
574 }
575
p_utimes(const char * path,const struct p_timeval times[2])576 int p_utimes(const char *path, const struct p_timeval times[2])
577 {
578 git_win32_path wpath;
579 int fd, error;
580 DWORD attrs_orig, attrs_new = 0;
581 struct open_opts opts = { 0 };
582
583 if (git_win32_path_from_utf8(wpath, path) < 0)
584 return -1;
585
586 attrs_orig = GetFileAttributesW(wpath);
587
588 if (attrs_orig & FILE_ATTRIBUTE_READONLY) {
589 attrs_new = attrs_orig & ~FILE_ATTRIBUTE_READONLY;
590
591 if (!SetFileAttributesW(wpath, attrs_new)) {
592 git_error_set(GIT_ERROR_OS, "failed to set attributes");
593 return -1;
594 }
595 }
596
597 open_opts_from_posix(&opts, O_RDWR, 0);
598
599 if ((fd = open_once(wpath, &opts)) < 0) {
600 error = -1;
601 goto done;
602 }
603
604 error = p_futimes(fd, times);
605 close(fd);
606
607 done:
608 if (attrs_orig != attrs_new) {
609 DWORD os_error = GetLastError();
610 SetFileAttributesW(wpath, attrs_orig);
611 SetLastError(os_error);
612 }
613
614 return error;
615 }
616
p_futimes(int fd,const struct p_timeval times[2])617 int p_futimes(int fd, const struct p_timeval times[2])
618 {
619 HANDLE handle;
620 FILETIME atime = { 0 }, mtime = { 0 };
621
622 if (times == NULL) {
623 SYSTEMTIME st;
624
625 GetSystemTime(&st);
626 SystemTimeToFileTime(&st, &atime);
627 SystemTimeToFileTime(&st, &mtime);
628 }
629 else {
630 git_win32__timeval_to_filetime(&atime, times[0]);
631 git_win32__timeval_to_filetime(&mtime, times[1]);
632 }
633
634 if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE)
635 return -1;
636
637 if (SetFileTime(handle, NULL, &atime, &mtime) == 0)
638 return -1;
639
640 return 0;
641 }
642
p_getcwd(char * buffer_out,size_t size)643 int p_getcwd(char *buffer_out, size_t size)
644 {
645 git_win32_path buf;
646 wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16);
647
648 if (!cwd)
649 return -1;
650
651 git_win32_path_remove_namespace(cwd, wcslen(cwd));
652
653 /* Convert the working directory back to UTF-8 */
654 if (git__utf16_to_8(buffer_out, size, cwd) < 0) {
655 DWORD code = GetLastError();
656
657 if (code == ERROR_INSUFFICIENT_BUFFER)
658 errno = ERANGE;
659 else
660 errno = EINVAL;
661
662 return -1;
663 }
664
665 git_path_mkposix(buffer_out);
666 return 0;
667 }
668
getfinalpath_w(git_win32_path dest,const wchar_t * path)669 static int getfinalpath_w(
670 git_win32_path dest,
671 const wchar_t *path)
672 {
673 HANDLE hFile;
674 DWORD dwChars;
675
676 /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
677 * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
678 * target of the link. */
679 hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
680 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
681
682 if (INVALID_HANDLE_VALUE == hFile)
683 return -1;
684
685 /* Call GetFinalPathNameByHandle */
686 dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
687 CloseHandle(hFile);
688
689 if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)
690 return -1;
691
692 /* The path may be delivered to us with a namespace prefix; remove */
693 return (int)git_win32_path_remove_namespace(dest, dwChars);
694 }
695
follow_and_lstat_link(git_win32_path path,struct stat * buf)696 static int follow_and_lstat_link(git_win32_path path, struct stat *buf)
697 {
698 git_win32_path target_w;
699
700 if (getfinalpath_w(target_w, path) < 0)
701 return -1;
702
703 return lstat_w(target_w, buf, false);
704 }
705
p_fstat(int fd,struct stat * buf)706 int p_fstat(int fd, struct stat *buf)
707 {
708 BY_HANDLE_FILE_INFORMATION fhInfo;
709
710 HANDLE fh = (HANDLE)_get_osfhandle(fd);
711
712 if (fh == INVALID_HANDLE_VALUE ||
713 !GetFileInformationByHandle(fh, &fhInfo)) {
714 errno = EBADF;
715 return -1;
716 }
717
718 git_win32__file_information_to_stat(buf, &fhInfo);
719 return 0;
720 }
721
p_stat(const char * path,struct stat * buf)722 int p_stat(const char *path, struct stat *buf)
723 {
724 git_win32_path path_w;
725 int len;
726
727 if ((len = git_win32_path_from_utf8(path_w, path)) < 0 ||
728 lstat_w(path_w, buf, false) < 0)
729 return -1;
730
731 /* The item is a symbolic link or mount point. No need to iterate
732 * to follow multiple links; use GetFinalPathNameFromHandle. */
733 if (S_ISLNK(buf->st_mode))
734 return follow_and_lstat_link(path_w, buf);
735
736 return 0;
737 }
738
p_chdir(const char * path)739 int p_chdir(const char *path)
740 {
741 git_win32_path buf;
742
743 if (git_win32_path_from_utf8(buf, path) < 0)
744 return -1;
745
746 return _wchdir(buf);
747 }
748
p_chmod(const char * path,mode_t mode)749 int p_chmod(const char *path, mode_t mode)
750 {
751 git_win32_path buf;
752
753 if (git_win32_path_from_utf8(buf, path) < 0)
754 return -1;
755
756 return _wchmod(buf, mode);
757 }
758
p_rmdir(const char * path)759 int p_rmdir(const char *path)
760 {
761 git_win32_path buf;
762 int error;
763
764 if (git_win32_path_from_utf8(buf, path) < 0)
765 return -1;
766
767 error = _wrmdir(buf);
768
769 if (error == -1) {
770 switch (GetLastError()) {
771 /* _wrmdir() is documented to return EACCES if "A program has an open
772 * handle to the directory." This sounds like what everybody else calls
773 * EBUSY. Let's convert appropriate error codes.
774 */
775 case ERROR_SHARING_VIOLATION:
776 errno = EBUSY;
777 break;
778
779 /* This error can be returned when trying to rmdir an extant file. */
780 case ERROR_DIRECTORY:
781 errno = ENOTDIR;
782 break;
783 }
784 }
785
786 return error;
787 }
788
p_realpath(const char * orig_path,char * buffer)789 char *p_realpath(const char *orig_path, char *buffer)
790 {
791 git_win32_path orig_path_w, buffer_w;
792
793 if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0)
794 return NULL;
795
796 /* Note that if the path provided is a relative path, then the current directory
797 * is used to resolve the path -- which is a concurrency issue because the current
798 * directory is a process-wide variable. */
799 if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) {
800 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
801 errno = ENAMETOOLONG;
802 else
803 errno = EINVAL;
804
805 return NULL;
806 }
807
808 /* The path must exist. */
809 if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
810 errno = ENOENT;
811 return NULL;
812 }
813
814 if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) {
815 errno = ENOMEM;
816 return NULL;
817 }
818
819 /* Convert the path to UTF-8. If the caller provided a buffer, then it
820 * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't,
821 * then we may overflow. */
822 if (git_win32_path_to_utf8(buffer, buffer_w) < 0)
823 return NULL;
824
825 git_path_mkposix(buffer);
826
827 return buffer;
828 }
829
p_vsnprintf(char * buffer,size_t count,const char * format,va_list argptr)830 int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
831 {
832 #if defined(_MSC_VER)
833 int len;
834
835 if (count == 0)
836 return _vscprintf(format, argptr);
837
838 #if _MSC_VER >= 1500
839 len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr);
840 #else
841 len = _vsnprintf(buffer, count, format, argptr);
842 #endif
843
844 if (len < 0)
845 return _vscprintf(format, argptr);
846
847 return len;
848 #else /* MinGW */
849 return vsnprintf(buffer, count, format, argptr);
850 #endif
851 }
852
p_snprintf(char * buffer,size_t count,const char * format,...)853 int p_snprintf(char *buffer, size_t count, const char *format, ...)
854 {
855 va_list va;
856 int r;
857
858 va_start(va, format);
859 r = p_vsnprintf(buffer, count, format, va);
860 va_end(va);
861
862 return r;
863 }
864
865 /* TODO: wut? */
p_mkstemp(char * tmp_path)866 int p_mkstemp(char *tmp_path)
867 {
868 #if defined(_MSC_VER) && _MSC_VER >= 1500
869 if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0)
870 return -1;
871 #else
872 if (_mktemp(tmp_path) == NULL)
873 return -1;
874 #endif
875
876 return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); /* -V536 */
877 }
878
p_access(const char * path,mode_t mode)879 int p_access(const char *path, mode_t mode)
880 {
881 git_win32_path buf;
882
883 if (git_win32_path_from_utf8(buf, path) < 0)
884 return -1;
885
886 return _waccess(buf, mode & WIN32_MODE_MASK);
887 }
888
rename_once(const wchar_t * from,const wchar_t * to)889 GIT_INLINE(int) rename_once(const wchar_t *from, const wchar_t *to)
890 {
891 if (MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
892 return 0;
893
894 if (last_error_retryable())
895 return GIT_RETRY;
896
897 set_errno();
898 return -1;
899 }
900
p_rename(const char * from,const char * to)901 int p_rename(const char *from, const char *to)
902 {
903 git_win32_path wfrom, wto;
904
905 if (git_win32_path_from_utf8(wfrom, from) < 0 ||
906 git_win32_path_from_utf8(wto, to) < 0)
907 return -1;
908
909 do_with_retries(rename_once(wfrom, wto), ensure_writable(wto));
910 }
911
p_recv(GIT_SOCKET socket,void * buffer,size_t length,int flags)912 int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags)
913 {
914 if ((size_t)((int)length) != length)
915 return -1; /* git_error_set will be done by caller */
916
917 return recv(socket, buffer, (int)length, flags);
918 }
919
p_send(GIT_SOCKET socket,const void * buffer,size_t length,int flags)920 int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags)
921 {
922 if ((size_t)((int)length) != length)
923 return -1; /* git_error_set will be done by caller */
924
925 return send(socket, buffer, (int)length, flags);
926 }
927
928 /**
929 * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html
930 * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that
931 */
932 struct tm *
p_localtime_r(const time_t * timer,struct tm * result)933 p_localtime_r (const time_t *timer, struct tm *result)
934 {
935 struct tm *local_result;
936 local_result = localtime (timer);
937
938 if (local_result == NULL || result == NULL)
939 return NULL;
940
941 memcpy (result, local_result, sizeof (struct tm));
942 return result;
943 }
944 struct tm *
p_gmtime_r(const time_t * timer,struct tm * result)945 p_gmtime_r (const time_t *timer, struct tm *result)
946 {
947 struct tm *local_result;
948 local_result = gmtime (timer);
949
950 if (local_result == NULL || result == NULL)
951 return NULL;
952
953 memcpy (result, local_result, sizeof (struct tm));
954 return result;
955 }
956
p_inet_pton(int af,const char * src,void * dst)957 int p_inet_pton(int af, const char *src, void *dst)
958 {
959 struct sockaddr_storage sin;
960 void *addr;
961 int sin_len = sizeof(struct sockaddr_storage), addr_len;
962 int error = 0;
963
964 if (af == AF_INET) {
965 addr = &((struct sockaddr_in *)&sin)->sin_addr;
966 addr_len = sizeof(struct in_addr);
967 } else if (af == AF_INET6) {
968 addr = &((struct sockaddr_in6 *)&sin)->sin6_addr;
969 addr_len = sizeof(struct in6_addr);
970 } else {
971 errno = EAFNOSUPPORT;
972 return -1;
973 }
974
975 if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) {
976 memcpy(dst, addr, addr_len);
977 return 1;
978 }
979
980 switch(WSAGetLastError()) {
981 case WSAEINVAL:
982 return 0;
983 case WSAEFAULT:
984 errno = ENOSPC;
985 return -1;
986 case WSA_NOT_ENOUGH_MEMORY:
987 errno = ENOMEM;
988 return -1;
989 }
990
991 errno = EINVAL;
992 return -1;
993 }
994
p_pread(int fd,void * data,size_t size,off64_t offset)995 ssize_t p_pread(int fd, void *data, size_t size, off64_t offset)
996 {
997 HANDLE fh;
998 DWORD rsize = 0;
999 OVERLAPPED ov = {0};
1000 LARGE_INTEGER pos = {0};
1001 off64_t final_offset = 0;
1002
1003 /* Fail if the final offset would have overflowed to match POSIX semantics. */
1004 if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) {
1005 errno = EINVAL;
1006 return -1;
1007 }
1008
1009 /*
1010 * Truncate large writes to the maximum allowable size: the caller
1011 * needs to always call this in a loop anyways.
1012 */
1013 if (size > INT32_MAX) {
1014 size = INT32_MAX;
1015 }
1016
1017 pos.QuadPart = offset;
1018 ov.Offset = pos.LowPart;
1019 ov.OffsetHigh = pos.HighPart;
1020 fh = (HANDLE)_get_osfhandle(fd);
1021
1022 if (ReadFile(fh, data, (DWORD)size, &rsize, &ov)) {
1023 return (ssize_t)rsize;
1024 }
1025
1026 set_errno();
1027 return -1;
1028 }
1029
p_pwrite(int fd,const void * data,size_t size,off64_t offset)1030 ssize_t p_pwrite(int fd, const void *data, size_t size, off64_t offset)
1031 {
1032 HANDLE fh;
1033 DWORD wsize = 0;
1034 OVERLAPPED ov = {0};
1035 LARGE_INTEGER pos = {0};
1036 off64_t final_offset = 0;
1037
1038 /* Fail if the final offset would have overflowed to match POSIX semantics. */
1039 if (!git__is_ssizet(size) || git__add_int64_overflow(&final_offset, offset, (int64_t)size)) {
1040 errno = EINVAL;
1041 return -1;
1042 }
1043
1044 /*
1045 * Truncate large writes to the maximum allowable size: the caller
1046 * needs to always call this in a loop anyways.
1047 */
1048 if (size > INT32_MAX) {
1049 size = INT32_MAX;
1050 }
1051
1052 pos.QuadPart = offset;
1053 ov.Offset = pos.LowPart;
1054 ov.OffsetHigh = pos.HighPart;
1055 fh = (HANDLE)_get_osfhandle(fd);
1056
1057 if (WriteFile(fh, data, (DWORD)size, &wsize, &ov)) {
1058 return (ssize_t)wsize;
1059 }
1060
1061 set_errno();
1062 return -1;
1063 }
1064