#include "common.h" #include "fileops.h" #include static int force_path(const char *to) { const int mode = 0755; /* or 0777 ? */ int error = GIT_SUCCESS; char target_folder_path[GIT_PATH_MAX]; error = git__dirname_r(target_folder_path, sizeof(target_folder_path), to); if (error < GIT_SUCCESS) return error; /* Does the containing folder exist? */ if (gitfo_isdir(target_folder_path)) { git__joinpath(target_folder_path, target_folder_path, ""); /* Ensure there's a trailing slash */ /* Let's create the tree structure */ error = gitfo_mkdir_recurs(target_folder_path, mode); if (error < GIT_SUCCESS) return error; } return GIT_SUCCESS; } int gitfo_open(const char *path, int flags) { int fd = open(path, flags | O_BINARY); return fd >= 0 ? fd : GIT_EOSERR; } int gitfo_creat(const char *path, int mode) { int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode); return fd >= 0 ? fd : GIT_EOSERR; } int gitfo_creat_force(const char *path, int mode) { if (force_path(path) < GIT_SUCCESS) return GIT_EOSERR; return gitfo_creat(path, mode); } int gitfo_read(git_file fd, void *buf, size_t cnt) { char *b = buf; while (cnt) { ssize_t r = read(fd, b, cnt); if (r < 0) { if (errno == EINTR || errno == EAGAIN) continue; return GIT_EOSERR; } if (!r) { errno = EPIPE; return GIT_EOSERR; } cnt -= r; b += r; } return GIT_SUCCESS; } int gitfo_write(git_file fd, void *buf, size_t cnt) { char *b = buf; while (cnt) { ssize_t r = write(fd, b, cnt); if (r < 0) { if (errno == EINTR || errno == EAGAIN) continue; return GIT_EOSERR; } if (!r) { errno = EPIPE; return GIT_EOSERR; } cnt -= r; b += r; } return GIT_SUCCESS; } int gitfo_isdir(const char *path) { struct stat st; int len, stat_error; if (!path) return GIT_ENOTFOUND; len = strlen(path); /* win32: stat path for folders cannot end in a slash */ if (path[len - 1] == '/') { char *path_fixed = NULL; path_fixed = git__strdup(path); path_fixed[len - 1] = 0; stat_error = gitfo_stat(path_fixed, &st); free(path_fixed); } else { stat_error = gitfo_stat(path, &st); } if (stat_error < GIT_SUCCESS) return GIT_ENOTFOUND; if (!S_ISDIR(st.st_mode)) return GIT_ENOTFOUND; return GIT_SUCCESS; } int gitfo_exists(const char *path) { return access(path, F_OK); } git_off_t gitfo_size(git_file fd) { struct stat sb; if (gitfo_fstat(fd, &sb)) return GIT_EOSERR; return sb.st_size; } int gitfo_read_file(gitfo_buf *obj, const char *path) { git_file fd; size_t len; git_off_t size; unsigned char *buff; assert(obj && path && *path); if ((fd = gitfo_open(path, O_RDONLY)) < 0) return GIT_ERROR; if (((size = gitfo_size(fd)) < 0) || !git__is_sizet(size+1)) { gitfo_close(fd); return GIT_ERROR; } len = (size_t) size; if ((buff = git__malloc(len + 1)) == NULL) { gitfo_close(fd); return GIT_ERROR; } if (gitfo_read(fd, buff, len) < 0) { gitfo_close(fd); free(buff); return GIT_ERROR; } buff[len] = '\0'; gitfo_close(fd); obj->data = buff; obj->len = len; return GIT_SUCCESS; } void gitfo_free_buf(gitfo_buf *obj) { assert(obj); free(obj->data); obj->data = NULL; } int gitfo_mv(const char *from, const char *to) { #ifdef GIT_WIN32 /* * Win32 POSIX compilance my ass. If the destination * file exists, the `rename` call fails. This is as * close as it gets with the Win32 API. */ return MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING) ? GIT_SUCCESS : GIT_EOSERR; #else /* Don't even try this on Win32 */ if (!link(from, to)) { gitfo_unlink(from); return GIT_SUCCESS; } if (!rename(from, to)) return GIT_SUCCESS; return GIT_EOSERR; #endif } int gitfo_mv_force(const char *from, const char *to) { if (force_path(to) < GIT_SUCCESS) return GIT_EOSERR; return gitfo_mv(from, to); } int gitfo_map_ro(git_map *out, git_file fd, git_off_t begin, size_t len) { if (git__mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin) < GIT_SUCCESS) return GIT_EOSERR; return GIT_SUCCESS; } void gitfo_free_map(git_map *out) { git__munmap(out); } /* cached diskio */ struct gitfo_cache { git_file fd; size_t cache_size, pos; unsigned char *cache; }; gitfo_cache *gitfo_enable_caching(git_file fd, size_t cache_size) { gitfo_cache *ioc; ioc = git__malloc(sizeof(*ioc)); if (!ioc) return NULL; ioc->fd = fd; ioc->pos = 0; ioc->cache_size = cache_size; ioc->cache = git__malloc(cache_size); if (!ioc->cache) { free(ioc); return NULL; } return ioc; } GIT_INLINE(void) gitfo_add_to_cache(gitfo_cache *ioc, void *buf, size_t len) { memcpy(ioc->cache + ioc->pos, buf, len); ioc->pos += len; } int gitfo_flush_cached(gitfo_cache *ioc) { int result = GIT_SUCCESS; if (ioc->pos) { result = gitfo_write(ioc->fd, ioc->cache, ioc->pos); ioc->pos = 0; } return result; } int gitfo_write_cached(gitfo_cache *ioc, void *buff, size_t len) { unsigned char *buf = buff; for (;;) { size_t space_left = ioc->cache_size - ioc->pos; /* cache if it's small */ if (space_left > len) { gitfo_add_to_cache(ioc, buf, len); return GIT_SUCCESS; } /* flush the cache if it doesn't fit */ if (ioc->pos) { int rc; gitfo_add_to_cache(ioc, buf, space_left); rc = gitfo_flush_cached(ioc); if (rc < 0) return rc; len -= space_left; buf += space_left; } /* write too-large chunks immediately */ if (len > ioc->cache_size) return gitfo_write(ioc->fd, buf, len); } } int gitfo_close_cached(gitfo_cache *ioc) { git_file fd; if (gitfo_flush_cached(ioc) < GIT_SUCCESS) return GIT_ERROR; fd = ioc->fd; free(ioc->cache); free(ioc); return gitfo_close(fd); } int gitfo_dirent( char *path, size_t path_sz, int (*fn)(void *, char *), void *arg) { size_t wd_len = strlen(path); DIR *dir; struct dirent *de; if (!wd_len || path_sz < wd_len + 2) return GIT_ERROR; while (path[wd_len - 1] == '/') wd_len--; path[wd_len++] = '/'; path[wd_len] = '\0'; dir = opendir(path); if (!dir) return GIT_EOSERR; while ((de = readdir(dir)) != NULL) { size_t de_len; int result; /* always skip '.' and '..' */ if (de->d_name[0] == '.') { if (de->d_name[1] == '\0') continue; if (de->d_name[1] == '.' && de->d_name[2] == '\0') continue; } de_len = strlen(de->d_name); if (path_sz < wd_len + de_len + 1) { closedir(dir); return GIT_ERROR; } strcpy(path + wd_len, de->d_name); result = fn(arg, path); if (result < GIT_SUCCESS) { closedir(dir); return result; } if (result > 0) { closedir(dir); return result; } } closedir(dir); return GIT_SUCCESS; } #ifdef GIT_WIN32 static int is_windows_rooted_path(const char *path) { /* Does the root of the path look like a windows drive ? */ if (isalpha(path[0]) && (path[1] == ':')) return GIT_SUCCESS; return GIT_ERROR; } #endif int gitfo_mkdir_recurs(const char *path, int mode) { int error; char *pp, *sp; char *path_copy = git__strdup(path); if (path_copy == NULL) return GIT_ENOMEM; error = GIT_SUCCESS; pp = path_copy; #ifdef GIT_WIN32 if (!is_windows_rooted_path(pp)) pp += 2; /* Skip the drive name (eg. C: or D:) */ #endif while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != 0) { if (sp != pp && gitfo_isdir(path_copy) < GIT_SUCCESS) { *sp = 0; error = gitfo_mkdir(path_copy, mode); /* Do not choke while trying to recreate an existing directory */ if (errno == EEXIST) error = GIT_SUCCESS; *sp = '/'; } pp = sp + 1; } if (*(pp - 1) != '/' && error == GIT_SUCCESS) error = gitfo_mkdir(path, mode); free(path_copy); return error; } static int retrieve_previous_path_component_start(const char *path) { int offset, len, start = 0; len = strlen(path); offset = len - 1; /* Skip leading slash */ if (path[start] == '/') start++; /* Skip trailing slash */ if (path[offset] == '/') offset--; if (offset < 0) return GIT_ERROR; while (offset > start && path[offset-1] != '/') { offset--; } return offset; } int gitfo_prettify_dir_path(char *buffer_out, const char *path) { int len = 0, segment_len, only_dots; char *current; const char *buffer_out_start, *buffer_end; buffer_out_start = buffer_out; current = (char *)path; buffer_end = path + strlen(path); while (current < buffer_end) { /* Prevent multiple slashes from being added to the output */ if (*current == '/' && len > 0 && buffer_out_start[len - 1] == '/') { current++; continue; } only_dots = 1; segment_len = 0; /* Copy path segment to the output */ while (current < buffer_end && *current !='/') { only_dots &= (*current == '.'); *buffer_out++ = *current++; segment_len++; len++; } /* Skip current directory */ if (only_dots && segment_len == 1) { current++; buffer_out -= segment_len; len -= segment_len; continue; } /* Handle the double-dot upward directory navigation */ if (only_dots && segment_len == 2) { current++; buffer_out -= segment_len; *buffer_out ='\0'; len = retrieve_previous_path_component_start(buffer_out_start); if (len < GIT_SUCCESS) return GIT_EINVALIDPATH; buffer_out = (char *)buffer_out_start + len; continue; } /* Guard against potential multiple dot path traversal (cf http://cwe.mitre.org/data/definitions/33.html) */ if (only_dots &&segment_len > 0) return GIT_EINVALIDPATH; *buffer_out++ = '/'; len++; } *buffer_out = '\0'; return GIT_SUCCESS; } int gitfo_prettify_file_path(char *buffer_out, const char *path) { int error, path_len, i; const char* pattern = "/.."; path_len = strlen(path); /* Let's make sure the filename doesn't end with "/", "/." or "/.." */ for (i = 1; path_len > i && i < 4; i++) { if (!strncmp(path + path_len - i, pattern, i)) return GIT_EINVALIDPATH; } error = gitfo_prettify_dir_path(buffer_out, path); if (error < GIT_SUCCESS) return error; path_len = strlen(buffer_out); if (path_len < 2) return GIT_EINVALIDPATH; /* Remove the trailing slash */ buffer_out[path_len - 1] = '\0'; return GIT_SUCCESS; } int gitfo_cmp_path(const char *name1, int len1, int isdir1, const char *name2, int len2, int isdir2) { int len = len1 < len2 ? len1 : len2; int cmp; cmp = memcmp(name1, name2, len); if (cmp) return cmp; if (len1 < len2) return ((!isdir1 && !isdir2) ? -1 : (isdir1 ? '/' - name2[len1] : name2[len1] - '/')); if (len1 > len2) return ((!isdir1 && !isdir2) ? 1 : (isdir2 ? name1[len2] - '/' : '/' - name1[len2])); return 0; }