2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
12 static const size_t WRITE_BUFFER_SIZE
= (4096 * 2);
21 #define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; }
23 static int verify_last_error(git_filebuf
*file
)
25 switch (file
->last_error
) {
27 git_error_set(GIT_ERROR_OS
, "failed to write out file");
35 git_error_set(GIT_ERROR_ZLIB
,
36 "Buffer error when writing out ZLib data");
44 static int lock_file(git_filebuf
*file
, int flags
, mode_t mode
)
46 if (git_path_exists(file
->path_lock
) == true) {
47 git_error_clear(); /* actual OS error code just confuses */
48 git_error_set(GIT_ERROR_OS
,
49 "failed to lock file '%s' for writing", file
->path_lock
);
53 /* create path to the file buffer is required */
54 if (flags
& GIT_FILEBUF_CREATE_LEADING_DIRS
) {
55 /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */
56 file
->fd
= git_futils_creat_locked_withpath(file
->path_lock
, 0777, mode
);
58 file
->fd
= git_futils_creat_locked(file
->path_lock
, mode
);
64 file
->fd_is_open
= true;
66 if ((flags
& GIT_FILEBUF_APPEND
) && git_path_exists(file
->path_original
) == true) {
68 char buffer
[FILEIO_BUFSIZE
];
72 source
= p_open(file
->path_original
, O_RDONLY
);
74 git_error_set(GIT_ERROR_OS
,
75 "failed to open file '%s' for reading",
80 while ((read_bytes
= p_read(source
, buffer
, sizeof(buffer
))) > 0) {
81 if ((error
= p_write(file
->fd
, buffer
, read_bytes
)) < 0)
83 if (file
->compute_digest
)
84 git_hash_update(&file
->digest
, buffer
, read_bytes
);
90 git_error_set(GIT_ERROR_OS
, "failed to read file '%s'", file
->path_original
);
92 } else if (error
< 0) {
93 git_error_set(GIT_ERROR_OS
, "failed to write file '%s'", file
->path_lock
);
101 void git_filebuf_cleanup(git_filebuf
*file
)
103 if (file
->fd_is_open
&& file
->fd
>= 0)
106 if (file
->created_lock
&& !file
->did_rename
&& file
->path_lock
&& git_path_exists(file
->path_lock
))
107 p_unlink(file
->path_lock
);
109 if (file
->compute_digest
) {
110 git_hash_ctx_cleanup(&file
->digest
);
111 file
->compute_digest
= 0;
115 git__free(file
->buffer
);
117 /* use the presence of z_buf to decide if we need to deflateEnd */
119 git__free(file
->z_buf
);
120 deflateEnd(&file
->zs
);
123 if (file
->path_original
)
124 git__free(file
->path_original
);
126 git__free(file
->path_lock
);
128 memset(file
, 0x0, sizeof(git_filebuf
));
132 GIT_INLINE(int) flush_buffer(git_filebuf
*file
)
134 int result
= file
->write(file
, file
->buffer
, file
->buf_pos
);
139 int git_filebuf_flush(git_filebuf
*file
)
141 return flush_buffer(file
);
144 static int write_normal(git_filebuf
*file
, void *source
, size_t len
)
147 if (p_write(file
->fd
, (void *)source
, len
) < 0) {
148 file
->last_error
= BUFERR_WRITE
;
152 if (file
->compute_digest
)
153 git_hash_update(&file
->digest
, source
, len
);
159 static int write_deflate(git_filebuf
*file
, void *source
, size_t len
)
161 z_stream
*zs
= &file
->zs
;
163 if (len
> 0 || file
->flush_mode
== Z_FINISH
) {
164 zs
->next_in
= source
;
165 zs
->avail_in
= (uInt
)len
;
170 zs
->next_out
= file
->z_buf
;
171 zs
->avail_out
= (uInt
)file
->buf_size
;
173 if (deflate(zs
, file
->flush_mode
) == Z_STREAM_ERROR
) {
174 file
->last_error
= BUFERR_ZLIB
;
178 have
= file
->buf_size
- (size_t)zs
->avail_out
;
180 if (p_write(file
->fd
, file
->z_buf
, have
) < 0) {
181 file
->last_error
= BUFERR_WRITE
;
185 } while (zs
->avail_out
== 0);
187 GIT_ASSERT(zs
->avail_in
== 0);
189 if (file
->compute_digest
)
190 git_hash_update(&file
->digest
, source
, len
);
196 #define MAX_SYMLINK_DEPTH 5
198 static int resolve_symlink(git_buf
*out
, const char *path
)
203 git_buf curpath
= GIT_BUF_INIT
, target
= GIT_BUF_INIT
;
205 if ((error
= git_buf_grow(&target
, GIT_PATH_MAX
+ 1)) < 0 ||
206 (error
= git_buf_puts(&curpath
, path
)) < 0)
209 for (i
= 0; i
< MAX_SYMLINK_DEPTH
; i
++) {
210 error
= p_lstat(curpath
.ptr
, &st
);
211 if (error
< 0 && errno
== ENOENT
) {
212 error
= git_buf_puts(out
, curpath
.ptr
);
217 git_error_set(GIT_ERROR_OS
, "failed to stat '%s'", curpath
.ptr
);
222 if (!S_ISLNK(st
.st_mode
)) {
223 error
= git_buf_puts(out
, curpath
.ptr
);
227 ret
= p_readlink(curpath
.ptr
, target
.ptr
, GIT_PATH_MAX
);
229 git_error_set(GIT_ERROR_OS
, "failed to read symlink '%s'", curpath
.ptr
);
234 if (ret
== GIT_PATH_MAX
) {
235 git_error_set(GIT_ERROR_INVALID
, "symlink target too long");
240 /* readlink(2) won't NUL-terminate for us */
241 target
.ptr
[ret
] = '\0';
244 root
= git_path_root(target
.ptr
);
246 if ((error
= git_buf_sets(&curpath
, target
.ptr
)) < 0)
249 git_buf dir
= GIT_BUF_INIT
;
251 if ((error
= git_path_dirname_r(&dir
, curpath
.ptr
)) < 0)
254 git_buf_swap(&curpath
, &dir
);
255 git_buf_dispose(&dir
);
257 if ((error
= git_path_apply_relative(&curpath
, target
.ptr
)) < 0)
262 git_error_set(GIT_ERROR_INVALID
, "maximum symlink depth reached");
266 git_buf_dispose(&curpath
);
267 git_buf_dispose(&target
);
271 int git_filebuf_open(git_filebuf
*file
, const char *path
, int flags
, mode_t mode
)
273 return git_filebuf_open_withsize(file
, path
, flags
, mode
, WRITE_BUFFER_SIZE
);
276 int git_filebuf_open_withsize(git_filebuf
*file
, const char *path
, int flags
, mode_t mode
, size_t size
)
278 int compression
, error
= -1;
279 size_t path_len
, alloc_len
;
281 GIT_ASSERT_ARG(file
);
282 GIT_ASSERT_ARG(path
);
283 GIT_ASSERT(file
->buffer
== NULL
);
285 memset(file
, 0x0, sizeof(git_filebuf
));
287 if (flags
& GIT_FILEBUF_DO_NOT_BUFFER
)
288 file
->do_not_buffer
= true;
290 if (flags
& GIT_FILEBUF_FSYNC
)
291 file
->do_fsync
= true;
293 file
->buf_size
= size
;
296 file
->last_error
= BUFERR_OK
;
298 /* Allocate the main cache buffer */
299 if (!file
->do_not_buffer
) {
300 file
->buffer
= git__malloc(file
->buf_size
);
301 GIT_ERROR_CHECK_ALLOC(file
->buffer
);
304 /* If we are hashing on-write, allocate a new hash context */
305 if (flags
& GIT_FILEBUF_HASH_CONTENTS
) {
306 file
->compute_digest
= 1;
308 if (git_hash_ctx_init(&file
->digest
) < 0)
312 compression
= flags
>> GIT_FILEBUF_DEFLATE_SHIFT
;
314 /* If we are deflating on-write, */
315 if (compression
!= 0) {
316 /* Initialize the ZLib stream */
317 if (deflateInit(&file
->zs
, compression
) != Z_OK
) {
318 git_error_set(GIT_ERROR_ZLIB
, "failed to initialize zlib");
322 /* Allocate the Zlib cache buffer */
323 file
->z_buf
= git__malloc(file
->buf_size
);
324 GIT_ERROR_CHECK_ALLOC(file
->z_buf
);
327 file
->flush_mode
= Z_NO_FLUSH
;
328 file
->write
= &write_deflate
;
330 file
->write
= &write_normal
;
333 /* If we are writing to a temp file */
334 if (flags
& GIT_FILEBUF_TEMPORARY
) {
335 git_buf tmp_path
= GIT_BUF_INIT
;
337 /* Open the file as temporary for locking */
338 file
->fd
= git_futils_mktmp(&tmp_path
, path
, mode
);
341 git_buf_dispose(&tmp_path
);
344 file
->fd_is_open
= true;
345 file
->created_lock
= true;
347 /* No original path */
348 file
->path_original
= NULL
;
349 file
->path_lock
= git_buf_detach(&tmp_path
);
350 GIT_ERROR_CHECK_ALLOC(file
->path_lock
);
352 git_buf resolved_path
= GIT_BUF_INIT
;
354 if ((error
= resolve_symlink(&resolved_path
, path
)) < 0)
357 /* Save the original path of the file */
358 path_len
= resolved_path
.size
;
359 file
->path_original
= git_buf_detach(&resolved_path
);
361 /* create the locking path by appending ".lock" to the original */
362 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len
, path_len
, GIT_FILELOCK_EXTLENGTH
);
363 file
->path_lock
= git__malloc(alloc_len
);
364 GIT_ERROR_CHECK_ALLOC(file
->path_lock
);
366 memcpy(file
->path_lock
, file
->path_original
, path_len
);
367 memcpy(file
->path_lock
+ path_len
, GIT_FILELOCK_EXTENSION
, GIT_FILELOCK_EXTLENGTH
);
369 if (git_path_isdir(file
->path_original
)) {
370 git_error_set(GIT_ERROR_FILESYSTEM
, "path '%s' is a directory", file
->path_original
);
371 error
= GIT_EDIRECTORY
;
375 /* open the file for locking */
376 if ((error
= lock_file(file
, flags
, mode
)) < 0)
379 file
->created_lock
= true;
385 git_filebuf_cleanup(file
);
389 int git_filebuf_hash(git_oid
*oid
, git_filebuf
*file
)
392 GIT_ASSERT_ARG(file
);
393 GIT_ASSERT_ARG(file
->compute_digest
);
397 if (verify_last_error(file
) < 0)
400 git_hash_final(oid
, &file
->digest
);
401 git_hash_ctx_cleanup(&file
->digest
);
402 file
->compute_digest
= 0;
407 int git_filebuf_commit_at(git_filebuf
*file
, const char *path
)
409 git__free(file
->path_original
);
410 file
->path_original
= git__strdup(path
);
411 GIT_ERROR_CHECK_ALLOC(file
->path_original
);
413 return git_filebuf_commit(file
);
416 int git_filebuf_commit(git_filebuf
*file
)
418 /* temporary files cannot be committed */
419 GIT_ASSERT_ARG(file
);
420 GIT_ASSERT(file
->path_original
);
422 file
->flush_mode
= Z_FINISH
;
425 if (verify_last_error(file
) < 0)
428 file
->fd_is_open
= false;
430 if (file
->do_fsync
&& p_fsync(file
->fd
) < 0) {
431 git_error_set(GIT_ERROR_OS
, "failed to fsync '%s'", file
->path_lock
);
435 if (p_close(file
->fd
) < 0) {
436 git_error_set(GIT_ERROR_OS
, "failed to close file at '%s'", file
->path_lock
);
442 if (p_rename(file
->path_lock
, file
->path_original
) < 0) {
443 git_error_set(GIT_ERROR_OS
, "failed to rename lockfile to '%s'", file
->path_original
);
447 if (file
->do_fsync
&& git_futils_fsync_parent(file
->path_original
) < 0)
450 file
->did_rename
= true;
452 git_filebuf_cleanup(file
);
456 git_filebuf_cleanup(file
);
460 GIT_INLINE(void) add_to_cache(git_filebuf
*file
, const void *buf
, size_t len
)
462 memcpy(file
->buffer
+ file
->buf_pos
, buf
, len
);
463 file
->buf_pos
+= len
;
466 int git_filebuf_write(git_filebuf
*file
, const void *buff
, size_t len
)
468 const unsigned char *buf
= buff
;
472 if (file
->do_not_buffer
)
473 return file
->write(file
, (void *)buff
, len
);
476 size_t space_left
= file
->buf_size
- file
->buf_pos
;
478 /* cache if it's small */
479 if (space_left
> len
) {
480 add_to_cache(file
, buf
, len
);
484 add_to_cache(file
, buf
, space_left
);
485 if (flush_buffer(file
) < 0)
493 int git_filebuf_reserve(git_filebuf
*file
, void **buffer
, size_t len
)
495 size_t space_left
= file
->buf_size
- file
->buf_pos
;
501 if (len
> file
->buf_size
) {
502 file
->last_error
= BUFERR_MEM
;
506 if (space_left
<= len
) {
507 if (flush_buffer(file
) < 0)
511 *buffer
= (file
->buffer
+ file
->buf_pos
);
512 file
->buf_pos
+= len
;
517 int git_filebuf_printf(git_filebuf
*file
, const char *format
, ...)
520 size_t space_left
, len
, alloclen
;
526 space_left
= file
->buf_size
- file
->buf_pos
;
529 va_start(arglist
, format
);
530 written
= p_vsnprintf((char *)file
->buffer
+ file
->buf_pos
, space_left
, format
, arglist
);
534 file
->last_error
= BUFERR_MEM
;
539 if (len
+ 1 <= space_left
) {
540 file
->buf_pos
+= len
;
544 if (flush_buffer(file
) < 0)
547 space_left
= file
->buf_size
- file
->buf_pos
;
549 } while (len
+ 1 <= space_left
);
551 if (GIT_ADD_SIZET_OVERFLOW(&alloclen
, len
, 1) ||
552 !(tmp_buffer
= git__malloc(alloclen
))) {
553 file
->last_error
= BUFERR_MEM
;
557 va_start(arglist
, format
);
558 written
= p_vsnprintf(tmp_buffer
, len
+ 1, format
, arglist
);
562 git__free(tmp_buffer
);
563 file
->last_error
= BUFERR_MEM
;
567 res
= git_filebuf_write(file
, tmp_buffer
, len
);
568 git__free(tmp_buffer
);
573 int git_filebuf_stats(time_t *mtime
, size_t *size
, git_filebuf
*file
)
578 if (file
->fd_is_open
)
579 res
= p_fstat(file
->fd
, &st
);
581 res
= p_stat(file
->path_original
, &st
);
584 git_error_set(GIT_ERROR_OS
, "could not get stat info for '%s'",
585 file
->path_original
);
590 *mtime
= st
.st_mtime
;
592 *size
= (size_t)st
.st_size
;