]>
Commit | Line | Data |
---|---|---|
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 | 11 | int 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 | 28 | int 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 | 47 | int 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 | 63 | int 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 | 85 | int 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 |
93 | int git_futils_open_ro(const char *path) |
94 | { | |
95 | int fd = p_open(path, O_RDONLY); | |
96 | if (fd < 0) { | |
97 | if (errno == ENOENT) | |
98 | fd = GIT_ENOTFOUND; | |
99 | giterr_set(GITERR_OS, "Failed to open '%s'", path); | |
100 | } | |
101 | return fd; | |
102 | } | |
103 | ||
f79026b4 | 104 | git_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 |
116 | mode_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 | 130 | int 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 | 194 | int 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 | 199 | int 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 | 212 | int 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 |
217 | int 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 | 237 | void git_futils_mmap_free(git_map *out) |
20e7f426 | 238 | { |
f79026b4 | 239 | p_munmap(out); |
20e7f426 SP |
240 | } |
241 | ||
97769280 | 242 | int 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 | 304 | static 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 | 344 | int 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 |
357 | struct win32_path { |
358 | wchar_t path[MAX_PATH]; | |
73b51450 | 359 | DWORD len; |
349fb6d7 | 360 | }; |
73b51450 | 361 | |
349fb6d7 | 362 | static 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 | 368 | static 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 | ||
408 | cleanup: | |
409 | git__free(file_utf16); | |
73b51450 RB |
410 | return error; |
411 | } | |
412 | #endif | |
413 | ||
414 | int 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 VM |
419 | if (win32_expand_path(&root, L"%PROGRAMFILES%\\Git\\etc\\") < 0 || |
420 | win32_find_file(path, &root, filename) < 0) { | |
421 | giterr_set(GITERR_OS, "Cannot find the system's Program Files directory"); | |
422 | return -1; | |
423 | } | |
424 | ||
425 | return 0; | |
426 | ||
427 | #else | |
cb8a7961 VM |
428 | if (git_buf_joinpath(path, "/etc", filename) < 0) |
429 | return -1; | |
73b51450 | 430 | |
1a481123 | 431 | if (git_path_exists(path->ptr) == true) |
deafee7b | 432 | return 0; |
73b51450 RB |
433 | |
434 | git_buf_clear(path); | |
349fb6d7 VM |
435 | return GIT_ENOTFOUND; |
436 | #endif | |
437 | } | |
73b51450 | 438 | |
349fb6d7 VM |
439 | int git_futils_find_global_file(git_buf *path, const char *filename) |
440 | { | |
73b51450 | 441 | #ifdef GIT_WIN32 |
349fb6d7 VM |
442 | struct win32_path root; |
443 | ||
444 | if (win32_expand_path(&root, L"%USERPROFILE%\\") < 0 || | |
445 | win32_find_file(path, &root, filename) < 0) { | |
446 | giterr_set(GITERR_OS, "Failed to lookup the current user's Windows profile"); | |
447 | return -1; | |
448 | } | |
449 | ||
450 | return 0; | |
73b51450 | 451 | #else |
349fb6d7 VM |
452 | const char *home = getenv("HOME"); |
453 | ||
454 | if (home == NULL) { | |
455 | giterr_set(GITERR_OS, "Global file lookup failed. " | |
456 | "Cannot locate the user's home directory"); | |
457 | return -1; | |
458 | } | |
459 | ||
460 | if (git_buf_joinpath(path, home, filename) < 0) | |
461 | return -1; | |
462 | ||
463 | if (git_path_exists(path->ptr) == false) { | |
464 | git_buf_clear(path); | |
465 | return GIT_ENOTFOUND; | |
466 | } | |
467 | ||
468 | return 0; | |
73b51450 RB |
469 | #endif |
470 | } |