fileops.c 25.2 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
int git_futils_mkpath2file(const char *file_path, const mode_t mode)
17
{
18
	return git_futils_mkdir(
19
		file_path, mode,
20
		GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
21 22
}

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

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

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

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

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

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

48
	return fd;
Vicent Marti committed
49 50
}

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

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

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

	return fd;
65 66
}

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

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

	return fd;
86 87
}

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

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

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

Vicent Marti committed
104
git_off_t git_futils_filesize(git_file fd)
105
{
106
	struct stat sb;
107 108

	if (p_fstat(fd, &sb)) {
109
		giterr_set(GITERR_OS, "failed to stat file descriptor");
110 111
		return -1;
	}
112

113 114
	return sb.st_size;
}
115

116 117 118
mode_t git_futils_canonical_mode(mode_t raw_mode)
{
	if (S_ISREG(raw_mode))
119
		return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
120 121 122 123
	else if (S_ISLNK(raw_mode))
		return S_IFLNK;
	else if (S_ISGITLINK(raw_mode))
		return S_IFGITLINK;
124 125
	else if (S_ISDIR(raw_mode))
		return S_IFDIR;
126 127 128 129
	else
		return 0;
}

130 131
int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
{
132
	ssize_t read_size = 0;
133
	size_t alloc_len;
134 135 136

	git_buf_clear(buf);

137
	if (!git__is_ssizet(len)) {
138
		giterr_set(GITERR_INVALID, "read too large");
139 140 141
		return -1;
	}

142 143
	GITERR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
	if (git_buf_grow(buf, alloc_len) < 0)
144 145
		return -1;

146 147
	/* p_read loops internally to read len bytes */
	read_size = p_read(fd, buf->ptr, len);
148

Vicent Marti committed
149
	if (read_size != (ssize_t)len) {
150
		giterr_set(GITERR_OS, "failed to read descriptor");
151
		git_buf_free(buf);
152
		return -1;
153 154
	}

155 156 157
	buf->ptr[read_size] = '\0';
	buf->size = read_size;

158 159 160 161
	return 0;
}

int git_futils_readbuffer_updated(
162
	git_buf *out, const char *path, git_oid *checksum, int *updated)
163
{
164
	int error;
165
	git_file fd;
166
	struct stat st;
167
	git_buf buf = GIT_BUF_INIT;
168
	git_oid checksum_new;
169

170
	assert(out && path && *path);
171

172 173
	if (updated != NULL)
		*updated = 0;
174

175 176
	if (p_stat(path, &st) < 0)
		return git_path_set_error(errno, path, "stat");
177

178 179 180 181 182 183 184

	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)) {
185
		giterr_set(GITERR_OS, "invalid regular file stat for '%s'", path);
186
		return -1;
187
	}
188

189 190 191
	if ((fd = git_futils_open_ro(path)) < 0)
		return fd;

192
	if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
193 194
		p_close(fd);
		return -1;
195 196
	}

Vicent Marti committed
197
	p_close(fd);
198

199 200 201 202 203
	if (checksum) {
		if ((error = git_hash_buf(&checksum_new, buf.ptr, buf.size)) < 0) {
			git_buf_free(&buf);
			return error;
		}
204

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

213 214 215 216
			return 0;
		}

		git_oid_cpy(checksum, &checksum_new);
217 218 219 220 221
	}

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

225 226 227
	git_buf_swap(out, &buf);
	git_buf_free(&buf);

228
	return 0;
229 230
}

231
int git_futils_readbuffer(git_buf *buf, const char *path)
232
{
233
	return git_futils_readbuffer_updated(buf, path, NULL, NULL);
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) {
247
		giterr_set(GITERR_OS, "could not open '%s' for writing", path);
248 249 250 251
		return fd;
	}

	if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
252
		giterr_set(GITERR_OS, "could not write to '%s'", path);
253
		(void)p_close(fd);
254
		return error;
255 256 257
	}

	if ((error = p_close(fd)) < 0)
258
		giterr_set(GITERR_OS, "error while closing '%s'", path);
259 260 261 262

	return error;
}

263
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
264
{
265 266
	if (git_futils_mkpath2file(to, dirmode) < 0)
		return -1;
267

268
	if (p_rename(from, to) < 0) {
269
		giterr_set(GITERR_OS, "failed to rename '%s' to '%s'", from, to);
270 271 272 273
		return -1;
	}

	return 0;
274 275
}

Vicent Marti committed
276
int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
277
{
Vicent Marti committed
278
	return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
279 280
}

281 282
int git_futils_mmap_ro_file(git_map *out, const char *path)
{
283 284
	git_file fd = git_futils_open_ro(path);
	git_off_t len;
285
	int result;
286 287 288 289 290

	if (fd < 0)
		return fd;

	len = git_futils_filesize(fd);
291
	if (!git__is_sizet(len)) {
292
		giterr_set(GITERR_OS, "file `%s` too large to mmap", path);
293 294
		return -1;
	}
295

296
	result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
297 298 299 300
	p_close(fd);
	return result;
}

Vicent Marti committed
301
void git_futils_mmap_free(git_map *out)
302
{
Vicent Marti committed
303
	p_munmap(out);
304 305
}

306 307
GIT_INLINE(int) mkdir_validate_dir(
	const char *path,
308 309 310
	struct stat *st,
	mode_t mode,
	uint32_t flags,
311
	struct git_futils_mkdir_options *opts)
312
{
313 314 315
	/* with exclusive create, existing dir is an error */
	if ((flags & GIT_MKDIR_EXCL) != 0) {
		giterr_set(GITERR_FILESYSTEM,
316
			"failed to make directory '%s': directory exists", path);
317 318 319
		return GIT_EEXISTS;
	}

320 321
	if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
		(S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
322
		if (p_unlink(path) < 0) {
323
			giterr_set(GITERR_OS, "failed to remove %s '%s'",
324
				S_ISLNK(st->st_mode) ? "symlink" : "file", path);
325 326 327
			return GIT_EEXISTS;
		}

328
		opts->perfdata.mkdir_calls++;
329

330
		if (p_mkdir(path, mode) < 0) {
331
			giterr_set(GITERR_OS, "failed to make directory '%s'", path);
332 333 334 335 336 337
			return GIT_EEXISTS;
		}
	}

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

340
		if (p_stat(path, st) < 0) {
341
			giterr_set(GITERR_OS, "failed to make directory '%s'", path);
342 343 344 345 346 347
			return GIT_EEXISTS;
		}
	}

	else if (!S_ISDIR(st->st_mode)) {
		giterr_set(GITERR_FILESYSTEM,
348
			"failed to make directory '%s': directory exists", path);
349 350 351 352 353 354
		return GIT_EEXISTS;
	}

	return 0;
}

355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
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;
}
376

377 378 379
GIT_INLINE(int) mkdir_canonicalize(
	git_buf *path,
	uint32_t flags)
380
{
381
	ssize_t root_len;
382

383 384
	if (path->size == 0) {
		giterr_set(GITERR_OS, "attempt to create empty path");
385
		return -1;
386
	}
387

388
	/* Trim trailing slashes (except the root) */
389
	if ((root_len = git_path_root(path->ptr)) < 0)
390 391 392 393
		root_len = 0;
	else
		root_len++;

394 395
	while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
		path->ptr[--path->size] = '\0';
396

397
	/* if we are not supposed to made the last element, truncate it */
398
	if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
399
		git_path_dirname_r(path, path->ptr);
400 401
		flags |= GIT_MKDIR_SKIP_LAST;
	}
402
	if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
403
		git_path_dirname_r(path, path->ptr);
404
	}
405

406
	/* We were either given the root path (or trimmed it to
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
	* 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;
425
	int len = 0, root_len, error;
426 427 428 429 430 431 432

	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;

433 434
	root_len = git_path_root(make_path.ptr);

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

479 480 481
		if (!error)
			error = mkdir_validate_mode(
				make_path.ptr, &st, true, mode, flags, &opts);
482

483 484 485
		goto done;
	}

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 523 524 525 526 527 528 529
	/* 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;

530 531 532 533
	/* if we are not supposed to make the whole path, reset root */
	if ((flags & GIT_MKDIR_PATH) == 0)
		root = git_buf_rfind(&make_path, '/');

534 535 536 537
	/* 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
538
	while (root >= 0 && make_path.ptr[root] == '/')
539 540
		++root;

541
	/* clip root to make_path length */
542 543
	if (root > (ssize_t)make_path.size)
		root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
544 545
	if (root < 0)
		root = 0;
546

547 548
	/* walk down tail of path making each directory */
	for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
549
		bool mkdir_attempted = false;
550

551 552 553 554 555 556 557 558 559
		/* advance tail to include next path component */
		while (*tail == '/')
			tail++;
		while (*tail && *tail != '/')
			tail++;

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

562 563 564
		if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
			continue;

565
		/* See what's going on with this path component */
566
		opts->perfdata.stat_calls++;
567

568
retry_lstat:
569
		if (p_lstat(make_path.ptr, &st) < 0) {
570
			if (mkdir_attempted || errno != ENOENT) {
571
				giterr_set(GITERR_OS, "cannot access component in path '%s'", make_path.ptr);
572
				error = -1;
573 574 575 576
				goto done;
			}

			giterr_clear();
577 578 579 580 581
			opts->perfdata.mkdir_calls++;
			mkdir_attempted = true;
			if (p_mkdir(make_path.ptr, mode) < 0) {
				if (errno == EEXIST)
					goto retry_lstat;
582
				giterr_set(GITERR_OS, "failed to make directory '%s'", make_path.ptr);
583 584 585
				error = -1;
				goto done;
			}
586
		} else {
587 588
			if ((error = mkdir_validate_dir(
				make_path.ptr, &st, mode, flags, opts)) < 0)
589
				goto done;
590
		}
591

592
		/* chmod if requested and necessary */
593 594 595
		if ((error = mkdir_validate_mode(
			make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
			goto done;
596 597

		if (opts->dir_map && opts->pool) {
598 599 600 601 602 603 604
			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);
605 606 607 608
			GITERR_CHECK_ALLOC(cache_path);

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

609
			git_strmap_insert(opts->dir_map, cache_path, cache_path, &error);
610 611 612
			if (error < 0)
				goto done;
		}
613
	}
614

615 616 617
	error = 0;

	/* check that full path really is a directory if requested & needed */
618
	if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
619
		lastch != '\0') {
620
		opts->perfdata.stat_calls++;
621 622

		if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
623
			giterr_set(GITERR_OS, "path is not a directory '%s'",
624
				make_path.ptr);
625 626
			error = GIT_ENOTFOUND;
		}
627
	}
628

629
done:
630
	git_buf_free(&make_path);
631
	return error;
632
}
633

634
typedef struct {
635
	const char *base;
636
	size_t baselen;
637
	uint32_t flags;
638
	int depth;
639 640
} futils__rmdir_data;

641 642
#define FUTILS_MAX_DEPTH 100

643
static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
644
{
645
	if (filemsg)
646
		giterr_set(GITERR_OS, "could not remove directory '%s': %s",
647 648
				   path, filemsg);
	else
649
		giterr_set(GITERR_OS, "could not remove directory '%s'", path);
650

651 652
	return -1;
}
653

654 655 656 657 658 659 660 661 662 663
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;
664
		else if (p_lstat_posixly(path->ptr, &st) == 0) {
665 666 667 668 669 670 671 672 673 674 675 676 677 678
			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;
}

679 680
static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
{
681
	int error = 0;
682
	futils__rmdir_data *data = opaque;
683
	struct stat st;
684

685 686 687
	if (data->depth > FUTILS_MAX_DEPTH)
		error = futils__error_cannot_rmdir(
			path->ptr, "directory nesting too deep");
688

689
	else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
690
		if (errno == ENOENT)
691
			error = 0;
692 693 694
		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)
695
				error = futils__rm_first_parent(path, data->base);
696 697 698 699 700
			else
				futils__error_cannot_rmdir(
					path->ptr, "parent is not directory");
		}
		else
701
			error = git_path_set_error(errno, path->ptr, "rmdir");
702 703 704
	}

	else if (S_ISDIR(st.st_mode)) {
705 706
		data->depth++;

707
		error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
708 709 710

		data->depth--;

711
		if (error < 0)
712
			return error;
713

714
		if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
715
			return error;
716

717
		if ((error = p_rmdir(path->ptr)) < 0) {
718
			if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
719
				(errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
720
				error = 0;
721
			else
722
				error = git_path_set_error(errno, path->ptr, "rmdir");
723
		}
724 725
	}

726
	else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
727 728
		if (p_unlink(path->ptr) < 0)
			error = git_path_set_error(errno, path->ptr, "remove");
729 730
	}

731
	else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
732
		error = futils__error_cannot_rmdir(path->ptr, "still present");
733

734
	return error;
735 736
}

737
static int futils__rmdir_empty_parent(void *opaque, const char *path)
738
{
739
	futils__rmdir_data *data = opaque;
740
	int error = 0;
741

742
	if (strlen(path) <= data->baselen)
743
		error = GIT_ITEROVER;
744

745
	else if (p_rmdir(path) < 0) {
746 747 748
		int en = errno;

		if (en == ENOENT || en == ENOTDIR) {
749
			/* do nothing */
750
		} else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
751 752
			error = GIT_ITEROVER;
		} else {
753
			error = git_path_set_error(errno, path, "rmdir");
754 755 756
		}
	}

757
	return error;
758 759
}

760
int git_futils_rmdir_r(
761
	const char *path, const char *base, uint32_t flags)
762
{
763
	int error;
764
	git_buf fullpath = GIT_BUF_INIT;
765
	futils__rmdir_data data;
766 767 768 769 770

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

771
	memset(&data, 0, sizeof(data));
772 773 774
	data.base    = base ? base : "";
	data.baselen = base ? strlen(base) : 0;
	data.flags   = flags;
775 776 777 778

	error = futils__rmdir_recurs_foreach(&data, &fullpath);

	/* remove now-empty parents if requested */
779
	if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
780 781 782
		error = git_path_walk_up(
			&fullpath, base, futils__rmdir_empty_parent, &data);

783 784
	if (error == GIT_ITEROVER) {
		giterr_clear();
785
		error = 0;
786
	}
787 788

	git_buf_free(&fullpath);
789 790

	return error;
791 792
}

793 794 795 796 797 798 799 800 801 802
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;
}
803

804
static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
805 806
{
	int error = 0;
807
	char buffer[FILEIO_BUFSIZE];
808 809 810 811 812 813 814 815 816
	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) {
817
		giterr_set(GITERR_OS, "read error while copying file");
818 819 820
		error = (int)len;
	}

821 822 823
	if (error < 0)
		giterr_set(GITERR_OS, "write error while copying file");

824
	if (close_fd_when_done) {
825 826 827 828 829 830 831
		p_close(ifd);
		p_close(ofd);
	}

	return error;
}

832
int git_futils_cp(const char *from, const char *to, mode_t filemode)
833 834 835 836 837 838 839 840
{
	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);
841
		return git_path_set_error(errno, to, "open for writing");
842 843
	}

844
	return cp_by_fd(ifd, ofd, true);
845 846
}

847
int git_futils_touch(const char *path, time_t *when)
848 849 850 851
{
	struct p_timeval times[2];
	int ret;

852 853
	times[0].tv_sec =  times[1].tv_sec  = when ? *when : time(NULL);
	times[0].tv_usec = times[1].tv_usec = 0;
854 855 856 857 858 859

	ret = p_utimes(path, times);

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

860
static int cp_link(const char *from, const char *to, size_t link_size)
861 862 863
{
	int error = 0;
	ssize_t read_len;
864
	char *link_data;
865
	size_t alloc_size;
866

867 868
	GITERR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
	link_data = git__malloc(alloc_size);
869 870
	GITERR_CHECK_ALLOC(link_data);

871 872
	read_len = p_readlink(from, link_data, link_size);
	if (read_len != (ssize_t)link_size) {
873
		giterr_set(GITERR_OS, "failed to read symlink data for '%s'", from);
874 875 876 877 878 879
		error = -1;
	}
	else {
		link_data[read_len] = '\0';

		if (p_symlink(link_data, to) < 0) {
880
			giterr_set(GITERR_OS, "could not symlink '%s' as '%s'",
881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898
				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;

899 900 901 902 903 904 905 906 907
#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(
908
			info->to_root, info->dirmode,
909
			(info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
910 911 912 913 914 915

		info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
	}

	/* create directory with root as base to prevent excess chmods */
	if (!error)
916
		error = git_futils_mkdir_relative(
917
			from->ptr + info->from_prefix, info->to_root,
918
			info->dirmode, info->mkdir_flags, NULL);
919 920 921 922

	return error;
}

923 924
static int _cp_r_callback(void *ref, git_buf *from)
{
925
	int error = 0;
926 927 928 929 930 931 932 933
	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;

934 935
	if ((error = git_buf_joinpath(
			&info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
936
		return error;
937

938
	if (!(error = git_path_lstat(info->to.ptr, &to_st)))
939
		exists = true;
940
	else if (error != GIT_ENOTFOUND)
941
		return error;
942 943 944 945
	else {
		giterr_clear();
		error = 0;
	}
946

947
	if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
948
		return error;
949 950 951 952 953

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

		/* if we are not chmod'ing, then overwrite dirmode */
954
		if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
955 956 957 958
			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)
959
			error = _cp_r_mkdir(info, from);
960 961

		/* recurse onto target directory */
962
		if (!error && (!exists || S_ISDIR(to_st.st_mode)))
963
			error = git_path_direach(from, 0, _cp_r_callback, info);
964 965 966 967

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

968
		return error;
969 970 971 972 973 974 975
	}

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

		if (p_unlink(info->to.ptr) < 0) {
976
			giterr_set(GITERR_OS, "cannot overwrite existing file '%s'",
977
				info->to.ptr);
978
			return GIT_EEXISTS;
979 980 981 982 983 984 985 986 987 988 989
		}
	}

	/* 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 &&
990
		(error = _cp_r_mkdir(info, from)) < 0)
991
		return error;
992 993

	/* make symlink or regular file */
994
	if (info->flags & GIT_CPDIR_LINK_FILES) {
995 996
		if ((error = p_link(from->ptr, info->to.ptr)) < 0)
			giterr_set(GITERR_OS, "failed to link '%s'", from->ptr);
997
	} else if (S_ISLNK(from_st.st_mode)) {
998
		error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
999
	} else {
1000 1001
		mode_t usemode = from_st.st_mode;

1002
		if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
1003
			usemode = GIT_PERMS_FOR_WRITE(usemode);
1004 1005 1006 1007

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

1008
	return error;
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
}

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;

1021
	if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */
1022 1023
		return -1;

1024
	memset(&info, 0, sizeof(info));
1025 1026 1027 1028 1029 1030 1031 1032
	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) {
1033 1034 1035
		/* if not creating empty dirs, then use mkdir to create the path on
		 * demand right before files are copied.
		 */
1036
		info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
1037
		if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
1038 1039
			info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
	} else {
1040
		/* otherwise, we will do simple mkdir as directories are encountered */
1041
		info.mkdir_flags =
1042
			((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
1043 1044 1045 1046 1047
	}

	error = _cp_r_callback(&info, &path);

	git_buf_free(&path);
Russell Belfer committed
1048
	git_buf_free(&info.to);
1049 1050 1051

	return error;
}
1052

Vicent Marti committed
1053 1054
int git_futils_filestamp_check(
	git_futils_filestamp *stamp, const char *path)
1055 1056 1057
{
	struct stat st;

1058 1059
	/* if the stamp is NULL, then always reload */
	if (stamp == NULL)
1060 1061
		return 1;

1062
	if (p_stat(path, &st) < 0)
1063 1064
		return GIT_ENOTFOUND;

1065
	if (stamp->mtime.tv_sec == st.st_mtime &&
1066
#if defined(GIT_USE_NSEC)
1067
		stamp->mtime.tv_nsec == st.st_mtime_nsec &&
1068
#endif
1069 1070
		stamp->size  == (git_off_t)st.st_size   &&
		stamp->ino   == (unsigned int)st.st_ino)
1071 1072
		return 0;

1073
	stamp->mtime.tv_sec = st.st_mtime;
1074
#if defined(GIT_USE_NSEC)
1075
	stamp->mtime.tv_nsec = st.st_mtime_nsec;
1076
#endif
1077 1078
	stamp->size  = (git_off_t)st.st_size;
	stamp->ino   = (unsigned int)st.st_ino;
1079 1080 1081 1082

	return 1;
}

Vicent Marti committed
1083 1084
void git_futils_filestamp_set(
	git_futils_filestamp *target, const git_futils_filestamp *source)
1085 1086 1087 1088 1089 1090 1091 1092
{
	assert(target);

	if (source)
		memcpy(target, source, sizeof(*target));
	else
		memset(target, 0, sizeof(*target));
}
1093 1094 1095 1096 1097 1098


void git_futils_filestamp_set_from_stat(
	git_futils_filestamp *stamp, struct stat *st)
{
	if (st) {
1099 1100 1101 1102
		stamp->mtime.tv_sec = st->st_mtime;
#if defined(GIT_USE_NSEC)
		stamp->mtime.tv_nsec = st->st_mtime_nsec;
#else
1103 1104
		stamp->mtime.tv_nsec = 0;
#endif
1105 1106 1107 1108 1109 1110
		stamp->size  = (git_off_t)st->st_size;
		stamp->ino   = (unsigned int)st->st_ino;
	} else {
		memset(stamp, 0, sizeof(*stamp));
	}
}