libgit2/src/filebuf.c
Russell Belfer e1de726c15 Migrate ODB files to new error handling
This migrates odb.c, odb_loose.c, odb_pack.c and pack.c to
the new style of error handling.  Also got the unix and win32
versions of map.c.  There are some minor changes to other
files but no others were completely converted.

This also contains an update to filebuf so that a zeroed out
filebuf will not think that the fd (== 0) is actually open
(and inadvertently call close() on fd 0 if cleaned up).

Lastly, this was built and tested on win32 and contains a
bunch of fixes for the win32 build which was pretty broken.
2012-03-12 22:55:40 -07:00

445 lines
9.3 KiB
C

/*
* Copyright (C) 2009-2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include <stdarg.h>
#include "common.h"
#include "filebuf.h"
#include "fileops.h"
#define GIT_LOCK_FILE_MODE 0644
static const size_t WRITE_BUFFER_SIZE = (4096 * 2);
enum buferr_t {
BUFERR_OK = 0,
BUFERR_WRITE,
BUFERR_ZLIB,
BUFERR_MEM
};
#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; }
static int verify_last_error(git_filebuf *file)
{
switch (file->last_error) {
case BUFERR_WRITE:
giterr_set(GITERR_OS, "Failed to write out file");
return -1;
case BUFERR_MEM:
giterr_set_oom();
return -1;
case BUFERR_ZLIB:
giterr_set(GITERR_ZLIB,
"Buffer error when writing out ZLib data");
return -1;
default:
return 0;
}
}
static int lock_file(git_filebuf *file, int flags)
{
if (git_path_exists(file->path_lock) == true) {
if (flags & GIT_FILEBUF_FORCE)
p_unlink(file->path_lock);
else {
giterr_set(GITERR_OS,
"Failed to lock file '%s' for writing", file->path_lock);
return -1;
}
}
/* create path to the file buffer is required */
if (flags & GIT_FILEBUF_FORCE) {
/* XXX: Should dirmode here be configurable? Or is 0777 always fine? */
file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, GIT_LOCK_FILE_MODE);
} else {
file->fd = git_futils_creat_locked(file->path_lock, GIT_LOCK_FILE_MODE);
}
if (file->fd < 0)
return -1;
file->fd_is_open = true;
if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == true) {
git_file source;
char buffer[2048];
size_t read_bytes;
source = p_open(file->path_original, O_RDONLY);
if (source < 0) {
giterr_set(GITERR_OS,
"Failed to open file '%s' for reading",
file->path_original);
return -1;
}
while ((read_bytes = p_read(source, buffer, 2048)) > 0) {
p_write(file->fd, buffer, read_bytes);
if (file->digest)
git_hash_update(file->digest, buffer, read_bytes);
}
p_close(source);
}
return GIT_SUCCESS;
}
void git_filebuf_cleanup(git_filebuf *file)
{
if (file->fd_is_open && file->fd >= 0)
p_close(file->fd);
if (file->fd_is_open && file->path_lock && git_path_exists(file->path_lock))
p_unlink(file->path_lock);
if (file->digest)
git_hash_free_ctx(file->digest);
if (file->buffer)
git__free(file->buffer);
/* use the presence of z_buf to decide if we need to deflateEnd */
if (file->z_buf) {
git__free(file->z_buf);
deflateEnd(&file->zs);
}
if (file->path_original)
git__free(file->path_original);
if (file->path_lock)
git__free(file->path_lock);
memset(file, 0x0, sizeof(git_filebuf));
file->fd = -1;
}
GIT_INLINE(int) flush_buffer(git_filebuf *file)
{
int result = file->write(file, file->buffer, file->buf_pos);
file->buf_pos = 0;
return result;
}
static int write_normal(git_filebuf *file, void *source, size_t len)
{
if (len > 0) {
if (p_write(file->fd, (void *)source, len) < 0) {
file->last_error = BUFERR_WRITE;
return -1;
}
if (file->digest)
git_hash_update(file->digest, source, len);
}
return 0;
}
static int write_deflate(git_filebuf *file, void *source, size_t len)
{
z_stream *zs = &file->zs;
if (len > 0 || file->flush_mode == Z_FINISH) {
zs->next_in = source;
zs->avail_in = (uInt)len;
do {
size_t have;
zs->next_out = file->z_buf;
zs->avail_out = (uInt)file->buf_size;
if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) {
file->last_error = BUFERR_ZLIB;
return -1;
}
have = file->buf_size - (size_t)zs->avail_out;
if (p_write(file->fd, file->z_buf, have) < 0) {
file->last_error = BUFERR_WRITE;
return -1;
}
} while (zs->avail_out == 0);
assert(zs->avail_in == 0);
if (file->digest)
git_hash_update(file->digest, source, len);
}
return 0;
}
int git_filebuf_open(git_filebuf *file, const char *path, int flags)
{
int compression;
size_t path_len;
/* opening an already open buffer is a programming error;
* assert that this never happens instead of returning
* an error code */
assert(file && path && file->buffer == NULL);
memset(file, 0x0, sizeof(git_filebuf));
file->buf_size = WRITE_BUFFER_SIZE;
file->buf_pos = 0;
file->fd = -1;
file->last_error = BUFERR_OK;
/* Allocate the main cache buffer */
file->buffer = git__malloc(file->buf_size);
GITERR_CHECK_ALLOC(file->buffer);
/* If we are hashing on-write, allocate a new hash context */
if (flags & GIT_FILEBUF_HASH_CONTENTS) {
file->digest = git_hash_new_ctx();
GITERR_CHECK_ALLOC(file->digest);
}
compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT;
/* If we are deflating on-write, */
if (compression != 0) {
/* Initialize the ZLib stream */
if (deflateInit(&file->zs, compression) != Z_OK) {
giterr_set(GITERR_ZLIB, "Failed to initialize zlib");
goto cleanup;
}
/* Allocate the Zlib cache buffer */
file->z_buf = git__malloc(file->buf_size);
GITERR_CHECK_ALLOC(file->z_buf);
/* Never flush */
file->flush_mode = Z_NO_FLUSH;
file->write = &write_deflate;
} else {
file->write = &write_normal;
}
/* If we are writing to a temp file */
if (flags & GIT_FILEBUF_TEMPORARY) {
git_buf tmp_path = GIT_BUF_INIT;
/* Open the file as temporary for locking */
file->fd = git_futils_mktmp(&tmp_path, path);
if (file->fd < 0) {
git_buf_free(&tmp_path);
goto cleanup;
}
file->fd_is_open = true;
/* No original path */
file->path_original = NULL;
file->path_lock = git_buf_detach(&tmp_path);
GITERR_CHECK_ALLOC(file->path_lock);
} else {
path_len = strlen(path);
/* Save the original path of the file */
file->path_original = git__strdup(path);
GITERR_CHECK_ALLOC(file->path_original);
/* create the locking path by appending ".lock" to the original */
file->path_lock = git__malloc(path_len + GIT_FILELOCK_EXTLENGTH);
GITERR_CHECK_ALLOC(file->path_lock);
memcpy(file->path_lock, file->path_original, path_len);
memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH);
/* open the file for locking */
if (lock_file(file, flags) < 0)
goto cleanup;
}
return 0;
cleanup:
git_filebuf_cleanup(file);
return -1;
}
int git_filebuf_hash(git_oid *oid, git_filebuf *file)
{
assert(oid && file && file->digest);
flush_buffer(file);
if (verify_last_error(file) < 0)
return -1;
git_hash_final(oid, file->digest);
git_hash_free_ctx(file->digest);
file->digest = NULL;
return 0;
}
int git_filebuf_commit_at(git_filebuf *file, const char *path, mode_t mode)
{
git__free(file->path_original);
file->path_original = git__strdup(path);
GITERR_CHECK_ALLOC(file->path_original);
return git_filebuf_commit(file, mode);
}
int git_filebuf_commit(git_filebuf *file, mode_t mode)
{
/* temporary files cannot be committed */
assert(file && file->path_original);
file->flush_mode = Z_FINISH;
flush_buffer(file);
if (verify_last_error(file) < 0)
goto on_error;
p_close(file->fd);
file->fd = -1;
file->fd_is_open = false;
if (p_chmod(file->path_lock, mode)) {
giterr_set(GITERR_OS, "Failed to set attributes for file at '%s'", file->path_lock);
goto on_error;
}
p_unlink(file->path_original);
if (p_rename(file->path_lock, file->path_original) < 0) {
giterr_set(GITERR_OS, "Failed to rename lockfile to '%s'", file->path_original);
goto on_error;
}
git_filebuf_cleanup(file);
return 0;
on_error:
git_filebuf_cleanup(file);
return -1;
}
GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len)
{
memcpy(file->buffer + file->buf_pos, buf, len);
file->buf_pos += len;
}
int git_filebuf_write(git_filebuf *file, const void *buff, size_t len)
{
const unsigned char *buf = buff;
ENSURE_BUF_OK(file);
for (;;) {
size_t space_left = file->buf_size - file->buf_pos;
/* cache if it's small */
if (space_left > len) {
add_to_cache(file, buf, len);
return 0;
}
add_to_cache(file, buf, space_left);
if (flush_buffer(file) < 0)
return -1;
len -= space_left;
buf += space_left;
}
}
int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len)
{
size_t space_left = file->buf_size - file->buf_pos;
*buffer = NULL;
ENSURE_BUF_OK(file);
if (len > file->buf_size) {
file->last_error = BUFERR_MEM;
return -1;
}
if (space_left <= len) {
if (flush_buffer(file) < 0)
return -1;
}
*buffer = (file->buffer + file->buf_pos);
file->buf_pos += len;
return 0;
}
int git_filebuf_printf(git_filebuf *file, const char *format, ...)
{
va_list arglist;
size_t space_left;
int len, res;
char *tmp_buffer;
ENSURE_BUF_OK(file);
space_left = file->buf_size - file->buf_pos;
do {
va_start(arglist, format);
len = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist);
va_end(arglist);
if (len < 0) {
file->last_error = BUFERR_MEM;
return -1;
}
if ((size_t)len + 1 <= space_left) {
file->buf_pos += len;
return 0;
}
if (flush_buffer(file) < 0)
return -1;
space_left = file->buf_size - file->buf_pos;
} while ((size_t)len + 1 <= space_left);
tmp_buffer = git__malloc(len + 1);
if (!tmp_buffer) {
file->last_error = BUFERR_MEM;
return -1;
}
va_start(arglist, format);
len = p_vsnprintf(tmp_buffer, len + 1, format, arglist);
va_end(arglist);
if (len < 0) {
git__free(tmp_buffer);
file->last_error = BUFERR_MEM;
return -1;
}
res = git_filebuf_write(file, tmp_buffer, len);
git__free(tmp_buffer);
return res;
}