]> git.proxmox.com Git - libgit2.git/blob - src/win32/posix_w32.c
Merge pull request #2880 from ethomson/mkdir_root
[libgit2.git] / src / win32 / posix_w32.c
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 #include "../posix.h"
8 #include "../fileops.h"
9 #include "path.h"
10 #include "path_w32.h"
11 #include "utf-conv.h"
12 #include "repository.h"
13 #include "reparse.h"
14 #include <errno.h>
15 #include <io.h>
16 #include <fcntl.h>
17 #include <ws2tcpip.h>
18
19 #ifndef FILE_NAME_NORMALIZED
20 # define FILE_NAME_NORMALIZED 0
21 #endif
22
23 #ifndef IO_REPARSE_TAG_SYMLINK
24 #define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
25 #endif
26
27 /* Options which we always provide to _wopen.
28 *
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)
35
36 /* GetFinalPathNameByHandleW signature */
37 typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD);
38
39 int p_ftruncate(int fd, long size)
40 {
41 #if defined(_MSC_VER) && _MSC_VER >= 1500
42 return _chsize_s(fd, size);
43 #else
44 return _chsize(fd, size);
45 #endif
46 }
47
48 int p_mkdir(const char *path, mode_t mode)
49 {
50 git_win32_path buf;
51
52 GIT_UNUSED(mode);
53
54 if (git_win32_path_from_utf8(buf, path) < 0)
55 return -1;
56
57 return _wmkdir(buf);
58 }
59
60 int p_link(const char *old, const char *new)
61 {
62 GIT_UNUSED(old);
63 GIT_UNUSED(new);
64 errno = ENOSYS;
65 return -1;
66 }
67
68 int p_unlink(const char *path)
69 {
70 git_win32_path buf;
71 int error;
72
73 if (git_win32_path_from_utf8(buf, path) < 0)
74 return -1;
75
76 error = _wunlink(buf);
77
78 /* If the file could not be deleted because it was
79 * read-only, clear the bit and try again */
80 if (error == -1 && errno == EACCES) {
81 _wchmod(buf, 0666);
82 error = _wunlink(buf);
83 }
84
85 return error;
86 }
87
88 int p_fsync(int fd)
89 {
90 HANDLE fh = (HANDLE)_get_osfhandle(fd);
91
92 if (fh == INVALID_HANDLE_VALUE) {
93 errno = EBADF;
94 return -1;
95 }
96
97 if (!FlushFileBuffers(fh)) {
98 DWORD code = GetLastError();
99
100 if (code == ERROR_INVALID_HANDLE)
101 errno = EINVAL;
102 else
103 errno = EIO;
104
105 return -1;
106 }
107
108 return 0;
109 }
110
111 GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft)
112 {
113 long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
114 winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
115 winTime /= 10000000; /* Nano to seconds resolution */
116 return (time_t)winTime;
117 }
118
119 static bool path_is_volume(wchar_t *target, size_t target_len)
120 {
121 return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0);
122 }
123
124 /* On success, returns the length, in characters, of the path stored in dest.
125 * On failure, returns a negative value. */
126 static int readlink_w(
127 git_win32_path dest,
128 const git_win32_path path)
129 {
130 BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
131 GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf;
132 HANDLE handle = NULL;
133 DWORD ioctl_ret;
134 wchar_t *target;
135 size_t target_len;
136
137 int error = -1;
138
139 handle = CreateFileW(path, GENERIC_READ,
140 FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
141 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
142
143 if (handle == INVALID_HANDLE_VALUE) {
144 errno = ENOENT;
145 return -1;
146 }
147
148 if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
149 reparse_buf, sizeof(buf), &ioctl_ret, NULL)) {
150 errno = EINVAL;
151 goto on_error;
152 }
153
154 switch (reparse_buf->ReparseTag) {
155 case IO_REPARSE_TAG_SYMLINK:
156 target = reparse_buf->SymbolicLinkReparseBuffer.PathBuffer +
157 (reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
158 target_len = reparse_buf->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
159 break;
160 case IO_REPARSE_TAG_MOUNT_POINT:
161 target = reparse_buf->MountPointReparseBuffer.PathBuffer +
162 (reparse_buf->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
163 target_len = reparse_buf->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
164 break;
165 default:
166 errno = EINVAL;
167 goto on_error;
168 }
169
170 if (path_is_volume(target, target_len)) {
171 /* This path is a reparse point that represents another volume mounted
172 * at this location, it is not a symbolic link our input was canonical.
173 */
174 errno = EINVAL;
175 error = -1;
176 } else if (target_len) {
177 /* The path may need to have a prefix removed. */
178 target_len = git_win32__canonicalize_path(target, target_len);
179
180 /* Need one additional character in the target buffer
181 * for the terminating NULL. */
182 if (GIT_WIN_PATH_UTF16 > target_len) {
183 wcscpy(dest, target);
184 error = (int)target_len;
185 }
186 }
187
188 on_error:
189 CloseHandle(handle);
190 return error;
191 }
192
193 #define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
194
195 static int lstat_w(
196 wchar_t *path,
197 struct stat *buf,
198 bool posix_enotdir)
199 {
200 WIN32_FILE_ATTRIBUTE_DATA fdata;
201
202 if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) {
203 int fMode = S_IREAD;
204
205 if (!buf)
206 return 0;
207
208 if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
209 fMode |= S_IFDIR;
210 else
211 fMode |= S_IFREG;
212
213 if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
214 fMode |= S_IWRITE;
215
216 buf->st_ino = 0;
217 buf->st_gid = 0;
218 buf->st_uid = 0;
219 buf->st_nlink = 1;
220 buf->st_mode = (mode_t)fMode;
221 buf->st_size = ((git_off_t)fdata.nFileSizeHigh << 32) + fdata.nFileSizeLow;
222 buf->st_dev = buf->st_rdev = (_getdrive() - 1);
223 buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
224 buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
225 buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
226
227 if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
228 git_win32_path target;
229
230 if (readlink_w(target, path) >= 0) {
231 buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFLNK;
232
233 /* st_size gets the UTF-8 length of the target name, in bytes,
234 * not counting the NULL terminator */
235 if ((buf->st_size = git__utf16_to_8(NULL, 0, target)) < 0)
236 return -1;
237 }
238 }
239
240 return 0;
241 }
242
243 errno = ENOENT;
244
245 /* To match POSIX behavior, set ENOTDIR when any of the folders in the
246 * file path is a regular file, otherwise set ENOENT.
247 */
248 if (posix_enotdir) {
249 size_t path_len = wcslen(path);
250
251 /* scan up path until we find an existing item */
252 while (1) {
253 DWORD attrs;
254
255 /* remove last directory component */
256 for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--);
257
258 if (path_len <= 0)
259 break;
260
261 path[path_len] = L'\0';
262 attrs = GetFileAttributesW(path);
263
264 if (attrs != INVALID_FILE_ATTRIBUTES) {
265 if (!(attrs & FILE_ATTRIBUTE_DIRECTORY))
266 errno = ENOTDIR;
267 break;
268 }
269 }
270 }
271
272 return -1;
273 }
274
275 static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
276 {
277 git_win32_path path_w;
278 int len;
279
280 if ((len = git_win32_path_from_utf8(path_w, path)) < 0)
281 return -1;
282
283 git_win32__path_trim_end(path_w, len);
284
285 return lstat_w(path_w, buf, posixly_correct);
286 }
287
288 int p_lstat(const char *filename, struct stat *buf)
289 {
290 return do_lstat(filename, buf, false);
291 }
292
293 int p_lstat_posixly(const char *filename, struct stat *buf)
294 {
295 return do_lstat(filename, buf, true);
296 }
297
298 int p_readlink(const char *path, char *buf, size_t bufsiz)
299 {
300 git_win32_path path_w, target_w;
301 git_win32_utf8_path target;
302 int len;
303
304 /* readlink(2) does not NULL-terminate the string written
305 * to the target buffer. Furthermore, the target buffer need
306 * not be large enough to hold the entire result. A truncated
307 * result should be written in this case. Since this truncation
308 * could occur in the middle of the encoding of a code point,
309 * we need to buffer the result on the stack. */
310
311 if (git_win32_path_from_utf8(path_w, path) < 0 ||
312 readlink_w(target_w, path_w) < 0 ||
313 (len = git_win32_path_to_utf8(target, target_w)) < 0)
314 return -1;
315
316 bufsiz = min((size_t)len, bufsiz);
317 memcpy(buf, target, bufsiz);
318
319 return (int)bufsiz;
320 }
321
322 int p_symlink(const char *old, const char *new)
323 {
324 /* Real symlinks on NTFS require admin privileges. Until this changes,
325 * libgit2 just creates a text file with the link target in the contents.
326 */
327 return git_futils_fake_symlink(old, new);
328 }
329
330 int p_open(const char *path, int flags, ...)
331 {
332 git_win32_path buf;
333 mode_t mode = 0;
334
335 if (git_win32_path_from_utf8(buf, path) < 0)
336 return -1;
337
338 if (flags & O_CREAT) {
339 va_list arg_list;
340
341 va_start(arg_list, flags);
342 mode = (mode_t)va_arg(arg_list, int);
343 va_end(arg_list);
344 }
345
346 return _wopen(buf, flags | STANDARD_OPEN_FLAGS, mode);
347 }
348
349 int p_creat(const char *path, mode_t mode)
350 {
351 git_win32_path buf;
352
353 if (git_win32_path_from_utf8(buf, path) < 0)
354 return -1;
355
356 return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | STANDARD_OPEN_FLAGS, mode);
357 }
358
359 int p_getcwd(char *buffer_out, size_t size)
360 {
361 git_win32_path buf;
362 wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16);
363
364 if (!cwd)
365 return -1;
366
367 /* Convert the working directory back to UTF-8 */
368 if (git__utf16_to_8(buffer_out, size, cwd) < 0) {
369 DWORD code = GetLastError();
370
371 if (code == ERROR_INSUFFICIENT_BUFFER)
372 errno = ERANGE;
373 else
374 errno = EINVAL;
375
376 return -1;
377 }
378
379 return 0;
380 }
381
382 /*
383 * Returns the address of the GetFinalPathNameByHandleW function.
384 * This function is available on Windows Vista and higher.
385 */
386 static PFGetFinalPathNameByHandleW get_fpnbyhandle(void)
387 {
388 static PFGetFinalPathNameByHandleW pFunc = NULL;
389 PFGetFinalPathNameByHandleW toReturn = pFunc;
390
391 if (!toReturn) {
392 HMODULE hModule = GetModuleHandleW(L"kernel32");
393
394 if (hModule)
395 toReturn = (PFGetFinalPathNameByHandleW)GetProcAddress(hModule, "GetFinalPathNameByHandleW");
396
397 pFunc = toReturn;
398 }
399
400 assert(toReturn);
401
402 return toReturn;
403 }
404
405 static int getfinalpath_w(
406 git_win32_path dest,
407 const wchar_t *path)
408 {
409 PFGetFinalPathNameByHandleW pgfp = get_fpnbyhandle();
410 HANDLE hFile;
411 DWORD dwChars;
412
413 if (!pgfp)
414 return -1;
415
416 /* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
417 * specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
418 * target of the link. */
419 hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
420 NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
421
422 if (INVALID_HANDLE_VALUE == hFile)
423 return -1;
424
425 /* Call GetFinalPathNameByHandle */
426 dwChars = pgfp(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
427 CloseHandle(hFile);
428
429 if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)
430 return -1;
431
432 /* The path may be delivered to us with a prefix; canonicalize */
433 return (int)git_win32__canonicalize_path(dest, dwChars);
434 }
435
436 static int follow_and_lstat_link(git_win32_path path, struct stat* buf)
437 {
438 git_win32_path target_w;
439
440 if (getfinalpath_w(target_w, path) < 0)
441 return -1;
442
443 return lstat_w(target_w, buf, false);
444 }
445
446 int p_stat(const char* path, struct stat* buf)
447 {
448 git_win32_path path_w;
449 int len;
450
451 if ((len = git_win32_path_from_utf8(path_w, path)) < 0 ||
452 lstat_w(path_w, buf, false) < 0)
453 return -1;
454
455 /* The item is a symbolic link or mount point. No need to iterate
456 * to follow multiple links; use GetFinalPathNameFromHandle. */
457 if (S_ISLNK(buf->st_mode))
458 return follow_and_lstat_link(path_w, buf);
459
460 return 0;
461 }
462
463 int p_chdir(const char* path)
464 {
465 git_win32_path buf;
466
467 if (git_win32_path_from_utf8(buf, path) < 0)
468 return -1;
469
470 return _wchdir(buf);
471 }
472
473 int p_chmod(const char* path, mode_t mode)
474 {
475 git_win32_path buf;
476
477 if (git_win32_path_from_utf8(buf, path) < 0)
478 return -1;
479
480 return _wchmod(buf, mode);
481 }
482
483 int p_rmdir(const char* path)
484 {
485 git_win32_path buf;
486 int error;
487
488 if (git_win32_path_from_utf8(buf, path) < 0)
489 return -1;
490
491 error = _wrmdir(buf);
492
493 if (error == -1) {
494 switch (GetLastError()) {
495 /* _wrmdir() is documented to return EACCES if "A program has an open
496 * handle to the directory." This sounds like what everybody else calls
497 * EBUSY. Let's convert appropriate error codes.
498 */
499 case ERROR_SHARING_VIOLATION:
500 errno = EBUSY;
501 break;
502
503 /* This error can be returned when trying to rmdir an extant file. */
504 case ERROR_DIRECTORY:
505 errno = ENOTDIR;
506 break;
507 }
508 }
509
510 return error;
511 }
512
513 char *p_realpath(const char *orig_path, char *buffer)
514 {
515 git_win32_path orig_path_w, buffer_w;
516
517 if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0)
518 return NULL;
519
520 /* Note that if the path provided is a relative path, then the current directory
521 * is used to resolve the path -- which is a concurrency issue because the current
522 * directory is a process-wide variable. */
523 if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) {
524 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
525 errno = ENAMETOOLONG;
526 else
527 errno = EINVAL;
528
529 return NULL;
530 }
531
532 /* The path must exist. */
533 if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
534 errno = ENOENT;
535 return NULL;
536 }
537
538 if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) {
539 errno = ENOMEM;
540 return NULL;
541 }
542
543 /* Convert the path to UTF-8. If the caller provided a buffer, then it
544 * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't,
545 * then we may overflow. */
546 if (git_win32_path_to_utf8(buffer, buffer_w) < 0)
547 return NULL;
548
549 git_path_mkposix(buffer);
550
551 return buffer;
552 }
553
554 int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
555 {
556 #if defined(_MSC_VER)
557 int len;
558
559 if (count == 0)
560 return _vscprintf(format, argptr);
561
562 #if _MSC_VER >= 1500
563 len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr);
564 #else
565 len = _vsnprintf(buffer, count, format, argptr);
566 #endif
567
568 if (len < 0)
569 return _vscprintf(format, argptr);
570
571 return len;
572 #else /* MinGW */
573 return vsnprintf(buffer, count, format, argptr);
574 #endif
575 }
576
577 int p_snprintf(char *buffer, size_t count, const char *format, ...)
578 {
579 va_list va;
580 int r;
581
582 va_start(va, format);
583 r = p_vsnprintf(buffer, count, format, va);
584 va_end(va);
585
586 return r;
587 }
588
589 /* TODO: wut? */
590 int p_mkstemp(char *tmp_path)
591 {
592 #if defined(_MSC_VER) && _MSC_VER >= 1500
593 if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0)
594 return -1;
595 #else
596 if (_mktemp(tmp_path) == NULL)
597 return -1;
598 #endif
599
600 return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); //-V536
601 }
602
603 int p_access(const char* path, mode_t mode)
604 {
605 git_win32_path buf;
606
607 if (git_win32_path_from_utf8(buf, path) < 0)
608 return -1;
609
610 return _waccess(buf, mode);
611 }
612
613 static int ensure_writable(wchar_t *fpath)
614 {
615 DWORD attrs;
616
617 attrs = GetFileAttributesW(fpath);
618 if (attrs == INVALID_FILE_ATTRIBUTES) {
619 if (GetLastError() == ERROR_FILE_NOT_FOUND)
620 return 0;
621
622 giterr_set(GITERR_OS, "failed to get attributes");
623 return -1;
624 }
625
626 if (!(attrs & FILE_ATTRIBUTE_READONLY))
627 return 0;
628
629 attrs &= ~FILE_ATTRIBUTE_READONLY;
630 if (!SetFileAttributesW(fpath, attrs)) {
631 giterr_set(GITERR_OS, "failed to set attributes");
632 return -1;
633 }
634
635 return 0;
636 }
637
638 int p_rename(const char *from, const char *to)
639 {
640 git_win32_path wfrom;
641 git_win32_path wto;
642 int rename_tries;
643 int rename_succeeded;
644 int error;
645
646 if (git_win32_path_from_utf8(wfrom, from) < 0 ||
647 git_win32_path_from_utf8(wto, to) < 0)
648 return -1;
649
650 /* wait up to 50ms if file is locked by another thread or process */
651 rename_tries = 0;
652 rename_succeeded = 0;
653 while (rename_tries < 10) {
654 if (ensure_writable(wto) == 0 &&
655 MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) {
656 rename_succeeded = 1;
657 break;
658 }
659
660 error = GetLastError();
661 if (error == ERROR_SHARING_VIOLATION || error == ERROR_ACCESS_DENIED) {
662 Sleep(5);
663 rename_tries++;
664 } else
665 break;
666 }
667
668 return rename_succeeded ? 0 : -1;
669 }
670
671 int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags)
672 {
673 if ((size_t)((int)length) != length)
674 return -1; /* giterr_set will be done by caller */
675
676 return recv(socket, buffer, (int)length, flags);
677 }
678
679 int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags)
680 {
681 if ((size_t)((int)length) != length)
682 return -1; /* giterr_set will be done by caller */
683
684 return send(socket, buffer, (int)length, flags);
685 }
686
687 /**
688 * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html
689 * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that
690 */
691 struct tm *
692 p_localtime_r (const time_t *timer, struct tm *result)
693 {
694 struct tm *local_result;
695 local_result = localtime (timer);
696
697 if (local_result == NULL || result == NULL)
698 return NULL;
699
700 memcpy (result, local_result, sizeof (struct tm));
701 return result;
702 }
703 struct tm *
704 p_gmtime_r (const time_t *timer, struct tm *result)
705 {
706 struct tm *local_result;
707 local_result = gmtime (timer);
708
709 if (local_result == NULL || result == NULL)
710 return NULL;
711
712 memcpy (result, local_result, sizeof (struct tm));
713 return result;
714 }
715
716 int p_inet_pton(int af, const char *src, void *dst)
717 {
718 struct sockaddr_storage sin;
719 void *addr;
720 int sin_len = sizeof(struct sockaddr_storage), addr_len;
721 int error = 0;
722
723 if (af == AF_INET) {
724 addr = &((struct sockaddr_in *)&sin)->sin_addr;
725 addr_len = sizeof(struct in_addr);
726 } else if (af == AF_INET6) {
727 addr = &((struct sockaddr_in6 *)&sin)->sin6_addr;
728 addr_len = sizeof(struct in6_addr);
729 } else {
730 errno = EAFNOSUPPORT;
731 return -1;
732 }
733
734 if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) {
735 memcpy(dst, addr, addr_len);
736 return 1;
737 }
738
739 switch(WSAGetLastError()) {
740 case WSAEINVAL:
741 return 0;
742 case WSAEFAULT:
743 errno = ENOSPC;
744 return -1;
745 case WSA_NOT_ENOUGH_MEMORY:
746 errno = ENOMEM;
747 return -1;
748 }
749
750 errno = EINVAL;
751 return -1;
752 }