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