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