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