]> git.proxmox.com Git - libgit2.git/blame - src/fileops.c
Merge pull request #183 from schu/errors
[libgit2.git] / src / fileops.c
CommitLineData
5ee2fe77 1#include "common.h"
ec250c6e 2#include "fileops.h"
2e29957a 3#include <ctype.h>
ec250c6e 4
72a3fe42 5int gitfo_mkdir_2file(const char *file_path)
55ffebe3
VM
6{
7 const int mode = 0755; /* or 0777 ? */
8 int error = GIT_SUCCESS;
9 char target_folder_path[GIT_PATH_MAX];
10
72a3fe42 11 error = git__dirname_r(target_folder_path, sizeof(target_folder_path), file_path);
55ffebe3
VM
12 if (error < GIT_SUCCESS)
13 return error;
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;
23 }
24
25 return GIT_SUCCESS;
26}
27
f6f72d7e 28int gitfo_mktemp(char *path_out, const char *filename)
72a3fe42
VM
29{
30 int fd;
31
f6f72d7e 32 strcpy(path_out, filename);
72a3fe42
VM
33 strcat(path_out, "_git2_XXXXXX");
34
7c80c19e 35#if defined(_MSC_VER)
72a3fe42
VM
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_EOSERR;
40
41 fd = gitfo_creat(path_out, 0744);
42#else
43 fd = mkstemp(path_out);
44#endif
45
46 return fd >= 0 ? fd : GIT_EOSERR;
47}
48
7dd8a9f7
SP
49int gitfo_open(const char *path, int flags)
50{
7a6cf815 51 int fd = open(path, flags | O_BINARY);
9f54fe48 52 return fd >= 0 ? fd : GIT_EOSERR;
7dd8a9f7
SP
53}
54
55int gitfo_creat(const char *path, int mode)
56{
7a6cf815 57 int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode);
9f54fe48 58 return fd >= 0 ? fd : GIT_EOSERR;
7dd8a9f7
SP
59}
60
55ffebe3
VM
61int gitfo_creat_force(const char *path, int mode)
62{
72a3fe42 63 if (gitfo_mkdir_2file(path) < GIT_SUCCESS)
55ffebe3
VM
64 return GIT_EOSERR;
65
66 return gitfo_creat(path, mode);
67}
68
ec250c6e
AE
69int 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;
9f54fe48 77 return GIT_EOSERR;
ec250c6e
AE
78 }
79 if (!r) {
80 errno = EPIPE;
9f54fe48 81 return GIT_EOSERR;
ec250c6e
AE
82 }
83 cnt -= r;
84 b += r;
85 }
86 return GIT_SUCCESS;
87}
88
89int 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;
9f54fe48 97 return GIT_EOSERR;
ec250c6e
AE
98 }
99 if (!r) {
100 errno = EPIPE;
9f54fe48 101 return GIT_EOSERR;
ec250c6e
AE
102 }
103 cnt -= r;
104 b += r;
105 }
106 return GIT_SUCCESS;
107}
108
6fd195d7
VM
109int gitfo_isdir(const char *path)
110{
111 struct stat st;
43e380a8
VM
112 int len, stat_error;
113
114 if (!path)
115 return GIT_ENOTFOUND;
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
1f080e2d
VM
130 if (stat_error < GIT_SUCCESS)
131 return GIT_ENOTFOUND;
132
133 if (!S_ISDIR(st.st_mode))
134 return GIT_ENOTFOUND;
135
136 return GIT_SUCCESS;
6fd195d7
VM
137}
138
7350e633
SP
139int gitfo_exists(const char *path)
140{
72a3fe42 141 assert(path);
4d503f88 142 return access(path, F_OK);
7350e633
SP
143}
144
f0bde7fa 145git_off_t gitfo_size(git_file fd)
ec250c6e 146{
7dd8a9f7 147 struct stat sb;
3cc60635 148 if (gitfo_fstat(fd, &sb))
9f54fe48 149 return GIT_EOSERR;
ec250c6e
AE
150 return sb.st_size;
151}
4188d28f 152
75d58430
RJ
153int gitfo_read_file(gitfo_buf *obj, const char *path)
154{
155 git_file fd;
90d4d2f0 156 size_t len;
f0bde7fa 157 git_off_t size;
42fd40db 158 unsigned char *buff;
75d58430
RJ
159
160 assert(obj && path && *path);
161
162 if ((fd = gitfo_open(path, O_RDONLY)) < 0)
64a47c01 163 return GIT_ERROR;
75d58430 164
90d4d2f0
RJ
165 if (((size = gitfo_size(fd)) < 0) || !git__is_sizet(size+1)) {
166 gitfo_close(fd);
167 return GIT_ERROR;
168 }
169 len = (size_t) size;
170
171 if ((buff = git__malloc(len + 1)) == NULL) {
75d58430 172 gitfo_close(fd);
64a47c01 173 return GIT_ERROR;
75d58430
RJ
174 }
175
176 if (gitfo_read(fd, buff, len) < 0) {
177 gitfo_close(fd);
178 free(buff);
64a47c01 179 return GIT_ERROR;
75d58430 180 }
42fd40db 181 buff[len] = '\0';
75d58430
RJ
182
183 gitfo_close(fd);
184
185 obj->data = buff;
186 obj->len = len;
187
188 return GIT_SUCCESS;
189}
190
191void gitfo_free_buf(gitfo_buf *obj)
192{
193 assert(obj);
194 free(obj->data);
195 obj->data = NULL;
196}
197
19a30a3f 198int gitfo_mv(const char *from, const char *to)
ca481fc4 199{
3eb47c9f
VM
200#ifdef GIT_WIN32
201 /*
202 * Win32 POSIX compilance my ass. If the destination
203 * file exists, the `rename` call fails. This is as
204 * close as it gets with the Win32 API.
205 */
ae6ba7f7 206 return MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR;
3eb47c9f
VM
207#else
208 /* Don't even try this on Win32 */
ca481fc4
RJ
209 if (!link(from, to)) {
210 gitfo_unlink(from);
211 return GIT_SUCCESS;
212 }
213
214 if (!rename(from, to))
215 return GIT_SUCCESS;
216
9f54fe48 217 return GIT_EOSERR;
7360122b 218#endif
ca481fc4
RJ
219}
220
19a30a3f
VM
221int gitfo_mv_force(const char *from, const char *to)
222{
72a3fe42 223 if (gitfo_mkdir_2file(to) < GIT_SUCCESS)
55ffebe3 224 return GIT_EOSERR;
19a30a3f
VM
225
226 return gitfo_mv(from, to);
227}
228
f0bde7fa 229int gitfo_map_ro(git_map *out, git_file fd, git_off_t begin, size_t len)
20e7f426 230{
6f02c3ba 231 if (git__mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin) < GIT_SUCCESS)
9f54fe48 232 return GIT_EOSERR;
20e7f426
SP
233 return GIT_SUCCESS;
234}
235
79ca2edc 236void gitfo_free_map(git_map *out)
20e7f426 237{
79ca2edc 238 git__munmap(out);
20e7f426
SP
239}
240
4188d28f
AE
241/* cached diskio */
242struct gitfo_cache {
243 git_file fd;
4c9a3973 244 size_t cache_size, pos;
fc3c3a20 245 unsigned char *cache;
4188d28f
AE
246};
247
248gitfo_cache *gitfo_enable_caching(git_file fd, size_t cache_size)
249{
250 gitfo_cache *ioc;
251
64a47c01 252 ioc = git__malloc(sizeof(*ioc));
4188d28f
AE
253 if (!ioc)
254 return NULL;
255
803a6b4d 256 ioc->fd = fd;
4188d28f
AE
257 ioc->pos = 0;
258 ioc->cache_size = cache_size;
64a47c01 259 ioc->cache = git__malloc(cache_size);
4188d28f
AE
260 if (!ioc->cache) {
261 free(ioc);
262 return NULL;
263 }
264
265 return ioc;
266}
267
213e720c 268GIT_INLINE(void) gitfo_add_to_cache(gitfo_cache *ioc, void *buf, size_t len)
4188d28f
AE
269{
270 memcpy(ioc->cache + ioc->pos, buf, len);
271 ioc->pos += len;
272}
273
274int gitfo_flush_cached(gitfo_cache *ioc)
275{
276 int result = GIT_SUCCESS;
277
278 if (ioc->pos) {
279 result = gitfo_write(ioc->fd, ioc->cache, ioc->pos);
280 ioc->pos = 0;
281 }
282
283 return result;
284}
285
fc3c3a20 286int gitfo_write_cached(gitfo_cache *ioc, void *buff, size_t len)
4188d28f 287{
fc3c3a20
RJ
288 unsigned char *buf = buff;
289
4188d28f
AE
290 for (;;) {
291 size_t space_left = ioc->cache_size - ioc->pos;
292 /* cache if it's small */
293 if (space_left > len) {
294 gitfo_add_to_cache(ioc, buf, len);
295 return GIT_SUCCESS;
296 }
297
298 /* flush the cache if it doesn't fit */
299 if (ioc->pos) {
300 int rc;
301 gitfo_add_to_cache(ioc, buf, space_left);
302 rc = gitfo_flush_cached(ioc);
303 if (rc < 0)
304 return rc;
305
306 len -= space_left;
307 buf += space_left;
308 }
309
310 /* write too-large chunks immediately */
311 if (len > ioc->cache_size)
312 return gitfo_write(ioc->fd, buf, len);
313 }
4188d28f
AE
314}
315
316int gitfo_close_cached(gitfo_cache *ioc)
317{
318 git_file fd;
319
6f02c3ba 320 if (gitfo_flush_cached(ioc) < GIT_SUCCESS)
7dd8a9f7 321 return GIT_ERROR;
4188d28f
AE
322
323 fd = ioc->fd;
324 free(ioc->cache);
325 free(ioc);
326
327 return gitfo_close(fd);
328}
ea790f33 329
5690f02e
SP
330int gitfo_dirent(
331 char *path,
332 size_t path_sz,
333 int (*fn)(void *, char *),
334 void *arg)
ea790f33 335{
5690f02e 336 size_t wd_len = strlen(path);
ea790f33
AE
337 DIR *dir;
338 struct dirent *de;
339
5690f02e 340 if (!wd_len || path_sz < wd_len + 2)
ea790f33
AE
341 return GIT_ERROR;
342
ea790f33
AE
343 while (path[wd_len - 1] == '/')
344 wd_len--;
345 path[wd_len++] = '/';
346 path[wd_len] = '\0';
347
5690f02e 348 dir = opendir(path);
ea790f33 349 if (!dir)
9f54fe48 350 return GIT_EOSERR;
ea790f33 351
a9984a4e 352 while ((de = readdir(dir)) != NULL) {
ea790f33
AE
353 size_t de_len;
354 int result;
355
356 /* always skip '.' and '..' */
357 if (de->d_name[0] == '.') {
358 if (de->d_name[1] == '\0')
359 continue;
360 if (de->d_name[1] == '.' && de->d_name[2] == '\0')
361 continue;
362 }
363
364 de_len = strlen(de->d_name);
5690f02e 365 if (path_sz < wd_len + de_len + 1) {
ea790f33
AE
366 closedir(dir);
367 return GIT_ERROR;
368 }
369
370 strcpy(path + wd_len, de->d_name);
371 result = fn(arg, path);
6f02c3ba 372 if (result < GIT_SUCCESS) {
ea790f33
AE
373 closedir(dir);
374 return result;
375 }
376 if (result > 0) {
377 closedir(dir);
378 return result;
379 }
380 }
381
382 closedir(dir);
383 return GIT_SUCCESS;
384}
1a5204a7 385
23a1edbd 386
c90292ce 387int retrieve_path_root_offset(const char *path)
23a1edbd 388{
c90292ce 389 int offset = 0;
390
391#ifdef GIT_WIN32
392
23a1edbd 393 /* Does the root of the path look like a windows drive ? */
2c08c3f0 394 if (isalpha(path[0]) && (path[1] == ':'))
c90292ce 395 offset += 2;
396
397#endif
398
399 if (*(path + offset) == '/')
400 return offset;
23a1edbd 401
402 return GIT_ERROR;
403}
404
23a1edbd 405
1a5204a7 406int gitfo_mkdir_recurs(const char *path, int mode)
407{
c90292ce 408 int error, root_path_offset;
40c44d2f
VM
409 char *pp, *sp;
410 char *path_copy = git__strdup(path);
f0b2bfe5 411
40c44d2f
VM
412 if (path_copy == NULL)
413 return GIT_ENOMEM;
f0b2bfe5 414
d5f25204
VM
415 error = GIT_SUCCESS;
416 pp = path_copy;
40c44d2f 417
c90292ce 418 root_path_offset = retrieve_path_root_offset(pp);
419 if (root_path_offset > 0)
420 pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */
2e29957a 421
412de9a6 422 while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != 0) {
d5f25204
VM
423 if (sp != pp && gitfo_isdir(path_copy) < GIT_SUCCESS) {
424 *sp = 0;
425 error = gitfo_mkdir(path_copy, mode);
412de9a6 426
427 /* Do not choke while trying to recreate an existing directory */
428 if (errno == EEXIST)
429 error = GIT_SUCCESS;
430
d5f25204
VM
431 *sp = '/';
432 }
40c44d2f 433
d5f25204
VM
434 pp = sp + 1;
435 }
f0b2bfe5 436
d5f25204
VM
437 if (*(pp - 1) != '/' && error == GIT_SUCCESS)
438 error = gitfo_mkdir(path, mode);
40c44d2f 439
d5f25204
VM
440 free(path_copy);
441 return error;
170d3f2f 442}
443
444static int retrieve_previous_path_component_start(const char *path)
445{
3644e98f 446 int offset, len, root_offset, start = 0;
447
448 root_offset = retrieve_path_root_offset(path);
449 if (root_offset > -1)
450 start += root_offset;
451
170d3f2f 452 len = strlen(path);
453 offset = len - 1;
40c44d2f 454
170d3f2f 455 /* Skip leading slash */
456 if (path[start] == '/')
457 start++;
458
459 /* Skip trailing slash */
460 if (path[offset] == '/')
461 offset--;
462
3644e98f 463 if (offset < root_offset)
170d3f2f 464 return GIT_ERROR;
465
466 while (offset > start && path[offset-1] != '/') {
467 offset--;
468 }
469
470 return offset;
40c44d2f 471}
170d3f2f 472
c90292ce 473int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path)
170d3f2f 474{
c90292ce 475 int len = 0, segment_len, only_dots, root_path_offset, error = GIT_SUCCESS;
4581c22a 476 char *current;
170d3f2f 477 const char *buffer_out_start, *buffer_end;
478
170d3f2f 479 current = (char *)path;
480 buffer_end = path + strlen(path);
c90292ce 481 buffer_out_start = buffer_out;
482
483 root_path_offset = retrieve_path_root_offset(path);
484 if (root_path_offset < 0) {
485 error = gitfo_getcwd(buffer_out, size);
486 if (error < GIT_SUCCESS)
487 return error;
488
489 len = strlen(buffer_out);
490 buffer_out += len;
491 }
170d3f2f 492
493 while (current < buffer_end) {
494 /* Prevent multiple slashes from being added to the output */
495 if (*current == '/' && len > 0 && buffer_out_start[len - 1] == '/') {
496 current++;
497 continue;
498 }
499
ae7ffea9 500 only_dots = 1;
4581c22a 501 segment_len = 0;
ae7ffea9 502
4581c22a 503 /* Copy path segment to the output */
c90292ce 504 while (current < buffer_end && *current != '/')
ae7ffea9 505 {
4581c22a 506 only_dots &= (*current == '.');
507 *buffer_out++ = *current++;
508 segment_len++;
509 len++;
ae7ffea9 510 }
e16c2f6a 511
ae7ffea9 512 /* Skip current directory */
4581c22a 513 if (only_dots && segment_len == 1)
ae7ffea9 514 {
4581c22a 515 current++;
516 buffer_out -= segment_len;
517 len -= segment_len;
ae7ffea9 518 continue;
519 }
40c44d2f 520
ae7ffea9 521 /* Handle the double-dot upward directory navigation */
4581c22a 522 if (only_dots && segment_len == 2)
ae7ffea9 523 {
4581c22a 524 current++;
525 buffer_out -= segment_len;
526
ae7ffea9 527 *buffer_out ='\0';
528 len = retrieve_previous_path_component_start(buffer_out_start);
c90292ce 529
530 /* Are we escaping out of the root dir? */
531 if (len < 0)
f2c24713 532 return GIT_EINVALIDPATH;
ae7ffea9 533
534 buffer_out = (char *)buffer_out_start + len;
ae7ffea9 535 continue;
536 }
170d3f2f 537
ae7ffea9 538 /* Guard against potential multiple dot path traversal (cf http://cwe.mitre.org/data/definitions/33.html) */
c90292ce 539 if (only_dots && segment_len > 0)
f2c24713 540 return GIT_EINVALIDPATH;
170d3f2f 541
ae7ffea9 542 *buffer_out++ = '/';
170d3f2f 543 len++;
544 }
545
170d3f2f 546 *buffer_out = '\0';
547
548 return GIT_SUCCESS;
40c44d2f 549}
618818dc 550
c90292ce 551int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path)
618818dc 552{
553 int error, path_len, i;
554 const char* pattern = "/..";
555
556 path_len = strlen(path);
557
c90292ce 558 /* Let's make sure the filename isn't empty nor a dot */
559 if (path_len == 0 || (path_len == 1 && *path == '.'))
560 return GIT_EINVALIDPATH;
561
618818dc 562 /* Let's make sure the filename doesn't end with "/", "/." or "/.." */
563 for (i = 1; path_len > i && i < 4; i++) {
564 if (!strncmp(path + path_len - i, pattern, i))
f2c24713 565 return GIT_EINVALIDPATH;
618818dc 566 }
567
c90292ce 568 error = gitfo_prettify_dir_path(buffer_out, size, path);
618818dc 569 if (error < GIT_SUCCESS)
570 return error;
571
572 path_len = strlen(buffer_out);
573 if (path_len < 2)
f2c24713 574 return GIT_EINVALIDPATH;
618818dc 575
576 /* Remove the trailing slash */
577 buffer_out[path_len - 1] = '\0';
578
579 return GIT_SUCCESS;
580}
ccef1c9d
VM
581
582int gitfo_cmp_path(const char *name1, int len1, int isdir1,
583 const char *name2, int len2, int isdir2)
584{
585 int len = len1 < len2 ? len1 : len2;
586 int cmp;
587
588 cmp = memcmp(name1, name2, len);
589 if (cmp)
590 return cmp;
591 if (len1 < len2)
592 return ((!isdir1 && !isdir2) ? -1 :
593 (isdir1 ? '/' - name2[len1] : name2[len1] - '/'));
594 if (len1 > len2)
595 return ((!isdir1 && !isdir2) ? 1 :
596 (isdir2 ? name1[len2] - '/' : '/' - name1[len2]));
597 return 0;
598}
599
677a3c07 600static void posixify_path(char *path)
601{
602 while (*path) {
603 if (*path == '\\')
604 *path = '/';
605
606 path++;
607 }
608}
609
610int gitfo_getcwd(char *buffer_out, size_t size)
611{
612 char *cwd_buffer;
613
614 assert(buffer_out && size > 0);
615
616#ifdef GIT_WIN32
617 cwd_buffer = _getcwd(buffer_out, size);
618#else
619 cwd_buffer = getcwd(buffer_out, size); //TODO: Fixme. Ensure the required headers are correctly included
620#endif
621
622 if (cwd_buffer == NULL)
623 return GIT_EOSERR;
624
625 posixify_path(buffer_out);
626
627 git__joinpath(buffer_out, buffer_out, ""); //Ensure the path ends with a trailing slash
628
629 return GIT_SUCCESS;
630}