]> git.proxmox.com Git - libgit2.git/blob - src/fileops.c
Fileops: Added gitfo_isfile.
[libgit2.git] / src / fileops.c
1 #include "common.h"
2 #include "fileops.h"
3 #include <ctype.h>
4
5 int gitfo_mkdir_2file(const char *file_path)
6 {
7 const int mode = 0755; /* or 0777 ? */
8 int error = GIT_SUCCESS;
9 char target_folder_path[GIT_PATH_MAX];
10
11 error = git__dirname_r(target_folder_path, sizeof(target_folder_path), file_path);
12 if (error < GIT_SUCCESS)
13 return git__throw(GIT_EINVALIDPATH, "Failed to recursively build `%s` tree structure. Unable to parse parent folder name", file_path);
14
15 /* Does the containing folder exist? */
16 if (gitfo_isdir(target_folder_path)) {
17 git__joinpath(target_folder_path, target_folder_path, ""); /* Ensure there's a trailing slash */
18
19 /* Let's create the tree structure */
20 error = gitfo_mkdir_recurs(target_folder_path, mode);
21 if (error < GIT_SUCCESS)
22 return error; /* The callee already takes care of setting the correct error message. */
23 }
24
25 return GIT_SUCCESS;
26 }
27
28 int gitfo_mktemp(char *path_out, const char *filename)
29 {
30 int fd;
31
32 strcpy(path_out, filename);
33 strcat(path_out, "_git2_XXXXXX");
34
35 #if defined(_MSC_VER)
36 /* FIXME: there may be race conditions when multi-threading
37 * with the library */
38 if (_mktemp_s(path_out, GIT_PATH_MAX) != 0)
39 return git__throw(GIT_EOSERR, "Failed to make temporary file %s", path_out);
40
41 fd = gitfo_creat(path_out, 0744);
42 #else
43 fd = mkstemp(path_out);
44 #endif
45
46 return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create temporary file %s", path_out);
47 }
48
49 int gitfo_open(const char *path, int flags)
50 {
51 int fd = open(path, flags | O_BINARY);
52 return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to open %s", path);
53 }
54
55 int gitfo_creat(const char *path, int mode)
56 {
57 int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode);
58 return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create file. Could not open %s", path);
59 }
60
61 int gitfo_creat_force(const char *path, int mode)
62 {
63 if (gitfo_mkdir_2file(path) < GIT_SUCCESS)
64 return git__throw(GIT_EOSERR, "Failed to create file %s", path);
65
66 return gitfo_creat(path, mode);
67 }
68
69 int gitfo_read(git_file fd, void *buf, size_t cnt)
70 {
71 char *b = buf;
72 while (cnt) {
73 ssize_t r = read(fd, b, cnt);
74 if (r < 0) {
75 if (errno == EINTR || errno == EAGAIN)
76 continue;
77 return git__throw(GIT_EOSERR, "Failed to read from file");
78 }
79 if (!r) {
80 errno = EPIPE;
81 return git__throw(GIT_EOSERR, "Failed to read from file");
82 }
83 cnt -= r;
84 b += r;
85 }
86 return GIT_SUCCESS;
87 }
88
89 int gitfo_write(git_file fd, void *buf, size_t cnt)
90 {
91 char *b = buf;
92 while (cnt) {
93 ssize_t r = write(fd, b, cnt);
94 if (r < 0) {
95 if (errno == EINTR || errno == EAGAIN)
96 continue;
97 return git__throw(GIT_EOSERR, "Failed to write to file");
98 }
99 if (!r) {
100 errno = EPIPE;
101 return git__throw(GIT_EOSERR, "Failed to write to file");
102 }
103 cnt -= r;
104 b += r;
105 }
106 return GIT_SUCCESS;
107 }
108
109 int gitfo_isdir(const char *path)
110 {
111 struct stat st;
112 int len, stat_error;
113
114 if (!path)
115 return git__throw(GIT_ENOTFOUND, "No path given to gitfo_isdir");
116
117 len = strlen(path);
118
119 /* win32: stat path for folders cannot end in a slash */
120 if (path[len - 1] == '/') {
121 char *path_fixed = NULL;
122 path_fixed = git__strdup(path);
123 path_fixed[len - 1] = 0;
124 stat_error = gitfo_stat(path_fixed, &st);
125 free(path_fixed);
126 } else {
127 stat_error = gitfo_stat(path, &st);
128 }
129
130 if (stat_error < GIT_SUCCESS)
131 return git__throw(GIT_ENOTFOUND, "%s does not exist", path);
132
133 if (!S_ISDIR(st.st_mode))
134 return git__throw(GIT_ENOTFOUND, "%s is not a directory", path);
135
136 return GIT_SUCCESS;
137 }
138
139 int gitfo_isfile(const char *path)
140 {
141 struct stat st;
142 int stat_error;
143
144 if (!path)
145 return git__throw(GIT_ENOTFOUND, "No path given to gitfo_isfile");
146
147 stat_error = gitfo_stat(path, &st);
148
149 if (stat_error < GIT_SUCCESS)
150 return git__throw(GIT_ENOTFOUND, "%s does not exist", path);
151
152 if (!S_ISREG(st.st_mode))
153 return git__throw(GIT_ENOTFOUND, "%s is not a file", path);
154
155 return GIT_SUCCESS;
156 }
157
158 int gitfo_exists(const char *path)
159 {
160 assert(path);
161 return access(path, F_OK);
162 }
163
164 git_off_t gitfo_size(git_file fd)
165 {
166 struct stat sb;
167 if (gitfo_fstat(fd, &sb))
168 return git__throw(GIT_EOSERR, "Failed to get size of file. File missing or corrupted");
169 return sb.st_size;
170 }
171
172 int gitfo_read_file(gitfo_buf *obj, const char *path)
173 {
174 git_file fd;
175 size_t len;
176 git_off_t size;
177 unsigned char *buff;
178
179 assert(obj && path && *path);
180
181 if ((fd = gitfo_open(path, O_RDONLY)) < 0)
182 return git__throw(GIT_ERROR, "Failed to open %s for reading", path);
183
184 if (((size = gitfo_size(fd)) < 0) || !git__is_sizet(size+1)) {
185 gitfo_close(fd);
186 return git__throw(GIT_ERROR, "Failed to read file `%s`. An error occured while calculating its size", path);
187 }
188 len = (size_t) size;
189
190 if ((buff = git__malloc(len + 1)) == NULL) {
191 gitfo_close(fd);
192 return GIT_ENOMEM;
193 }
194
195 if (gitfo_read(fd, buff, len) < 0) {
196 gitfo_close(fd);
197 free(buff);
198 return git__throw(GIT_ERROR, "Failed to read file `%s`", path);
199 }
200 buff[len] = '\0';
201
202 gitfo_close(fd);
203
204 obj->data = buff;
205 obj->len = len;
206
207 return GIT_SUCCESS;
208 }
209
210 void gitfo_free_buf(gitfo_buf *obj)
211 {
212 assert(obj);
213 free(obj->data);
214 obj->data = NULL;
215 }
216
217 int gitfo_mv(const char *from, const char *to)
218 {
219 int error;
220
221 #ifdef GIT_WIN32
222 /*
223 * Win32 POSIX compilance my ass. If the destination
224 * file exists, the `rename` call fails. This is as
225 * close as it gets with the Win32 API.
226 */
227 error = MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR;
228 #else
229 /* Don't even try this on Win32 */
230 if (!link(from, to)) {
231 gitfo_unlink(from);
232 return GIT_SUCCESS;
233 }
234
235 if (!rename(from, to))
236 return GIT_SUCCESS;
237
238 error = GIT_EOSERR;
239 #endif
240
241 if (error < GIT_SUCCESS)
242 return git__throw(error, "Failed to move file from `%s` to `%s`", from, to);
243
244 return GIT_SUCCESS;
245 }
246
247 int gitfo_mv_force(const char *from, const char *to)
248 {
249 if (gitfo_mkdir_2file(to) < GIT_SUCCESS)
250 return GIT_EOSERR; /* The callee already takes care of setting the correct error message. */
251
252 return gitfo_mv(from, to); /* The callee already takes care of setting the correct error message. */
253 }
254
255 int gitfo_map_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
256 {
257 if (git__mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin) < GIT_SUCCESS)
258 return GIT_EOSERR;
259 return GIT_SUCCESS;
260 }
261
262 void gitfo_free_map(git_map *out)
263 {
264 git__munmap(out);
265 }
266
267 int gitfo_dirent(
268 char *path,
269 size_t path_sz,
270 int (*fn)(void *, char *),
271 void *arg)
272 {
273 size_t wd_len = strlen(path);
274 DIR *dir;
275 struct dirent *de;
276
277 if (!wd_len || path_sz < wd_len + 2)
278 return git__throw(GIT_EINVALIDARGS, "Failed to process `%s` tree structure. Path is either empty or buffer size is too short", path);
279
280 while (path[wd_len - 1] == '/')
281 wd_len--;
282 path[wd_len++] = '/';
283 path[wd_len] = '\0';
284
285 dir = opendir(path);
286 if (!dir)
287 return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure. An error occured while opening the directory", path);
288
289 while ((de = readdir(dir)) != NULL) {
290 size_t de_len;
291 int result;
292
293 /* always skip '.' and '..' */
294 if (de->d_name[0] == '.') {
295 if (de->d_name[1] == '\0')
296 continue;
297 if (de->d_name[1] == '.' && de->d_name[2] == '\0')
298 continue;
299 }
300
301 de_len = strlen(de->d_name);
302 if (path_sz < wd_len + de_len + 1) {
303 closedir(dir);
304 return git__throw(GIT_ERROR, "Failed to process `%s` tree structure. Buffer size is too short", path);
305 }
306
307 strcpy(path + wd_len, de->d_name);
308 result = fn(arg, path);
309 if (result < GIT_SUCCESS) {
310 closedir(dir);
311 return result; /* The callee is reponsible for setting the correct error message */
312 }
313 if (result > 0) {
314 closedir(dir);
315 return result;
316 }
317 }
318
319 closedir(dir);
320 return GIT_SUCCESS;
321 }
322
323 static void posixify_path(char *path)
324 {
325 #if GIT_PLATFORM_PATH_SEP != '/'
326 while (*path) {
327 if (*path == GIT_PLATFORM_PATH_SEP)
328 *path = '/';
329
330 path++;
331 }
332 #endif
333 }
334
335 int gitfo_retrieve_path_root_offset(const char *path)
336 {
337 int offset = 0;
338
339 #ifdef GIT_WIN32
340
341 /* Does the root of the path look like a windows drive ? */
342 if (isalpha(path[0]) && (path[1] == ':'))
343 offset += 2;
344
345 #endif
346
347 if (*(path + offset) == '/')
348 return offset;
349
350 return -1; /* Not a real error. Rather a signal than the path is not rooted */
351 }
352
353 int gitfo_mkdir_recurs(const char *path, int mode)
354 {
355 int error, root_path_offset;
356 char *pp, *sp;
357 char *path_copy = git__strdup(path);
358
359 if (path_copy == NULL)
360 return GIT_ENOMEM;
361
362 error = GIT_SUCCESS;
363 pp = path_copy;
364
365 root_path_offset = gitfo_retrieve_path_root_offset(pp);
366 if (root_path_offset > 0)
367 pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */
368
369 while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != 0) {
370 if (sp != pp && gitfo_isdir(path_copy) < GIT_SUCCESS) {
371 *sp = 0;
372 error = gitfo_mkdir(path_copy, mode);
373
374 /* Do not choke while trying to recreate an existing directory */
375 if (errno == EEXIST)
376 error = GIT_SUCCESS;
377
378 *sp = '/';
379 }
380
381 pp = sp + 1;
382 }
383
384 if (*(pp - 1) != '/' && error == GIT_SUCCESS)
385 error = gitfo_mkdir(path, mode);
386
387 free(path_copy);
388
389 if (error < GIT_SUCCESS)
390 return git__throw(error, "Failed to recursively create `%s` tree structure", path);
391
392 return GIT_SUCCESS;
393 }
394
395 static int retrieve_previous_path_component_start(const char *path)
396 {
397 int offset, len, root_offset, start = 0;
398
399 root_offset = gitfo_retrieve_path_root_offset(path);
400 if (root_offset > -1)
401 start += root_offset;
402
403 len = strlen(path);
404 offset = len - 1;
405
406 /* Skip leading slash */
407 if (path[start] == '/')
408 start++;
409
410 /* Skip trailing slash */
411 if (path[offset] == '/')
412 offset--;
413
414 if (offset < root_offset)
415 return git__throw(GIT_ERROR, "Failed to retrieve path component. Wrong offset");
416
417 while (offset > start && path[offset-1] != '/') {
418 offset--;
419 }
420
421 return offset;
422 }
423
424 int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path, const char *base_path)
425 {
426 int len = 0, segment_len, only_dots, root_path_offset, error = GIT_SUCCESS;
427 char *current;
428 const char *buffer_out_start, *buffer_end;
429
430 current = (char *)path;
431 buffer_end = path + strlen(path);
432 buffer_out_start = buffer_out;
433
434 root_path_offset = gitfo_retrieve_path_root_offset(path);
435 if (root_path_offset < 0) {
436 if (base_path == NULL) {
437 error = gitfo_getcwd(buffer_out, size);
438 if (error < GIT_SUCCESS)
439 return error; /* The callee already takes care of setting the correct error message. */
440 } else {
441 if (size < (strlen(base_path) + 1) * sizeof(char))
442 return git__throw(GIT_EOVERFLOW, "Failed to prettify dir path: the base path is too long for the buffer.");
443
444 strcpy(buffer_out, base_path);
445 posixify_path(buffer_out);
446 git__joinpath(buffer_out, buffer_out, "");
447 }
448
449 len = strlen(buffer_out);
450 buffer_out += len;
451 }
452
453 while (current < buffer_end) {
454 /* Prevent multiple slashes from being added to the output */
455 if (*current == '/' && len > 0 && buffer_out_start[len - 1] == '/') {
456 current++;
457 continue;
458 }
459
460 only_dots = 1;
461 segment_len = 0;
462
463 /* Copy path segment to the output */
464 while (current < buffer_end && *current != '/')
465 {
466 only_dots &= (*current == '.');
467 *buffer_out++ = *current++;
468 segment_len++;
469 len++;
470 }
471
472 /* Skip current directory */
473 if (only_dots && segment_len == 1)
474 {
475 current++;
476 buffer_out -= segment_len;
477 len -= segment_len;
478 continue;
479 }
480
481 /* Handle the double-dot upward directory navigation */
482 if (only_dots && segment_len == 2)
483 {
484 current++;
485 buffer_out -= segment_len;
486
487 *buffer_out ='\0';
488 len = retrieve_previous_path_component_start(buffer_out_start);
489
490 /* Are we escaping out of the root dir? */
491 if (len < 0)
492 return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path escapes out of the root directory", path);
493
494 buffer_out = (char *)buffer_out_start + len;
495 continue;
496 }
497
498 /* Guard against potential multiple dot path traversal (cf http://cwe.mitre.org/data/definitions/33.html) */
499 if (only_dots && segment_len > 0)
500 return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path contains a segment with three `.` or more", path);
501
502 *buffer_out++ = '/';
503 len++;
504 }
505
506 *buffer_out = '\0';
507
508 return GIT_SUCCESS;
509 }
510
511 int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path, const char *base_path)
512 {
513 int error, path_len, i, root_offset;
514 const char* pattern = "/..";
515
516 path_len = strlen(path);
517
518 /* Let's make sure the filename isn't empty nor a dot */
519 if (path_len == 0 || (path_len == 1 && *path == '.'))
520 return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path is either empty or equals `.`", path);
521
522 /* Let's make sure the filename doesn't end with "/", "/." or "/.." */
523 for (i = 1; path_len > i && i < 4; i++) {
524 if (!strncmp(path + path_len - i, pattern, i))
525 return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path);
526 }
527
528 error = gitfo_prettify_dir_path(buffer_out, size, path, base_path);
529 if (error < GIT_SUCCESS)
530 return error; /* The callee already takes care of setting the correct error message. */
531
532 path_len = strlen(buffer_out);
533 root_offset = gitfo_retrieve_path_root_offset(buffer_out) + 1;
534 if (path_len == root_offset)
535 return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path);
536
537 /* Remove the trailing slash */
538 buffer_out[path_len - 1] = '\0';
539
540 return GIT_SUCCESS;
541 }
542
543 int gitfo_cmp_path(const char *name1, int len1, int isdir1,
544 const char *name2, int len2, int isdir2)
545 {
546 int len = len1 < len2 ? len1 : len2;
547 int cmp;
548
549 cmp = memcmp(name1, name2, len);
550 if (cmp)
551 return cmp;
552 if (len1 < len2)
553 return ((!isdir1 && !isdir2) ? -1 :
554 (isdir1 ? '/' - name2[len1] : name2[len1] - '/'));
555 if (len1 > len2)
556 return ((!isdir1 && !isdir2) ? 1 :
557 (isdir2 ? name1[len2] - '/' : '/' - name1[len2]));
558 return 0;
559 }
560
561 int gitfo_getcwd(char *buffer_out, size_t size)
562 {
563 char *cwd_buffer;
564
565 assert(buffer_out && size > 0);
566
567 #ifdef GIT_WIN32
568 cwd_buffer = _getcwd(buffer_out, size);
569 #else
570 cwd_buffer = getcwd(buffer_out, size);
571 #endif
572
573 if (cwd_buffer == NULL)
574 return git__throw(GIT_EOSERR, "Failed to retrieve current working directory");
575
576 posixify_path(buffer_out);
577
578 git__joinpath(buffer_out, buffer_out, ""); //Ensure the path ends with a trailing slash
579
580 return GIT_SUCCESS;
581 }