fileops.c 24.8 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
#include "common.h"
8
#include "fileops.h"
9
#include "global.h"
10
#include "strmap.h"
11
#include <ctype.h>
12 13 14
#if GIT_WIN32
#include "win32/findfile.h"
#endif
15

16
GIT__USE_STRMAP
17

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

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

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

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

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

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

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

50
	return fd;
Vicent Marti committed
51 52
}

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

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

	fd = p_creat(path, mode);
	if (fd < 0) {
62
		giterr_set(GITERR_OS, "Failed to create file '%s'", path);
63 64 65 66
		return -1;
	}

	return fd;
67 68
}

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

74
	if (fd < 0) {
75
		giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
76
		return errno == EEXIST ? GIT_ELOCKED : -1;
77 78 79
	}

	return fd;
80 81
}

82
int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
83
{
84 85
	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;
86

Vicent Marti committed
87
	return git_futils_creat_locked(path, mode);
88 89
}

90 91 92
int git_futils_open_ro(const char *path)
{
	int fd = p_open(path, O_RDONLY);
93 94
	if (fd < 0)
		return git_path_set_error(errno, path, "open");
95 96 97
	return fd;
}

Vicent Marti committed
98
git_off_t git_futils_filesize(git_file fd)
99
{
100
	struct stat sb;
101 102 103 104 105

	if (p_fstat(fd, &sb)) {
		giterr_set(GITERR_OS, "Failed to stat file descriptor");
		return -1;
	}
106

107 108
	return sb.st_size;
}
109

110 111 112
mode_t git_futils_canonical_mode(mode_t raw_mode)
{
	if (S_ISREG(raw_mode))
113
		return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
114 115 116 117
	else if (S_ISLNK(raw_mode))
		return S_IFLNK;
	else if (S_ISGITLINK(raw_mode))
		return S_IFGITLINK;
118 119
	else if (S_ISDIR(raw_mode))
		return S_IFDIR;
120 121 122 123
	else
		return 0;
}

124 125
int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
{
126
	ssize_t read_size = 0;
127
	size_t alloc_len;
128 129 130

	git_buf_clear(buf);

131 132 133 134 135
	if (!git__is_ssizet(len)) {
		giterr_set(GITERR_INVALID, "Read too large.");
		return -1;
	}

136 137
	GITERR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
	if (git_buf_grow(buf, alloc_len) < 0)
138 139
		return -1;

140 141
	/* p_read loops internally to read len bytes */
	read_size = p_read(fd, buf->ptr, len);
142

Vicent Marti committed
143
	if (read_size != (ssize_t)len) {
144
		giterr_set(GITERR_OS, "Failed to read descriptor");
145
		git_buf_free(buf);
146
		return -1;
147 148
	}

149 150 151
	buf->ptr[read_size] = '\0';
	buf->size = read_size;

152 153 154 155
	return 0;
}

int git_futils_readbuffer_updated(
156
	git_buf *out, const char *path, git_oid *checksum, int *updated)
157
{
158
	int error;
159
	git_file fd;
160
	struct stat st;
161
	git_buf buf = GIT_BUF_INIT;
162
	git_oid checksum_new;
163

164
	assert(out && path && *path);
165

166 167
	if (updated != NULL)
		*updated = 0;
168

169 170
	if (p_stat(path, &st) < 0)
		return git_path_set_error(errno, path, "stat");
171

172 173 174 175 176 177 178

	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)) {
179 180
		giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path);
		return -1;
181
	}
182

183 184 185
	if ((fd = git_futils_open_ro(path)) < 0)
		return fd;

186
	if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
187 188
		p_close(fd);
		return -1;
189 190
	}

Vicent Marti committed
191
	p_close(fd);
192

193 194
	if ((error = git_hash_buf(&checksum_new, buf.ptr, buf.size)) < 0) {
		git_buf_free(&buf);
195 196 197 198 199 200 201
		return error;
	}

	/*
	 * If we were given a checksum, we only want to use it if it's different
	 */
	if (checksum && !git_oid__cmp(checksum, &checksum_new)) {
202
		git_buf_free(&buf);
203 204 205 206 207 208 209 210 211 212 213 214
		if (updated)
			*updated = 0;

		return 0;
	}

	/*
	 * If we're here, the file did change, or the user didn't have an old version
	 */
	if (checksum)
		git_oid_cpy(checksum, &checksum_new);

215 216 217
	if (updated != NULL)
		*updated = 1;

218 219 220
	git_buf_swap(out, &buf);
	git_buf_free(&buf);

221
	return 0;
222 223
}

224
int git_futils_readbuffer(git_buf *buf, const char *path)
225
{
226
	return git_futils_readbuffer_updated(buf, path, NULL, NULL);
227 228
}

229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
int git_futils_writebuffer(
	const git_buf *buf,	const char *path, int flags, mode_t mode)
{
	int fd, error = 0;

	if (flags <= 0)
		flags = O_CREAT | O_TRUNC | O_WRONLY;
	if (!mode)
		mode = GIT_FILEMODE_BLOB;

	if ((fd = p_open(path, flags, mode)) < 0) {
		giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
		return fd;
	}

	if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
		giterr_set(GITERR_OS, "Could not write to '%s'", path);
		(void)p_close(fd);
247
		return error;
248 249 250 251 252 253 254 255
	}

	if ((error = p_close(fd)) < 0)
		giterr_set(GITERR_OS, "Error while closing '%s'", path);

	return error;
}

256
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
257
{
258 259
	if (git_futils_mkpath2file(to, dirmode) < 0)
		return -1;
260

261 262 263 264 265 266
	if (p_rename(from, to) < 0) {
		giterr_set(GITERR_OS, "Failed to rename '%s' to '%s'", from, to);
		return -1;
	}

	return 0;
267 268
}

Vicent Marti committed
269
int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
270
{
Vicent Marti committed
271
	return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
272 273
}

274 275
int git_futils_mmap_ro_file(git_map *out, const char *path)
{
276 277
	git_file fd = git_futils_open_ro(path);
	git_off_t len;
278
	int result;
279 280 281 282 283

	if (fd < 0)
		return fd;

	len = git_futils_filesize(fd);
284 285 286 287
	if (!git__is_sizet(len)) {
		giterr_set(GITERR_OS, "File `%s` too large to mmap", path);
		return -1;
	}
288

289
	result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
290 291 292 293
	p_close(fd);
	return result;
}

Vicent Marti committed
294
void git_futils_mmap_free(git_map *out)
295
{
Vicent Marti committed
296
	p_munmap(out);
297 298
}

299 300
GIT_INLINE(int) mkdir_validate_dir(
	const char *path,
301 302 303
	struct stat *st,
	mode_t mode,
	uint32_t flags,
304
	struct git_futils_mkdir_options *opts)
305
{
306 307 308 309 310 311 312
	/* with exclusive create, existing dir is an error */
	if ((flags & GIT_MKDIR_EXCL) != 0) {
		giterr_set(GITERR_FILESYSTEM,
			"Failed to make directory '%s': directory exists", path);
		return GIT_EEXISTS;
	}

313 314
	if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
		(S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
315
		if (p_unlink(path) < 0) {
316
			giterr_set(GITERR_OS, "Failed to remove %s '%s'",
317
				S_ISLNK(st->st_mode) ? "symlink" : "file", path);
318 319 320
			return GIT_EEXISTS;
		}

321
		opts->perfdata.mkdir_calls++;
322

323 324
		if (p_mkdir(path, mode) < 0) {
			giterr_set(GITERR_OS, "Failed to make directory '%s'", path);
325 326 327 328 329 330
			return GIT_EEXISTS;
		}
	}

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

333 334
		if (p_stat(path, st) < 0) {
			giterr_set(GITERR_OS, "Failed to make directory '%s'", path);
335 336 337 338 339 340
			return GIT_EEXISTS;
		}
	}

	else if (!S_ISDIR(st->st_mode)) {
		giterr_set(GITERR_FILESYSTEM,
341
			"Failed to make directory '%s': directory exists", path);
342 343 344 345 346 347
		return GIT_EEXISTS;
	}

	return 0;
}

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
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;
}
369

370 371 372
GIT_INLINE(int) mkdir_canonicalize(
	git_buf *path,
	uint32_t flags)
373
{
374
	ssize_t root_len;
375

376 377
	if (path->size == 0) {
		giterr_set(GITERR_OS, "attempt to create empty path");
378
		return -1;
379
	}
380

381
	/* Trim trailing slashes (except the root) */
382
	if ((root_len = git_path_root(path->ptr)) < 0)
383 384 385 386
		root_len = 0;
	else
		root_len++;

387 388
	while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
		path->ptr[--path->size] = '\0';
389

390
	/* if we are not supposed to made the last element, truncate it */
391
	if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
392
		git_path_dirname_r(path, path->ptr);
393 394
		flags |= GIT_MKDIR_SKIP_LAST;
	}
395
	if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
396
		git_path_dirname_r(path, path->ptr);
397
	}
398

399
	/* We were either given the root path (or trimmed it to
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
	* 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;
418
	int len = 0, root_len, error;
419 420 421 422 423 424 425

	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;

426 427
	root_len = git_path_root(make_path.ptr);

428 429
	/* find the first parent directory that exists.  this will be used
	 * as the base to dirname_relative.
430
	 */
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
	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.
		 */
454
		if ((len == 1 && parent_path.ptr[0] == '.') || len == root_len+1) {
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
			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) {
470
		error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts);
471

472 473 474
		if (!error)
			error = mkdir_validate_mode(
				make_path.ptr, &st, true, mode, flags, &opts);
475

476 477 478
		goto done;
	}

479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
	/* 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:
	git_buf_free(&make_path);
	git_buf_free(&parent_path);
	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;

523 524 525 526
	/* if we are not supposed to make the whole path, reset root */
	if ((flags & GIT_MKDIR_PATH) == 0)
		root = git_buf_rfind(&make_path, '/');

527 528 529 530
	/* 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
531
	while (root >= 0 && make_path.ptr[root] == '/')
532 533
		++root;

534
	/* clip root to make_path length */
535 536
	if (root > (ssize_t)make_path.size)
		root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
537 538
	if (root < 0)
		root = 0;
539

540 541
	/* walk down tail of path making each directory */
	for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
542
		bool mkdir_attempted = false;
543

544 545 546 547 548 549 550 551 552
		/* advance tail to include next path component */
		while (*tail == '/')
			tail++;
		while (*tail && *tail != '/')
			tail++;

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

555 556 557
		if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
			continue;

558
		/* See what's going on with this path component */
559
		opts->perfdata.stat_calls++;
560

561
retry_lstat:
562
		if (p_lstat(make_path.ptr, &st) < 0) {
563 564 565
			if (mkdir_attempted || errno != ENOENT) {
				giterr_set(GITERR_OS, "Cannot access component in path '%s'", make_path.ptr);
				error = -1;
566 567 568 569
				goto done;
			}

			giterr_clear();
570 571 572 573 574 575 576 577 578
			opts->perfdata.mkdir_calls++;
			mkdir_attempted = true;
			if (p_mkdir(make_path.ptr, mode) < 0) {
				if (errno == EEXIST)
					goto retry_lstat;
				giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
				error = -1;
				goto done;
			}
579
		} else {
580 581
			if ((error = mkdir_validate_dir(
				make_path.ptr, &st, mode, flags, opts)) < 0)
582
				goto done;
583
		}
584

585
		/* chmod if requested and necessary */
586 587 588
		if ((error = mkdir_validate_mode(
			make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
			goto done;
589 590

		if (opts->dir_map && opts->pool) {
591 592 593 594 595 596 597
			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);
598 599 600 601 602 603 604 605
			GITERR_CHECK_ALLOC(cache_path);

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

			git_strmap_insert(opts->dir_map, cache_path, cache_path, error);
			if (error < 0)
				goto done;
		}
606
	}
607

608 609 610
	error = 0;

	/* check that full path really is a directory if requested & needed */
611
	if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
612
		lastch != '\0') {
613
		opts->perfdata.stat_calls++;
614 615

		if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
616 617
			giterr_set(GITERR_OS, "Path is not a directory '%s'",
				make_path.ptr);
618 619
			error = GIT_ENOTFOUND;
		}
620
	}
621

622
done:
623
	git_buf_free(&make_path);
624
	return error;
625
}
626

627
typedef struct {
628
	const char *base;
629
	size_t baselen;
630
	uint32_t flags;
631
	int depth;
632 633
} futils__rmdir_data;

634 635
#define FUTILS_MAX_DEPTH 100

636
static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
637
{
638 639 640 641 642
	if (filemsg)
		giterr_set(GITERR_OS, "Could not remove directory. File '%s' %s",
				   path, filemsg);
	else
		giterr_set(GITERR_OS, "Could not remove directory '%s'", path);
643

644 645
	return -1;
}
646

647 648 649 650 651 652 653 654 655 656
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;
657
		else if (p_lstat_posixly(path->ptr, &st) == 0) {
658 659 660 661 662 663 664 665 666 667 668 669 670 671
			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;
}

672 673
static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
{
674
	int error = 0;
675
	futils__rmdir_data *data = opaque;
676
	struct stat st;
677

678 679 680
	if (data->depth > FUTILS_MAX_DEPTH)
		error = futils__error_cannot_rmdir(
			path->ptr, "directory nesting too deep");
681

682
	else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
683
		if (errno == ENOENT)
684
			error = 0;
685 686 687
		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)
688
				error = futils__rm_first_parent(path, data->base);
689 690 691 692 693
			else
				futils__error_cannot_rmdir(
					path->ptr, "parent is not directory");
		}
		else
694
			error = git_path_set_error(errno, path->ptr, "rmdir");
695 696 697
	}

	else if (S_ISDIR(st.st_mode)) {
698 699
		data->depth++;

700
		error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
701 702 703

		data->depth--;

704
		if (error < 0)
705
			return error;
706

707
		if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
708
			return error;
709

710
		if ((error = p_rmdir(path->ptr)) < 0) {
711
			if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
712
				(errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
713
				error = 0;
714
			else
715
				error = git_path_set_error(errno, path->ptr, "rmdir");
716
		}
717 718
	}

719
	else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
720 721
		if (p_unlink(path->ptr) < 0)
			error = git_path_set_error(errno, path->ptr, "remove");
722 723
	}

724
	else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
725
		error = futils__error_cannot_rmdir(path->ptr, "still present");
726

727
	return error;
728 729
}

730
static int futils__rmdir_empty_parent(void *opaque, const char *path)
731
{
732
	futils__rmdir_data *data = opaque;
733
	int error = 0;
734

735
	if (strlen(path) <= data->baselen)
736
		error = GIT_ITEROVER;
737

738
	else if (p_rmdir(path) < 0) {
739 740 741
		int en = errno;

		if (en == ENOENT || en == ENOTDIR) {
742
			/* do nothing */
743
		} else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
744 745
			error = GIT_ITEROVER;
		} else {
746
			error = git_path_set_error(errno, path, "rmdir");
747 748 749
		}
	}

750
	return error;
751 752
}

753
int git_futils_rmdir_r(
754
	const char *path, const char *base, uint32_t flags)
755
{
756
	int error;
757
	git_buf fullpath = GIT_BUF_INIT;
758
	futils__rmdir_data data;
759 760 761 762 763

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

764
	memset(&data, 0, sizeof(data));
765 766 767
	data.base    = base ? base : "";
	data.baselen = base ? strlen(base) : 0;
	data.flags   = flags;
768 769 770 771

	error = futils__rmdir_recurs_foreach(&data, &fullpath);

	/* remove now-empty parents if requested */
772
	if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
773 774 775
		error = git_path_walk_up(
			&fullpath, base, futils__rmdir_empty_parent, &data);

776 777
	if (error == GIT_ITEROVER) {
		giterr_clear();
778
		error = 0;
779
	}
780 781

	git_buf_free(&fullpath);
782 783

	return error;
784 785
}

786 787 788 789 790 791 792 793 794 795
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;
}
796

797
static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
798 799
{
	int error = 0;
800
	char buffer[FILEIO_BUFSIZE];
801 802 803 804 805 806 807 808 809 810 811 812 813
	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) {
		giterr_set(GITERR_OS, "Read error while copying file");
		error = (int)len;
	}

814 815 816
	if (error < 0)
		giterr_set(GITERR_OS, "write error while copying file");

817
	if (close_fd_when_done) {
818 819 820 821 822 823 824
		p_close(ifd);
		p_close(ofd);
	}

	return error;
}

825
int git_futils_cp(const char *from, const char *to, mode_t filemode)
826 827 828 829 830 831 832 833
{
	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);
834
		return git_path_set_error(errno, to, "open for writing");
835 836
	}

837
	return cp_by_fd(ifd, ofd, true);
838 839
}

840
static int cp_link(const char *from, const char *to, size_t link_size)
841 842 843
{
	int error = 0;
	ssize_t read_len;
844
	char *link_data;
845
	size_t alloc_size;
846

847 848
	GITERR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
	link_data = git__malloc(alloc_size);
849 850
	GITERR_CHECK_ALLOC(link_data);

851 852
	read_len = p_readlink(from, link_data, link_size);
	if (read_len != (ssize_t)link_size) {
853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
		giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", from);
		error = -1;
	}
	else {
		link_data[read_len] = '\0';

		if (p_symlink(link_data, to) < 0) {
			giterr_set(GITERR_OS, "Could not symlink '%s' as '%s'",
				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;

879 880 881 882 883 884 885 886 887
#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(
888
			info->to_root, info->dirmode,
889
			(info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
890 891 892 893 894 895

		info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
	}

	/* create directory with root as base to prevent excess chmods */
	if (!error)
896
		error = git_futils_mkdir_relative(
897
			from->ptr + info->from_prefix, info->to_root,
898
			info->dirmode, info->mkdir_flags, NULL);
899 900 901 902

	return error;
}

903 904
static int _cp_r_callback(void *ref, git_buf *from)
{
905
	int error = 0;
906 907 908 909 910 911 912 913
	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;

914 915
	if ((error = git_buf_joinpath(
			&info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
916
		return error;
917

918
	if (!(error = git_path_lstat(info->to.ptr, &to_st)))
919
		exists = true;
920
	else if (error != GIT_ENOTFOUND)
921
		return error;
922 923 924 925
	else {
		giterr_clear();
		error = 0;
	}
926

927
	if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
928
		return error;
929 930 931 932 933

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

		/* if we are not chmod'ing, then overwrite dirmode */
934
		if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
935 936 937 938
			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)
939
			error = _cp_r_mkdir(info, from);
940 941

		/* recurse onto target directory */
942
		if (!error && (!exists || S_ISDIR(to_st.st_mode)))
943
			error = git_path_direach(from, 0, _cp_r_callback, info);
944 945 946 947

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

948
		return error;
949 950 951 952 953 954 955 956 957
	}

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

		if (p_unlink(info->to.ptr) < 0) {
			giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'",
				info->to.ptr);
958
			return GIT_EEXISTS;
959 960 961 962 963 964 965 966 967 968 969
		}
	}

	/* 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 &&
970
		(error = _cp_r_mkdir(info, from)) < 0)
971
		return error;
972 973

	/* make symlink or regular file */
974
	if (info->flags & GIT_CPDIR_LINK_FILES) {
975 976
		if ((error = p_link(from->ptr, info->to.ptr)) < 0)
			giterr_set(GITERR_OS, "failed to link '%s'", from->ptr);
977
	} else if (S_ISLNK(from_st.st_mode)) {
978
		error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
979
	} else {
980 981
		mode_t usemode = from_st.st_mode;

982
		if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
983
			usemode = GIT_PERMS_FOR_WRITE(usemode);
984 985 986 987

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

988
	return error;
989 990 991 992 993 994 995 996 997 998 999 1000
}

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;

1001
	if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */
1002 1003
		return -1;

1004
	memset(&info, 0, sizeof(info));
1005 1006 1007 1008 1009 1010 1011 1012
	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) {
1013 1014 1015
		/* if not creating empty dirs, then use mkdir to create the path on
		 * demand right before files are copied.
		 */
1016
		info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
1017
		if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
1018 1019
			info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
	} else {
1020
		/* otherwise, we will do simple mkdir as directories are encountered */
1021
		info.mkdir_flags =
1022
			((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
1023 1024 1025 1026 1027
	}

	error = _cp_r_callback(&info, &path);

	git_buf_free(&path);
Russell Belfer committed
1028
	git_buf_free(&info.to);
1029 1030 1031

	return error;
}
1032

Vicent Marti committed
1033 1034
int git_futils_filestamp_check(
	git_futils_filestamp *stamp, const char *path)
1035 1036 1037
{
	struct stat st;

1038 1039
	/* if the stamp is NULL, then always reload */
	if (stamp == NULL)
1040 1041
		return 1;

1042
	if (p_stat(path, &st) < 0)
1043 1044
		return GIT_ENOTFOUND;

1045
	if (stamp->mtime.tv_sec == st.st_mtime &&
1046
#if defined(GIT_USE_NSEC)
1047
		stamp->mtime.tv_nsec == st.st_mtime_nsec &&
1048
#endif
1049 1050
		stamp->size  == (git_off_t)st.st_size   &&
		stamp->ino   == (unsigned int)st.st_ino)
1051 1052
		return 0;

1053
	stamp->mtime.tv_sec = st.st_mtime;
1054
#if defined(GIT_USE_NSEC)
1055
	stamp->mtime.tv_nsec = st.st_mtime_nsec;
1056
#endif
1057 1058
	stamp->size  = (git_off_t)st.st_size;
	stamp->ino   = (unsigned int)st.st_ino;
1059 1060 1061 1062

	return 1;
}

Vicent Marti committed
1063 1064
void git_futils_filestamp_set(
	git_futils_filestamp *target, const git_futils_filestamp *source)
1065 1066 1067 1068 1069 1070 1071 1072
{
	assert(target);

	if (source)
		memcpy(target, source, sizeof(*target));
	else
		memset(target, 0, sizeof(*target));
}
1073 1074 1075 1076 1077 1078


void git_futils_filestamp_set_from_stat(
	git_futils_filestamp *stamp, struct stat *st)
{
	if (st) {
1079 1080 1081 1082
		stamp->mtime.tv_sec = st->st_mtime;
#if defined(GIT_USE_NSEC)
		stamp->mtime.tv_nsec = st->st_mtime_nsec;
#else
1083 1084
		stamp->mtime.tv_nsec = 0;
#endif
1085 1086 1087 1088 1089 1090
		stamp->size  = (git_off_t)st->st_size;
		stamp->ino   = (unsigned int)st->st_ino;
	} else {
		memset(stamp, 0, sizeof(*stamp));
	}
}