]> git.proxmox.com Git - libgit2.git/blob - src/fileops.c
6e45ff8a8bc65185d6a77bc2f60444a81551d2d8
[libgit2.git] / src / fileops.c
1 /*
2 * Copyright (C) 2009-2012 the libgit2 contributors
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 */
7 #include "common.h"
8 #include "fileops.h"
9 #include <ctype.h>
10
11 int git_futils_mkpath2file(const char *file_path, const mode_t mode)
12 {
13 int error;
14 git_buf target_folder = GIT_BUF_INIT;
15
16 error = git_path_dirname_r(&target_folder, file_path);
17 if (error < GIT_SUCCESS) {
18 git_buf_free(&target_folder);
19 return git__throw(GIT_EINVALIDPATH, "Failed to recursively build `%s` tree structure. Unable to parse parent folder name", file_path);
20 } else {
21 /* reset error */
22 error = GIT_SUCCESS;
23 }
24
25 /* Does the containing folder exist? */
26 if (git_path_isdir(target_folder.ptr) != GIT_SUCCESS)
27 /* Let's create the tree structure */
28 error = git_futils_mkdir_r(target_folder.ptr, NULL, mode);
29
30 git_buf_free(&target_folder);
31 return error;
32 }
33
34 int git_futils_mktmp(git_buf *path_out, const char *filename)
35 {
36 int fd;
37
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);
44
45 if ((fd = p_mkstemp(path_out->ptr)) < 0)
46 return git__throw(GIT_EOSERR, "Failed to create temporary file %s", path_out->ptr);
47
48 return fd;
49 }
50
51 int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
52 {
53 if (git_futils_mkpath2file(path, dirmode) < GIT_SUCCESS)
54 return git__throw(GIT_EOSERR, "Failed to create file %s", path);
55
56 return p_creat(path, mode);
57 }
58
59 int git_futils_creat_locked(const char *path, const mode_t mode)
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
65 int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
66 {
67 if (git_futils_mkpath2file(path, dirmode) < GIT_SUCCESS)
68 return git__throw(GIT_EOSERR, "Failed to create locked file %s", path);
69
70 return git_futils_creat_locked(path, mode);
71 }
72
73 git_off_t git_futils_filesize(git_file fd)
74 {
75 struct stat sb;
76 if (p_fstat(fd, &sb))
77 return GIT_ERROR;
78
79 return sb.st_size;
80 }
81
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
96 int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, int *updated)
97 {
98 git_file fd;
99 size_t len;
100 struct stat st;
101
102 assert(buf && path && *path);
103
104 if (updated != NULL)
105 *updated = 0;
106
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 }
110
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 }
115
116 /*
117 * If we were given a time, we only want to read the file if it
118 * has been modified.
119 */
120 if (mtime != NULL && *mtime >= st.st_mtime) {
121 close(fd);
122 return 0;
123 }
124
125 if (mtime != NULL)
126 *mtime = st.st_mtime;
127
128 len = (size_t) st.st_size;
129
130 git_buf_clear(buf);
131
132 if (git_buf_grow(buf, len + 1) < 0) {
133 close(fd);
134 return GIT_ENOMEM;
135 }
136
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;
149 }
150
151 p_close(fd);
152
153 if (mtime != NULL)
154 *mtime = st.st_mtime;
155
156 if (updated != NULL)
157 *updated = 1;
158
159 return 0;
160 }
161
162 int git_futils_readbuffer(git_buf *buf, const char *path)
163 {
164 return git_futils_readbuffer_updated(buf, path, NULL, NULL);
165 }
166
167 int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
168 {
169 if (git_futils_mkpath2file(to, dirmode) < GIT_SUCCESS)
170 return GIT_EOSERR; /* The callee already takes care of setting the correct error message. */
171
172 return p_rename(from, to); /* The callee already takes care of setting the correct error message. */
173 }
174
175 int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
176 {
177 return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
178 }
179
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 */);
183 size_t len = git_futils_filesize(fd);
184 int result = git_futils_mmap_ro(out, fd, 0, len);
185 p_close(fd);
186 return result;
187 }
188
189 void git_futils_mmap_free(git_map *out)
190 {
191 p_munmap(out);
192 }
193
194 int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
195 {
196 int error, root_path_offset;
197 git_buf make_path = GIT_BUF_INIT;
198 size_t start;
199 char *pp, *sp;
200
201 if (base != NULL) {
202 start = strlen(base);
203 error = git_buf_joinpath(&make_path, base, path);
204 } else {
205 start = 0;
206 error = git_buf_puts(&make_path, path);
207 }
208 if (error < GIT_SUCCESS)
209 return git__rethrow(error, "Failed to create `%s` tree structure", path);
210
211 pp = make_path.ptr + start;
212
213 root_path_offset = git_path_root(make_path.ptr);
214 if (root_path_offset > 0)
215 pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */
216
217 while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != NULL) {
218 if (sp != pp && git_path_isdir(make_path.ptr) < GIT_SUCCESS) {
219 *sp = 0;
220 error = p_mkdir(make_path.ptr, mode);
221
222 /* Do not choke while trying to recreate an existing directory */
223 if (errno == EEXIST)
224 error = GIT_SUCCESS;
225
226 *sp = '/';
227 }
228
229 pp = sp + 1;
230 }
231
232 if (*pp != '\0' && error == GIT_SUCCESS) {
233 error = p_mkdir(make_path.ptr, mode);
234 if (errno == EEXIST)
235 error = GIT_SUCCESS;
236 }
237
238 git_buf_free(&make_path);
239
240 if (error < GIT_SUCCESS)
241 return git__throw(error, "Failed to recursively create `%s` tree structure", path);
242
243 return GIT_SUCCESS;
244 }
245
246 static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
247 {
248 int error = GIT_SUCCESS;
249 int force = *(int *)opaque;
250
251 if (git_path_isdir(path->ptr) == GIT_SUCCESS) {
252 error = git_path_direach(path, _rmdir_recurs_foreach, opaque);
253 if (error < GIT_SUCCESS)
254 return git__rethrow(error, "Failed to remove directory `%s`", path->ptr);
255 return p_rmdir(path->ptr);
256
257 } else if (force) {
258 return p_unlink(path->ptr);
259 }
260
261 return git__rethrow(error, "Failed to remove directory. `%s` is not empty", path->ptr);
262 }
263
264 int git_futils_rmdir_r(const char *path, int force)
265 {
266 int error;
267 git_buf p = GIT_BUF_INIT;
268
269 error = git_buf_sets(&p, path);
270 if (error == GIT_SUCCESS)
271 error = _rmdir_recurs_foreach(&force, &p);
272 git_buf_free(&p);
273 return error;
274 }
275
276 int git_futils_find_global_file(git_buf *path, const char *filename)
277 {
278 int error;
279 const char *home = getenv("HOME");
280
281 #ifdef GIT_WIN32
282 if (home == NULL)
283 home = getenv("USERPROFILE");
284 #endif
285
286 if (home == NULL)
287 return git__throw(GIT_EOSERR, "Failed to open global %s file. "
288 "Cannot locate the user's home directory.", filename);
289
290 if ((error = git_buf_joinpath(path, home, filename)) < GIT_SUCCESS)
291 return error;
292
293 if (git_path_exists(path->ptr) < GIT_SUCCESS) {
294 git_buf_clear(path);
295 return GIT_ENOTFOUND;
296 }
297
298 return GIT_SUCCESS;
299 }
300
301 #ifdef GIT_WIN32
302 typedef struct {
303 wchar_t *path;
304 DWORD len;
305 } win32_path;
306
307 static const win32_path *win32_system_root(void)
308 {
309 static win32_path s_root = { 0, 0 };
310
311 if (s_root.path == NULL) {
312 const wchar_t *root_tmpl = L"%PROGRAMFILES%\\Git\\etc\\";
313
314 s_root.len = ExpandEnvironmentStringsW(root_tmpl, NULL, 0);
315
316 if (s_root.len <= 0) {
317 git__throw(GIT_EOSERR, "Failed to expand environment strings");
318 return NULL;
319 }
320
321 s_root.path = git__calloc(s_root.len, sizeof(wchar_t));
322 if (s_root.path == NULL)
323 return NULL;
324
325 if (ExpandEnvironmentStringsW(root_tmpl, s_root.path, s_root.len) != s_root.len) {
326 git__throw(GIT_EOSERR, "Failed to expand environment strings");
327 git__free(s_root.path);
328 s_root.path = NULL;
329 return NULL;
330 }
331 }
332
333 return &s_root;
334 }
335
336 static int win32_find_system_file(git_buf *path, const char *filename)
337 {
338 int error = GIT_SUCCESS;
339 const win32_path *root = win32_system_root();
340 size_t len;
341 wchar_t *file_utf16 = NULL, *scan;
342 char *file_utf8 = NULL;
343
344 if (!root || !filename || (len = strlen(filename)) == 0)
345 return GIT_ENOTFOUND;
346
347 /* allocate space for wchar_t path to file */
348 file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t));
349 if (!file_utf16)
350 return GIT_ENOMEM;
351
352 /* append root + '\\' + filename as wchar_t */
353 memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));
354
355 if (*filename == '/' || *filename == '\\')
356 filename++;
357
358 if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) !=
359 (int)len + 1) {
360 error = git__throw(GIT_EOSERR, "Failed to build file path");
361 goto cleanup;
362 }
363
364 for (scan = file_utf16; *scan; scan++)
365 if (*scan == L'/')
366 *scan = L'\\';
367
368 /* check access */
369 if (_waccess(file_utf16, F_OK) < 0) {
370 error = GIT_ENOTFOUND;
371 goto cleanup;
372 }
373
374 /* convert to utf8 */
375 if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL)
376 error = GIT_ENOMEM;
377
378 if (file_utf8) {
379 git_path_mkposix(file_utf8);
380 git_buf_attach(path, file_utf8, 0);
381 }
382
383 cleanup:
384 git__free(file_utf16);
385
386 return error;
387 }
388 #endif
389
390 int git_futils_find_system_file(git_buf *path, const char *filename)
391 {
392 if (git_buf_joinpath(path, "/etc", filename) < GIT_SUCCESS)
393 return git_buf_lasterror(path);
394
395 if (git_path_exists(path->ptr) == GIT_SUCCESS)
396 return GIT_SUCCESS;
397
398 git_buf_clear(path);
399
400 #ifdef GIT_WIN32
401 return win32_find_system_file(path, filename);
402 #else
403 return GIT_ENOTFOUND;
404 #endif
405 }