]> git.proxmox.com Git - libgit2.git/blame - src/fileops.c
Update master-tip to fix unit test.
[libgit2.git] / src / fileops.c
CommitLineData
bb742ede 1/*
5e0de328 2 * Copyright (C) 2009-2012 the libgit2 contributors
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"
2e29957a 9#include <ctype.h>
ec250c6e 10
ce8cd006 11int git_futils_mkpath2file(const char *file_path, const mode_t mode)
55ffebe3 12{
1a481123 13 int result = 0;
97769280 14 git_buf target_folder = GIT_BUF_INIT;
55ffebe3 15
1a481123
VM
16 if (git_path_dirname_r(&target_folder, file_path) < 0)
17 return -1;
55ffebe3
VM
18
19 /* Does the containing folder exist? */
1a481123 20 if (git_path_isdir(target_folder.ptr) == false)
55ffebe3 21 /* Let's create the tree structure */
1a481123 22 result = git_futils_mkdir_r(target_folder.ptr, NULL, mode);
55ffebe3 23
97769280 24 git_buf_free(&target_folder);
1a481123 25 return result;
55ffebe3
VM
26}
27
97769280 28int git_futils_mktmp(git_buf *path_out, const char *filename)
72a3fe42
VM
29{
30 int fd;
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
f978b748 44 return fd;
72a3fe42
VM
45}
46
ce8cd006 47int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
7dd8a9f7 48{
1a481123 49 int fd;
55ffebe3 50
1a481123
VM
51 if (git_futils_mkpath2file(path, dirmode) < 0)
52 return -1;
53
54 fd = p_creat(path, mode);
55 if (fd < 0) {
ae9e29fd 56 giterr_set(GITERR_OS, "Failed to create file '%s'", path);
1a481123
VM
57 return -1;
58 }
59
60 return fd;
55ffebe3
VM
61}
62
33127043 63int git_futils_creat_locked(const char *path, const mode_t mode)
1549cba9 64{
09719c50 65 int fd;
66
67#ifdef GIT_WIN32
68 wchar_t* buf;
69
70 buf = gitwin_to_utf16(path);
71 fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
72 git__free(buf);
73#else
74 fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
75#endif
76
1a481123 77 if (fd < 0) {
ae9e29fd 78 giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
1a481123
VM
79 return -1;
80 }
81
82 return fd;
1549cba9
RG
83}
84
ce8cd006 85int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
1549cba9 86{
1a481123
VM
87 if (git_futils_mkpath2file(path, dirmode) < 0)
88 return -1;
1549cba9 89
f79026b4 90 return git_futils_creat_locked(path, mode);
ec250c6e
AE
91}
92
deafee7b
RB
93int git_futils_open_ro(const char *path)
94{
95 int fd = p_open(path, O_RDONLY);
96 if (fd < 0) {
d0a920a6 97 if (errno == ENOENT || errno == ENOTDIR)
deafee7b
RB
98 fd = GIT_ENOTFOUND;
99 giterr_set(GITERR_OS, "Failed to open '%s'", path);
100 }
101 return fd;
102}
103
f79026b4 104git_off_t git_futils_filesize(git_file fd)
ec250c6e 105{
7dd8a9f7 106 struct stat sb;
deafee7b
RB
107
108 if (p_fstat(fd, &sb)) {
109 giterr_set(GITERR_OS, "Failed to stat file descriptor");
110 return -1;
111 }
5ad739e8 112
ec250c6e
AE
113 return sb.st_size;
114}
4188d28f 115
b6c93aef
RB
116mode_t git_futils_canonical_mode(mode_t raw_mode)
117{
118 if (S_ISREG(raw_mode))
119 return S_IFREG | GIT_CANONICAL_PERMS(raw_mode);
120 else if (S_ISLNK(raw_mode))
121 return S_IFLNK;
b6c93aef
RB
122 else if (S_ISGITLINK(raw_mode))
123 return S_IFGITLINK;
7c7ff7d1
RB
124 else if (S_ISDIR(raw_mode))
125 return S_IFDIR;
b6c93aef
RB
126 else
127 return 0;
128}
129
13224ea4 130int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, int *updated)
75d58430
RJ
131{
132 git_file fd;
90d4d2f0 133 size_t len;
c3da9f06 134 struct stat st;
75d58430 135
13224ea4 136 assert(buf && path && *path);
75d58430 137
c3da9f06
CMN
138 if (updated != NULL)
139 *updated = 0;
75d58430 140
ae9e29fd
RB
141 if ((fd = git_futils_open_ro(path)) < 0)
142 return fd;
c3da9f06 143
13224ea4 144 if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) {
e1de726c 145 p_close(fd);
1a481123
VM
146 giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path);
147 return -1;
13224ea4 148 }
c3da9f06
CMN
149
150 /*
151 * If we were given a time, we only want to read the file if it
152 * has been modified.
153 */
13224ea4 154 if (mtime != NULL && *mtime >= st.st_mtime) {
e1de726c 155 p_close(fd);
13224ea4
VM
156 return 0;
157 }
c3da9f06
CMN
158
159 if (mtime != NULL)
160 *mtime = st.st_mtime;
c3da9f06
CMN
161
162 len = (size_t) st.st_size;
163
13224ea4 164 git_buf_clear(buf);
90d4d2f0 165
13224ea4 166 if (git_buf_grow(buf, len + 1) < 0) {
e1de726c
RB
167 p_close(fd);
168 return -1;
75d58430
RJ
169 }
170
13224ea4
VM
171 buf->ptr[len] = '\0';
172
173 while (len > 0) {
174 ssize_t read_size = p_read(fd, buf->ptr, len);
175
176 if (read_size < 0) {
e1de726c 177 p_close(fd);
ae9e29fd 178 giterr_set(GITERR_OS, "Failed to read descriptor for '%s'", path);
1a481123 179 return -1;
13224ea4
VM
180 }
181
182 len -= read_size;
183 buf->size += read_size;
75d58430
RJ
184 }
185
f79026b4 186 p_close(fd);
75d58430 187
c3da9f06
CMN
188 if (updated != NULL)
189 *updated = 1;
190
13224ea4 191 return 0;
c3da9f06
CMN
192}
193
13224ea4 194int git_futils_readbuffer(git_buf *buf, const char *path)
97769280 195{
13224ea4 196 return git_futils_readbuffer_updated(buf, path, NULL, NULL);
97769280
RB
197}
198
ce8cd006 199int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
19a30a3f 200{
deafee7b
RB
201 if (git_futils_mkpath2file(to, dirmode) < 0)
202 return -1;
19a30a3f 203
deafee7b
RB
204 if (p_rename(from, to) < 0) {
205 giterr_set(GITERR_OS, "Failed to rename '%s' to '%s'", from, to);
206 return -1;
207 }
208
209 return 0;
19a30a3f
VM
210}
211
f79026b4 212int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
20e7f426 213{
f79026b4 214 return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
20e7f426
SP
215}
216
74fa4bfa
RB
217int git_futils_mmap_ro_file(git_map *out, const char *path)
218{
0d0fa7c3
RB
219 git_file fd = git_futils_open_ro(path);
220 git_off_t len;
da9abdd6 221 int result;
0d0fa7c3
RB
222
223 if (fd < 0)
224 return fd;
225
226 len = git_futils_filesize(fd);
deafee7b
RB
227 if (!git__is_sizet(len)) {
228 giterr_set(GITERR_OS, "File `%s` too large to mmap", path);
229 return -1;
230 }
0d0fa7c3 231
da9abdd6 232 result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
74fa4bfa
RB
233 p_close(fd);
234 return result;
235}
236
f79026b4 237void git_futils_mmap_free(git_map *out)
20e7f426 238{
f79026b4 239 p_munmap(out);
20e7f426
SP
240}
241
97769280 242int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
1a5204a7 243{
97769280 244 git_buf make_path = GIT_BUF_INIT;
dc07184f 245 size_t start = 0;
40c44d2f 246 char *pp, *sp;
cb8a7961 247 bool failed = false;
f0b2bfe5 248
97769280 249 if (base != NULL) {
dc07184f 250 /*
251 * when a base is being provided, it is supposed to already exist.
252 * Therefore, no attempt is being made to recursively create this leading path
253 * segment. It's just skipped. */
97769280 254 start = strlen(base);
cb8a7961
VM
255 if (git_buf_joinpath(&make_path, base, path) < 0)
256 return -1;
97769280 257 } else {
dc07184f 258 int root_path_offset;
259
cb8a7961
VM
260 if (git_buf_puts(&make_path, path) < 0)
261 return -1;
dc07184f 262
263 root_path_offset = git_path_root(make_path.ptr);
264 if (root_path_offset > 0) {
265 /*
266 * On Windows, will skip the drive name (eg. C: or D:)
267 * or the leading part of a network path (eg. //computer_name ) */
268 start = root_path_offset;
269 }
97769280 270 }
f0b2bfe5 271
97769280 272 pp = make_path.ptr + start;
40c44d2f 273
cb8a7961 274 while (!failed && (sp = strchr(pp, '/')) != NULL) {
1a481123 275 if (sp != pp && git_path_isdir(make_path.ptr) == false) {
d5f25204 276 *sp = 0;
412de9a6 277
278 /* Do not choke while trying to recreate an existing directory */
cb8a7961
VM
279 if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST)
280 failed = true;
412de9a6 281
d5f25204
VM
282 *sp = '/';
283 }
40c44d2f 284
d5f25204
VM
285 pp = sp + 1;
286 }
f0b2bfe5 287
cb8a7961
VM
288 if (*pp != '\0' && !failed) {
289 if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST)
290 failed = true;
a993e4fe 291 }
40c44d2f 292
97769280 293 git_buf_free(&make_path);
77c3999c 294
cb8a7961
VM
295 if (failed) {
296 giterr_set(GITERR_OS,
297 "Failed to create directory structure at '%s'", path);
298 return -1;
299 }
77c3999c 300
cb8a7961 301 return 0;
170d3f2f 302}
303
97769280 304static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
42b3a460 305{
1a2b8725 306 git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque;
555aa453 307
308 assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
309 || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
310 || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);
42b3a460 311
1a481123 312 if (git_path_isdir(path->ptr) == true) {
deafee7b
RB
313 if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)
314 return -1;
315
316 if (p_rmdir(path->ptr) < 0) {
54bdc64a 317 if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST))
555aa453 318 return 0;
319
deafee7b
RB
320 giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr);
321 return -1;
322 }
42b3a460 323
deafee7b 324 return 0;
858dba58
VM
325 }
326
555aa453 327 if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) {
deafee7b
RB
328 if (p_unlink(path->ptr) < 0) {
329 giterr_set(GITERR_OS, "Could not remove directory. File '%s' cannot be removed", path->ptr);
330 return -1;
331 }
332
333 return 0;
334 }
335
555aa453 336 if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) {
337 giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);
338 return -1;
339 }
340
341 return 0;
42b3a460
MS
342}
343
1a2b8725 344int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type)
42b3a460 345{
97769280
RB
346 int error;
347 git_buf p = GIT_BUF_INIT;
348
349 error = git_buf_sets(&p, path);
deafee7b 350 if (!error)
555aa453 351 error = _rmdir_recurs_foreach(&removal_type, &p);
97769280
RB
352 git_buf_free(&p);
353 return error;
42b3a460
MS
354}
355
23059130 356#ifdef GIT_WIN32
349fb6d7
VM
357struct win32_path {
358 wchar_t path[MAX_PATH];
73b51450 359 DWORD len;
349fb6d7 360};
73b51450 361
349fb6d7 362static int win32_expand_path(struct win32_path *s_root, const wchar_t *templ)
73b51450 363{
349fb6d7
VM
364 s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH);
365 return s_root->len ? 0 : -1;
73b51450
RB
366}
367
349fb6d7 368static int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename)
73b51450 369{
deafee7b 370 int error = 0;
73b51450 371 size_t len;
349fb6d7 372 wchar_t *file_utf16 = NULL;
73b51450
RB
373 char *file_utf8 = NULL;
374
375 if (!root || !filename || (len = strlen(filename)) == 0)
376 return GIT_ENOTFOUND;
377
378 /* allocate space for wchar_t path to file */
379 file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t));
deafee7b 380 GITERR_CHECK_ALLOC(file_utf16);
73b51450
RB
381
382 /* append root + '\\' + filename as wchar_t */
383 memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));
384
385 if (*filename == '/' || *filename == '\\')
386 filename++;
387
388 if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) !=
f46e6226 389 (int)len + 1) {
deafee7b 390 error = -1;
73b51450
RB
391 goto cleanup;
392 }
393
73b51450
RB
394 /* check access */
395 if (_waccess(file_utf16, F_OK) < 0) {
396 error = GIT_ENOTFOUND;
397 goto cleanup;
398 }
399
400 /* convert to utf8 */
401 if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL)
deafee7b
RB
402 error = -1;
403 else {
73b51450
RB
404 git_path_mkposix(file_utf8);
405 git_buf_attach(path, file_utf8, 0);
406 }
407
408cleanup:
409 git__free(file_utf16);
73b51450
RB
410 return error;
411}
412#endif
413
414int git_futils_find_system_file(git_buf *path, const char *filename)
415{
349fb6d7
VM
416#ifdef GIT_WIN32
417 struct win32_path root;
9cde607c 418
349fb6d7 419 if (win32_expand_path(&root, L"%PROGRAMFILES%\\Git\\etc\\") < 0 ||
29ef309e
RB
420 root.path[0] == L'%') /* i.e. no expansion happened */
421 {
422 giterr_set(GITERR_OS, "Cannot locate the system's Program Files directory");
349fb6d7
VM
423 return -1;
424 }
425
29ef309e
RB
426 if (win32_find_file(path, &root, filename) < 0) {
427 git_buf_clear(path);
428 return GIT_ENOTFOUND;
429 }
430
349fb6d7
VM
431 return 0;
432
433#else
cb8a7961
VM
434 if (git_buf_joinpath(path, "/etc", filename) < 0)
435 return -1;
73b51450 436
1a481123 437 if (git_path_exists(path->ptr) == true)
deafee7b 438 return 0;
73b51450
RB
439
440 git_buf_clear(path);
349fb6d7
VM
441 return GIT_ENOTFOUND;
442#endif
443}
73b51450 444
349fb6d7
VM
445int git_futils_find_global_file(git_buf *path, const char *filename)
446{
73b51450 447#ifdef GIT_WIN32
349fb6d7
VM
448 struct win32_path root;
449
450 if (win32_expand_path(&root, L"%USERPROFILE%\\") < 0 ||
29ef309e
RB
451 root.path[0] == L'%') /* i.e. no expansion happened */
452 {
453 giterr_set(GITERR_OS, "Cannot locate the user's profile directory");
349fb6d7
VM
454 return -1;
455 }
456
29ef309e
RB
457 if (win32_find_file(path, &root, filename) < 0) {
458 git_buf_clear(path);
459 return GIT_ENOTFOUND;
460 }
461
349fb6d7 462 return 0;
73b51450 463#else
349fb6d7
VM
464 const char *home = getenv("HOME");
465
466 if (home == NULL) {
467 giterr_set(GITERR_OS, "Global file lookup failed. "
468 "Cannot locate the user's home directory");
469 return -1;
470 }
471
472 if (git_buf_joinpath(path, home, filename) < 0)
473 return -1;
474
475 if (git_path_exists(path->ptr) == false) {
476 git_buf_clear(path);
477 return GIT_ENOTFOUND;
478 }
479
480 return 0;
73b51450
RB
481#endif
482}