posix_w32.c 15.5 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
Vicent Marti committed
3 4 5 6
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
7
#include "../posix.h"
Russell Belfer committed
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 15
#include "global.h"
#include "buffer.h"
Vicent Marti committed
16
#include <errno.h>
17
#include <io.h>
18
#include <fcntl.h>
19
#include <ws2tcpip.h>
Vicent Marti committed
20

21
#ifndef FILE_NAME_NORMALIZED
22 23 24
# define FILE_NAME_NORMALIZED 0
#endif

25 26 27 28
#ifndef IO_REPARSE_TAG_SYMLINK
#define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
#endif

29 30 31 32 33 34 35 36 37
/* Options which we always provide to _wopen.
 *
 * _O_BINARY - Raw access; no translation of CR or LF characters
 * _O_NOINHERIT - Do not mark the created handle as inheritable by child processes.
 *    The Windows default is 'not inheritable', but the CRT's default (following
 *    POSIX convention) is 'inheritable'. We have no desire for our handles to be
 *    inheritable on Windows, so specify the flag to get default behavior back. */
#define STANDARD_OPEN_FLAGS (_O_BINARY | _O_NOINHERIT)

38 39 40 41 42 43
/* Allowable mode bits on Win32.  Using mode bits that are not supported on
 * Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it
 * so we simply remove them.
 */
#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE)

44 45 46
/* GetFinalPathNameByHandleW signature */
typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD);

47 48 49 50 51 52 53
/**
 * Truncate or extend file.
 *
 * We now take a "git_off_t" rather than "long" because
 * files may be longer than 2Gb.
 */
int p_ftruncate(int fd, git_off_t size)
54
{
55 56 57 58 59
	if (size < 0) {
		errno = EINVAL;
		return -1;
	}

60
#if !defined(__MINGW32__) || defined(MINGW_HAS_SECURE_API)
61
	return ((_chsize_s(fd, size) == 0) ? 0 : -1);
62
#else
63
	/* TODO MINGW32 Find a replacement for _chsize() that handles big files. */
64 65 66 67 68
	if (size > INT32_MAX) {
		errno = EFBIG;
		return -1;
	}
	return _chsize(fd, (long)size);
69 70 71
#endif
}

72 73 74 75 76 77
int p_mkdir(const char *path, mode_t mode)
{
	git_win32_path buf;

	GIT_UNUSED(mode);

78
	if (git_win32_path_from_utf8(buf, path) < 0)
79 80 81 82 83
		return -1;

	return _wmkdir(buf);
}

84 85 86 87 88 89 90 91
int p_link(const char *old, const char *new)
{
	GIT_UNUSED(old);
	GIT_UNUSED(new);
	errno = ENOSYS;
	return -1;
}

Vicent Marti committed
92 93
int p_unlink(const char *path)
{
94
	git_win32_path buf;
95 96
	int error;

97
	if (git_win32_path_from_utf8(buf, path) < 0)
98 99 100 101 102 103
		return -1;

	error = _wunlink(buf);

	/* If the file could not be deleted because it was
	 * read-only, clear the bit and try again */
104
	if (error == -1 && errno == EACCES) {
105 106 107 108 109
		_wchmod(buf, 0666);
		error = _wunlink(buf);
	}

	return error;
Vicent Marti committed
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
}

int p_fsync(int fd)
{
	HANDLE fh = (HANDLE)_get_osfhandle(fd);

	if (fh == INVALID_HANDLE_VALUE) {
		errno = EBADF;
		return -1;
	}

	if (!FlushFileBuffers(fh)) {
		DWORD code = GetLastError();

		if (code == ERROR_INVALID_HANDLE)
			errno = EINVAL;
		else
			errno = EIO;

		return -1;
	}

	return 0;
}

135 136 137 138 139 140 141 142 143 144
#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')

static int lstat_w(
	wchar_t *path,
	struct stat *buf,
	bool posix_enotdir)
{
	WIN32_FILE_ATTRIBUTE_DATA fdata;

	if (GetFileAttributesExW(path, GetFileExInfoStandard, &fdata)) {
145 146 147
		if (!buf)
			return 0;

148
		return git_win32__file_attribute_to_stat(buf, &fdata, path);
Vicent Marti committed
149 150
	}

151 152 153 154 155 156 157 158
	switch (GetLastError()) {
	case ERROR_ACCESS_DENIED:
		errno = EACCES;
		break;
	default:
		errno = ENOENT;
		break;
	}
159

160 161
	/* To match POSIX behavior, set ENOTDIR when any of the folders in the
	 * file path is a regular file, otherwise set ENOENT.
162
	 */
163
	if (errno == ENOENT && posix_enotdir) {
164 165
		size_t path_len = wcslen(path);

166 167
		/* scan up path until we find an existing item */
		while (1) {
168 169
			DWORD attrs;

170
			/* remove last directory component */
171
			for (path_len--; path_len > 0 && !WIN32_IS_WSEP(path[path_len]); path_len--);
172

173
			if (path_len <= 0)
174 175
				break;

176 177
			path[path_len] = L'\0';
			attrs = GetFileAttributesW(path);
178

179
			if (attrs != INVALID_FILE_ATTRIBUTES) {
180
				if (!(attrs & FILE_ATTRIBUTE_DIRECTORY))
Eduardo Bart committed
181
					errno = ENOTDIR;
182
				break;
183 184 185 186
			}
		}
	}

187
	return -1;
Vicent Marti committed
188 189
}

190
static int do_lstat(const char *path, struct stat *buf, bool posixly_correct)
Vicent Marti committed
191
{
192 193 194
	git_win32_path path_w;
	int len;

195
	if ((len = git_win32_path_from_utf8(path_w, path)) < 0)
196 197 198 199 200
		return -1;

	git_win32__path_trim_end(path_w, len);

	return lstat_w(path_w, buf, posixly_correct);
201
}
Vicent Marti committed
202

203
int p_lstat(const char *filename, struct stat *buf)
204
{
205
	return do_lstat(filename, buf, false);
Vicent Marti committed
206 207
}

208
int p_lstat_posixly(const char *filename, struct stat *buf)
209
{
210
	return do_lstat(filename, buf, true);
211
}
212

213
int p_utimes(const char *filename, const struct p_timeval times[2])
214 215 216 217 218 219 220 221 222 223 224 225
{
	int fd, error;

	if ((fd = p_open(filename, O_RDWR)) < 0)
		return fd;

	error = p_futimes(fd, times);

	close(fd);
	return error;
}

226
int p_futimes(int fd, const struct p_timeval times[2])
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
{
	HANDLE handle;
	FILETIME atime = {0}, mtime = {0};

	if (times == NULL) {
		SYSTEMTIME st;

		GetSystemTime(&st);
		SystemTimeToFileTime(&st, &atime);
		SystemTimeToFileTime(&st, &mtime);
	} else {
		git_win32__timeval_to_filetime(&atime, times[0]);
		git_win32__timeval_to_filetime(&mtime, times[1]);
	}

	if ((handle = (HANDLE)_get_osfhandle(fd)) == INVALID_HANDLE_VALUE)
		return -1;

	if (SetFileTime(handle, NULL, &atime, &mtime) == 0)
		return -1;

	return 0;
}

251
int p_readlink(const char *path, char *buf, size_t bufsiz)
Vicent Marti committed
252
{
253 254 255 256 257 258 259 260 261 262 263
	git_win32_path path_w, target_w;
	git_win32_utf8_path target;
	int len;

	/* readlink(2) does not NULL-terminate the string written
	 * to the target buffer. Furthermore, the target buffer need
	 * not be large enough to hold the entire result. A truncated
	 * result should be written in this case. Since this truncation
	 * could occur in the middle of the encoding of a code point,
	 * we need to buffer the result on the stack. */

264
	if (git_win32_path_from_utf8(path_w, path) < 0 ||
265
		git_win32_path_readlink_w(target_w, path_w) < 0 ||
266 267
		(len = git_win32_path_to_utf8(target, target_w)) < 0)
		return -1;
268

269 270
	bufsiz = min((size_t)len, bufsiz);
	memcpy(buf, target, bufsiz);
271

272
	return (int)bufsiz;
Vicent Marti committed
273 274
}

Ben Straub committed
275 276
int p_symlink(const char *old, const char *new)
{
277 278 279 280
	/* Real symlinks on NTFS require admin privileges. Until this changes,
	 * libgit2 just creates a text file with the link target in the contents.
	 */
	return git_futils_fake_symlink(old, new);
Ben Straub committed
281 282
}

283
int p_open(const char *path, int flags, ...)
284
{
285
	git_win32_path buf;
286 287
	mode_t mode = 0;

288
	if (git_win32_path_from_utf8(buf, path) < 0)
289
		return -1;
290

291
	if (flags & O_CREAT) {
292 293 294
		va_list arg_list;

		va_start(arg_list, flags);
liyuray committed
295
		mode = (mode_t)va_arg(arg_list, int);
296 297 298
		va_end(arg_list);
	}

299
	return _wopen(buf, flags | STANDARD_OPEN_FLAGS, mode & WIN32_MODE_MASK);
300 301
}

302
int p_creat(const char *path, mode_t mode)
303
{
304
	git_win32_path buf;
305

306
	if (git_win32_path_from_utf8(buf, path) < 0)
307 308
		return -1;

309 310 311
	return _wopen(buf,
		_O_WRONLY | _O_CREAT | _O_TRUNC | STANDARD_OPEN_FLAGS,
		mode & WIN32_MODE_MASK);
312 313 314 315
}

int p_getcwd(char *buffer_out, size_t size)
{
316 317
	git_win32_path buf;
	wchar_t *cwd = _wgetcwd(buf, GIT_WIN_PATH_UTF16);
318

319
	if (!cwd)
320 321
		return -1;

322 323 324
	/* Convert the working directory back to UTF-8 */
	if (git__utf16_to_8(buffer_out, size, cwd) < 0) {
		DWORD code = GetLastError();
325

326 327 328 329
		if (code == ERROR_INSUFFICIENT_BUFFER)
			errno = ERANGE;
		else
			errno = EINVAL;
330

331 332
		return -1;
	}
333

334
	return 0;
335 336
}

337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
/*
 * Returns the address of the GetFinalPathNameByHandleW function.
 * This function is available on Windows Vista and higher.
 */
static PFGetFinalPathNameByHandleW get_fpnbyhandle(void)
{
	static PFGetFinalPathNameByHandleW pFunc = NULL;
	PFGetFinalPathNameByHandleW toReturn = pFunc;

	if (!toReturn) {
		HMODULE hModule = GetModuleHandleW(L"kernel32");

		if (hModule)
			toReturn = (PFGetFinalPathNameByHandleW)GetProcAddress(hModule, "GetFinalPathNameByHandleW");

		pFunc = toReturn;
	}

	assert(toReturn);

	return toReturn;
}

static int getfinalpath_w(
	git_win32_path dest,
	const wchar_t *path)
{
	PFGetFinalPathNameByHandleW pgfp = get_fpnbyhandle();
	HANDLE hFile;
	DWORD dwChars;

	if (!pgfp)
		return -1;

	/* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
	* specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
	* target of the link. */
	hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
		NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

377
	if (INVALID_HANDLE_VALUE == hFile)
378 379 380 381
		return -1;

	/* Call GetFinalPathNameByHandle */
	dwChars = pgfp(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
382
	CloseHandle(hFile);
383

384
	if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)
385 386 387
		return -1;

	/* The path may be delivered to us with a prefix; canonicalize */
388
	return (int)git_win32__canonicalize_path(dest, dwChars);
389 390 391 392 393 394 395 396 397 398 399 400
}

static int follow_and_lstat_link(git_win32_path path, struct stat* buf)
{
	git_win32_path target_w;

	if (getfinalpath_w(target_w, path) < 0)
		return -1;

	return lstat_w(target_w, buf, false);
}

401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
int p_fstat(int fd, struct stat *buf)
{
	BY_HANDLE_FILE_INFORMATION fhInfo;

	HANDLE fh = (HANDLE)_get_osfhandle(fd);

	if (fh == INVALID_HANDLE_VALUE ||
		!GetFileInformationByHandle(fh, &fhInfo)) {
		errno = EBADF;
		return -1;
	}

	git_win32__file_information_to_stat(buf, &fhInfo);
	return 0;
}

417 418
int p_stat(const char* path, struct stat* buf)
{
419 420
	git_win32_path path_w;
	int len;
421

422 423
	if ((len = git_win32_path_from_utf8(path_w, path)) < 0 ||
		lstat_w(path_w, buf, false) < 0)
424 425 426 427 428 429 430 431
		return -1;

	/* The item is a symbolic link or mount point. No need to iterate
	 * to follow multiple links; use GetFinalPathNameFromHandle. */
	if (S_ISLNK(buf->st_mode))
		return follow_and_lstat_link(path_w, buf);

	return 0;
432 433 434 435
}

int p_chdir(const char* path)
{
436
	git_win32_path buf;
437

438
	if (git_win32_path_from_utf8(buf, path) < 0)
439 440
		return -1;

441
	return _wchdir(buf);
442 443
}

444
int p_chmod(const char* path, mode_t mode)
445
{
446
	git_win32_path buf;
447

448
	if (git_win32_path_from_utf8(buf, path) < 0)
449 450
		return -1;

451
	return _wchmod(buf, mode);
452 453 454 455
}

int p_rmdir(const char* path)
{
456
	git_win32_path buf;
457 458
	int error;

459
	if (git_win32_path_from_utf8(buf, path) < 0)
460
		return -1;
461 462 463

	error = _wrmdir(buf);

464
	if (error == -1) {
465 466 467 468 469 470 471 472
		switch (GetLastError()) {
			/* _wrmdir() is documented to return EACCES if "A program has an open
			 * handle to the directory."  This sounds like what everybody else calls
			 * EBUSY.  Let's convert appropriate error codes.
			 */
			case ERROR_SHARING_VIOLATION:
				errno = EBUSY;
				break;
473

474 475 476 477 478 479
			/* This error can be returned when trying to rmdir an extant file. */
			case ERROR_DIRECTORY:
				errno = ENOTDIR;
				break;
		}
	}
480

481
	return error;
Vicent Marti committed
482 483
}

484
char *p_realpath(const char *orig_path, char *buffer)
485
{
486
	git_win32_path orig_path_w, buffer_w;
487

488
	if (git_win32_path_from_utf8(orig_path_w, orig_path) < 0)
489
		return NULL;
490

491 492 493 494 495 496 497 498
	/* Note that if the path provided is a relative path, then the current directory
	 * is used to resolve the path -- which is a concurrency issue because the current
	 * directory is a process-wide variable. */
	if (!GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL)) {
		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
			errno = ENAMETOOLONG;
		else
			errno = EINVAL;
499

500 501
		return NULL;
	}
502

503
	/* The path must exist. */
504
	if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
505
		errno = ENOENT;
506
		return NULL;
507
	}
508

509 510 511
	if (!buffer && !(buffer = git__malloc(GIT_WIN_PATH_UTF8))) {
		errno = ENOMEM;
		return NULL;
512 513
	}

514 515 516 517 518 519
	/* Convert the path to UTF-8. If the caller provided a buffer, then it
	 * is assumed to be GIT_WIN_PATH_UTF8 characters in size. If it isn't,
	 * then we may overflow. */
	if (git_win32_path_to_utf8(buffer, buffer_w) < 0)
		return NULL;

520
	git_path_mkposix(buffer);
521

522
	return buffer;
523 524
}

525 526
int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
{
527
#if defined(_MSC_VER)
528 529
	int len;

530 531 532 533 534 535 536 537 538 539
	if (count == 0)
		return _vscprintf(format, argptr);

	#if _MSC_VER >= 1500
	len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr);
	#else
	len = _vsnprintf(buffer, count, format, argptr);
	#endif

	if (len < 0)
540
		return _vscprintf(format, argptr);
541 542

	return len;
543 544 545 546
#else /* MinGW */
	return vsnprintf(buffer, count, format, argptr);
#endif
}
547 548 549 550 551 552 553 554 555 556 557 558

int p_snprintf(char *buffer, size_t count, const char *format, ...)
{
	va_list va;
	int r;

	va_start(va, format);
	r = p_vsnprintf(buffer, count, format, va);
	va_end(va);

	return r;
}
559

560
/* TODO: wut? */
561 562
int p_mkstemp(char *tmp_path)
{
563
#if defined(_MSC_VER) && _MSC_VER >= 1500
564
	if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0)
565
		return -1;
566
#else
Vicent Marti committed
567
	if (_mktemp(tmp_path) == NULL)
568
		return -1;
Vicent Marti committed
569
#endif
570

571
	return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); //-V536
572
}
573

574
int p_access(const char* path, mode_t mode)
575
{
576
	git_win32_path buf;
577

578
	if (git_win32_path_from_utf8(buf, path) < 0)
579 580
		return -1;

581
	return _waccess(buf, mode & WIN32_MODE_MASK);
582
}
583

584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
static int ensure_writable(wchar_t *fpath)
{
	DWORD attrs;

	attrs = GetFileAttributesW(fpath);
	if (attrs == INVALID_FILE_ATTRIBUTES) {
		if (GetLastError() == ERROR_FILE_NOT_FOUND)
			return 0;

		giterr_set(GITERR_OS, "failed to get attributes");
		return -1;
	}

	if (!(attrs & FILE_ATTRIBUTE_READONLY))
		return 0;

	attrs &= ~FILE_ATTRIBUTE_READONLY;
	if (!SetFileAttributesW(fpath, attrs)) {
		giterr_set(GITERR_OS, "failed to set attributes");
		return -1;
	}

	return 0;
}

609
int p_rename(const char *from, const char *to)
610
{
611 612
	git_win32_path wfrom;
	git_win32_path wto;
613 614 615
	int rename_tries;
	int rename_succeeded;
	int error;
616

617 618
	if (git_win32_path_from_utf8(wfrom, from) < 0 ||
		git_win32_path_from_utf8(wto, to) < 0)
619
		return -1;
620

621 622 623 624
	/* wait up to 50ms if file is locked by another thread or process */
	rename_tries = 0;
	rename_succeeded = 0;
	while (rename_tries < 10) {
625 626
		if (ensure_writable(wto) == 0 &&
		    MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) {
627 628 629 630 631 632 633 634 635 636 637 638 639
			rename_succeeded = 1;
			break;
		}
		
		error = GetLastError();
		if (error == ERROR_SHARING_VIOLATION || error == ERROR_ACCESS_DENIED) {
			Sleep(5);
			rename_tries++;
		} else
			break;
	}
	
	return rename_succeeded ? 0 : -1;
640
}
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656

int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags)
{
	if ((size_t)((int)length) != length)
		return -1; /* giterr_set will be done by caller */

	return recv(socket, buffer, (int)length, flags);
}

int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags)
{
	if ((size_t)((int)length) != length)
		return -1; /* giterr_set will be done by caller */

	return send(socket, buffer, (int)length, flags);
}
Ben Straub committed
657 658 659 660 661

/**
 * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html
 * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that
 */
Linquize committed
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
struct tm *
p_localtime_r (const time_t *timer, struct tm *result)
{
	struct tm *local_result;
	local_result = localtime (timer);

	if (local_result == NULL || result == NULL)
		return NULL;

	memcpy (result, local_result, sizeof (struct tm));
	return result;
}
struct tm *
p_gmtime_r (const time_t *timer, struct tm *result)
{
	struct tm *local_result;
	local_result = gmtime (timer);

	if (local_result == NULL || result == NULL)
		return NULL;

	memcpy (result, local_result, sizeof (struct tm));
	return result;
Ben Straub committed
685 686
}

687
int p_inet_pton(int af, const char *src, void *dst)
688
{
689 690 691 692
	struct sockaddr_storage sin;
	void *addr;
	int sin_len = sizeof(struct sockaddr_storage), addr_len;
	int error = 0;
693

694 695 696 697 698 699 700 701 702
	if (af == AF_INET) {
		addr = &((struct sockaddr_in *)&sin)->sin_addr;
		addr_len = sizeof(struct in_addr);
	} else if (af == AF_INET6) {
		addr = &((struct sockaddr_in6 *)&sin)->sin6_addr;
		addr_len = sizeof(struct in6_addr);
	} else {
		errno = EAFNOSUPPORT;
		return -1;
703 704
	}

705 706 707
	if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) {
		memcpy(dst, addr, addr_len);
		return 1;
708 709
	}

710 711 712 713 714 715 716 717 718
	switch(WSAGetLastError()) {
	case WSAEINVAL:
		return 0;
	case WSAEFAULT:
		errno = ENOSPC;
		return -1;
	case WSA_NOT_ENOUGH_MEMORY:
		errno = ENOMEM;
		return -1;
719 720
	}

721 722
	errno = EINVAL;
	return -1;
723
}