libgit2/src/fileops.c
nulltoken 170d3f2fbb Added git_prettify_dir_path().
Clean up a provided absolute or relative directory path.

This prettification relies on basic operations such as coalescing multiple forward slashes into a single slash, removing '.' and './' current directory segments, and removing parent directory whenever '..' is encountered. If not empty, the returned path ends with a forward slash.

For instance, this will turn "d1/s1///s2/..//../s3" into "d1/s3/".

This only performs a string based analysis of the path. No checks are done to make sure the path actually makes sense from the file system perspective.
2011-01-11 20:12:53 +01:00

445 lines
7.7 KiB
C

#include "common.h"
#include "fileops.h"
#include <ctype.h>
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_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);
}
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;
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_move_file(char *from, char *to)
{
if (!link(from, to)) {
gitfo_unlink(from);
return GIT_SUCCESS;
}
if (!rename(from, to))
return GIT_SUCCESS;
return GIT_EOSERR;
}
int gitfo_map_ro(git_map *out, git_file fd, 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 error = GIT_SUCCESS;
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 git_prettify_dir_path(char *buffer_out, const char *path)
{
int len = 0;
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;
}
/* Skip current directory */
if (*current == '.') {
current++;
/* Handle the double-dot upward directory navigation */
if (*current == '.') {
current++;
*buffer_out ='\0';
len = retrieve_previous_path_component_start(buffer_out_start);
if (len < GIT_SUCCESS)
return GIT_ERROR;
buffer_out = (char *)buffer_out_start + len;
}
if (*current == '/')
current++;
continue;
}
*buffer_out++ = *current++;
len++;
}
/* Add a trailing slash if required */
if (len > 0 && buffer_out_start[len-1] != '/')
*buffer_out++ = '/';
*buffer_out = '\0';
return GIT_SUCCESS;
}