fileops.c 26.5 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 "fileops.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 38
	if ((fd = p_mkstemp(path_out->ptr)) < 0) {
		giterr_set(GITERR_OS,
39
			"failed to create temporary file '%s'", path_out->ptr);
40 41
		return -1;
	}
Vicent Marti committed
42

43 44
	if (p_chmod(path_out->ptr, (mode & ~mask))) {
		giterr_set(GITERR_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
		giterr_set(GITERR_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
		giterr_set(GITERR_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;
}

Vicent Marti committed
115
git_off_t git_futils_filesize(git_file fd)
116
{
117
	struct stat sb;
118 119

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

124 125
	return sb.st_size;
}
126

127 128 129
mode_t git_futils_canonical_mode(mode_t raw_mode)
{
	if (S_ISREG(raw_mode))
130
		return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
131 132 133 134
	else if (S_ISLNK(raw_mode))
		return S_IFLNK;
	else if (S_ISGITLINK(raw_mode))
		return S_IFGITLINK;
135 136
	else if (S_ISDIR(raw_mode))
		return S_IFDIR;
137 138 139 140
	else
		return 0;
}

141 142
int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
{
143
	ssize_t read_size = 0;
144
	size_t alloc_len;
145 146 147

	git_buf_clear(buf);

148
	if (!git__is_ssizet(len)) {
149
		giterr_set(GITERR_INVALID, "read too large");
150 151 152
		return -1;
	}

153 154
	GITERR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
	if (git_buf_grow(buf, alloc_len) < 0)
155 156
		return -1;

157 158
	/* p_read loops internally to read len bytes */
	read_size = p_read(fd, buf->ptr, len);
159

Vicent Marti committed
160
	if (read_size != (ssize_t)len) {
161
		giterr_set(GITERR_OS, "failed to read descriptor");
162
		git_buf_dispose(buf);
163
		return -1;
164 165
	}

166 167 168
	buf->ptr[read_size] = '\0';
	buf->size = read_size;

169 170 171 172
	return 0;
}

int git_futils_readbuffer_updated(
173
	git_buf *out, const char *path, git_oid *checksum, int *updated)
174
{
175
	int error;
176
	git_file fd;
177
	struct stat st;
178
	git_buf buf = GIT_BUF_INIT;
179
	git_oid checksum_new;
180

181
	assert(out && path && *path);
182

183 184
	if (updated != NULL)
		*updated = 0;
185

186 187
	if (p_stat(path, &st) < 0)
		return git_path_set_error(errno, path, "stat");
188

189 190 191 192 193 194 195

	if (S_ISDIR(st.st_mode)) {
		giterr_set(GITERR_INVALID, "requested file is a directory");
		return GIT_ENOTFOUND;
	}

	if (!git__is_sizet(st.st_size+1)) {
196
		giterr_set(GITERR_OS, "invalid regular file stat for '%s'", path);
197
		return -1;
198
	}
199

200 201 202
	if ((fd = git_futils_open_ro(path)) < 0)
		return fd;

203
	if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
204 205
		p_close(fd);
		return -1;
206 207
	}

Vicent Marti committed
208
	p_close(fd);
209

210 211
	if (checksum) {
		if ((error = git_hash_buf(&checksum_new, buf.ptr, buf.size)) < 0) {
212
			git_buf_dispose(&buf);
213 214
			return error;
		}
215

216 217 218 219
		/*
		 * If we were given a checksum, we only want to use it if it's different
		 */
		if (!git_oid__cmp(checksum, &checksum_new)) {
220
			git_buf_dispose(&buf);
221 222
			if (updated)
				*updated = 0;
223

224 225 226 227
			return 0;
		}

		git_oid_cpy(checksum, &checksum_new);
228 229 230 231 232
	}

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

236
	git_buf_swap(out, &buf);
237
	git_buf_dispose(&buf);
238

239
	return 0;
240 241
}

242
int git_futils_readbuffer(git_buf *buf, const char *path)
243
{
244
	return git_futils_readbuffer_updated(buf, path, NULL, NULL);
245 246
}

247 248 249
int git_futils_writebuffer(
	const git_buf *buf,	const char *path, int flags, mode_t mode)
{
250
	int fd, do_fsync = 0, error = 0;
251

252
	if (!flags)
253
		flags = O_CREAT | O_TRUNC | O_WRONLY;
254

255 256 257 258
	if ((flags & O_FSYNC) != 0)
		do_fsync = 1;

	flags &= ~O_FSYNC;
259 260 261 262 263

	if (!mode)
		mode = GIT_FILEMODE_BLOB;

	if ((fd = p_open(path, flags, mode)) < 0) {
264
		giterr_set(GITERR_OS, "could not open '%s' for writing", path);
265 266 267 268
		return fd;
	}

	if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
269
		giterr_set(GITERR_OS, "could not write to '%s'", path);
270
		(void)p_close(fd);
271
		return error;
272 273
	}

274 275 276 277 278 279
	if (do_fsync && (error = p_fsync(fd)) < 0) {
		giterr_set(GITERR_OS, "could not fsync '%s'", path);
		p_close(fd);
		return error;
	}

280
	if ((error = p_close(fd)) < 0) {
281
		giterr_set(GITERR_OS, "error while closing '%s'", path);
282 283 284 285 286
		return error;
	}

	if (do_fsync && (flags & O_CREAT))
		error = git_futils_fsync_parent(path);
287 288 289 290

	return error;
}

291
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
292
{
293 294
	if (git_futils_mkpath2file(to, dirmode) < 0)
		return -1;
295

296
	if (p_rename(from, to) < 0) {
297
		giterr_set(GITERR_OS, "failed to rename '%s' to '%s'", from, to);
298 299 300 301
		return -1;
	}

	return 0;
302 303
}

Vicent Marti committed
304
int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
305
{
Vicent Marti committed
306
	return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
307 308
}

309 310
int git_futils_mmap_ro_file(git_map *out, const char *path)
{
311 312
	git_file fd = git_futils_open_ro(path);
	git_off_t len;
313
	int result;
314 315 316 317

	if (fd < 0)
		return fd;

318 319 320 321
	if ((len = git_futils_filesize(fd)) < 0) {
		result = -1;
		goto out;
	}
322

323
	if (!git__is_sizet(len)) {
324
		giterr_set(GITERR_OS, "file `%s` too large to mmap", path);
325 326
		result = -1;
		goto out;
327
	}
328

329
	result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
330
out:
331 332 333 334
	p_close(fd);
	return result;
}

Vicent Marti committed
335
void git_futils_mmap_free(git_map *out)
336
{
Vicent Marti committed
337
	p_munmap(out);
338 339
}

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

354 355
	if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
		(S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
356
		if (p_unlink(path) < 0) {
357
			giterr_set(GITERR_OS, "failed to remove %s '%s'",
358
				S_ISLNK(st->st_mode) ? "symlink" : "file", path);
359 360 361
			return GIT_EEXISTS;
		}

362
		opts->perfdata.mkdir_calls++;
363

364
		if (p_mkdir(path, mode) < 0) {
365
			giterr_set(GITERR_OS, "failed to make directory '%s'", path);
366 367 368 369 370 371
			return GIT_EEXISTS;
		}
	}

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

374
		if (p_stat(path, st) < 0) {
375
			giterr_set(GITERR_OS, "failed to make directory '%s'", path);
376 377 378 379 380 381
			return GIT_EEXISTS;
		}
	}

	else if (!S_ISDIR(st->st_mode)) {
		giterr_set(GITERR_FILESYSTEM,
382
			"failed to make directory '%s': directory exists", path);
383 384 385 386 387 388
		return GIT_EEXISTS;
	}

	return 0;
}

389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
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) {
			giterr_set(GITERR_OS, "failed to set permissions on '%s'", path);
			return -1;
		}
	}

	return 0;
}
410

411 412 413
GIT_INLINE(int) mkdir_canonicalize(
	git_buf *path,
	uint32_t flags)
414
{
415
	ssize_t root_len;
416

417 418
	if (path->size == 0) {
		giterr_set(GITERR_OS, "attempt to create empty path");
419
		return -1;
420
	}
421

422
	/* Trim trailing slashes (except the root) */
423
	if ((root_len = git_path_root(path->ptr)) < 0)
424 425 426 427
		root_len = 0;
	else
		root_len++;

428 429
	while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
		path->ptr[--path->size] = '\0';
430

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

440
	/* We were either given the root path (or trimmed it to
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
	* 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;
459
	int len = 0, root_len, error;
460 461 462 463 464 465 466

	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;

467 468
	root_len = git_path_root(make_path.ptr);

469 470
	/* find the first parent directory that exists.  this will be used
	 * as the base to dirname_relative.
471
	 */
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494
	for (relative = make_path.ptr; parent_path.size; ) {
		error = p_lstat(parent_path.ptr, &st);

		if (error == 0) {
			break;
		} else if (errno != ENOENT) {
			giterr_set(GITERR_OS, "failed to stat '%s'", parent_path.ptr);
			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);

		/* we've walked all the given path's parents and it's either relative
		 * or rooted.  either way, give up and make the entire path.
		 */
495
		if ((len == 1 && parent_path.ptr[0] == '.') || len == root_len+1) {
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
			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) {
511
		error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts);
512

513 514 515
		if (!error)
			error = mkdir_validate_mode(
				make_path.ptr, &st, true, mode, flags, &opts);
516

517 518 519
		goto done;
	}

520 521 522 523 524 525 526 527 528
	/* 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:
529 530
	git_buf_dispose(&make_path);
	git_buf_dispose(&parent_path);
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
	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;

564 565 566 567
	/* if we are not supposed to make the whole path, reset root */
	if ((flags & GIT_MKDIR_PATH) == 0)
		root = git_buf_rfind(&make_path, '/');

568 569 570 571
	/* 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
572
	while (root >= 0 && make_path.ptr[root] == '/')
573 574
		++root;

575
	/* clip root to make_path length */
576 577
	if (root > (ssize_t)make_path.size)
		root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
578 579
	if (root < 0)
		root = 0;
580

581 582
	/* walk down tail of path making each directory */
	for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
583
		bool mkdir_attempted = false;
584

585 586 587 588 589 590 591 592 593
		/* advance tail to include next path component */
		while (*tail == '/')
			tail++;
		while (*tail && *tail != '/')
			tail++;

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

596 597 598
		if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
			continue;

599
		/* See what's going on with this path component */
600
		opts->perfdata.stat_calls++;
601

602
retry_lstat:
603
		if (p_lstat(make_path.ptr, &st) < 0) {
604
			if (mkdir_attempted || errno != ENOENT) {
605
				giterr_set(GITERR_OS, "cannot access component in path '%s'", make_path.ptr);
606
				error = -1;
607 608 609 610
				goto done;
			}

			giterr_clear();
611 612 613 614 615
			opts->perfdata.mkdir_calls++;
			mkdir_attempted = true;
			if (p_mkdir(make_path.ptr, mode) < 0) {
				if (errno == EEXIST)
					goto retry_lstat;
616
				giterr_set(GITERR_OS, "failed to make directory '%s'", make_path.ptr);
617 618 619
				error = -1;
				goto done;
			}
620
		} else {
621 622
			if ((error = mkdir_validate_dir(
				make_path.ptr, &st, mode, flags, opts)) < 0)
623
				goto done;
624
		}
625

626
		/* chmod if requested and necessary */
627 628 629
		if ((error = mkdir_validate_mode(
			make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
			goto done;
630 631

		if (opts->dir_map && opts->pool) {
632 633 634 635 636 637 638
			char *cache_path;
			size_t alloc_size;

			GITERR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1);
			if (!git__is_uint32(alloc_size))
				return -1;
			cache_path = git_pool_malloc(opts->pool, (uint32_t)alloc_size);
639 640 641 642
			GITERR_CHECK_ALLOC(cache_path);

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

643
			git_strmap_insert(opts->dir_map, cache_path, cache_path, &error);
644 645 646
			if (error < 0)
				goto done;
		}
647
	}
648

649 650 651
	error = 0;

	/* check that full path really is a directory if requested & needed */
652
	if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
653
		lastch != '\0') {
654
		opts->perfdata.stat_calls++;
655 656

		if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
657
			giterr_set(GITERR_OS, "path is not a directory '%s'",
658
				make_path.ptr);
659 660
			error = GIT_ENOTFOUND;
		}
661
	}
662

663
done:
664
	git_buf_dispose(&make_path);
665
	return error;
666
}
667

668
typedef struct {
669
	const char *base;
670
	size_t baselen;
671
	uint32_t flags;
672
	int depth;
673 674
} futils__rmdir_data;

675 676
#define FUTILS_MAX_DEPTH 100

677
static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
678
{
679
	if (filemsg)
680
		giterr_set(GITERR_OS, "could not remove directory '%s': %s",
681 682
				   path, filemsg);
	else
683
		giterr_set(GITERR_OS, "could not remove directory '%s'", path);
684

685 686
	return -1;
}
687

688 689 690 691 692 693 694 695 696 697
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;
698
		else if (p_lstat_posixly(path->ptr, &st) == 0) {
699 700 701 702 703 704 705 706 707 708 709 710 711 712
			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;
}

713 714
static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
{
715
	int error = 0;
716
	futils__rmdir_data *data = opaque;
717
	struct stat st;
718

719 720 721
	if (data->depth > FUTILS_MAX_DEPTH)
		error = futils__error_cannot_rmdir(
			path->ptr, "directory nesting too deep");
722

723
	else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
724
		if (errno == ENOENT)
725
			error = 0;
726 727 728
		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)
729
				error = futils__rm_first_parent(path, data->base);
730 731 732 733 734
			else
				futils__error_cannot_rmdir(
					path->ptr, "parent is not directory");
		}
		else
735
			error = git_path_set_error(errno, path->ptr, "rmdir");
736 737 738
	}

	else if (S_ISDIR(st.st_mode)) {
739 740
		data->depth++;

741
		error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
742 743 744

		data->depth--;

745
		if (error < 0)
746
			return error;
747

748
		if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
749
			return error;
750

751
		if ((error = p_rmdir(path->ptr)) < 0) {
752
			if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
753
				(errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
754
				error = 0;
755
			else
756
				error = git_path_set_error(errno, path->ptr, "rmdir");
757
		}
758 759
	}

760
	else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
761 762
		if (p_unlink(path->ptr) < 0)
			error = git_path_set_error(errno, path->ptr, "remove");
763 764
	}

765
	else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
766
		error = futils__error_cannot_rmdir(path->ptr, "still present");
767

768
	return error;
769 770
}

771
static int futils__rmdir_empty_parent(void *opaque, const char *path)
772
{
773
	futils__rmdir_data *data = opaque;
774
	int error = 0;
775

776
	if (strlen(path) <= data->baselen)
777
		error = GIT_ITEROVER;
778

779
	else if (p_rmdir(path) < 0) {
780 781 782
		int en = errno;

		if (en == ENOENT || en == ENOTDIR) {
783
			/* do nothing */
784 785 786
		} else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 &&
			en == EBUSY) {
			error = git_path_set_error(errno, path, "rmdir");
787
		} else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
788 789
			error = GIT_ITEROVER;
		} else {
790
			error = git_path_set_error(errno, path, "rmdir");
791 792 793
		}
	}

794
	return error;
795 796
}

797
int git_futils_rmdir_r(
798
	const char *path, const char *base, uint32_t flags)
799
{
800
	int error;
801
	git_buf fullpath = GIT_BUF_INIT;
802
	futils__rmdir_data data;
803 804 805 806 807

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

808
	memset(&data, 0, sizeof(data));
809 810 811
	data.base    = base ? base : "";
	data.baselen = base ? strlen(base) : 0;
	data.flags   = flags;
812 813 814 815

	error = futils__rmdir_recurs_foreach(&data, &fullpath);

	/* remove now-empty parents if requested */
816
	if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
817 818 819
		error = git_path_walk_up(
			&fullpath, base, futils__rmdir_empty_parent, &data);

820 821
	if (error == GIT_ITEROVER) {
		giterr_clear();
822
		error = 0;
823
	}
824

825
	git_buf_dispose(&fullpath);
826 827

	return error;
828 829
}

830 831 832 833 834 835 836 837 838 839
int git_futils_fake_symlink(const char *old, const char *new)
{
	int retcode = GIT_ERROR;
	int fd = git_futils_creat_withpath(new, 0755, 0644);
	if (fd >= 0) {
		retcode = p_write(fd, old, strlen(old));
		p_close(fd);
	}
	return retcode;
}
840

841
static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
842 843
{
	int error = 0;
844
	char buffer[FILEIO_BUFSIZE];
845 846 847 848 849 850 851 852 853
	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) {
854
		giterr_set(GITERR_OS, "read error while copying file");
855 856 857
		error = (int)len;
	}

858 859 860
	if (error < 0)
		giterr_set(GITERR_OS, "write error while copying file");

861
	if (close_fd_when_done) {
862 863 864 865 866 867 868
		p_close(ifd);
		p_close(ofd);
	}

	return error;
}

869
int git_futils_cp(const char *from, const char *to, mode_t filemode)
870 871 872 873 874 875 876 877
{
	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);
878
		return git_path_set_error(errno, to, "open for writing");
879 880
	}

881
	return cp_by_fd(ifd, ofd, true);
882 883
}

884
int git_futils_touch(const char *path, time_t *when)
885 886 887 888
{
	struct p_timeval times[2];
	int ret;

889 890
	times[0].tv_sec =  times[1].tv_sec  = when ? *when : time(NULL);
	times[0].tv_usec = times[1].tv_usec = 0;
891 892 893 894 895 896

	ret = p_utimes(path, times);

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

897
static int cp_link(const char *from, const char *to, size_t link_size)
898 899 900
{
	int error = 0;
	ssize_t read_len;
901
	char *link_data;
902
	size_t alloc_size;
903

904 905
	GITERR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
	link_data = git__malloc(alloc_size);
906 907
	GITERR_CHECK_ALLOC(link_data);

908 909
	read_len = p_readlink(from, link_data, link_size);
	if (read_len != (ssize_t)link_size) {
910
		giterr_set(GITERR_OS, "failed to read symlink data for '%s'", from);
911 912 913 914 915 916
		error = -1;
	}
	else {
		link_data[read_len] = '\0';

		if (p_symlink(link_data, to) < 0) {
917
			giterr_set(GITERR_OS, "could not symlink '%s' as '%s'",
918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
				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;

936 937 938 939 940 941 942 943 944
#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(
945
			info->to_root, info->dirmode,
946
			(info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
947 948 949 950 951 952

		info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
	}

	/* create directory with root as base to prevent excess chmods */
	if (!error)
953
		error = git_futils_mkdir_relative(
954
			from->ptr + info->from_prefix, info->to_root,
955
			info->dirmode, info->mkdir_flags, NULL);
956 957 958 959

	return error;
}

960 961
static int _cp_r_callback(void *ref, git_buf *from)
{
962
	int error = 0;
963 964 965 966 967 968 969 970
	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;

971 972
	if ((error = git_buf_joinpath(
			&info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
973
		return error;
974

975
	if (!(error = git_path_lstat(info->to.ptr, &to_st)))
976
		exists = true;
977
	else if (error != GIT_ENOTFOUND)
978
		return error;
979 980 981 982
	else {
		giterr_clear();
		error = 0;
	}
983

984
	if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
985
		return error;
986 987 988 989 990

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

		/* if we are not chmod'ing, then overwrite dirmode */
991
		if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
992 993 994 995
			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)
996
			error = _cp_r_mkdir(info, from);
997 998

		/* recurse onto target directory */
999
		if (!error && (!exists || S_ISDIR(to_st.st_mode)))
1000
			error = git_path_direach(from, 0, _cp_r_callback, info);
1001 1002 1003 1004

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

1005
		return error;
1006 1007 1008 1009 1010 1011 1012
	}

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

		if (p_unlink(info->to.ptr) < 0) {
1013
			giterr_set(GITERR_OS, "cannot overwrite existing file '%s'",
1014
				info->to.ptr);
1015
			return GIT_EEXISTS;
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
		}
	}

	/* 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 &&
1027
		(error = _cp_r_mkdir(info, from)) < 0)
1028
		return error;
1029 1030

	/* make symlink or regular file */
1031
	if (info->flags & GIT_CPDIR_LINK_FILES) {
1032 1033
		if ((error = p_link(from->ptr, info->to.ptr)) < 0)
			giterr_set(GITERR_OS, "failed to link '%s'", from->ptr);
1034
	} else if (S_ISLNK(from_st.st_mode)) {
1035
		error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
1036
	} else {
1037 1038
		mode_t usemode = from_st.st_mode;

1039
		if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
1040
			usemode = GIT_PERMS_FOR_WRITE(usemode);
1041 1042 1043 1044

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

1045
	return error;
1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
}

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;

1058
	if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */
1059 1060
		return -1;

1061
	memset(&info, 0, sizeof(info));
1062 1063 1064 1065 1066 1067 1068 1069
	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) {
1070 1071 1072
		/* if not creating empty dirs, then use mkdir to create the path on
		 * demand right before files are copied.
		 */
1073
		info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
1074
		if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
1075 1076
			info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
	} else {
1077
		/* otherwise, we will do simple mkdir as directories are encountered */
1078
		info.mkdir_flags =
1079
			((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
1080 1081 1082 1083
	}

	error = _cp_r_callback(&info, &path);

1084 1085
	git_buf_dispose(&path);
	git_buf_dispose(&info.to);
1086 1087 1088

	return error;
}
1089

Vicent Marti committed
1090 1091
int git_futils_filestamp_check(
	git_futils_filestamp *stamp, const char *path)
1092 1093 1094
{
	struct stat st;

1095 1096
	/* if the stamp is NULL, then always reload */
	if (stamp == NULL)
1097 1098
		return 1;

1099
	if (p_stat(path, &st) < 0)
1100 1101
		return GIT_ENOTFOUND;

1102
	if (stamp->mtime.tv_sec == st.st_mtime &&
1103
#if defined(GIT_USE_NSEC)
1104
		stamp->mtime.tv_nsec == st.st_mtime_nsec &&
1105
#endif
1106 1107
		stamp->size  == (git_off_t)st.st_size   &&
		stamp->ino   == (unsigned int)st.st_ino)
1108 1109
		return 0;

1110
	stamp->mtime.tv_sec = st.st_mtime;
1111
#if defined(GIT_USE_NSEC)
1112
	stamp->mtime.tv_nsec = st.st_mtime_nsec;
1113
#endif
1114 1115
	stamp->size  = (git_off_t)st.st_size;
	stamp->ino   = (unsigned int)st.st_ino;
1116 1117 1118 1119

	return 1;
}

Vicent Marti committed
1120 1121
void git_futils_filestamp_set(
	git_futils_filestamp *target, const git_futils_filestamp *source)
1122 1123 1124 1125 1126 1127 1128 1129
{
	assert(target);

	if (source)
		memcpy(target, source, sizeof(*target));
	else
		memset(target, 0, sizeof(*target));
}
1130 1131 1132 1133 1134 1135


void git_futils_filestamp_set_from_stat(
	git_futils_filestamp *stamp, struct stat *st)
{
	if (st) {
1136 1137 1138 1139
		stamp->mtime.tv_sec = st->st_mtime;
#if defined(GIT_USE_NSEC)
		stamp->mtime.tv_nsec = st->st_mtime_nsec;
#else
1140 1141
		stamp->mtime.tv_nsec = 0;
#endif
1142 1143 1144 1145 1146 1147
		stamp->size  = (git_off_t)st->st_size;
		stamp->ino   = (unsigned int)st->st_ino;
	} else {
		memset(stamp, 0, sizeof(*stamp));
	}
}
1148 1149 1150

int git_futils_fsync_dir(const char *path)
{
1151 1152 1153 1154
#ifdef GIT_WIN32
	GIT_UNUSED(path);
	return 0;
#else
1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166
	int fd, error = -1;

	if ((fd = p_open(path, O_RDONLY)) < 0) {
		giterr_set(GITERR_OS, "failed to open directory '%s' for fsync", path);
		return -1;
	}

	if ((error = p_fsync(fd)) < 0)
		giterr_set(GITERR_OS, "failed to fsync directory '%s'", path);

	p_close(fd);
	return error;
1167
#endif
1168 1169 1170 1171
}

int git_futils_fsync_parent(const char *path)
{
1172 1173 1174 1175 1176
	char *parent;
	int error;

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

1178
	error = git_futils_fsync_dir(parent);
1179 1180 1181
	git__free(parent);
	return error;
}