posix_w32.c 12.4 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 "utf-conv.h"
11
#include "repository.h"
Vicent Marti committed
12
#include <errno.h>
13
#include <io.h>
14
#include <fcntl.h>
15
#include <ws2tcpip.h>
Vicent Marti committed
16 17 18

int p_unlink(const char *path)
{
19
	git_win32_path buf;
20
	git_win32_path_from_c(buf, path);
21 22
	_wchmod(buf, 0666);
	return _wunlink(buf);
Vicent Marti committed
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
}

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;
}

GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft)
{
	long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime;
	winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */
	winTime /= 10000000;		 /* Nano to seconds resolution */
	return (time_t)winTime;
}

56 57 58 59
#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')

static int do_lstat(
	const char *file_name, struct stat *buf, int posix_enotdir)
Vicent Marti committed
60 61
{
	WIN32_FILE_ATTRIBUTE_DATA fdata;
62
	git_win32_path fbuf;
63
	wchar_t lastch;
64 65
	int flen;

66
	flen = git_win32_path_from_c(fbuf, file_name);
67

68 69 70 71 72 73 74 75
	/* truncate trailing slashes */
	for (; flen > 0; --flen) {
		lastch = fbuf[flen - 1];
		if (WIN32_IS_WSEP(lastch))
			fbuf[flen - 1] = L'\0';
		else if (lastch != L'\0')
			break;
	}
Vicent Marti committed
76

77
	if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {
Vicent Marti committed
78 79
		int fMode = S_IREAD;

80 81 82
		if (!buf)
			return 0;

Vicent Marti committed
83 84 85 86 87 88 89 90 91 92 93
		if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			fMode |= S_IFDIR;
		else
			fMode |= S_IFREG;

		if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
			fMode |= S_IWRITE;

		if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
			fMode |= S_IFLNK;

94 95 96
		if ((fMode & (S_IFDIR | S_IFLNK)) == (S_IFDIR | S_IFLNK)) // junction
			fMode ^= S_IFLNK;

Vicent Marti committed
97 98 99 100 101
		buf->st_ino = 0;
		buf->st_gid = 0;
		buf->st_uid = 0;
		buf->st_nlink = 1;
		buf->st_mode = (mode_t)fMode;
nulltoken committed
102
		buf->st_size = ((git_off_t)fdata.nFileSizeHigh << 32) + fdata.nFileSizeLow;
Vicent Marti committed
103 104 105 106
		buf->st_dev = buf->st_rdev = (_getdrive() - 1);
		buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
		buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
		buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
107

Patrick Pokatilo committed
108 109 110
		/* Windows symlinks have zero file size, call readlink to determine
		 * the length of the path pointed to, which we expect everywhere else
		 */
Jameson Miller committed
111
		if (S_ISLNK(fMode)) {
112
			git_win32_path_as_utf8 target;
113
			int readlink_result;
114

115
			readlink_result = p_readlink(file_name, target, sizeof(target));
116

Patrick Pokatilo committed
117
			if (readlink_result == -1)
118
				return -1;
119 120

			buf->st_size = strlen(target);
121
		}
122

123
		return 0;
Vicent Marti committed
124 125
	}

Eduardo Bart committed
126
	errno = ENOENT;
127

128 129
	/* To match POSIX behavior, set ENOTDIR when any of the folders in the
	 * file path is a regular file, otherwise set ENOENT.
130
	 */
Eduardo Bart committed
131
	if (posix_enotdir) {
132 133 134 135 136
		/* scan up path until we find an existing item */
		while (1) {
			/* remove last directory component */
			for (--flen; flen > 0 && !WIN32_IS_WSEP(fbuf[flen]); --flen);

Eduardo Bart committed
137
			if (flen <= 0)
138 139 140 141 142
				break;

			fbuf[flen] = L'\0';

			if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {
143
				if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
Eduardo Bart committed
144
					errno = ENOTDIR;
145
				break;
146 147 148 149
			}
		}
	}

150
	return -1;
Vicent Marti committed
151 152
}

153
int p_lstat(const char *filename, struct stat *buf)
Vicent Marti committed
154
{
155 156
	return do_lstat(filename, buf, 0);
}
Vicent Marti committed
157

158 159 160
int p_lstat_posixly(const char *filename, struct stat *buf)
{
	return do_lstat(filename, buf, 1);
Vicent Marti committed
161 162
}

163 164 165 166 167 168 169 170 171

/*
 * Parts of the The p_readlink function are heavily inspired by the php 
 * readlink function in link_win32.c
 *
 * Copyright (c) 1999 - 2012 The PHP Group. All rights reserved.
 *
 * For details of the PHP license see http://www.php.net/license/3_01.txt
 */
Vicent Marti committed
172 173
int p_readlink(const char *link, char *target, size_t target_len)
{
174
	typedef DWORD (WINAPI *fpath_func)(HANDLE, LPWSTR, DWORD, DWORD);
Vicent Marti committed
175 176 177
	static fpath_func pGetFinalPath = NULL;
	HANDLE hFile;
	DWORD dwRet;
178
	git_win32_path link_w;
179
	wchar_t* target_w;
180 181 182
	int error = 0;

	assert(link && target && target_len > 0);
Vicent Marti committed
183 184 185 186 187 188

	/*
	 * Try to load the pointer to pGetFinalPath dynamically, because
	 * it is not available in platforms older than Vista
	 */
	if (pGetFinalPath == NULL) {
189
		HMODULE module = GetModuleHandle("kernel32");
Vicent Marti committed
190

191 192
		if (module != NULL)
			pGetFinalPath = (fpath_func)GetProcAddress(module, "GetFinalPathNameByHandleW");
Vicent Marti committed
193

194 195
		if (pGetFinalPath == NULL) {
			giterr_set(GITERR_OS,
196
				"'GetFinalPathNameByHandleW' is not available in this platform");
197 198
			return -1;
		}
Vicent Marti committed
199 200
	}

201
	git_win32_path_from_c(link_w, link);
202 203 204 205 206 207 208 209 210

	hFile = CreateFileW(link_w,			// file to open
			GENERIC_READ,			// open for reading
			FILE_SHARE_READ,		// share for reading
			NULL,					// default security
			OPEN_EXISTING,			// existing file only
			FILE_FLAG_BACKUP_SEMANTICS, // normal file
			NULL);					// no attr. template

211 212 213
	if (hFile == INVALID_HANDLE_VALUE) {
		giterr_set(GITERR_OS, "Cannot open '%s' for reading", link);
		return -1;
214 215 216
	}

	target_w = (wchar_t*)git__malloc(target_len * sizeof(wchar_t));
217
	GITERR_CHECK_ALLOC(target_w);
218

219
	dwRet = pGetFinalPath(hFile, target_w, (DWORD)target_len, 0x0);
220 221 222
	if (dwRet == 0 ||
		dwRet >= target_len ||
		!WideCharToMultiByte(CP_UTF8, 0, target_w, -1, target,
223
			(int)(target_len * sizeof(char)), NULL, NULL))
224
		error = -1;
Vicent Marti committed
225

226
	git__free(target_w);
Vicent Marti committed
227 228
	CloseHandle(hFile);

229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
	if (error)
		return error;

	/* Skip first 4 characters if they are "\\?\" */
	if (dwRet > 4 &&
		target[0] == '\\' && target[1] == '\\' &&
		target[2] == '?' && target[3] == '\\')
	{
		unsigned int offset = 4;
		dwRet -= 4;

		/* \??\UNC\ */
		if (dwRet > 7 &&
			target[4] == 'U' && target[5] == 'N' && target[6] == 'C')
		{
			offset += 2;
			dwRet -= 2;
			target[offset] = '\\';
Vicent Marti committed
247
		}
248 249

		memmove(target, target + offset, dwRet);
Vicent Marti committed
250 251 252
	}

	target[dwRet] = '\0';
253

Vicent Marti committed
254 255 256
	return dwRet;
}

Ben Straub committed
257 258
int p_symlink(const char *old, const char *new)
{
259 260 261 262
	/* 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
263 264
}

265
int p_open(const char *path, int flags, ...)
266
{
267
	git_win32_path buf;
268 269
	mode_t mode = 0;

270
	git_win32_path_from_c(buf, path);
271

272
	if (flags & O_CREAT) {
273 274 275
		va_list arg_list;

		va_start(arg_list, flags);
liyuray committed
276
		mode = (mode_t)va_arg(arg_list, int);
277 278 279
		va_end(arg_list);
	}

280
	return _wopen(buf, flags | _O_BINARY, mode);
281 282
}

283
int p_creat(const char *path, mode_t mode)
284
{
285
	git_win32_path buf;
286
	git_win32_path_from_c(buf, path);
287
	return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode);
288 289 290 291
}

int p_getcwd(char *buffer_out, size_t size)
{
292
	int ret;
293
	wchar_t *buf;
294 295 296 297 298 299

	if ((size_t)((int)size) != size)
		return -1;

	buf = (wchar_t*)git__malloc(sizeof(wchar_t) * (int)size);
	GITERR_CHECK_ALLOC(buf);
300

301 302
	_wgetcwd(buf, (int)size);

303
	ret = WideCharToMultiByte(
304
		CP_UTF8, 0, buf, -1, buffer_out, (int)size, NULL, NULL);
305

306
	git__free(buf);
307
	return !ret ? -1 : 0;
308 309 310 311
}

int p_stat(const char* path, struct stat* buf)
{
312
	git_win32_path_as_utf8 target;
313 314 315 316 317 318 319
	int error = 0;

	error = do_lstat(path, buf, 0);

	/* We need not do this in a loop to unwind chains of symlinks since
	 * p_readlink calls GetFinalPathNameByHandle which does it for us. */
	if (error >= 0 && S_ISLNK(buf->st_mode) &&
320
		(error = p_readlink(path, target, sizeof(target))) >= 0)
321 322 323
		error = do_lstat(target, buf, 0);

	return error;
324 325 326 327
}

int p_chdir(const char* path)
{
328
	git_win32_path buf;
329
	git_win32_path_from_c(buf, path);
330
	return _wchdir(buf);
331 332
}

333
int p_chmod(const char* path, mode_t mode)
334
{
335
	git_win32_path buf;
336
	git_win32_path_from_c(buf, path);
337
	return _wchmod(buf, mode);
338 339 340 341
}

int p_rmdir(const char* path)
{
342
	int error;
343
	git_win32_path buf;
344
	git_win32_path_from_c(buf, path);
345 346 347 348 349 350 351 352 353 354 355

	error = _wrmdir(buf);

	/* _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.
	 */
	if (GetLastError() == ERROR_SHARING_VIOLATION)
		errno = EBUSY;

	return error;
356 357
}

Vicent Marti committed
358 359
int p_hide_directory__w32(const char *path)
{
360
	git_win32_path buf;
361
	git_win32_path_from_c(buf, path);
362
	return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1;
Vicent Marti committed
363 364
}

365
char *p_realpath(const char *orig_path, char *buffer)
366
{
367
	int ret;
368 369
	git_win32_path orig_path_w;
	git_win32_path buffer_w;
370

371
	git_win32_path_from_c(orig_path_w, orig_path);
372 373

	/* Implicitly use GetCurrentDirectory which can be a threading issue */
374
	ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH_UTF16, buffer_w, NULL);
375

376
	/* According to MSDN, a return value equals to zero means a failure. */
377
	if (ret == 0 || ret > GIT_WIN_PATH_UTF16)
378
		buffer = NULL;
379 380 381 382

	else if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
		buffer = NULL;
		errno = ENOENT;
383
	}
384

385 386 387
	else if (buffer == NULL) {
		int buffer_sz = WideCharToMultiByte(
			CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL);
388 389 390

		if (!buffer_sz ||
			!(buffer = (char *)git__malloc(buffer_sz)) ||
391 392
			!WideCharToMultiByte(
				CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL))
393 394 395
		{
			git__free(buffer);
			buffer = NULL;
396 397 398
		}
	}

399 400
	else if (!WideCharToMultiByte(
		CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL))
401
		buffer = NULL;
402 403 404

	if (buffer)
		git_path_mkposix(buffer);
405

406
	return buffer;
407 408
}

409 410
int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr)
{
411
#ifdef _MSC_VER
412 413
	int len;

414 415
	if (count == 0 ||
		(len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr)) < 0)
416
		return _vscprintf(format, argptr);
417 418

	return len;
419 420 421 422
#else /* MinGW */
	return vsnprintf(buffer, count, format, argptr);
#endif
}
423 424 425 426 427 428 429 430 431 432 433 434

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;
}
435

436
extern int p_creat(const char *path, mode_t mode);
437

438 439 440
int p_mkstemp(char *tmp_path)
{
#if defined(_MSC_VER)
441
	if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0)
442
		return -1;
443
#else
Vicent Marti committed
444
	if (_mktemp(tmp_path) == NULL)
445
		return -1;
Vicent Marti committed
446
#endif
447

448
	return p_creat(tmp_path, 0744); //-V536
449
}
450 451 452 453

int p_setenv(const char* name, const char* value, int overwrite)
{
	if (overwrite != 1)
454
		return -1;
455

456
	return (SetEnvironmentVariableA(name, value) == 0 ? -1 : 0);
457
}
458

459
int p_access(const char* path, mode_t mode)
460
{
461
	git_win32_path buf;
462
	git_win32_path_from_c(buf, path);
463
	return _waccess(buf, mode);
464
}
465

466
int p_rename(const char *from, const char *to)
467
{
468 469
	git_win32_path wfrom;
	git_win32_path wto;
470

471 472
	git_win32_path_from_c(wfrom, from);
	git_win32_path_from_c(wto, to);
473
	return MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1;
474
}
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490

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
491 492 493 494 495

/**
 * 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
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
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
519 520
}

521
int p_inet_pton(int af, const char *src, void *dst)
522
{
523 524 525 526
	struct sockaddr_storage sin;
	void *addr;
	int sin_len = sizeof(struct sockaddr_storage), addr_len;
	int error = 0;
527

528 529 530 531 532 533 534 535 536
	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;
537 538
	}

539 540 541
	if ((error = WSAStringToAddressA((LPSTR)src, af, NULL, (LPSOCKADDR)&sin, &sin_len)) == 0) {
		memcpy(dst, addr, addr_len);
		return 1;
542 543
	}

544 545 546 547 548 549 550 551 552
	switch(WSAGetLastError()) {
	case WSAEINVAL:
		return 0;
	case WSAEFAULT:
		errno = ENOSPC;
		return -1;
	case WSA_NOT_ENOUGH_MEMORY:
		errno = ENOMEM;
		return -1;
553 554
	}

555 556
	errno = EINVAL;
	return -1;
557
}