]>
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 | { |
97769280 RB |
13 | int error; |
14 | git_buf target_folder = GIT_BUF_INIT; | |
55ffebe3 | 15 | |
97769280 RB |
16 | error = git_path_dirname_r(&target_folder, file_path); |
17 | if (error < GIT_SUCCESS) { | |
18 | git_buf_free(&target_folder); | |
77c3999c | 19 | return git__throw(GIT_EINVALIDPATH, "Failed to recursively build `%s` tree structure. Unable to parse parent folder name", file_path); |
97769280 RB |
20 | } else { |
21 | /* reset error */ | |
22 | error = GIT_SUCCESS; | |
23 | } | |
55ffebe3 VM |
24 | |
25 | /* Does the containing folder exist? */ | |
1744fafe | 26 | if (git_path_isdir(target_folder.ptr) != GIT_SUCCESS) |
55ffebe3 | 27 | /* Let's create the tree structure */ |
97769280 | 28 | error = git_futils_mkdir_r(target_folder.ptr, NULL, mode); |
55ffebe3 | 29 | |
97769280 RB |
30 | git_buf_free(&target_folder); |
31 | return error; | |
55ffebe3 VM |
32 | } |
33 | ||
97769280 | 34 | int git_futils_mktmp(git_buf *path_out, const char *filename) |
72a3fe42 VM |
35 | { |
36 | int fd; | |
37 | ||
97769280 RB |
38 | git_buf_sets(path_out, filename); |
39 | git_buf_puts(path_out, "_git2_XXXXXX"); | |
40 | ||
41 | if (git_buf_oom(path_out)) | |
42 | return git__rethrow(git_buf_lasterror(path_out), | |
43 | "Failed to create temporary file for %s", filename); | |
72a3fe42 | 44 | |
97769280 RB |
45 | if ((fd = p_mkstemp(path_out->ptr)) < 0) |
46 | return git__throw(GIT_EOSERR, "Failed to create temporary file %s", path_out->ptr); | |
72a3fe42 | 47 | |
f978b748 | 48 | return fd; |
72a3fe42 VM |
49 | } |
50 | ||
ce8cd006 | 51 | int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode) |
7dd8a9f7 | 52 | { |
ce8cd006 | 53 | if (git_futils_mkpath2file(path, dirmode) < GIT_SUCCESS) |
cc2ac058 | 54 | return git__throw(GIT_EOSERR, "Failed to create file %s", path); |
55ffebe3 | 55 | |
f79026b4 | 56 | return p_creat(path, mode); |
55ffebe3 VM |
57 | } |
58 | ||
33127043 | 59 | int git_futils_creat_locked(const char *path, const mode_t mode) |
1549cba9 RG |
60 | { |
61 | int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); | |
62 | return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create locked file. Could not open %s", path); | |
63 | } | |
64 | ||
ce8cd006 | 65 | int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode) |
1549cba9 | 66 | { |
ce8cd006 | 67 | if (git_futils_mkpath2file(path, dirmode) < GIT_SUCCESS) |
1549cba9 RG |
68 | return git__throw(GIT_EOSERR, "Failed to create locked file %s", path); |
69 | ||
f79026b4 | 70 | return git_futils_creat_locked(path, mode); |
ec250c6e AE |
71 | } |
72 | ||
f79026b4 | 73 | git_off_t git_futils_filesize(git_file fd) |
ec250c6e | 74 | { |
7dd8a9f7 | 75 | struct stat sb; |
f79026b4 | 76 | if (p_fstat(fd, &sb)) |
5ad739e8 VM |
77 | return GIT_ERROR; |
78 | ||
ec250c6e AE |
79 | return sb.st_size; |
80 | } | |
4188d28f | 81 | |
b6c93aef RB |
82 | mode_t git_futils_canonical_mode(mode_t raw_mode) |
83 | { | |
84 | if (S_ISREG(raw_mode)) | |
85 | return S_IFREG | GIT_CANONICAL_PERMS(raw_mode); | |
86 | else if (S_ISLNK(raw_mode)) | |
87 | return S_IFLNK; | |
88 | else if (S_ISDIR(raw_mode)) | |
89 | return S_IFDIR; | |
90 | else if (S_ISGITLINK(raw_mode)) | |
91 | return S_IFGITLINK; | |
92 | else | |
93 | return 0; | |
94 | } | |
95 | ||
13224ea4 | 96 | int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, int *updated) |
75d58430 RJ |
97 | { |
98 | git_file fd; | |
90d4d2f0 | 99 | size_t len; |
c3da9f06 | 100 | struct stat st; |
75d58430 | 101 | |
13224ea4 | 102 | assert(buf && path && *path); |
75d58430 | 103 | |
c3da9f06 CMN |
104 | if (updated != NULL) |
105 | *updated = 0; | |
75d58430 | 106 | |
13224ea4 VM |
107 | if ((fd = p_open(path, O_RDONLY)) < 0) { |
108 | return git__throw(GIT_ENOTFOUND, "Failed to read file '%s': %s", path, strerror(errno)); | |
109 | } | |
c3da9f06 | 110 | |
13224ea4 VM |
111 | if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) { |
112 | close(fd); | |
113 | return git__throw(GIT_EOSERR, "Failed to stat file '%s'", path); | |
114 | } | |
c3da9f06 CMN |
115 | |
116 | /* | |
117 | * If we were given a time, we only want to read the file if it | |
118 | * has been modified. | |
119 | */ | |
13224ea4 VM |
120 | if (mtime != NULL && *mtime >= st.st_mtime) { |
121 | close(fd); | |
122 | return 0; | |
123 | } | |
c3da9f06 CMN |
124 | |
125 | if (mtime != NULL) | |
126 | *mtime = st.st_mtime; | |
c3da9f06 CMN |
127 | |
128 | len = (size_t) st.st_size; | |
129 | ||
13224ea4 | 130 | git_buf_clear(buf); |
90d4d2f0 | 131 | |
13224ea4 VM |
132 | if (git_buf_grow(buf, len + 1) < 0) { |
133 | close(fd); | |
77c3999c | 134 | return GIT_ENOMEM; |
75d58430 RJ |
135 | } |
136 | ||
13224ea4 VM |
137 | buf->ptr[len] = '\0'; |
138 | ||
139 | while (len > 0) { | |
140 | ssize_t read_size = p_read(fd, buf->ptr, len); | |
141 | ||
142 | if (read_size < 0) { | |
143 | close(fd); | |
144 | return git__throw(GIT_EOSERR, "Failed to read from FD"); | |
145 | } | |
146 | ||
147 | len -= read_size; | |
148 | buf->size += read_size; | |
75d58430 RJ |
149 | } |
150 | ||
f79026b4 | 151 | p_close(fd); |
75d58430 | 152 | |
c3da9f06 CMN |
153 | if (mtime != NULL) |
154 | *mtime = st.st_mtime; | |
13224ea4 | 155 | |
c3da9f06 CMN |
156 | if (updated != NULL) |
157 | *updated = 1; | |
158 | ||
13224ea4 | 159 | return 0; |
c3da9f06 CMN |
160 | } |
161 | ||
13224ea4 | 162 | int git_futils_readbuffer(git_buf *buf, const char *path) |
97769280 | 163 | { |
13224ea4 | 164 | return git_futils_readbuffer_updated(buf, path, NULL, NULL); |
97769280 RB |
165 | } |
166 | ||
ce8cd006 | 167 | int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) |
19a30a3f | 168 | { |
ce8cd006 | 169 | if (git_futils_mkpath2file(to, dirmode) < GIT_SUCCESS) |
77c3999c | 170 | return GIT_EOSERR; /* The callee already takes care of setting the correct error message. */ |
19a30a3f | 171 | |
0c49ec2d | 172 | return p_rename(from, to); /* The callee already takes care of setting the correct error message. */ |
19a30a3f VM |
173 | } |
174 | ||
f79026b4 | 175 | int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len) |
20e7f426 | 176 | { |
f79026b4 | 177 | return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin); |
20e7f426 SP |
178 | } |
179 | ||
74fa4bfa RB |
180 | int git_futils_mmap_ro_file(git_map *out, const char *path) |
181 | { | |
182 | git_file fd = p_open(path, O_RDONLY /* | O_NOATIME */); | |
da9abdd6 RB |
183 | git_off_t len = git_futils_filesize(fd); |
184 | int result; | |
185 | if (!git__is_sizet(len)) | |
186 | return git__throw(GIT_ERROR, "File `%s` too large to mmap", path); | |
187 | result = git_futils_mmap_ro(out, fd, 0, (size_t)len); | |
74fa4bfa RB |
188 | p_close(fd); |
189 | return result; | |
190 | } | |
191 | ||
f79026b4 | 192 | void git_futils_mmap_free(git_map *out) |
20e7f426 | 193 | { |
f79026b4 | 194 | p_munmap(out); |
20e7f426 SP |
195 | } |
196 | ||
97769280 | 197 | int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) |
1a5204a7 | 198 | { |
c90292ce | 199 | int error, root_path_offset; |
97769280 RB |
200 | git_buf make_path = GIT_BUF_INIT; |
201 | size_t start; | |
40c44d2f | 202 | char *pp, *sp; |
f0b2bfe5 | 203 | |
97769280 RB |
204 | if (base != NULL) { |
205 | start = strlen(base); | |
206 | error = git_buf_joinpath(&make_path, base, path); | |
207 | } else { | |
208 | start = 0; | |
209 | error = git_buf_puts(&make_path, path); | |
210 | } | |
211 | if (error < GIT_SUCCESS) | |
212 | return git__rethrow(error, "Failed to create `%s` tree structure", path); | |
f0b2bfe5 | 213 | |
97769280 | 214 | pp = make_path.ptr + start; |
40c44d2f | 215 | |
97769280 | 216 | root_path_offset = git_path_root(make_path.ptr); |
c90292ce | 217 | if (root_path_offset > 0) |
218 | pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */ | |
2e29957a | 219 | |
87d9869f | 220 | while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != NULL) { |
1744fafe | 221 | if (sp != pp && git_path_isdir(make_path.ptr) < GIT_SUCCESS) { |
d5f25204 | 222 | *sp = 0; |
97769280 | 223 | error = p_mkdir(make_path.ptr, mode); |
412de9a6 | 224 | |
225 | /* Do not choke while trying to recreate an existing directory */ | |
226 | if (errno == EEXIST) | |
227 | error = GIT_SUCCESS; | |
228 | ||
d5f25204 VM |
229 | *sp = '/'; |
230 | } | |
40c44d2f | 231 | |
d5f25204 VM |
232 | pp = sp + 1; |
233 | } | |
f0b2bfe5 | 234 | |
a993e4fe | 235 | if (*pp != '\0' && error == GIT_SUCCESS) { |
97769280 | 236 | error = p_mkdir(make_path.ptr, mode); |
a993e4fe RG |
237 | if (errno == EEXIST) |
238 | error = GIT_SUCCESS; | |
239 | } | |
40c44d2f | 240 | |
97769280 | 241 | git_buf_free(&make_path); |
77c3999c | 242 | |
243 | if (error < GIT_SUCCESS) | |
244 | return git__throw(error, "Failed to recursively create `%s` tree structure", path); | |
245 | ||
246 | return GIT_SUCCESS; | |
170d3f2f | 247 | } |
248 | ||
97769280 | 249 | static int _rmdir_recurs_foreach(void *opaque, git_buf *path) |
42b3a460 MS |
250 | { |
251 | int error = GIT_SUCCESS; | |
858dba58 | 252 | int force = *(int *)opaque; |
42b3a460 | 253 | |
1744fafe RB |
254 | if (git_path_isdir(path->ptr) == GIT_SUCCESS) { |
255 | error = git_path_direach(path, _rmdir_recurs_foreach, opaque); | |
97769280 RB |
256 | if (error < GIT_SUCCESS) |
257 | return git__rethrow(error, "Failed to remove directory `%s`", path->ptr); | |
258 | return p_rmdir(path->ptr); | |
42b3a460 | 259 | |
858dba58 | 260 | } else if (force) { |
97769280 | 261 | return p_unlink(path->ptr); |
858dba58 VM |
262 | } |
263 | ||
97769280 | 264 | return git__rethrow(error, "Failed to remove directory. `%s` is not empty", path->ptr); |
42b3a460 MS |
265 | } |
266 | ||
858dba58 | 267 | int git_futils_rmdir_r(const char *path, int force) |
42b3a460 | 268 | { |
97769280 RB |
269 | int error; |
270 | git_buf p = GIT_BUF_INIT; | |
271 | ||
272 | error = git_buf_sets(&p, path); | |
273 | if (error == GIT_SUCCESS) | |
274 | error = _rmdir_recurs_foreach(&force, &p); | |
275 | git_buf_free(&p); | |
276 | return error; | |
42b3a460 MS |
277 | } |
278 | ||
73b51450 RB |
279 | int git_futils_find_global_file(git_buf *path, const char *filename) |
280 | { | |
281 | int error; | |
282 | const char *home = getenv("HOME"); | |
283 | ||
284 | #ifdef GIT_WIN32 | |
285 | if (home == NULL) | |
286 | home = getenv("USERPROFILE"); | |
287 | #endif | |
288 | ||
289 | if (home == NULL) | |
290 | return git__throw(GIT_EOSERR, "Failed to open global %s file. " | |
291 | "Cannot locate the user's home directory.", filename); | |
292 | ||
293 | if ((error = git_buf_joinpath(path, home, filename)) < GIT_SUCCESS) | |
294 | return error; | |
295 | ||
1744fafe | 296 | if (git_path_exists(path->ptr) < GIT_SUCCESS) { |
73b51450 RB |
297 | git_buf_clear(path); |
298 | return GIT_ENOTFOUND; | |
299 | } | |
300 | ||
301 | return GIT_SUCCESS; | |
302 | } | |
303 | ||
304 | #ifdef GIT_WIN32 | |
305 | typedef struct { | |
306 | wchar_t *path; | |
307 | DWORD len; | |
308 | } win32_path; | |
309 | ||
310 | static const win32_path *win32_system_root(void) | |
311 | { | |
312 | static win32_path s_root = { 0, 0 }; | |
313 | ||
314 | if (s_root.path == NULL) { | |
315 | const wchar_t *root_tmpl = L"%PROGRAMFILES%\\Git\\etc\\"; | |
316 | ||
317 | s_root.len = ExpandEnvironmentStringsW(root_tmpl, NULL, 0); | |
318 | ||
319 | if (s_root.len <= 0) { | |
320 | git__throw(GIT_EOSERR, "Failed to expand environment strings"); | |
321 | return NULL; | |
322 | } | |
323 | ||
324 | s_root.path = git__calloc(s_root.len, sizeof(wchar_t)); | |
325 | if (s_root.path == NULL) | |
326 | return NULL; | |
327 | ||
328 | if (ExpandEnvironmentStringsW(root_tmpl, s_root.path, s_root.len) != s_root.len) { | |
329 | git__throw(GIT_EOSERR, "Failed to expand environment strings"); | |
330 | git__free(s_root.path); | |
331 | s_root.path = NULL; | |
332 | return NULL; | |
333 | } | |
334 | } | |
335 | ||
336 | return &s_root; | |
337 | } | |
338 | ||
339 | static int win32_find_system_file(git_buf *path, const char *filename) | |
340 | { | |
341 | int error = GIT_SUCCESS; | |
342 | const win32_path *root = win32_system_root(); | |
343 | size_t len; | |
344 | wchar_t *file_utf16 = NULL, *scan; | |
345 | char *file_utf8 = NULL; | |
346 | ||
347 | if (!root || !filename || (len = strlen(filename)) == 0) | |
348 | return GIT_ENOTFOUND; | |
349 | ||
350 | /* allocate space for wchar_t path to file */ | |
351 | file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t)); | |
352 | if (!file_utf16) | |
353 | return GIT_ENOMEM; | |
354 | ||
355 | /* append root + '\\' + filename as wchar_t */ | |
356 | memcpy(file_utf16, root->path, root->len * sizeof(wchar_t)); | |
357 | ||
358 | if (*filename == '/' || *filename == '\\') | |
359 | filename++; | |
360 | ||
361 | if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) != | |
f46e6226 | 362 | (int)len + 1) { |
73b51450 RB |
363 | error = git__throw(GIT_EOSERR, "Failed to build file path"); |
364 | goto cleanup; | |
365 | } | |
366 | ||
367 | for (scan = file_utf16; *scan; scan++) | |
368 | if (*scan == L'/') | |
369 | *scan = L'\\'; | |
370 | ||
371 | /* check access */ | |
372 | if (_waccess(file_utf16, F_OK) < 0) { | |
373 | error = GIT_ENOTFOUND; | |
374 | goto cleanup; | |
375 | } | |
376 | ||
377 | /* convert to utf8 */ | |
378 | if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL) | |
379 | error = GIT_ENOMEM; | |
380 | ||
381 | if (file_utf8) { | |
382 | git_path_mkposix(file_utf8); | |
383 | git_buf_attach(path, file_utf8, 0); | |
384 | } | |
385 | ||
386 | cleanup: | |
387 | git__free(file_utf16); | |
388 | ||
389 | return error; | |
390 | } | |
391 | #endif | |
392 | ||
393 | int git_futils_find_system_file(git_buf *path, const char *filename) | |
394 | { | |
395 | if (git_buf_joinpath(path, "/etc", filename) < GIT_SUCCESS) | |
396 | return git_buf_lasterror(path); | |
397 | ||
1744fafe | 398 | if (git_path_exists(path->ptr) == GIT_SUCCESS) |
73b51450 RB |
399 | return GIT_SUCCESS; |
400 | ||
401 | git_buf_clear(path); | |
402 | ||
403 | #ifdef GIT_WIN32 | |
404 | return win32_find_system_file(path, filename); | |
405 | #else | |
406 | return GIT_ENOTFOUND; | |
407 | #endif | |
408 | } |