#include "common.h" #include "fileops.h" #include int git_futils_mv_atomic(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 | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR; #else /* Don't even try this on Win32 */ if (!link(from, to)) { p_unlink(from); return GIT_SUCCESS; } if (!rename(from, to)) return GIT_SUCCESS; return GIT_ERROR; #endif } int git_futils_mkpath2file(const char *file_path) { const int mode = 0755; /* or 0777 ? */ int error = GIT_SUCCESS; char target_folder_path[GIT_PATH_MAX]; error = git_path_dirname_r(target_folder_path, sizeof(target_folder_path), file_path); if (error < GIT_SUCCESS) return git__throw(GIT_EINVALIDPATH, "Failed to recursively build `%s` tree structure. Unable to parse parent folder name", file_path); /* Does the containing folder exist? */ if (git_futils_isdir(target_folder_path)) { git_path_join(target_folder_path, target_folder_path, ""); /* Ensure there's a trailing slash */ /* Let's create the tree structure */ error = git_futils_mkdir_r(target_folder_path, mode); if (error < GIT_SUCCESS) return error; /* The callee already takes care of setting the correct error message. */ } return GIT_SUCCESS; } int git_futils_mktmp(char *path_out, const char *filename) { int fd; strcpy(path_out, filename); strcat(path_out, "_git2_XXXXXX"); #if defined(_MSC_VER) /* FIXME: there may be race conditions when multi-threading * with the library */ if (_mktemp_s(path_out, GIT_PATH_MAX) != 0) return git__throw(GIT_EOSERR, "Failed to make temporary file %s", path_out); fd = p_creat(path_out, 0744); #else fd = mkstemp(path_out); #endif return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create temporary file %s", path_out); } int git_futils_creat_withpath(const char *path, int mode) { if (git_futils_mkpath2file(path) < GIT_SUCCESS) return git__throw(GIT_EOSERR, "Failed to create file %s", path); return p_creat(path, mode); } int git_futils_creat_locked(const char *path, int mode) { int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create locked file. Could not open %s", path); } int git_futils_creat_locked_withpath(const char *path, int mode) { if (git_futils_mkpath2file(path) < GIT_SUCCESS) return git__throw(GIT_EOSERR, "Failed to create locked file %s", path); return git_futils_creat_locked(path, mode); } int git_futils_isdir(const char *path) { struct stat st; int len, stat_error; assert(path); 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 = p_stat(path_fixed, &st); free(path_fixed); } else { stat_error = p_stat(path, &st); } if (stat_error < GIT_SUCCESS) return GIT_ERROR; if (!S_ISDIR(st.st_mode)) return GIT_ERROR; return GIT_SUCCESS; } int git_futils_isfile(const char *path) { struct stat st; int stat_error; if (!path) return git__throw(GIT_ENOTFOUND, "No path given to git_futils_isfile"); stat_error = p_stat(path, &st); if (stat_error < GIT_SUCCESS) return git__throw(GIT_ENOTFOUND, "%s does not exist", path); if (!S_ISREG(st.st_mode)) return git__throw(GIT_ENOTFOUND, "%s is not a file", path); return GIT_SUCCESS; } int git_futils_exists(const char *path) { assert(path); return access(path, F_OK); } git_off_t git_futils_filesize(git_file fd) { struct stat sb; if (p_fstat(fd, &sb)) return git__throw(GIT_EOSERR, "Failed to get size of file. File missing or corrupted"); return sb.st_size; } int git_futils_readbuffer(git_fbuffer *obj, const char *path) { git_file fd; size_t len; git_off_t size; unsigned char *buff; assert(obj && path && *path); if ((fd = p_open(path, O_RDONLY)) < 0) return git__throw(GIT_ERROR, "Failed to open %s for reading", path); if (((size = git_futils_filesize(fd)) < 0) || !git__is_sizet(size+1)) { p_close(fd); return git__throw(GIT_ERROR, "Failed to read file `%s`. An error occured while calculating its size", path); } len = (size_t) size; if ((buff = git__malloc(len + 1)) == NULL) { p_close(fd); return GIT_ENOMEM; } if (p_read(fd, buff, len) < 0) { p_close(fd); free(buff); return git__throw(GIT_ERROR, "Failed to read file `%s`", path); } buff[len] = '\0'; p_close(fd); obj->data = buff; obj->len = len; return GIT_SUCCESS; } void git_futils_freebuffer(git_fbuffer *obj) { assert(obj); free(obj->data); obj->data = NULL; } int git_futils_mv_withpath(const char *from, const char *to) { if (git_futils_mkpath2file(to) < GIT_SUCCESS) return GIT_EOSERR; /* The callee already takes care of setting the correct error message. */ return git_futils_mv_atomic(from, to); /* The callee already takes care of setting the correct error message. */ } int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len) { return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin); } void git_futils_mmap_free(git_map *out) { p_munmap(out); } int git_futils_direach( 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__throw(GIT_EINVALIDARGS, "Failed to process `%s` tree structure. Path is either empty or buffer size is too short", path); while (path[wd_len - 1] == '/') wd_len--; path[wd_len++] = '/'; path[wd_len] = '\0'; dir = opendir(path); if (!dir) return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure. An error occured while opening the directory", path); 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__throw(GIT_ERROR, "Failed to process `%s` tree structure. Buffer size is too short", path); } strcpy(path + wd_len, de->d_name); result = fn(arg, path); if (result < GIT_SUCCESS) { closedir(dir); return result; /* The callee is reponsible for setting the correct error message */ } if (result > 0) { closedir(dir); return result; } } closedir(dir); return GIT_SUCCESS; } int git_futils_root_offset(const char *path) { int offset = 0; #ifdef GIT_WIN32 /* Does the root of the path look like a windows drive ? */ if (isalpha(path[0]) && (path[1] == ':')) offset += 2; #endif if (*(path + offset) == '/') return offset; return -1; /* Not a real error. Rather a signal than the path is not rooted */ } int git_futils_mkdir_r(const char *path, int mode) { int error, root_path_offset; char *pp, *sp; char *path_copy = git__strdup(path); if (path_copy == NULL) return GIT_ENOMEM; error = GIT_SUCCESS; pp = path_copy; root_path_offset = git_futils_root_offset(pp); if (root_path_offset > 0) pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */ while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != NULL) { if (sp != pp && git_futils_isdir(path_copy) < GIT_SUCCESS) { *sp = 0; error = p_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 != '\0' && error == GIT_SUCCESS) { error = p_mkdir(path, mode); if (errno == EEXIST) error = GIT_SUCCESS; } free(path_copy); if (error < GIT_SUCCESS) return git__throw(error, "Failed to recursively create `%s` tree structure", path); return GIT_SUCCESS; } static int retrieve_previous_path_component_start(const char *path) { int offset, len, root_offset, start = 0; root_offset = git_futils_root_offset(path); if (root_offset > -1) start += root_offset; len = strlen(path); offset = len - 1; /* Skip leading slash */ if (path[start] == '/') start++; /* Skip trailing slash */ if (path[offset] == '/') offset--; if (offset < root_offset) return git__throw(GIT_ERROR, "Failed to retrieve path component. Wrong offset"); while (offset > start && path[offset-1] != '/') { offset--; } return offset; } int git_futils_prettify_dir(char *buffer_out, size_t size, const char *path, const char *base_path) { int len = 0, segment_len, only_dots, root_path_offset, error = GIT_SUCCESS; char *current; const char *buffer_out_start, *buffer_end; current = (char *)path; buffer_end = path + strlen(path); buffer_out_start = buffer_out; root_path_offset = git_futils_root_offset(path); if (root_path_offset < 0) { if (base_path == NULL) { error = p_getcwd(buffer_out, size); if (error < GIT_SUCCESS) return error; /* The callee already takes care of setting the correct error message. */ } else { if (size < (strlen(base_path) + 1) * sizeof(char)) return git__throw(GIT_EOVERFLOW, "Failed to prettify dir path: the base path is too long for the buffer."); strcpy(buffer_out, base_path); git_path_mkposix(buffer_out); git_path_join(buffer_out, buffer_out, ""); } len = strlen(buffer_out); buffer_out += len; } 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); /* Are we escaping out of the root dir? */ if (len < 0) return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path escapes out of the root directory", path); 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__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path contains a segment with three `.` or more", path); *buffer_out++ = '/'; len++; } *buffer_out = '\0'; return GIT_SUCCESS; } int git_futils_prettyify_file(char *buffer_out, size_t size, const char *path, const char *base_path) { int error, path_len, i, root_offset; const char* pattern = "/.."; path_len = strlen(path); /* Let's make sure the filename isn't empty nor a dot */ if (path_len == 0 || (path_len == 1 && *path == '.')) return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path is either empty or equals `.`", 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__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); } error = git_futils_prettify_dir(buffer_out, size, path, base_path); if (error < GIT_SUCCESS) return error; /* The callee already takes care of setting the correct error message. */ path_len = strlen(buffer_out); root_offset = git_futils_root_offset(buffer_out) + 1; if (path_len == root_offset) return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); /* Remove the trailing slash */ buffer_out[path_len - 1] = '\0'; return GIT_SUCCESS; } int git_futils_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; }