futils.c 27 KB
Newer Older
Vicent Marti committed
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

8
#include "futils.h"
9

10
#include "global.h"
11
#include "strmap.h"
12
#include <ctype.h>
13 14 15
#if GIT_WIN32
#include "win32/findfile.h"
#endif
16

17
int git_futils_mkpath2file(const char *file_path, const mode_t mode)
18
{
19
	return git_futils_mkdir(
20
		file_path, mode,
21
		GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
22 23
}

24
int git_futils_mktmp(git_buf *path_out, const char *filename, mode_t mode)
Vicent Marti committed
25 26
{
	int fd;
27 28 29
	mode_t mask;

	p_umask(mask = p_umask(0));
Vicent Marti committed
30

31 32 33 34
	git_buf_sets(path_out, filename);
	git_buf_puts(path_out, "_git2_XXXXXX");

	if (git_buf_oom(path_out))
35
		return -1;
Vicent Marti committed
36

37
	if ((fd = p_mkstemp(path_out->ptr)) < 0) {
38
		git_error_set(GIT_ERROR_OS,
39
			"failed to create temporary file '%s'", path_out->ptr);
40 41
		return -1;
	}
Vicent Marti committed
42

43
	if (p_chmod(path_out->ptr, (mode & ~mask))) {
44
		git_error_set(GIT_ERROR_OS,
45
			"failed to set permissions on file '%s'", path_out->ptr);
46 47 48
		return -1;
	}

49
	return fd;
Vicent Marti committed
50 51
}

52
int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
53
{
54
	int fd;
55

56 57 58 59 60
	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;

	fd = p_creat(path, mode);
	if (fd < 0) {
61
		git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path);
62 63 64 65
		return -1;
	}

	return fd;
66 67
}

68
int git_futils_creat_locked(const char *path, const mode_t mode)
69
{
70 71
	int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC,
		mode);
72

73
	if (fd < 0) {
74
		int error = errno;
75
		git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path);
76 77 78 79 80 81 82 83
		switch (error) {
		case EEXIST:
			return GIT_ELOCKED;
		case ENOENT:
			return GIT_ENOTFOUND;
		default:
			return -1;
		}
84 85 86
	}

	return fd;
87 88
}

89
int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
90
{
91 92
	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;
93

Vicent Marti committed
94
	return git_futils_creat_locked(path, mode);
95 96
}

97 98 99
int git_futils_open_ro(const char *path)
{
	int fd = p_open(path, O_RDONLY);
100 101
	if (fd < 0)
		return git_path_set_error(errno, path, "open");
102 103 104
	return fd;
}

105 106 107 108 109 110 111 112 113 114
int git_futils_truncate(const char *path, int mode)
{
	int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
	if (fd < 0)
		return git_path_set_error(errno, path, "open");

	close(fd);
	return 0;
}

115
int git_futils_filesize(uint64_t *out, git_file fd)
116
{
117
	struct stat sb;
118 119

	if (p_fstat(fd, &sb)) {
120
		git_error_set(GIT_ERROR_OS, "failed to stat file descriptor");
121 122
		return -1;
	}
123

124 125 126 127 128 129 130
	if (sb.st_size < 0) {
		git_error_set(GIT_ERROR_INVALID, "invalid file size");
		return -1;
	}

	*out = sb.st_size;
	return 0;
131
}
132

133 134 135
mode_t git_futils_canonical_mode(mode_t raw_mode)
{
	if (S_ISREG(raw_mode))
136
		return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
137 138 139 140
	else if (S_ISLNK(raw_mode))
		return S_IFLNK;
	else if (S_ISGITLINK(raw_mode))
		return S_IFGITLINK;
141 142
	else if (S_ISDIR(raw_mode))
		return S_IFDIR;
143 144 145 146
	else
		return 0;
}

147 148
int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
{
149
	ssize_t read_size = 0;
150
	size_t alloc_len;
151 152 153

	git_buf_clear(buf);

154
	if (!git__is_ssizet(len)) {
155
		git_error_set(GIT_ERROR_INVALID, "read too large");
156 157 158
		return -1;
	}

159
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
160
	if (git_buf_grow(buf, alloc_len) < 0)
161 162
		return -1;

163 164
	/* p_read loops internally to read len bytes */
	read_size = p_read(fd, buf->ptr, len);
165

Vicent Marti committed
166
	if (read_size != (ssize_t)len) {
167
		git_error_set(GIT_ERROR_OS, "failed to read descriptor");
168
		git_buf_dispose(buf);
169
		return -1;
170 171
	}

172 173 174
	buf->ptr[read_size] = '\0';
	buf->size = read_size;

175 176 177 178
	return 0;
}

int git_futils_readbuffer_updated(
179
	git_buf *out, const char *path, git_oid *checksum, int *updated)
180
{
181
	int error;
182
	git_file fd;
183
	struct stat st;
184
	git_buf buf = GIT_BUF_INIT;
185
	git_oid checksum_new;
186

187
	assert(out && path && *path);
188

189 190
	if (updated != NULL)
		*updated = 0;
191

192 193
	if (p_stat(path, &st) < 0)
		return git_path_set_error(errno, path, "stat");
194

195 196

	if (S_ISDIR(st.st_mode)) {
197
		git_error_set(GIT_ERROR_INVALID, "requested file is a directory");
198 199 200 201
		return GIT_ENOTFOUND;
	}

	if (!git__is_sizet(st.st_size+1)) {
202
		git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path);
203
		return -1;
204
	}
205

206 207 208
	if ((fd = git_futils_open_ro(path)) < 0)
		return fd;

209
	if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
210 211
		p_close(fd);
		return -1;
212 213
	}

Vicent Marti committed
214
	p_close(fd);
215

216 217
	if (checksum) {
		if ((error = git_hash_buf(&checksum_new, buf.ptr, buf.size)) < 0) {
218
			git_buf_dispose(&buf);
219 220
			return error;
		}
221

222 223 224 225
		/*
		 * If we were given a checksum, we only want to use it if it's different
		 */
		if (!git_oid__cmp(checksum, &checksum_new)) {
226
			git_buf_dispose(&buf);
227 228
			if (updated)
				*updated = 0;
229

230 231 232 233
			return 0;
		}

		git_oid_cpy(checksum, &checksum_new);
234 235 236 237 238
	}

	/*
	 * If we're here, the file did change, or the user didn't have an old version
	 */
239 240 241
	if (updated != NULL)
		*updated = 1;

242
	git_buf_swap(out, &buf);
243
	git_buf_dispose(&buf);
244

245
	return 0;
246 247
}

248
int git_futils_readbuffer(git_buf *buf, const char *path)
249
{
250
	return git_futils_readbuffer_updated(buf, path, NULL, NULL);
251 252
}

253 254 255
int git_futils_writebuffer(
	const git_buf *buf,	const char *path, int flags, mode_t mode)
{
256
	int fd, do_fsync = 0, error = 0;
257

258
	if (!flags)
259
		flags = O_CREAT | O_TRUNC | O_WRONLY;
260

261 262 263 264
	if ((flags & O_FSYNC) != 0)
		do_fsync = 1;

	flags &= ~O_FSYNC;
265 266 267 268 269

	if (!mode)
		mode = GIT_FILEMODE_BLOB;

	if ((fd = p_open(path, flags, mode)) < 0) {
270
		git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path);
271 272 273 274
		return fd;
	}

	if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
275
		git_error_set(GIT_ERROR_OS, "could not write to '%s'", path);
276
		(void)p_close(fd);
277
		return error;
278 279
	}

280
	if (do_fsync && (error = p_fsync(fd)) < 0) {
281
		git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path);
282 283 284 285
		p_close(fd);
		return error;
	}

286
	if ((error = p_close(fd)) < 0) {
287
		git_error_set(GIT_ERROR_OS, "error while closing '%s'", path);
288 289 290 291 292
		return error;
	}

	if (do_fsync && (flags & O_CREAT))
		error = git_futils_fsync_parent(path);
293 294 295 296

	return error;
}

297
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
298
{
299 300
	if (git_futils_mkpath2file(to, dirmode) < 0)
		return -1;
301

302
	if (p_rename(from, to) < 0) {
303
		git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to);
304 305 306 307
		return -1;
	}

	return 0;
308 309
}

310
int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len)
311
{
Vicent Marti committed
312
	return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
313 314
}

315 316
int git_futils_mmap_ro_file(git_map *out, const char *path)
{
317
	git_file fd = git_futils_open_ro(path);
318
	uint64_t len;
319
	int result;
320 321 322 323

	if (fd < 0)
		return fd;

324
	if ((result = git_futils_filesize(&len, fd)) < 0)
325
		goto out;
326

327
	if (!git__is_sizet(len)) {
328
		git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path);
329 330
		result = -1;
		goto out;
331
	}
332

333
	result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
334
out:
335 336 337 338
	p_close(fd);
	return result;
}

Vicent Marti committed
339
void git_futils_mmap_free(git_map *out)
340
{
Vicent Marti committed
341
	p_munmap(out);
342 343
}

344 345
GIT_INLINE(int) mkdir_validate_dir(
	const char *path,
346 347 348
	struct stat *st,
	mode_t mode,
	uint32_t flags,
349
	struct git_futils_mkdir_options *opts)
350
{
351 352
	/* with exclusive create, existing dir is an error */
	if ((flags & GIT_MKDIR_EXCL) != 0) {
353
		git_error_set(GIT_ERROR_FILESYSTEM,
354
			"failed to make directory '%s': directory exists", path);
355 356 357
		return GIT_EEXISTS;
	}

358 359
	if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
		(S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
360
		if (p_unlink(path) < 0) {
361
			git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'",
362
				S_ISLNK(st->st_mode) ? "symlink" : "file", path);
363 364 365
			return GIT_EEXISTS;
		}

366
		opts->perfdata.mkdir_calls++;
367

368
		if (p_mkdir(path, mode) < 0) {
369
			git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
370 371 372 373 374 375
			return GIT_EEXISTS;
		}
	}

	else if (S_ISLNK(st->st_mode)) {
		/* Re-stat the target, make sure it's a directory */
376
		opts->perfdata.stat_calls++;
377

378
		if (p_stat(path, st) < 0) {
379
			git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
380 381 382 383 384
			return GIT_EEXISTS;
		}
	}

	else if (!S_ISDIR(st->st_mode)) {
385
		git_error_set(GIT_ERROR_FILESYSTEM,
386
			"failed to make directory '%s': directory exists", path);
387 388 389 390 391 392
		return GIT_EEXISTS;
	}

	return 0;
}

393 394 395 396 397 398 399 400 401 402 403 404 405 406
GIT_INLINE(int) mkdir_validate_mode(
	const char *path,
	struct stat *st,
	bool terminal_path,
	mode_t mode,
	uint32_t flags,
	struct git_futils_mkdir_options *opts)
{
	if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) ||
		(flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) {

		opts->perfdata.chmod_calls++;

		if (p_chmod(path, mode) < 0) {
407
			git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path);
408 409 410 411 412 413
			return -1;
		}
	}

	return 0;
}
414

415 416 417
GIT_INLINE(int) mkdir_canonicalize(
	git_buf *path,
	uint32_t flags)
418
{
419
	ssize_t root_len;
420

421
	if (path->size == 0) {
422
		git_error_set(GIT_ERROR_OS, "attempt to create empty path");
423
		return -1;
424
	}
425

426
	/* Trim trailing slashes (except the root) */
427
	if ((root_len = git_path_root(path->ptr)) < 0)
428 429 430 431
		root_len = 0;
	else
		root_len++;

432 433
	while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
		path->ptr[--path->size] = '\0';
434

435
	/* if we are not supposed to made the last element, truncate it */
436
	if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
437
		git_path_dirname_r(path, path->ptr);
438 439
		flags |= GIT_MKDIR_SKIP_LAST;
	}
440
	if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
441
		git_path_dirname_r(path, path->ptr);
442
	}
443

444
	/* We were either given the root path (or trimmed it to
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
	* the root), we don't have anything to do.
	*/
	if (path->size <= (size_t)root_len)
		git_buf_clear(path);

	return 0;
}

int git_futils_mkdir(
	const char *path,
	mode_t mode,
	uint32_t flags)
{
	git_buf make_path = GIT_BUF_INIT, parent_path = GIT_BUF_INIT;
	const char *relative;
	struct git_futils_mkdir_options opts = { 0 };
	struct stat st;
	size_t depth = 0;
463
	int len = 0, root_len, error;
464 465 466 467 468 469 470

	if ((error = git_buf_puts(&make_path, path)) < 0 ||
		(error = mkdir_canonicalize(&make_path, flags)) < 0 ||
		(error = git_buf_puts(&parent_path, make_path.ptr)) < 0 ||
		make_path.size == 0)
		goto done;

471 472
	root_len = git_path_root(make_path.ptr);

473 474
	/* find the first parent directory that exists.  this will be used
	 * as the base to dirname_relative.
475
	 */
476 477 478 479 480 481
	for (relative = make_path.ptr; parent_path.size; ) {
		error = p_lstat(parent_path.ptr, &st);

		if (error == 0) {
			break;
		} else if (errno != ENOENT) {
482
			git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr);
483
			error = -1;
484 485 486 487 488 489 490 491 492 493 494 495 496
			goto done;
		}

		depth++;

		/* examine the parent of the current path */
		if ((len = git_path_dirname_r(&parent_path, parent_path.ptr)) < 0) {
			error = len;
			goto done;
		}

		assert(len);

497 498 499 500 501
		/*
		 * We've walked all the given path's parents and it's either relative
		 * (the parent is simply '.') or rooted (the length is less than or
		 * equal to length of the root path).  The path may be less than the
		 * root path length on Windows, where `C:` == `C:/`.
502
		 */
503 504 505
		if ((len == 1 && parent_path.ptr[0] == '.') ||
		    (len == 1 && parent_path.ptr[0] == '/') ||
		    len <= root_len) {
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
			relative = make_path.ptr;
			break;
		}

		relative = make_path.ptr + len + 1;

		/* not recursive? just make this directory relative to its parent. */
		if ((flags & GIT_MKDIR_PATH) == 0)
			break;
	}

	/* we found an item at the location we're trying to create,
	 * validate it.
	 */
	if (depth == 0) {
521
		error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts);
522

523 524 525
		if (!error)
			error = mkdir_validate_mode(
				make_path.ptr, &st, true, mode, flags, &opts);
526

527 528 529
		goto done;
	}

530 531 532 533 534 535 536 537 538
	/* we already took `SKIP_LAST` and `SKIP_LAST2` into account when
	 * canonicalizing `make_path`.
	 */
	flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST);

	error = git_futils_mkdir_relative(relative,
		parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts);

done:
539 540
	git_buf_dispose(&make_path);
	git_buf_dispose(&parent_path);
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
	return error;
}

int git_futils_mkdir_r(const char *path, const mode_t mode)
{
	return git_futils_mkdir(path, mode, GIT_MKDIR_PATH);
}

int git_futils_mkdir_relative(
	const char *relative_path,
	const char *base,
	mode_t mode,
	uint32_t flags,
	struct git_futils_mkdir_options *opts)
{
	git_buf make_path = GIT_BUF_INIT;
	ssize_t root = 0, min_root_len;
	char lastch = '/', *tail;
	struct stat st;
	struct git_futils_mkdir_options empty_opts = {0};
	int error;

	if (!opts)
		opts = &empty_opts;

	/* build path and find "root" where we should start calling mkdir */
	if (git_path_join_unrooted(&make_path, relative_path, base, &root) < 0)
		return -1;

	if ((error = mkdir_canonicalize(&make_path, flags)) < 0 ||
		make_path.size == 0)
		goto done;

574 575 576 577
	/* if we are not supposed to make the whole path, reset root */
	if ((flags & GIT_MKDIR_PATH) == 0)
		root = git_buf_rfind(&make_path, '/');

578 579 580 581
	/* advance root past drive name or network mount prefix */
	min_root_len = git_path_root(make_path.ptr);
	if (root < min_root_len)
		root = min_root_len;
yorah committed
582
	while (root >= 0 && make_path.ptr[root] == '/')
583 584
		++root;

585
	/* clip root to make_path length */
586 587
	if (root > (ssize_t)make_path.size)
		root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
588 589
	if (root < 0)
		root = 0;
590

591 592
	/* walk down tail of path making each directory */
	for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
593
		bool mkdir_attempted = false;
594

595 596 597 598 599 600 601 602 603
		/* advance tail to include next path component */
		while (*tail == '/')
			tail++;
		while (*tail && *tail != '/')
			tail++;

		/* truncate path at next component */
		lastch = *tail;
		*tail = '\0';
604
		st.st_mode = 0;
605

606 607 608
		if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
			continue;

609
		/* See what's going on with this path component */
610
		opts->perfdata.stat_calls++;
611

612
retry_lstat:
613
		if (p_lstat(make_path.ptr, &st) < 0) {
614
			if (mkdir_attempted || errno != ENOENT) {
615
				git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr);
616
				error = -1;
617 618 619
				goto done;
			}

620
			git_error_clear();
621 622 623 624 625
			opts->perfdata.mkdir_calls++;
			mkdir_attempted = true;
			if (p_mkdir(make_path.ptr, mode) < 0) {
				if (errno == EEXIST)
					goto retry_lstat;
626
				git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr);
627 628 629
				error = -1;
				goto done;
			}
630
		} else {
631 632
			if ((error = mkdir_validate_dir(
				make_path.ptr, &st, mode, flags, opts)) < 0)
633
				goto done;
634
		}
635

636
		/* chmod if requested and necessary */
637 638 639
		if ((error = mkdir_validate_mode(
			make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
			goto done;
640 641

		if (opts->dir_map && opts->pool) {
642 643 644
			char *cache_path;
			size_t alloc_size;

645
			GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1);
646
			cache_path = git_pool_malloc(opts->pool, alloc_size);
647
			GIT_ERROR_CHECK_ALLOC(cache_path);
648 649 650

			memcpy(cache_path, make_path.ptr, make_path.size + 1);

651
			if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0)
652 653
				goto done;
		}
654
	}
655

656 657 658
	error = 0;

	/* check that full path really is a directory if requested & needed */
659
	if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
660
		lastch != '\0') {
661
		opts->perfdata.stat_calls++;
662 663

		if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
664
			git_error_set(GIT_ERROR_OS, "path is not a directory '%s'",
665
				make_path.ptr);
666 667
			error = GIT_ENOTFOUND;
		}
668
	}
669

670
done:
671
	git_buf_dispose(&make_path);
672
	return error;
673
}
674

675
typedef struct {
676
	const char *base;
677
	size_t baselen;
678
	uint32_t flags;
679
	int depth;
680 681
} futils__rmdir_data;

682 683
#define FUTILS_MAX_DEPTH 100

684
static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
685
{
686
	if (filemsg)
687
		git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s",
688 689
				   path, filemsg);
	else
690
		git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path);
691

692 693
	return -1;
}
694

695 696 697 698 699 700 701 702 703 704
static int futils__rm_first_parent(git_buf *path, const char *ceiling)
{
	int error = GIT_ENOTFOUND;
	struct stat st;

	while (error == GIT_ENOTFOUND) {
		git_buf_rtruncate_at_char(path, '/');

		if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)
			error = 0;
705
		else if (p_lstat_posixly(path->ptr, &st) == 0) {
706 707 708 709 710 711 712 713 714 715 716 717 718 719
			if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
				error = p_unlink(path->ptr);
			else if (!S_ISDIR(st.st_mode))
				error = -1; /* fail to remove non-regular file */
		} else if (errno != ENOTDIR)
			error = -1;
	}

	if (error)
		futils__error_cannot_rmdir(path->ptr, "cannot remove parent");

	return error;
}

720 721
static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
{
722
	int error = 0;
723
	futils__rmdir_data *data = opaque;
724
	struct stat st;
725

726 727 728
	if (data->depth > FUTILS_MAX_DEPTH)
		error = futils__error_cannot_rmdir(
			path->ptr, "directory nesting too deep");
729

730
	else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
731
		if (errno == ENOENT)
732
			error = 0;
733 734 735
		else if (errno == ENOTDIR) {
			/* asked to remove a/b/c/d/e and a/b is a normal file */
			if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)
736
				error = futils__rm_first_parent(path, data->base);
737 738 739 740 741
			else
				futils__error_cannot_rmdir(
					path->ptr, "parent is not directory");
		}
		else
742
			error = git_path_set_error(errno, path->ptr, "rmdir");
743 744 745
	}

	else if (S_ISDIR(st.st_mode)) {
746 747
		data->depth++;

748
		error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
749 750 751

		data->depth--;

752
		if (error < 0)
753
			return error;
754

755
		if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
756
			return error;
757

758
		if ((error = p_rmdir(path->ptr)) < 0) {
759
			if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
760
				(errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
761
				error = 0;
762
			else
763
				error = git_path_set_error(errno, path->ptr, "rmdir");
764
		}
765 766
	}

767
	else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
768 769
		if (p_unlink(path->ptr) < 0)
			error = git_path_set_error(errno, path->ptr, "remove");
770 771
	}

772
	else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
773
		error = futils__error_cannot_rmdir(path->ptr, "still present");
774

775
	return error;
776 777
}

778
static int futils__rmdir_empty_parent(void *opaque, const char *path)
779
{
780
	futils__rmdir_data *data = opaque;
781
	int error = 0;
782

783
	if (strlen(path) <= data->baselen)
784
		error = GIT_ITEROVER;
785

786
	else if (p_rmdir(path) < 0) {
787 788 789
		int en = errno;

		if (en == ENOENT || en == ENOTDIR) {
790
			/* do nothing */
791 792 793
		} else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 &&
			en == EBUSY) {
			error = git_path_set_error(errno, path, "rmdir");
794
		} else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
795 796
			error = GIT_ITEROVER;
		} else {
797
			error = git_path_set_error(errno, path, "rmdir");
798 799 800
		}
	}

801
	return error;
802 803
}

804
int git_futils_rmdir_r(
805
	const char *path, const char *base, uint32_t flags)
806
{
807
	int error;
808
	git_buf fullpath = GIT_BUF_INIT;
809
	futils__rmdir_data data;
810 811 812 813 814

	/* build path and find "root" where we should start calling mkdir */
	if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
		return -1;

815
	memset(&data, 0, sizeof(data));
816 817 818
	data.base    = base ? base : "";
	data.baselen = base ? strlen(base) : 0;
	data.flags   = flags;
819 820 821 822

	error = futils__rmdir_recurs_foreach(&data, &fullpath);

	/* remove now-empty parents if requested */
823
	if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
824 825 826
		error = git_path_walk_up(
			&fullpath, base, futils__rmdir_empty_parent, &data);

827
	if (error == GIT_ITEROVER) {
828
		git_error_clear();
829
		error = 0;
830
	}
831

832
	git_buf_dispose(&fullpath);
833 834

	return error;
835 836
}

837
int git_futils_fake_symlink(const char *target, const char *path)
838 839
{
	int retcode = GIT_ERROR;
840
	int fd = git_futils_creat_withpath(path, 0755, 0644);
841
	if (fd >= 0) {
842
		retcode = p_write(fd, target, strlen(target));
843 844 845 846
		p_close(fd);
	}
	return retcode;
}
847

848
static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
849 850
{
	int error = 0;
851
	char buffer[FILEIO_BUFSIZE];
852 853 854 855 856 857 858 859 860
	ssize_t len = 0;

	while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)
		/* p_write() does not have the same semantics as write().  It loops
		 * internally and will return 0 when it has completed writing.
		 */
		error = p_write(ofd, buffer, len);

	if (len < 0) {
861
		git_error_set(GIT_ERROR_OS, "read error while copying file");
862 863 864
		error = (int)len;
	}

865
	if (error < 0)
866
		git_error_set(GIT_ERROR_OS, "write error while copying file");
867

868
	if (close_fd_when_done) {
869 870 871 872 873 874 875
		p_close(ifd);
		p_close(ofd);
	}

	return error;
}

876
int git_futils_cp(const char *from, const char *to, mode_t filemode)
877 878 879 880 881 882 883 884
{
	int ifd, ofd;

	if ((ifd = git_futils_open_ro(from)) < 0)
		return ifd;

	if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
		p_close(ifd);
885
		return git_path_set_error(errno, to, "open for writing");
886 887
	}

888
	return cp_by_fd(ifd, ofd, true);
889 890
}

891
int git_futils_touch(const char *path, time_t *when)
892 893 894 895
{
	struct p_timeval times[2];
	int ret;

896 897
	times[0].tv_sec =  times[1].tv_sec  = when ? *when : time(NULL);
	times[0].tv_usec = times[1].tv_usec = 0;
898 899 900 901 902 903

	ret = p_utimes(path, times);

	return (ret < 0) ? git_path_set_error(errno, path, "touch") : 0;
}

904
static int cp_link(const char *from, const char *to, size_t link_size)
905 906 907
{
	int error = 0;
	ssize_t read_len;
908
	char *link_data;
909
	size_t alloc_size;
910

911
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
912
	link_data = git__malloc(alloc_size);
913
	GIT_ERROR_CHECK_ALLOC(link_data);
914

915 916
	read_len = p_readlink(from, link_data, link_size);
	if (read_len != (ssize_t)link_size) {
917
		git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from);
918 919 920 921 922 923
		error = -1;
	}
	else {
		link_data[read_len] = '\0';

		if (p_symlink(link_data, to) < 0) {
924
			git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'",
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942
				link_data, to);
			error = -1;
		}
	}

	git__free(link_data);
	return error;
}

typedef struct {
	const char *to_root;
	git_buf to;
	ssize_t from_prefix;
	uint32_t flags;
	uint32_t mkdir_flags;
	mode_t dirmode;
} cp_r_info;

943 944 945 946 947 948 949 950 951
#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)

static int _cp_r_mkdir(cp_r_info *info, git_buf *from)
{
	int error = 0;

	/* create root directory the first time we need to create a directory */
	if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) {
		error = git_futils_mkdir(
952
			info->to_root, info->dirmode,
953
			(info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
954 955 956 957 958 959

		info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
	}

	/* create directory with root as base to prevent excess chmods */
	if (!error)
960
		error = git_futils_mkdir_relative(
961
			from->ptr + info->from_prefix, info->to_root,
962
			info->dirmode, info->mkdir_flags, NULL);
963 964 965 966

	return error;
}

967 968
static int _cp_r_callback(void *ref, git_buf *from)
{
969
	int error = 0;
970 971 972 973 974 975 976 977
	cp_r_info *info = ref;
	struct stat from_st, to_st;
	bool exists = false;

	if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
		from->ptr[git_path_basename_offset(from)] == '.')
		return 0;

978 979
	if ((error = git_buf_joinpath(
			&info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
980
		return error;
981

982
	if (!(error = git_path_lstat(info->to.ptr, &to_st)))
983
		exists = true;
984
	else if (error != GIT_ENOTFOUND)
985
		return error;
986
	else {
987
		git_error_clear();
988 989
		error = 0;
	}
990

991
	if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
992
		return error;
993 994 995 996 997

	if (S_ISDIR(from_st.st_mode)) {
		mode_t oldmode = info->dirmode;

		/* if we are not chmod'ing, then overwrite dirmode */
998
		if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
999 1000 1001 1002
			info->dirmode = from_st.st_mode;

		/* make directory now if CREATE_EMPTY_DIRS is requested and needed */
		if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)
1003
			error = _cp_r_mkdir(info, from);
1004 1005

		/* recurse onto target directory */
1006
		if (!error && (!exists || S_ISDIR(to_st.st_mode)))
1007
			error = git_path_direach(from, 0, _cp_r_callback, info);
1008 1009 1010 1011

		if (oldmode != 0)
			info->dirmode = oldmode;

1012
		return error;
1013 1014 1015 1016 1017 1018 1019
	}

	if (exists) {
		if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)
			return 0;

		if (p_unlink(info->to.ptr) < 0) {
1020
			git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'",
1021
				info->to.ptr);
1022
			return GIT_EEXISTS;
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
		}
	}

	/* Done if this isn't a regular file or a symlink */
	if (!S_ISREG(from_st.st_mode) &&
		(!S_ISLNK(from_st.st_mode) ||
		 (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0))
		return 0;

	/* Make container directory on demand if needed */
	if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
1034
		(error = _cp_r_mkdir(info, from)) < 0)
1035
		return error;
1036 1037

	/* make symlink or regular file */
1038
	if (info->flags & GIT_CPDIR_LINK_FILES) {
1039
		if ((error = p_link(from->ptr, info->to.ptr)) < 0)
1040
			git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr);
1041
	} else if (S_ISLNK(from_st.st_mode)) {
1042
		error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
1043
	} else {
1044 1045
		mode_t usemode = from_st.st_mode;

1046
		if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
1047
			usemode = GIT_PERMS_FOR_WRITE(usemode);
1048 1049 1050 1051

		error = git_futils_cp(from->ptr, info->to.ptr, usemode);
	}

1052
	return error;
1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
}

int git_futils_cp_r(
	const char *from,
	const char *to,
	uint32_t flags,
	mode_t dirmode)
{
	int error;
	git_buf path = GIT_BUF_INIT;
	cp_r_info info;

1065
	if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */
1066 1067
		return -1;

1068
	memset(&info, 0, sizeof(info));
1069 1070 1071 1072 1073 1074 1075 1076
	info.to_root = to;
	info.flags   = flags;
	info.dirmode = dirmode;
	info.from_prefix = path.size;
	git_buf_init(&info.to, 0);

	/* precalculate mkdir flags */
	if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
1077 1078 1079
		/* if not creating empty dirs, then use mkdir to create the path on
		 * demand right before files are copied.
		 */
1080
		info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
1081
		if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
1082 1083
			info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
	} else {
1084
		/* otherwise, we will do simple mkdir as directories are encountered */
1085
		info.mkdir_flags =
1086
			((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
1087 1088 1089 1090
	}

	error = _cp_r_callback(&info, &path);

1091 1092
	git_buf_dispose(&path);
	git_buf_dispose(&info.to);
1093 1094 1095

	return error;
}
1096

Vicent Marti committed
1097 1098
int git_futils_filestamp_check(
	git_futils_filestamp *stamp, const char *path)
1099 1100 1101
{
	struct stat st;

1102 1103
	/* if the stamp is NULL, then always reload */
	if (stamp == NULL)
1104 1105
		return 1;

1106
	if (p_stat(path, &st) < 0)
1107 1108
		return GIT_ENOTFOUND;

1109
	if (stamp->mtime.tv_sec == st.st_mtime &&
1110
#if defined(GIT_USE_NSEC)
1111
		stamp->mtime.tv_nsec == st.st_mtime_nsec &&
1112
#endif
1113
		stamp->size  == (uint64_t)st.st_size   &&
1114
		stamp->ino   == (unsigned int)st.st_ino)
1115 1116
		return 0;

1117
	stamp->mtime.tv_sec = st.st_mtime;
1118
#if defined(GIT_USE_NSEC)
1119
	stamp->mtime.tv_nsec = st.st_mtime_nsec;
1120
#endif
1121
	stamp->size  = (uint64_t)st.st_size;
1122
	stamp->ino   = (unsigned int)st.st_ino;
1123 1124 1125 1126

	return 1;
}

Vicent Marti committed
1127 1128
void git_futils_filestamp_set(
	git_futils_filestamp *target, const git_futils_filestamp *source)
1129 1130 1131 1132 1133 1134 1135 1136
{
	assert(target);

	if (source)
		memcpy(target, source, sizeof(*target));
	else
		memset(target, 0, sizeof(*target));
}
1137 1138 1139 1140 1141 1142


void git_futils_filestamp_set_from_stat(
	git_futils_filestamp *stamp, struct stat *st)
{
	if (st) {
1143 1144 1145 1146
		stamp->mtime.tv_sec = st->st_mtime;
#if defined(GIT_USE_NSEC)
		stamp->mtime.tv_nsec = st->st_mtime_nsec;
#else
1147 1148
		stamp->mtime.tv_nsec = 0;
#endif
1149
		stamp->size  = (uint64_t)st->st_size;
1150 1151 1152 1153 1154
		stamp->ino   = (unsigned int)st->st_ino;
	} else {
		memset(stamp, 0, sizeof(*stamp));
	}
}
1155 1156 1157

int git_futils_fsync_dir(const char *path)
{
1158 1159 1160 1161
#ifdef GIT_WIN32
	GIT_UNUSED(path);
	return 0;
#else
1162 1163 1164
	int fd, error = -1;

	if ((fd = p_open(path, O_RDONLY)) < 0) {
1165
		git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path);
1166 1167 1168 1169
		return -1;
	}

	if ((error = p_fsync(fd)) < 0)
1170
		git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path);
1171 1172 1173

	p_close(fd);
	return error;
1174
#endif
1175 1176 1177 1178
}

int git_futils_fsync_parent(const char *path)
{
1179 1180 1181 1182 1183
	char *parent;
	int error;

	if ((parent = git_path_dirname(path)) == NULL)
		return -1;
1184

1185
	error = git_futils_fsync_dir(parent);
1186 1187 1188
	git__free(parent);
	return error;
}