]> git.proxmox.com Git - libgit2.git/blame - src/futils.c
Update branching information in d/gbp.conf
[libgit2.git] / src / futils.c
CommitLineData
bb742ede 1/*
359fc2d2 2 * Copyright (C) the libgit2 contributors. All rights reserved.
bb742ede
VM
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
eae0bfdc 7
0c9c969a 8#include "futils.h"
eae0bfdc 9
a3aa5f4d 10#include "global.h"
500ec543 11#include "strmap.h"
2e29957a 12#include <ctype.h>
997579be
SS
13#if GIT_WIN32
14#include "win32/findfile.h"
15#endif
ec250c6e 16
ce8cd006 17int git_futils_mkpath2file(const char *file_path, const mode_t mode)
55ffebe3 18{
ca1b6e54 19 return git_futils_mkdir(
ac2fba0e 20 file_path, mode,
331e7de9 21 GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
55ffebe3
VM
22}
23
1d3a8aeb 24int git_futils_mktmp(git_buf *path_out, const char *filename, mode_t mode)
72a3fe42
VM
25{
26 int fd;
1d3a8aeb
ET
27 mode_t mask;
28
29 p_umask(mask = p_umask(0));
72a3fe42 30
97769280
RB
31 git_buf_sets(path_out, filename);
32 git_buf_puts(path_out, "_git2_XXXXXX");
33
34 if (git_buf_oom(path_out))
1a481123 35 return -1;
72a3fe42 36
1a481123 37 if ((fd = p_mkstemp(path_out->ptr)) < 0) {
ac3d33df 38 git_error_set(GIT_ERROR_OS,
909d5494 39 "failed to create temporary file '%s'", path_out->ptr);
1a481123
VM
40 return -1;
41 }
72a3fe42 42
1d3a8aeb 43 if (p_chmod(path_out->ptr, (mode & ~mask))) {
ac3d33df 44 git_error_set(GIT_ERROR_OS,
909d5494 45 "failed to set permissions on file '%s'", path_out->ptr);
1d3a8aeb
ET
46 return -1;
47 }
48
f978b748 49 return fd;
72a3fe42
VM
50}
51
ce8cd006 52int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
7dd8a9f7 53{
1a481123 54 int fd;
55ffebe3 55
1a481123
VM
56 if (git_futils_mkpath2file(path, dirmode) < 0)
57 return -1;
58
59 fd = p_creat(path, mode);
60 if (fd < 0) {
ac3d33df 61 git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path);
1a481123
VM
62 return -1;
63 }
64
65 return fd;
55ffebe3
VM
66}
67
33127043 68int git_futils_creat_locked(const char *path, const mode_t mode)
1549cba9 69{
4a26915d
ET
70 int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC,
71 mode);
09719c50 72
1a481123 73 if (fd < 0) {
f94825c1 74 int error = errno;
ac3d33df 75 git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path);
f94825c1
CMN
76 switch (error) {
77 case EEXIST:
78 return GIT_ELOCKED;
79 case ENOENT:
80 return GIT_ENOTFOUND;
81 default:
82 return -1;
83 }
1a481123
VM
84 }
85
86 return fd;
1549cba9
RG
87}
88
ce8cd006 89int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
1549cba9 90{
1a481123
VM
91 if (git_futils_mkpath2file(path, dirmode) < 0)
92 return -1;
1549cba9 93
f79026b4 94 return git_futils_creat_locked(path, mode);
ec250c6e
AE
95}
96
deafee7b
RB
97int git_futils_open_ro(const char *path)
98{
99 int fd = p_open(path, O_RDONLY);
14997dc5
RB
100 if (fd < 0)
101 return git_path_set_error(errno, path, "open");
deafee7b
RB
102 return fd;
103}
104
eae0bfdc
PP
105int git_futils_truncate(const char *path, int mode)
106{
107 int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
108 if (fd < 0)
109 return git_path_set_error(errno, path, "open");
110
111 close(fd);
112 return 0;
113}
114
0c9c969a 115int git_futils_filesize(uint64_t *out, git_file fd)
ec250c6e 116{
7dd8a9f7 117 struct stat sb;
deafee7b
RB
118
119 if (p_fstat(fd, &sb)) {
ac3d33df 120 git_error_set(GIT_ERROR_OS, "failed to stat file descriptor");
deafee7b
RB
121 return -1;
122 }
5ad739e8 123
0c9c969a
UG
124 if (sb.st_size < 0) {
125 git_error_set(GIT_ERROR_INVALID, "invalid file size");
126 return -1;
127 }
128
129 *out = sb.st_size;
130 return 0;
ec250c6e 131}
4188d28f 132
b6c93aef
RB
133mode_t git_futils_canonical_mode(mode_t raw_mode)
134{
135 if (S_ISREG(raw_mode))
f240acce 136 return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
b6c93aef
RB
137 else if (S_ISLNK(raw_mode))
138 return S_IFLNK;
b6c93aef
RB
139 else if (S_ISGITLINK(raw_mode))
140 return S_IFGITLINK;
7c7ff7d1
RB
141 else if (S_ISDIR(raw_mode))
142 return S_IFDIR;
b6c93aef
RB
143 else
144 return 0;
145}
146
60b9d3fc
RB
147int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
148{
de590550 149 ssize_t read_size = 0;
f1453c59 150 size_t alloc_len;
60b9d3fc
RB
151
152 git_buf_clear(buf);
153
8d534b47 154 if (!git__is_ssizet(len)) {
ac3d33df 155 git_error_set(GIT_ERROR_INVALID, "read too large");
8d534b47
ET
156 return -1;
157 }
158
ac3d33df 159 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
f1453c59 160 if (git_buf_grow(buf, alloc_len) < 0)
60b9d3fc
RB
161 return -1;
162
1f35e89d
RB
163 /* p_read loops internally to read len bytes */
164 read_size = p_read(fd, buf->ptr, len);
60b9d3fc 165
c859184b 166 if (read_size != (ssize_t)len) {
ac3d33df
JK
167 git_error_set(GIT_ERROR_OS, "failed to read descriptor");
168 git_buf_dispose(buf);
1f35e89d 169 return -1;
60b9d3fc
RB
170 }
171
1f35e89d
RB
172 buf->ptr[read_size] = '\0';
173 buf->size = read_size;
174
60b9d3fc
RB
175 return 0;
176}
177
178int git_futils_readbuffer_updated(
3547b122 179 git_buf *out, const char *path, git_oid *checksum, int *updated)
75d58430 180{
eb597799 181 int error;
75d58430 182 git_file fd;
c3da9f06 183 struct stat st;
3547b122 184 git_buf buf = GIT_BUF_INIT;
eb597799 185 git_oid checksum_new;
75d58430 186
3547b122 187 assert(out && path && *path);
75d58430 188
c3da9f06
CMN
189 if (updated != NULL)
190 *updated = 0;
75d58430 191
14997dc5
RB
192 if (p_stat(path, &st) < 0)
193 return git_path_set_error(errno, path, "stat");
c3da9f06 194
7c9f5bec
CMN
195
196 if (S_ISDIR(st.st_mode)) {
ac3d33df 197 git_error_set(GIT_ERROR_INVALID, "requested file is a directory");
7c9f5bec
CMN
198 return GIT_ENOTFOUND;
199 }
200
201 if (!git__is_sizet(st.st_size+1)) {
ac3d33df 202 git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path);
1a481123 203 return -1;
13224ea4 204 }
c3da9f06 205
9ccdb211
BR
206 if ((fd = git_futils_open_ro(path)) < 0)
207 return fd;
208
3547b122 209 if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
e1de726c
RB
210 p_close(fd);
211 return -1;
75d58430
RJ
212 }
213
f79026b4 214 p_close(fd);
75d58430 215
11c8e756
ET
216 if (checksum) {
217 if ((error = git_hash_buf(&checksum_new, buf.ptr, buf.size)) < 0) {
ac3d33df 218 git_buf_dispose(&buf);
11c8e756
ET
219 return error;
220 }
eb597799 221
11c8e756
ET
222 /*
223 * If we were given a checksum, we only want to use it if it's different
224 */
225 if (!git_oid__cmp(checksum, &checksum_new)) {
ac3d33df 226 git_buf_dispose(&buf);
11c8e756
ET
227 if (updated)
228 *updated = 0;
eb597799 229
11c8e756
ET
230 return 0;
231 }
232
233 git_oid_cpy(checksum, &checksum_new);
eb597799
CMN
234 }
235
236 /*
237 * If we're here, the file did change, or the user didn't have an old version
238 */
c3da9f06
CMN
239 if (updated != NULL)
240 *updated = 1;
241
3547b122 242 git_buf_swap(out, &buf);
ac3d33df 243 git_buf_dispose(&buf);
3547b122 244
13224ea4 245 return 0;
c3da9f06
CMN
246}
247
13224ea4 248int git_futils_readbuffer(git_buf *buf, const char *path)
97769280 249{
eb597799 250 return git_futils_readbuffer_updated(buf, path, NULL, NULL);
97769280
RB
251}
252
4742148d
RB
253int git_futils_writebuffer(
254 const git_buf *buf, const char *path, int flags, mode_t mode)
255{
5312621b 256 int fd, do_fsync = 0, error = 0;
4742148d 257
5a747e0c 258 if (!flags)
4742148d 259 flags = O_CREAT | O_TRUNC | O_WRONLY;
5a747e0c 260
5312621b
ET
261 if ((flags & O_FSYNC) != 0)
262 do_fsync = 1;
263
264 flags &= ~O_FSYNC;
4742148d 265
4742148d
RB
266 if (!mode)
267 mode = GIT_FILEMODE_BLOB;
268
269 if ((fd = p_open(path, flags, mode)) < 0) {
ac3d33df 270 git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path);
4742148d
RB
271 return fd;
272 }
273
274 if ((error = p_write(fd, git_buf_cstr(buf), git_buf_len(buf))) < 0) {
ac3d33df 275 git_error_set(GIT_ERROR_OS, "could not write to '%s'", path);
4742148d 276 (void)p_close(fd);
f5254d78 277 return error;
4742148d
RB
278 }
279
5312621b 280 if (do_fsync && (error = p_fsync(fd)) < 0) {
ac3d33df 281 git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path);
5312621b
ET
282 p_close(fd);
283 return error;
284 }
285
1229e1c4 286 if ((error = p_close(fd)) < 0) {
ac3d33df 287 git_error_set(GIT_ERROR_OS, "error while closing '%s'", path);
1229e1c4
ET
288 return error;
289 }
290
291 if (do_fsync && (flags & O_CREAT))
292 error = git_futils_fsync_parent(path);
4742148d
RB
293
294 return error;
295}
296
ce8cd006 297int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
19a30a3f 298{
deafee7b
RB
299 if (git_futils_mkpath2file(to, dirmode) < 0)
300 return -1;
19a30a3f 301
deafee7b 302 if (p_rename(from, to) < 0) {
ac3d33df 303 git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to);
deafee7b
RB
304 return -1;
305 }
306
307 return 0;
19a30a3f
VM
308}
309
0c9c969a 310int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len)
20e7f426 311{
f79026b4 312 return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
20e7f426
SP
313}
314
74fa4bfa
RB
315int git_futils_mmap_ro_file(git_map *out, const char *path)
316{
0d0fa7c3 317 git_file fd = git_futils_open_ro(path);
0c9c969a 318 uint64_t len;
da9abdd6 319 int result;
0d0fa7c3
RB
320
321 if (fd < 0)
322 return fd;
323
0c9c969a 324 if ((result = git_futils_filesize(&len, fd)) < 0)
38b6e700 325 goto out;
9daba9f4 326
deafee7b 327 if (!git__is_sizet(len)) {
ac3d33df 328 git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path);
38b6e700
PS
329 result = -1;
330 goto out;
deafee7b 331 }
0d0fa7c3 332
da9abdd6 333 result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
38b6e700 334out:
74fa4bfa
RB
335 p_close(fd);
336 return result;
337}
338
f79026b4 339void git_futils_mmap_free(git_map *out)
20e7f426 340{
f79026b4 341 p_munmap(out);
20e7f426
SP
342}
343
81aaf370
ET
344GIT_INLINE(int) mkdir_validate_dir(
345 const char *path,
e74340b0
ET
346 struct stat *st,
347 mode_t mode,
348 uint32_t flags,
81aaf370 349 struct git_futils_mkdir_options *opts)
e74340b0 350{
81aaf370
ET
351 /* with exclusive create, existing dir is an error */
352 if ((flags & GIT_MKDIR_EXCL) != 0) {
ac3d33df 353 git_error_set(GIT_ERROR_FILESYSTEM,
909d5494 354 "failed to make directory '%s': directory exists", path);
81aaf370
ET
355 return GIT_EEXISTS;
356 }
357
e74340b0
ET
358 if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
359 (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
81aaf370 360 if (p_unlink(path) < 0) {
ac3d33df 361 git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'",
81aaf370 362 S_ISLNK(st->st_mode) ? "symlink" : "file", path);
e74340b0
ET
363 return GIT_EEXISTS;
364 }
365
81aaf370 366 opts->perfdata.mkdir_calls++;
e74340b0 367
81aaf370 368 if (p_mkdir(path, mode) < 0) {
ac3d33df 369 git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
e74340b0
ET
370 return GIT_EEXISTS;
371 }
372 }
373
374 else if (S_ISLNK(st->st_mode)) {
375 /* Re-stat the target, make sure it's a directory */
81aaf370 376 opts->perfdata.stat_calls++;
e74340b0 377
81aaf370 378 if (p_stat(path, st) < 0) {
ac3d33df 379 git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
e74340b0
ET
380 return GIT_EEXISTS;
381 }
382 }
383
384 else if (!S_ISDIR(st->st_mode)) {
ac3d33df 385 git_error_set(GIT_ERROR_FILESYSTEM,
909d5494 386 "failed to make directory '%s': directory exists", path);
e74340b0
ET
387 return GIT_EEXISTS;
388 }
389
390 return 0;
391}
392
81aaf370
ET
393GIT_INLINE(int) mkdir_validate_mode(
394 const char *path,
395 struct stat *st,
396 bool terminal_path,
397 mode_t mode,
398 uint32_t flags,
399 struct git_futils_mkdir_options *opts)
400{
401 if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) ||
402 (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) {
403
404 opts->perfdata.chmod_calls++;
405
406 if (p_chmod(path, mode) < 0) {
ac3d33df 407 git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path);
81aaf370
ET
408 return -1;
409 }
410 }
411
412 return 0;
413}
28cdb315 414
e24c60db
ET
415GIT_INLINE(int) mkdir_canonicalize(
416 git_buf *path,
417 uint32_t flags)
1a5204a7 418{
e24c60db 419 ssize_t root_len;
ac2fba0e 420
e24c60db 421 if (path->size == 0) {
ac3d33df 422 git_error_set(GIT_ERROR_OS, "attempt to create empty path");
ca1b6e54 423 return -1;
97769280 424 }
f0b2bfe5 425
9cb5b0f7 426 /* Trim trailing slashes (except the root) */
e24c60db 427 if ((root_len = git_path_root(path->ptr)) < 0)
9cb5b0f7
ET
428 root_len = 0;
429 else
430 root_len++;
431
e24c60db
ET
432 while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
433 path->ptr[--path->size] = '\0';
412de9a6 434
ca1b6e54 435 /* if we are not supposed to made the last element, truncate it */
3c42e4ef 436 if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
e24c60db 437 git_path_dirname_r(path, path->ptr);
3c42e4ef
RB
438 flags |= GIT_MKDIR_SKIP_LAST;
439 }
9cb5b0f7 440 if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
e24c60db 441 git_path_dirname_r(path, path->ptr);
9cb5b0f7 442 }
ca1b6e54 443
9cb5b0f7 444 /* We were either given the root path (or trimmed it to
e24c60db
ET
445 * the root), we don't have anything to do.
446 */
447 if (path->size <= (size_t)root_len)
448 git_buf_clear(path);
449
450 return 0;
451}
452
453int git_futils_mkdir(
454 const char *path,
455 mode_t mode,
456 uint32_t flags)
457{
458 git_buf make_path = GIT_BUF_INIT, parent_path = GIT_BUF_INIT;
459 const char *relative;
460 struct git_futils_mkdir_options opts = { 0 };
461 struct stat st;
462 size_t depth = 0;
9ce2e7b3 463 int len = 0, root_len, error;
e24c60db
ET
464
465 if ((error = git_buf_puts(&make_path, path)) < 0 ||
466 (error = mkdir_canonicalize(&make_path, flags)) < 0 ||
467 (error = git_buf_puts(&parent_path, make_path.ptr)) < 0 ||
468 make_path.size == 0)
469 goto done;
470
9ce2e7b3
ET
471 root_len = git_path_root(make_path.ptr);
472
e24c60db
ET
473 /* find the first parent directory that exists. this will be used
474 * as the base to dirname_relative.
9cb5b0f7 475 */
e24c60db
ET
476 for (relative = make_path.ptr; parent_path.size; ) {
477 error = p_lstat(parent_path.ptr, &st);
478
479 if (error == 0) {
480 break;
481 } else if (errno != ENOENT) {
ac3d33df 482 git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr);
0c9c969a 483 error = -1;
e24c60db
ET
484 goto done;
485 }
486
487 depth++;
488
489 /* examine the parent of the current path */
490 if ((len = git_path_dirname_r(&parent_path, parent_path.ptr)) < 0) {
491 error = len;
492 goto done;
493 }
494
495 assert(len);
496
6147f643
PP
497 /*
498 * We've walked all the given path's parents and it's either relative
499 * (the parent is simply '.') or rooted (the length is less than or
500 * equal to length of the root path). The path may be less than the
501 * root path length on Windows, where `C:` == `C:/`.
e24c60db 502 */
0c9c969a
UG
503 if ((len == 1 && parent_path.ptr[0] == '.') ||
504 (len == 1 && parent_path.ptr[0] == '/') ||
505 len <= root_len) {
e24c60db
ET
506 relative = make_path.ptr;
507 break;
508 }
509
510 relative = make_path.ptr + len + 1;
511
512 /* not recursive? just make this directory relative to its parent. */
513 if ((flags & GIT_MKDIR_PATH) == 0)
514 break;
515 }
516
517 /* we found an item at the location we're trying to create,
518 * validate it.
519 */
520 if (depth == 0) {
81aaf370 521 error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts);
e24c60db 522
81aaf370
ET
523 if (!error)
524 error = mkdir_validate_mode(
525 make_path.ptr, &st, true, mode, flags, &opts);
e24c60db 526
f7e56150
RB
527 goto done;
528 }
529
e24c60db
ET
530 /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when
531 * canonicalizing `make_path`.
532 */
533 flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST);
534
535 error = git_futils_mkdir_relative(relative,
536 parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts);
537
538done:
ac3d33df
JK
539 git_buf_dispose(&make_path);
540 git_buf_dispose(&parent_path);
e24c60db
ET
541 return error;
542}
543
544int git_futils_mkdir_r(const char *path, const mode_t mode)
545{
546 return git_futils_mkdir(path, mode, GIT_MKDIR_PATH);
547}
548
549int git_futils_mkdir_relative(
550 const char *relative_path,
551 const char *base,
552 mode_t mode,
553 uint32_t flags,
554 struct git_futils_mkdir_options *opts)
555{
556 git_buf make_path = GIT_BUF_INIT;
557 ssize_t root = 0, min_root_len;
558 char lastch = '/', *tail;
559 struct stat st;
560 struct git_futils_mkdir_options empty_opts = {0};
561 int error;
562
563 if (!opts)
564 opts = &empty_opts;
565
566 /* build path and find "root" where we should start calling mkdir */
567 if (git_path_join_unrooted(&make_path, relative_path, base, &root) < 0)
568 return -1;
569
570 if ((error = mkdir_canonicalize(&make_path, flags)) < 0 ||
571 make_path.size == 0)
572 goto done;
573
ca1b6e54
RB
574 /* if we are not supposed to make the whole path, reset root */
575 if ((flags & GIT_MKDIR_PATH) == 0)
576 root = git_buf_rfind(&make_path, '/');
577
f7e56150
RB
578 /* advance root past drive name or network mount prefix */
579 min_root_len = git_path_root(make_path.ptr);
580 if (root < min_root_len)
581 root = min_root_len;
2da72fb2 582 while (root >= 0 && make_path.ptr[root] == '/')
f7e56150
RB
583 ++root;
584
ca1b6e54 585 /* clip root to make_path length */
f7e56150
RB
586 if (root > (ssize_t)make_path.size)
587 root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
0e26202c
RB
588 if (root < 0)
589 root = 0;
ca1b6e54 590
f7e56150
RB
591 /* walk down tail of path making each directory */
592 for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
d88e6e9b 593 bool mkdir_attempted = false;
999d4405 594
ca1b6e54
RB
595 /* advance tail to include next path component */
596 while (*tail == '/')
597 tail++;
598 while (*tail && *tail != '/')
599 tail++;
600
601 /* truncate path at next component */
602 lastch = *tail;
603 *tail = '\0';
999d4405 604 st.st_mode = 0;
ca1b6e54 605
500ec543
ET
606 if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
607 continue;
608
fe598f09 609 /* See what's going on with this path component */
500ec543 610 opts->perfdata.stat_calls++;
1d50b364 611
d88e6e9b 612retry_lstat:
fe598f09 613 if (p_lstat(make_path.ptr, &st) < 0) {
d88e6e9b 614 if (mkdir_attempted || errno != ENOENT) {
ac3d33df 615 git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr);
d88e6e9b 616 error = -1;
fe598f09
ET
617 goto done;
618 }
619
ac3d33df 620 git_error_clear();
d88e6e9b
VM
621 opts->perfdata.mkdir_calls++;
622 mkdir_attempted = true;
623 if (p_mkdir(make_path.ptr, mode) < 0) {
624 if (errno == EEXIST)
625 goto retry_lstat;
ac3d33df 626 git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr);
d88e6e9b
VM
627 error = -1;
628 goto done;
629 }
fe598f09 630 } else {
81aaf370
ET
631 if ((error = mkdir_validate_dir(
632 make_path.ptr, &st, mode, flags, opts)) < 0)
fe598f09 633 goto done;
d5f25204 634 }
40c44d2f 635
999d4405 636 /* chmod if requested and necessary */
81aaf370
ET
637 if ((error = mkdir_validate_mode(
638 make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
639 goto done;
500ec543
ET
640
641 if (opts->dir_map && opts->pool) {
f1453c59
ET
642 char *cache_path;
643 size_t alloc_size;
644
ac3d33df 645 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1);
0c9c969a 646 cache_path = git_pool_malloc(opts->pool, alloc_size);
ac3d33df 647 GIT_ERROR_CHECK_ALLOC(cache_path);
500ec543
ET
648
649 memcpy(cache_path, make_path.ptr, make_path.size + 1);
650
0c9c969a 651 if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0)
500ec543
ET
652 goto done;
653 }
a993e4fe 654 }
40c44d2f 655
999d4405
RB
656 error = 0;
657
658 /* check that full path really is a directory if requested & needed */
f7e56150 659 if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
1d50b364 660 lastch != '\0') {
500ec543 661 opts->perfdata.stat_calls++;
1d50b364
ET
662
663 if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
ac3d33df 664 git_error_set(GIT_ERROR_OS, "path is not a directory '%s'",
e74340b0 665 make_path.ptr);
1d50b364
ET
666 error = GIT_ENOTFOUND;
667 }
999d4405 668 }
77c3999c 669
999d4405 670done:
ac3d33df 671 git_buf_dispose(&make_path);
331e7de9 672 return error;
ca1b6e54 673}
77c3999c 674
331e7de9 675typedef struct {
ad9a921b 676 const char *base;
7e5c8a5b 677 size_t baselen;
331e7de9 678 uint32_t flags;
219d3457 679 int depth;
331e7de9
RB
680} futils__rmdir_data;
681
219d3457
RB
682#define FUTILS_MAX_DEPTH 100
683
331e7de9 684static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
42b3a460 685{
331e7de9 686 if (filemsg)
ac3d33df 687 git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s",
331e7de9
RB
688 path, filemsg);
689 else
ac3d33df 690 git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path);
555aa453 691
331e7de9
RB
692 return -1;
693}
deafee7b 694
ad9a921b
RB
695static int futils__rm_first_parent(git_buf *path, const char *ceiling)
696{
697 int error = GIT_ENOTFOUND;
698 struct stat st;
699
700 while (error == GIT_ENOTFOUND) {
701 git_buf_rtruncate_at_char(path, '/');
702
703 if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)
704 error = 0;
cccacac5 705 else if (p_lstat_posixly(path->ptr, &st) == 0) {
ad9a921b
RB
706 if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
707 error = p_unlink(path->ptr);
708 else if (!S_ISDIR(st.st_mode))
709 error = -1; /* fail to remove non-regular file */
710 } else if (errno != ENOTDIR)
711 error = -1;
712 }
713
714 if (error)
715 futils__error_cannot_rmdir(path->ptr, "cannot remove parent");
716
717 return error;
718}
719
331e7de9
RB
720static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
721{
96869a4e 722 int error = 0;
331e7de9 723 futils__rmdir_data *data = opaque;
14997dc5 724 struct stat st;
555aa453 725
14997dc5
RB
726 if (data->depth > FUTILS_MAX_DEPTH)
727 error = futils__error_cannot_rmdir(
728 path->ptr, "directory nesting too deep");
219d3457 729
14997dc5 730 else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
ad9a921b 731 if (errno == ENOENT)
14997dc5 732 error = 0;
ad9a921b
RB
733 else if (errno == ENOTDIR) {
734 /* asked to remove a/b/c/d/e and a/b is a normal file */
735 if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)
14997dc5 736 error = futils__rm_first_parent(path, data->base);
ad9a921b
RB
737 else
738 futils__error_cannot_rmdir(
739 path->ptr, "parent is not directory");
740 }
741 else
14997dc5 742 error = git_path_set_error(errno, path->ptr, "rmdir");
ad9a921b
RB
743 }
744
745 else if (S_ISDIR(st.st_mode)) {
219d3457
RB
746 data->depth++;
747
14997dc5 748 error = git_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
219d3457
RB
749
750 data->depth--;
751
96869a4e 752 if (error < 0)
25e0b157 753 return error;
96869a4e 754
219d3457 755 if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
25e0b157 756 return error;
331e7de9 757
14997dc5 758 if ((error = p_rmdir(path->ptr)) < 0) {
331e7de9 759 if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
e09d18ee 760 (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
14997dc5 761 error = 0;
331e7de9 762 else
14997dc5 763 error = git_path_set_error(errno, path->ptr, "rmdir");
deafee7b 764 }
858dba58
VM
765 }
766
331e7de9 767 else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
14997dc5
RB
768 if (p_unlink(path->ptr) < 0)
769 error = git_path_set_error(errno, path->ptr, "remove");
deafee7b
RB
770 }
771
ad9a921b 772 else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
14997dc5 773 error = futils__error_cannot_rmdir(path->ptr, "still present");
555aa453 774
25e0b157 775 return error;
331e7de9
RB
776}
777
bbb988a5 778static int futils__rmdir_empty_parent(void *opaque, const char *path)
331e7de9 779{
7e5c8a5b 780 futils__rmdir_data *data = opaque;
96869a4e 781 int error = 0;
7e5c8a5b 782
bbb988a5 783 if (strlen(path) <= data->baselen)
25e0b157 784 error = GIT_ITEROVER;
331e7de9 785
bbb988a5 786 else if (p_rmdir(path) < 0) {
331e7de9
RB
787 int en = errno;
788
789 if (en == ENOENT || en == ENOTDIR) {
96869a4e 790 /* do nothing */
4a0df574
ET
791 } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 &&
792 en == EBUSY) {
793 error = git_path_set_error(errno, path, "rmdir");
e09d18ee 794 } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
331e7de9
RB
795 error = GIT_ITEROVER;
796 } else {
bbb988a5 797 error = git_path_set_error(errno, path, "rmdir");
331e7de9
RB
798 }
799 }
800
25e0b157 801 return error;
42b3a460
MS
802}
803
0d64bef9 804int git_futils_rmdir_r(
331e7de9 805 const char *path, const char *base, uint32_t flags)
42b3a460 806{
97769280 807 int error;
0d64bef9 808 git_buf fullpath = GIT_BUF_INIT;
96869a4e 809 futils__rmdir_data data;
0d64bef9
RB
810
811 /* build path and find "root" where we should start calling mkdir */
812 if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
813 return -1;
814
96869a4e 815 memset(&data, 0, sizeof(data));
7e5c8a5b
RB
816 data.base = base ? base : "";
817 data.baselen = base ? strlen(base) : 0;
818 data.flags = flags;
331e7de9
RB
819
820 error = futils__rmdir_recurs_foreach(&data, &fullpath);
821
822 /* remove now-empty parents if requested */
96869a4e 823 if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
331e7de9
RB
824 error = git_path_walk_up(
825 &fullpath, base, futils__rmdir_empty_parent, &data);
826
25e0b157 827 if (error == GIT_ITEROVER) {
ac3d33df 828 git_error_clear();
96869a4e 829 error = 0;
25e0b157 830 }
0d64bef9 831
ac3d33df 832 git_buf_dispose(&fullpath);
97769280 833
97769280 834 return error;
42b3a460
MS
835}
836
8651c10f
BS
837int git_futils_fake_symlink(const char *old, const char *new)
838{
839 int retcode = GIT_ERROR;
840 int fd = git_futils_creat_withpath(new, 0755, 0644);
841 if (fd >= 0) {
842 retcode = p_write(fd, old, strlen(old));
843 p_close(fd);
844 }
845 return retcode;
846}
ca1b6e54 847
85bd1746 848static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
ca1b6e54
RB
849{
850 int error = 0;
7dd22538 851 char buffer[FILEIO_BUFSIZE];
ca1b6e54
RB
852 ssize_t len = 0;
853
854 while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)
855 /* p_write() does not have the same semantics as write(). It loops
856 * internally and will return 0 when it has completed writing.
857 */
858 error = p_write(ofd, buffer, len);
859
860 if (len < 0) {
ac3d33df 861 git_error_set(GIT_ERROR_OS, "read error while copying file");
ca1b6e54
RB
862 error = (int)len;
863 }
864
edef91ee 865 if (error < 0)
ac3d33df 866 git_error_set(GIT_ERROR_OS, "write error while copying file");
edef91ee 867
85bd1746 868 if (close_fd_when_done) {
ca1b6e54
RB
869 p_close(ifd);
870 p_close(ofd);
871 }
872
873 return error;
874}
875
85bd1746 876int git_futils_cp(const char *from, const char *to, mode_t filemode)
ca1b6e54
RB
877{
878 int ifd, ofd;
879
ca1b6e54
RB
880 if ((ifd = git_futils_open_ro(from)) < 0)
881 return ifd;
882
883 if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
ca1b6e54 884 p_close(ifd);
14997dc5 885 return git_path_set_error(errno, to, "open for writing");
ca1b6e54
RB
886 }
887
85bd1746 888 return cp_by_fd(ifd, ofd, true);
ca1b6e54
RB
889}
890
27051d4e 891int git_futils_touch(const char *path, time_t *when)
8f09a98e
ET
892{
893 struct p_timeval times[2];
8f09a98e
ET
894 int ret;
895
27051d4e
ET
896 times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL);
897 times[0].tv_usec = times[1].tv_usec = 0;
8f09a98e
ET
898
899 ret = p_utimes(path, times);
900
901 return (ret < 0) ? git_path_set_error(errno, path, "touch") : 0;
902}
903
85bd1746 904static int cp_link(const char *from, const char *to, size_t link_size)
ca1b6e54
RB
905{
906 int error = 0;
907 ssize_t read_len;
392702ee 908 char *link_data;
f1453c59 909 size_t alloc_size;
392702ee 910
ac3d33df 911 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
f1453c59 912 link_data = git__malloc(alloc_size);
ac3d33df 913 GIT_ERROR_CHECK_ALLOC(link_data);
ca1b6e54 914
85bd1746
RB
915 read_len = p_readlink(from, link_data, link_size);
916 if (read_len != (ssize_t)link_size) {
ac3d33df 917 git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from);
ca1b6e54
RB
918 error = -1;
919 }
920 else {
921 link_data[read_len] = '\0';
922
923 if (p_symlink(link_data, to) < 0) {
ac3d33df 924 git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'",
ca1b6e54
RB
925 link_data, to);
926 error = -1;
927 }
928 }
929
930 git__free(link_data);
931 return error;
932}
933
934typedef struct {
935 const char *to_root;
936 git_buf to;
937 ssize_t from_prefix;
938 uint32_t flags;
939 uint32_t mkdir_flags;
940 mode_t dirmode;
941} cp_r_info;
942
3c42e4ef
RB
943#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)
944
945static int _cp_r_mkdir(cp_r_info *info, git_buf *from)
946{
947 int error = 0;
948
949 /* create root directory the first time we need to create a directory */
950 if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) {
951 error = git_futils_mkdir(
ac2fba0e 952 info->to_root, info->dirmode,
18f08264 953 (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
3c42e4ef
RB
954
955 info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
956 }
957
958 /* create directory with root as base to prevent excess chmods */
959 if (!error)
ac2fba0e 960 error = git_futils_mkdir_relative(
3c42e4ef 961 from->ptr + info->from_prefix, info->to_root,
ac2fba0e 962 info->dirmode, info->mkdir_flags, NULL);
3c42e4ef
RB
963
964 return error;
965}
966
ca1b6e54
RB
967static int _cp_r_callback(void *ref, git_buf *from)
968{
3c42e4ef 969 int error = 0;
ca1b6e54
RB
970 cp_r_info *info = ref;
971 struct stat from_st, to_st;
972 bool exists = false;
973
974 if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
975 from->ptr[git_path_basename_offset(from)] == '.')
976 return 0;
977
96869a4e
RB
978 if ((error = git_buf_joinpath(
979 &info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
25e0b157 980 return error;
ca1b6e54 981
14997dc5 982 if (!(error = git_path_lstat(info->to.ptr, &to_st)))
ca1b6e54 983 exists = true;
14997dc5 984 else if (error != GIT_ENOTFOUND)
25e0b157 985 return error;
14997dc5 986 else {
ac3d33df 987 git_error_clear();
14997dc5
RB
988 error = 0;
989 }
ca1b6e54 990
3c42e4ef 991 if ((error = git_path_lstat(from->ptr, &from_st)) < 0)
25e0b157 992 return error;
ca1b6e54
RB
993
994 if (S_ISDIR(from_st.st_mode)) {
ca1b6e54
RB
995 mode_t oldmode = info->dirmode;
996
997 /* if we are not chmod'ing, then overwrite dirmode */
18f08264 998 if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
ca1b6e54
RB
999 info->dirmode = from_st.st_mode;
1000
1001 /* make directory now if CREATE_EMPTY_DIRS is requested and needed */
1002 if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)
3c42e4ef 1003 error = _cp_r_mkdir(info, from);
ca1b6e54
RB
1004
1005 /* recurse onto target directory */
25e0b157 1006 if (!error && (!exists || S_ISDIR(to_st.st_mode)))
219d3457 1007 error = git_path_direach(from, 0, _cp_r_callback, info);
ca1b6e54
RB
1008
1009 if (oldmode != 0)
1010 info->dirmode = oldmode;
1011
25e0b157 1012 return error;
ca1b6e54
RB
1013 }
1014
1015 if (exists) {
1016 if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)
1017 return 0;
1018
1019 if (p_unlink(info->to.ptr) < 0) {
ac3d33df 1020 git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'",
ca1b6e54 1021 info->to.ptr);
25e0b157 1022 return GIT_EEXISTS;
ca1b6e54
RB
1023 }
1024 }
1025
1026 /* Done if this isn't a regular file or a symlink */
1027 if (!S_ISREG(from_st.st_mode) &&
1028 (!S_ISLNK(from_st.st_mode) ||
1029 (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0))
1030 return 0;
1031
1032 /* Make container directory on demand if needed */
1033 if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
3c42e4ef 1034 (error = _cp_r_mkdir(info, from)) < 0)
25e0b157 1035 return error;
ca1b6e54
RB
1036
1037 /* make symlink or regular file */
94f742ba 1038 if (info->flags & GIT_CPDIR_LINK_FILES) {
54738368 1039 if ((error = p_link(from->ptr, info->to.ptr)) < 0)
ac3d33df 1040 git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr);
94f742ba 1041 } else if (S_ISLNK(from_st.st_mode)) {
3c42e4ef 1042 error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
94f742ba 1043 } else {
3c42e4ef
RB
1044 mode_t usemode = from_st.st_mode;
1045
18f08264 1046 if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
f240acce 1047 usemode = GIT_PERMS_FOR_WRITE(usemode);
3c42e4ef
RB
1048
1049 error = git_futils_cp(from->ptr, info->to.ptr, usemode);
3c42e4ef
RB
1050 }
1051
25e0b157 1052 return error;
ca1b6e54
RB
1053}
1054
1055int git_futils_cp_r(
1056 const char *from,
1057 const char *to,
1058 uint32_t flags,
1059 mode_t dirmode)
1060{
1061 int error;
1062 git_buf path = GIT_BUF_INIT;
1063 cp_r_info info;
1064
3c42e4ef 1065 if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */
ca1b6e54
RB
1066 return -1;
1067
96869a4e 1068 memset(&info, 0, sizeof(info));
ca1b6e54
RB
1069 info.to_root = to;
1070 info.flags = flags;
1071 info.dirmode = dirmode;
1072 info.from_prefix = path.size;
1073 git_buf_init(&info.to, 0);
1074
1075 /* precalculate mkdir flags */
1076 if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
3c42e4ef
RB
1077 /* if not creating empty dirs, then use mkdir to create the path on
1078 * demand right before files are copied.
1079 */
ca1b6e54 1080 info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
18f08264 1081 if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
ca1b6e54
RB
1082 info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
1083 } else {
3c42e4ef 1084 /* otherwise, we will do simple mkdir as directories are encountered */
ca1b6e54 1085 info.mkdir_flags =
18f08264 1086 ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
ca1b6e54
RB
1087 }
1088
1089 error = _cp_r_callback(&info, &path);
1090
ac3d33df
JK
1091 git_buf_dispose(&path);
1092 git_buf_dispose(&info.to);
ca1b6e54
RB
1093
1094 return error;
1095}
744cc03e 1096
c1f61af6
VM
1097int git_futils_filestamp_check(
1098 git_futils_filestamp *stamp, const char *path)
744cc03e
RB
1099{
1100 struct stat st;
1101
c8b511f3
RB
1102 /* if the stamp is NULL, then always reload */
1103 if (stamp == NULL)
744cc03e
RB
1104 return 1;
1105
7d490872 1106 if (p_stat(path, &st) < 0)
744cc03e
RB
1107 return GIT_ENOTFOUND;
1108
3d6a42d1 1109 if (stamp->mtime.tv_sec == st.st_mtime &&
0226f7dd 1110#if defined(GIT_USE_NSEC)
3d6a42d1 1111 stamp->mtime.tv_nsec == st.st_mtime_nsec &&
0226f7dd 1112#endif
0c9c969a 1113 stamp->size == (uint64_t)st.st_size &&
c8b511f3 1114 stamp->ino == (unsigned int)st.st_ino)
744cc03e
RB
1115 return 0;
1116
3d6a42d1 1117 stamp->mtime.tv_sec = st.st_mtime;
0226f7dd 1118#if defined(GIT_USE_NSEC)
3d6a42d1 1119 stamp->mtime.tv_nsec = st.st_mtime_nsec;
0226f7dd 1120#endif
0c9c969a 1121 stamp->size = (uint64_t)st.st_size;
c8b511f3 1122 stamp->ino = (unsigned int)st.st_ino;
744cc03e
RB
1123
1124 return 1;
1125}
1126
c1f61af6
VM
1127void git_futils_filestamp_set(
1128 git_futils_filestamp *target, const git_futils_filestamp *source)
c8b511f3
RB
1129{
1130 assert(target);
1131
1132 if (source)
1133 memcpy(target, source, sizeof(*target));
1134 else
1135 memset(target, 0, sizeof(*target));
1136}
823c0e9c
RB
1137
1138
1139void git_futils_filestamp_set_from_stat(
1140 git_futils_filestamp *stamp, struct stat *st)
1141{
1142 if (st) {
3d6a42d1
ET
1143 stamp->mtime.tv_sec = st->st_mtime;
1144#if defined(GIT_USE_NSEC)
1145 stamp->mtime.tv_nsec = st->st_mtime_nsec;
1146#else
973a09a4
AR
1147 stamp->mtime.tv_nsec = 0;
1148#endif
0c9c969a 1149 stamp->size = (uint64_t)st->st_size;
823c0e9c
RB
1150 stamp->ino = (unsigned int)st->st_ino;
1151 } else {
1152 memset(stamp, 0, sizeof(*stamp));
1153 }
1154}
1229e1c4
ET
1155
1156int git_futils_fsync_dir(const char *path)
1157{
3ac05d11
ET
1158#ifdef GIT_WIN32
1159 GIT_UNUSED(path);
1160 return 0;
1161#else
1229e1c4
ET
1162 int fd, error = -1;
1163
1164 if ((fd = p_open(path, O_RDONLY)) < 0) {
ac3d33df 1165 git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path);
1229e1c4
ET
1166 return -1;
1167 }
1168
1169 if ((error = p_fsync(fd)) < 0)
ac3d33df 1170 git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path);
1229e1c4
ET
1171
1172 p_close(fd);
1173 return error;
3ac05d11 1174#endif
1229e1c4
ET
1175}
1176
1177int git_futils_fsync_parent(const char *path)
1178{
0c28c72d
PS
1179 char *parent;
1180 int error;
1181
1182 if ((parent = git_path_dirname(path)) == NULL)
1183 return -1;
1229e1c4 1184
0c28c72d 1185 error = git_futils_fsync_dir(parent);
1229e1c4
ET
1186 git__free(parent);
1187 return error;
1188}