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