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
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
	if (p_chmod(path_out->ptr, (mode & ~mask))) {
		giterr_set(GITERR_OS,
46
			"failed to set permissions on file '%s'", path_out->ptr);
47 48 49
		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
		int error = errno;
76
		giterr_set(GITERR_OS, "failed to create locked file '%s'", path);
77 78 79 80 81 82 83 84
		switch (error) {
		case EEXIST:
			return GIT_ELOCKED;
		case ENOENT:
			return GIT_ENOTFOUND;
		default:
			return -1;
		}
85 86 87
	}

	return fd;
88 89
}

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

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

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

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

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

115 116
	return sb.st_size;
}
117

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

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

	git_buf_clear(buf);

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

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

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

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

157 158 159
	buf->ptr[read_size] = '\0';
	buf->size = read_size;

160 161 162 163
	return 0;
}

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

172
	assert(out && path && *path);
173

174 175
	if (updated != NULL)
		*updated = 0;
176

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

180 181 182 183 184 185 186

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

191 192 193
	if ((fd = git_futils_open_ro(path)) < 0)
		return fd;

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

Vicent Marti committed
199
	p_close(fd);
200

201 202
	if ((error = git_hash_buf(&checksum_new, buf.ptr, buf.size)) < 0) {
		git_buf_free(&buf);
203 204 205 206 207 208 209
		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)) {
210
		git_buf_free(&buf);
211 212 213 214 215 216 217 218 219 220 221 222
		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);

223 224 225
	if (updated != NULL)
		*updated = 1;

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

229
	return 0;
230 231
}

232
int git_futils_readbuffer(git_buf *buf, const char *path)
233
{
234
	return git_futils_readbuffer_updated(buf, path, NULL, NULL);
235 236
}

237 238 239 240 241 242 243 244 245 246 247
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) {
248
		giterr_set(GITERR_OS, "could not open '%s' for writing", path);
249 250 251 252
		return fd;
	}

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

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

	return error;
}

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

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

	return 0;
275 276
}

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

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

	if (fd < 0)
		return fd;

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

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

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

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

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

329
		opts->perfdata.mkdir_calls++;
330

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

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

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

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

	return 0;
}

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

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

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

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

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

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

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

	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;

434 435
	root_len = git_path_root(make_path.ptr);

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

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

484 485 486
		goto done;
	}

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 530
	/* 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;

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

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

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

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

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

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

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

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

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

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

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

		if (opts->dir_map && opts->pool) {
599 600 601 602 603 604 605
			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);
606 607 608 609 610 611 612 613
			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;
		}
614
	}
615

616 617 618
	error = 0;

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

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

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

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

642 643
#define FUTILS_MAX_DEPTH 100

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

652 653
	return -1;
}
654

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

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

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

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

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

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

		data->depth--;

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

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

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

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

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

735
	return error;
736 737
}

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

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

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

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

758
	return error;
759 760
}

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

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

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

	error = futils__rmdir_recurs_foreach(&data, &fullpath);

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

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

	git_buf_free(&fullpath);
790 791

	return error;
792 793
}

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

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

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

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

	return error;
}

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

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

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

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

	ret = p_utimes(path, times);

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

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

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

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

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

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

		info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
	}

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

	return error;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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;

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

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

	error = _cp_r_callback(&info, &path);

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

	return error;
}
1053

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

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

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

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

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

	return 1;
}

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

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


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