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