futils.c 28.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

8
#include "futils.h"
9

10
#include "runtime.h"
11
#include "strmap.h"
12
#include "hash.h"
13 14
#include "rand.h"

15
#include <ctype.h>
16 17 18
#if GIT_WIN32
#include "win32/findfile.h"
#endif
19

20 21
#define GIT_FILEMODE_DEFAULT 0100666

22
int git_futils_mkpath2file(const char *file_path, const mode_t mode)
23
{
24
	return git_futils_mkdir(
25
		file_path, mode,
26
		GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
27 28
}

29
int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode)
Vicent Marti committed
30
{
31
	const int open_flags = O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC;
32
	unsigned int tries = 32;
Vicent Marti committed
33
	int fd;
34

35
	while (tries--) {
36 37
		uint64_t rand = git_rand_next();

38
		git_str_sets(path_out, filename);
39 40
		git_str_puts(path_out, "_git2_");
		git_str_encode_hexstr(path_out, (void *)&rand, sizeof(uint64_t));
Vicent Marti committed
41

42 43
		if (git_str_oom(path_out))
			return -1;
44

45
		/* Note that we open with O_CREAT | O_EXCL */
46 47
		if ((fd = p_open(path_out->ptr, open_flags, mode)) >= 0)
			return fd;
48
	}
Vicent Marti committed
49

50 51 52 53
	git_error_set(GIT_ERROR_OS,
		"failed to create temporary file '%s'", path_out->ptr);
	git_str_dispose(path_out);
	return -1;
Vicent Marti committed
54 55
}

56
int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
57
{
58
	int fd;
59

60 61 62 63 64
	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;

	fd = p_creat(path, mode);
	if (fd < 0) {
65
		git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path);
66 67 68 69
		return -1;
	}

	return fd;
70 71
}

72
int git_futils_creat_locked(const char *path, const mode_t mode)
73
{
74 75
	int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC,
		mode);
76

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

	return fd;
91 92
}

93
int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
94
{
95 96
	if (git_futils_mkpath2file(path, dirmode) < 0)
		return -1;
97

Vicent Marti committed
98
	return git_futils_creat_locked(path, mode);
99 100
}

101 102 103
int git_futils_open_ro(const char *path)
{
	int fd = p_open(path, O_RDONLY);
104
	if (fd < 0)
105
		return git_fs_path_set_error(errno, path, "open");
106 107 108
	return fd;
}

109 110 111 112
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)
113
		return git_fs_path_set_error(errno, path, "open");
114 115 116 117 118

	close(fd);
	return 0;
}

119
int git_futils_filesize(uint64_t *out, git_file fd)
120
{
121
	struct stat sb;
122 123

	if (p_fstat(fd, &sb)) {
124
		git_error_set(GIT_ERROR_OS, "failed to stat file descriptor");
125 126
		return -1;
	}
127

128 129 130 131 132 133 134
	if (sb.st_size < 0) {
		git_error_set(GIT_ERROR_INVALID, "invalid file size");
		return -1;
	}

	*out = sb.st_size;
	return 0;
135
}
136

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

151
int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len)
152
{
153
	ssize_t read_size = 0;
154
	size_t alloc_len;
155

156
	git_str_clear(buf);
157

158
	if (!git__is_ssizet(len)) {
159
		git_error_set(GIT_ERROR_INVALID, "read too large");
160 161 162
		return -1;
	}

163
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
164
	if (git_str_grow(buf, alloc_len) < 0)
165 166
		return -1;

167 168
	/* p_read loops internally to read len bytes */
	read_size = p_read(fd, buf->ptr, len);
169

170
	if (read_size < 0) {
171
		git_error_set(GIT_ERROR_OS, "failed to read descriptor");
172
		git_str_dispose(buf);
173
		return -1;
174 175
	}

176 177 178 179 180 181
	if ((size_t)read_size != len) {
		git_error_set(GIT_ERROR_FILESYSTEM, "could not read (expected %" PRIuZ " bytes, read %" PRIuZ ")", len, (size_t)read_size);
		git_str_dispose(buf);
		return -1;
	}

182 183 184
	buf->ptr[read_size] = '\0';
	buf->size = read_size;

185 186 187
	return 0;
}

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
int git_futils_readbuffer_fd_full(git_str *buf, git_file fd)
{
	static size_t blocksize = 10240;
	size_t alloc_len = 0, total_size = 0;
	ssize_t read_size = 0;

	git_str_clear(buf);

	while (true) {
		GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, blocksize);

		if (git_str_grow(buf, alloc_len) < 0)
			return -1;

		/* p_read loops internally to read blocksize bytes */
		read_size = p_read(fd, buf->ptr, blocksize);

		if (read_size < 0) {
			git_error_set(GIT_ERROR_OS, "failed to read descriptor");
			git_str_dispose(buf);
			return -1;
		}

		total_size += read_size;

		if ((size_t)read_size < blocksize) {
			break;
		}
	}

	buf->ptr[total_size] = '\0';
	buf->size = total_size;

	return 0;
}

224
int git_futils_readbuffer_updated(
225
	git_str *out,
226 227 228
	const char *path,
	unsigned char checksum[GIT_HASH_SHA1_SIZE],
	int *updated)
229
{
230
	int error;
231
	git_file fd;
232
	struct stat st;
233
	git_str buf = GIT_STR_INIT;
234
	unsigned char checksum_new[GIT_HASH_SHA1_SIZE];
235

236 237
	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(path && *path);
238

239 240
	if (updated != NULL)
		*updated = 0;
241

242
	if (p_stat(path, &st) < 0)
243
		return git_fs_path_set_error(errno, path, "stat");
244

245 246

	if (S_ISDIR(st.st_mode)) {
247
		git_error_set(GIT_ERROR_INVALID, "requested file is a directory");
248 249 250 251
		return GIT_ENOTFOUND;
	}

	if (!git__is_sizet(st.st_size+1)) {
252
		git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path);
253
		return -1;
254
	}
255

256 257 258
	if ((fd = git_futils_open_ro(path)) < 0)
		return fd;

259
	if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
260 261
		p_close(fd);
		return -1;
262 263
	}

Vicent Marti committed
264
	p_close(fd);
265

266
	if (checksum) {
267
		if ((error = git_hash_buf(checksum_new, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) {
268
			git_str_dispose(&buf);
269 270
			return error;
		}
271

272 273 274
		/*
		 * If we were given a checksum, we only want to use it if it's different
		 */
275
		if (!memcmp(checksum, checksum_new, GIT_HASH_SHA1_SIZE)) {
276
			git_str_dispose(&buf);
277 278
			if (updated)
				*updated = 0;
279

280 281 282
			return 0;
		}

283
		memcpy(checksum, checksum_new, GIT_HASH_SHA1_SIZE);
284 285 286 287 288
	}

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

292 293
	git_str_swap(out, &buf);
	git_str_dispose(&buf);
294

295
	return 0;
296 297
}

298
int git_futils_readbuffer(git_str *buf, const char *path)
299
{
300
	return git_futils_readbuffer_updated(buf, path, NULL, NULL);
301 302
}

303
int git_futils_writebuffer(
304
	const git_str *buf, const char *path, int flags, mode_t mode)
305
{
306
	int fd, do_fsync = 0, error = 0;
307

308
	if (!flags)
309
		flags = O_CREAT | O_TRUNC | O_WRONLY;
310

311 312 313 314
	if ((flags & O_FSYNC) != 0)
		do_fsync = 1;

	flags &= ~O_FSYNC;
315 316

	if (!mode)
317
		mode = GIT_FILEMODE_DEFAULT;
318 319

	if ((fd = p_open(path, flags, mode)) < 0) {
320
		git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path);
321 322 323
		return fd;
	}

324
	if ((error = p_write(fd, git_str_cstr(buf), git_str_len(buf))) < 0) {
325
		git_error_set(GIT_ERROR_OS, "could not write to '%s'", path);
326
		(void)p_close(fd);
327
		return error;
328 329
	}

330
	if (do_fsync && (error = p_fsync(fd)) < 0) {
331
		git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path);
332 333 334 335
		p_close(fd);
		return error;
	}

336
	if ((error = p_close(fd)) < 0) {
337
		git_error_set(GIT_ERROR_OS, "error while closing '%s'", path);
338 339 340 341 342
		return error;
	}

	if (do_fsync && (flags & O_CREAT))
		error = git_futils_fsync_parent(path);
343 344 345 346

	return error;
}

347
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
348
{
349 350
	if (git_futils_mkpath2file(to, dirmode) < 0)
		return -1;
351

352
	if (p_rename(from, to) < 0) {
353
		git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to);
354 355 356 357
		return -1;
	}

	return 0;
358 359
}

360
int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len)
361
{
Vicent Marti committed
362
	return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
363 364
}

365 366
int git_futils_mmap_ro_file(git_map *out, const char *path)
{
367
	git_file fd = git_futils_open_ro(path);
368
	uint64_t len;
369
	int result;
370 371 372 373

	if (fd < 0)
		return fd;

374
	if ((result = git_futils_filesize(&len, fd)) < 0)
375
		goto out;
376

377
	if (!git__is_sizet(len)) {
378
		git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path);
379 380
		result = -1;
		goto out;
381
	}
382

383
	result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
384
out:
385 386 387 388
	p_close(fd);
	return result;
}

Vicent Marti committed
389
void git_futils_mmap_free(git_map *out)
390
{
Vicent Marti committed
391
	p_munmap(out);
392 393
}

394 395
GIT_INLINE(int) mkdir_validate_dir(
	const char *path,
396 397 398
	struct stat *st,
	mode_t mode,
	uint32_t flags,
399
	struct git_futils_mkdir_options *opts)
400
{
401 402
	/* with exclusive create, existing dir is an error */
	if ((flags & GIT_MKDIR_EXCL) != 0) {
403
		git_error_set(GIT_ERROR_FILESYSTEM,
404
			"failed to make directory '%s': directory exists", path);
405 406 407
		return GIT_EEXISTS;
	}

408 409
	if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
		(S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
410
		if (p_unlink(path) < 0) {
411
			git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'",
412
				S_ISLNK(st->st_mode) ? "symlink" : "file", path);
413 414 415
			return GIT_EEXISTS;
		}

416
		opts->perfdata.mkdir_calls++;
417

418
		if (p_mkdir(path, mode) < 0) {
419
			git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
420 421 422 423 424 425
			return GIT_EEXISTS;
		}
	}

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

428
		if (p_stat(path, st) < 0) {
429
			git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
430 431 432 433 434
			return GIT_EEXISTS;
		}
	}

	else if (!S_ISDIR(st->st_mode)) {
435
		git_error_set(GIT_ERROR_FILESYSTEM,
436
			"failed to make directory '%s': directory exists", path);
437 438 439 440 441 442
		return GIT_EEXISTS;
	}

	return 0;
}

443 444 445 446 447 448 449 450 451 452 453 454 455 456
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) {
457
			git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path);
458 459 460 461 462 463
			return -1;
		}
	}

	return 0;
}
464

465
GIT_INLINE(int) mkdir_canonicalize(
466
	git_str *path,
467
	uint32_t flags)
468
{
469
	ssize_t root_len;
470

471
	if (path->size == 0) {
472
		git_error_set(GIT_ERROR_OS, "attempt to create empty path");
473
		return -1;
474
	}
475

476
	/* Trim trailing slashes (except the root) */
477
	if ((root_len = git_fs_path_root(path->ptr)) < 0)
478 479 480 481
		root_len = 0;
	else
		root_len++;

482 483
	while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
		path->ptr[--path->size] = '\0';
484

485
	/* if we are not supposed to made the last element, truncate it */
486
	if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
487
		git_fs_path_dirname_r(path, path->ptr);
488 489
		flags |= GIT_MKDIR_SKIP_LAST;
	}
490
	if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
491
		git_fs_path_dirname_r(path, path->ptr);
492
	}
493

494
	/* We were either given the root path (or trimmed it to
495 496 497
	* the root), we don't have anything to do.
	*/
	if (path->size <= (size_t)root_len)
498
		git_str_clear(path);
499 500 501 502 503 504 505 506 507

	return 0;
}

int git_futils_mkdir(
	const char *path,
	mode_t mode,
	uint32_t flags)
{
508
	git_str make_path = GIT_STR_INIT, parent_path = GIT_STR_INIT;
509 510 511 512
	const char *relative;
	struct git_futils_mkdir_options opts = { 0 };
	struct stat st;
	size_t depth = 0;
513
	int len = 0, root_len, error;
514

515
	if ((error = git_str_puts(&make_path, path)) < 0 ||
516
		(error = mkdir_canonicalize(&make_path, flags)) < 0 ||
517
		(error = git_str_puts(&parent_path, make_path.ptr)) < 0 ||
518 519 520
		make_path.size == 0)
		goto done;

521
	root_len = git_fs_path_root(make_path.ptr);
522

523 524
	/* find the first parent directory that exists.  this will be used
	 * as the base to dirname_relative.
525
	 */
526 527 528 529 530 531
	for (relative = make_path.ptr; parent_path.size; ) {
		error = p_lstat(parent_path.ptr, &st);

		if (error == 0) {
			break;
		} else if (errno != ENOENT) {
532
			git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr);
533
			error = -1;
534 535 536 537 538 539
			goto done;
		}

		depth++;

		/* examine the parent of the current path */
540
		if ((len = git_fs_path_dirname_r(&parent_path, parent_path.ptr)) < 0) {
541 542 543 544
			error = len;
			goto done;
		}

545
		GIT_ASSERT(len);
546

547 548 549 550 551
		/*
		 * We've walked all the given path's parents and it's either relative
		 * (the parent is simply '.') or rooted (the length is less than or
		 * equal to length of the root path).  The path may be less than the
		 * root path length on Windows, where `C:` == `C:/`.
552
		 */
553 554 555
		if ((len == 1 && parent_path.ptr[0] == '.') ||
		    (len == 1 && parent_path.ptr[0] == '/') ||
		    len <= root_len) {
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
			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) {
571
		error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts);
572

573 574 575
		if (!error)
			error = mkdir_validate_mode(
				make_path.ptr, &st, true, mode, flags, &opts);
576

577 578 579
		goto done;
	}

580 581 582 583 584 585 586 587 588
	/* 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:
589 590
	git_str_dispose(&make_path);
	git_str_dispose(&parent_path);
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
	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)
{
606
	git_str make_path = GIT_STR_INIT;
607 608 609 610 611 612 613 614 615 616
	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 */
617
	if (git_fs_path_join_unrooted(&make_path, relative_path, base, &root) < 0)
618 619 620 621 622 623
		return -1;

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

624 625
	/* if we are not supposed to make the whole path, reset root */
	if ((flags & GIT_MKDIR_PATH) == 0)
626
		root = git_str_rfind(&make_path, '/');
627

628
	/* advance root past drive name or network mount prefix */
629
	min_root_len = git_fs_path_root(make_path.ptr);
630 631
	if (root < min_root_len)
		root = min_root_len;
yorah committed
632
	while (root >= 0 && make_path.ptr[root] == '/')
633 634
		++root;

635
	/* clip root to make_path length */
636 637
	if (root > (ssize_t)make_path.size)
		root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
638 639
	if (root < 0)
		root = 0;
640

641 642
	/* walk down tail of path making each directory */
	for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
643
		bool mkdir_attempted = false;
644

645 646 647 648 649 650 651 652 653
		/* advance tail to include next path component */
		while (*tail == '/')
			tail++;
		while (*tail && *tail != '/')
			tail++;

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

656 657 658
		if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
			continue;

659
		/* See what's going on with this path component */
660
		opts->perfdata.stat_calls++;
661

662
retry_lstat:
663
		if (p_lstat(make_path.ptr, &st) < 0) {
664
			if (mkdir_attempted || errno != ENOENT) {
665
				git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr);
666
				error = -1;
667 668 669
				goto done;
			}

670
			git_error_clear();
671 672 673 674 675
			opts->perfdata.mkdir_calls++;
			mkdir_attempted = true;
			if (p_mkdir(make_path.ptr, mode) < 0) {
				if (errno == EEXIST)
					goto retry_lstat;
676
				git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr);
677 678 679
				error = -1;
				goto done;
			}
680
		} else {
681 682
			if ((error = mkdir_validate_dir(
				make_path.ptr, &st, mode, flags, opts)) < 0)
683
				goto done;
684
		}
685

686
		/* chmod if requested and necessary */
687 688 689
		if ((error = mkdir_validate_mode(
			make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
			goto done;
690 691

		if (opts->dir_map && opts->pool) {
692 693 694
			char *cache_path;
			size_t alloc_size;

695
			GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1);
696
			cache_path = git_pool_malloc(opts->pool, alloc_size);
697
			GIT_ERROR_CHECK_ALLOC(cache_path);
698 699 700

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

701
			if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0)
702 703
				goto done;
		}
704
	}
705

706 707 708
	error = 0;

	/* check that full path really is a directory if requested & needed */
709
	if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
710
		lastch != '\0') {
711
		opts->perfdata.stat_calls++;
712 713

		if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
714
			git_error_set(GIT_ERROR_OS, "path is not a directory '%s'",
715
				make_path.ptr);
716 717
			error = GIT_ENOTFOUND;
		}
718
	}
719

720
done:
721
	git_str_dispose(&make_path);
722
	return error;
723
}
724

725
typedef struct {
726
	const char *base;
727
	size_t baselen;
728
	uint32_t flags;
729
	int depth;
730 731
} futils__rmdir_data;

732 733
#define FUTILS_MAX_DEPTH 100

734
static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
735
{
736
	if (filemsg)
737
		git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s",
738 739
				   path, filemsg);
	else
740
		git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path);
741

742 743
	return -1;
}
744

745
static int futils__rm_first_parent(git_str *path, const char *ceiling)
746 747 748 749 750
{
	int error = GIT_ENOTFOUND;
	struct stat st;

	while (error == GIT_ENOTFOUND) {
751
		git_str_rtruncate_at_char(path, '/');
752 753 754

		if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)
			error = 0;
755
		else if (p_lstat_posixly(path->ptr, &st) == 0) {
756 757 758 759 760 761 762 763 764 765 766 767 768 769
			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;
}

770
static int futils__rmdir_recurs_foreach(void *opaque, git_str *path)
771
{
772
	int error = 0;
773
	futils__rmdir_data *data = opaque;
774
	struct stat st;
775

776 777 778
	if (data->depth > FUTILS_MAX_DEPTH)
		error = futils__error_cannot_rmdir(
			path->ptr, "directory nesting too deep");
779

780
	else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
781
		if (errno == ENOENT)
782
			error = 0;
783 784 785
		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)
786
				error = futils__rm_first_parent(path, data->base);
787 788 789 790 791
			else
				futils__error_cannot_rmdir(
					path->ptr, "parent is not directory");
		}
		else
792
			error = git_fs_path_set_error(errno, path->ptr, "rmdir");
793 794 795
	}

	else if (S_ISDIR(st.st_mode)) {
796 797
		data->depth++;

798
		error = git_fs_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
799 800 801

		data->depth--;

802
		if (error < 0)
803
			return error;
804

805
		if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
806
			return error;
807

808
		if ((error = p_rmdir(path->ptr)) < 0) {
809
			if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
810
				(errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
811
				error = 0;
812
			else
813
				error = git_fs_path_set_error(errno, path->ptr, "rmdir");
814
		}
815 816
	}

817
	else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
818
		if (p_unlink(path->ptr) < 0)
819
			error = git_fs_path_set_error(errno, path->ptr, "remove");
820 821
	}

822
	else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
823
		error = futils__error_cannot_rmdir(path->ptr, "still present");
824

825
	return error;
826 827
}

828
static int futils__rmdir_empty_parent(void *opaque, const char *path)
829
{
830
	futils__rmdir_data *data = opaque;
831
	int error = 0;
832

833
	if (strlen(path) <= data->baselen)
834
		error = GIT_ITEROVER;
835

836
	else if (p_rmdir(path) < 0) {
837 838 839
		int en = errno;

		if (en == ENOENT || en == ENOTDIR) {
840
			/* do nothing */
841 842
		} else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 &&
			en == EBUSY) {
843
			error = git_fs_path_set_error(errno, path, "rmdir");
844
		} else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
845 846
			error = GIT_ITEROVER;
		} else {
847
			error = git_fs_path_set_error(errno, path, "rmdir");
848 849 850
		}
	}

851
	return error;
852 853
}

854
int git_futils_rmdir_r(
855
	const char *path, const char *base, uint32_t flags)
856
{
857
	int error;
858
	git_str fullpath = GIT_STR_INIT;
859
	futils__rmdir_data data;
860 861

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

865
	memset(&data, 0, sizeof(data));
866 867 868
	data.base    = base ? base : "";
	data.baselen = base ? strlen(base) : 0;
	data.flags   = flags;
869 870 871 872

	error = futils__rmdir_recurs_foreach(&data, &fullpath);

	/* remove now-empty parents if requested */
873
	if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
874
		error = git_fs_path_walk_up(
875 876
			&fullpath, base, futils__rmdir_empty_parent, &data);

877
	if (error == GIT_ITEROVER) {
878
		git_error_clear();
879
		error = 0;
880
	}
881

882
	git_str_dispose(&fullpath);
883 884

	return error;
885 886
}

887
int git_futils_fake_symlink(const char *target, const char *path)
888 889
{
	int retcode = GIT_ERROR;
890
	int fd = git_futils_creat_withpath(path, 0755, 0644);
891
	if (fd >= 0) {
892
		retcode = p_write(fd, target, strlen(target));
893 894 895 896
		p_close(fd);
	}
	return retcode;
}
897

898
static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
899 900
{
	int error = 0;
901
	char buffer[GIT_BUFSIZE_FILEIO];
902 903 904 905 906 907 908 909 910
	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) {
911
		git_error_set(GIT_ERROR_OS, "read error while copying file");
912 913 914
		error = (int)len;
	}

915
	if (error < 0)
916
		git_error_set(GIT_ERROR_OS, "write error while copying file");
917

918
	if (close_fd_when_done) {
919 920 921 922 923 924 925
		p_close(ifd);
		p_close(ofd);
	}

	return error;
}

926
int git_futils_cp(const char *from, const char *to, mode_t filemode)
927 928 929 930 931 932 933 934
{
	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);
935
		return git_fs_path_set_error(errno, to, "open for writing");
936 937
	}

938
	return cp_by_fd(ifd, ofd, true);
939 940
}

941
int git_futils_touch(const char *path, time_t *when)
942 943 944 945
{
	struct p_timeval times[2];
	int ret;

946 947
	times[0].tv_sec =  times[1].tv_sec  = when ? *when : time(NULL);
	times[0].tv_usec = times[1].tv_usec = 0;
948 949 950

	ret = p_utimes(path, times);

951
	return (ret < 0) ? git_fs_path_set_error(errno, path, "touch") : 0;
952 953
}

954
static int cp_link(const char *from, const char *to, size_t link_size)
955 956 957
{
	int error = 0;
	ssize_t read_len;
958
	char *link_data;
959
	size_t alloc_size;
960

961
	GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
962
	link_data = git__malloc(alloc_size);
963
	GIT_ERROR_CHECK_ALLOC(link_data);
964

965 966
	read_len = p_readlink(from, link_data, link_size);
	if (read_len != (ssize_t)link_size) {
967
		git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from);
968 969 970 971 972 973
		error = -1;
	}
	else {
		link_data[read_len] = '\0';

		if (p_symlink(link_data, to) < 0) {
974
			git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'",
975 976 977 978 979 980 981 982 983 984 985
				link_data, to);
			error = -1;
		}
	}

	git__free(link_data);
	return error;
}

typedef struct {
	const char *to_root;
986
	git_str to;
987 988 989 990 991 992
	ssize_t from_prefix;
	uint32_t flags;
	uint32_t mkdir_flags;
	mode_t dirmode;
} cp_r_info;

993 994
#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)

995
static int _cp_r_mkdir(cp_r_info *info, git_str *from)
996 997 998 999 1000 1001
{
	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(
1002
			info->to_root, info->dirmode,
1003
			(info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
1004 1005 1006 1007 1008 1009

		info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
	}

	/* create directory with root as base to prevent excess chmods */
	if (!error)
1010
		error = git_futils_mkdir_relative(
1011
			from->ptr + info->from_prefix, info->to_root,
1012
			info->dirmode, info->mkdir_flags, NULL);
1013 1014 1015 1016

	return error;
}

1017
static int _cp_r_callback(void *ref, git_str *from)
1018
{
1019
	int error = 0;
1020 1021 1022 1023 1024
	cp_r_info *info = ref;
	struct stat from_st, to_st;
	bool exists = false;

	if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
1025
		from->ptr[git_fs_path_basename_offset(from)] == '.')
1026 1027
		return 0;

1028
	if ((error = git_str_joinpath(
1029
			&info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
1030
		return error;
1031

1032
	if (!(error = git_fs_path_lstat(info->to.ptr, &to_st)))
1033
		exists = true;
1034
	else if (error != GIT_ENOTFOUND)
1035
		return error;
1036
	else {
1037
		git_error_clear();
1038 1039
		error = 0;
	}
1040

1041
	if ((error = git_fs_path_lstat(from->ptr, &from_st)) < 0)
1042
		return error;
1043 1044 1045 1046 1047

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

		/* if we are not chmod'ing, then overwrite dirmode */
1048
		if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
1049 1050 1051 1052
			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)
1053
			error = _cp_r_mkdir(info, from);
1054 1055

		/* recurse onto target directory */
1056
		if (!error && (!exists || S_ISDIR(to_st.st_mode)))
1057
			error = git_fs_path_direach(from, 0, _cp_r_callback, info);
1058 1059 1060 1061

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

1062
		return error;
1063 1064 1065 1066 1067 1068 1069
	}

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

		if (p_unlink(info->to.ptr) < 0) {
1070
			git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'",
1071
				info->to.ptr);
1072
			return GIT_EEXISTS;
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
		}
	}

	/* 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 &&
1084
		(error = _cp_r_mkdir(info, from)) < 0)
1085
		return error;
1086 1087

	/* make symlink or regular file */
1088
	if (info->flags & GIT_CPDIR_LINK_FILES) {
1089
		if ((error = p_link(from->ptr, info->to.ptr)) < 0)
1090
			git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr);
1091
	} else if (S_ISLNK(from_st.st_mode)) {
1092
		error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
1093
	} else {
1094 1095
		mode_t usemode = from_st.st_mode;

1096
		if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
1097
			usemode = GIT_PERMS_FOR_WRITE(usemode);
1098 1099 1100 1101

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

1102
	return error;
1103 1104 1105 1106 1107 1108 1109 1110 1111
}

int git_futils_cp_r(
	const char *from,
	const char *to,
	uint32_t flags,
	mode_t dirmode)
{
	int error;
1112
	git_str path = GIT_STR_INIT;
1113 1114
	cp_r_info info;

1115
	if (git_str_joinpath(&path, from, "") < 0) /* ensure trailing slash */
1116 1117
		return -1;

1118
	memset(&info, 0, sizeof(info));
1119 1120 1121 1122
	info.to_root = to;
	info.flags   = flags;
	info.dirmode = dirmode;
	info.from_prefix = path.size;
1123
	git_str_init(&info.to, 0);
1124 1125 1126

	/* precalculate mkdir flags */
	if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
1127 1128 1129
		/* if not creating empty dirs, then use mkdir to create the path on
		 * demand right before files are copied.
		 */
1130
		info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
1131
		if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
1132 1133
			info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
	} else {
1134
		/* otherwise, we will do simple mkdir as directories are encountered */
1135
		info.mkdir_flags =
1136
			((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
1137 1138 1139 1140
	}

	error = _cp_r_callback(&info, &path);

1141 1142
	git_str_dispose(&path);
	git_str_dispose(&info.to);
1143 1144 1145

	return error;
}
1146

Vicent Marti committed
1147 1148
int git_futils_filestamp_check(
	git_futils_filestamp *stamp, const char *path)
1149 1150 1151
{
	struct stat st;

1152 1153
	/* if the stamp is NULL, then always reload */
	if (stamp == NULL)
1154 1155
		return 1;

1156
	if (p_stat(path, &st) < 0)
1157 1158
		return GIT_ENOTFOUND;

1159
	if (stamp->mtime.tv_sec == st.st_mtime &&
1160
#if defined(GIT_USE_NSEC)
1161
		stamp->mtime.tv_nsec == st.st_mtime_nsec &&
1162
#endif
1163
		stamp->size  == (uint64_t)st.st_size   &&
1164
		stamp->ino   == (unsigned int)st.st_ino)
1165 1166
		return 0;

1167
	stamp->mtime.tv_sec = st.st_mtime;
1168
#if defined(GIT_USE_NSEC)
1169
	stamp->mtime.tv_nsec = st.st_mtime_nsec;
1170
#endif
1171
	stamp->size  = (uint64_t)st.st_size;
1172
	stamp->ino   = (unsigned int)st.st_ino;
1173 1174 1175 1176

	return 1;
}

Vicent Marti committed
1177 1178
void git_futils_filestamp_set(
	git_futils_filestamp *target, const git_futils_filestamp *source)
1179 1180 1181 1182 1183 1184
{
	if (source)
		memcpy(target, source, sizeof(*target));
	else
		memset(target, 0, sizeof(*target));
}
1185 1186 1187 1188 1189 1190


void git_futils_filestamp_set_from_stat(
	git_futils_filestamp *stamp, struct stat *st)
{
	if (st) {
1191 1192 1193 1194
		stamp->mtime.tv_sec = st->st_mtime;
#if defined(GIT_USE_NSEC)
		stamp->mtime.tv_nsec = st->st_mtime_nsec;
#else
1195 1196
		stamp->mtime.tv_nsec = 0;
#endif
1197
		stamp->size  = (uint64_t)st->st_size;
1198 1199 1200 1201 1202
		stamp->ino   = (unsigned int)st->st_ino;
	} else {
		memset(stamp, 0, sizeof(*stamp));
	}
}
1203 1204 1205

int git_futils_fsync_dir(const char *path)
{
1206 1207 1208 1209
#ifdef GIT_WIN32
	GIT_UNUSED(path);
	return 0;
#else
1210 1211 1212
	int fd, error = -1;

	if ((fd = p_open(path, O_RDONLY)) < 0) {
1213
		git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path);
1214 1215 1216 1217
		return -1;
	}

	if ((error = p_fsync(fd)) < 0)
1218
		git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path);
1219 1220 1221

	p_close(fd);
	return error;
1222
#endif
1223 1224 1225 1226
}

int git_futils_fsync_parent(const char *path)
{
1227 1228 1229
	char *parent;
	int error;

1230
	if ((parent = git_fs_path_dirname(path)) == NULL)
1231
		return -1;
1232

1233
	error = git_futils_fsync_dir(parent);
1234 1235 1236
	git__free(parent);
	return error;
}