/src/libgit2/src/util/filebuf.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) the libgit2 contributors. All rights reserved. |
3 | | * |
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. |
6 | | */ |
7 | | |
8 | | #include "filebuf.h" |
9 | | |
10 | | #include "futils.h" |
11 | | |
12 | | static const size_t WRITE_BUFFER_SIZE = (4096 * 2); |
13 | | |
14 | | enum buferr_t { |
15 | | BUFERR_OK = 0, |
16 | | BUFERR_WRITE, |
17 | | BUFERR_ZLIB, |
18 | | BUFERR_MEM |
19 | | }; |
20 | | |
21 | 267 | #define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; } |
22 | | |
23 | | static int verify_last_error(git_filebuf *file) |
24 | 10 | { |
25 | 10 | switch (file->last_error) { |
26 | 0 | case BUFERR_WRITE: |
27 | 0 | git_error_set(GIT_ERROR_OS, "failed to write out file"); |
28 | 0 | return -1; |
29 | | |
30 | 0 | case BUFERR_MEM: |
31 | 0 | git_error_set_oom(); |
32 | 0 | return -1; |
33 | | |
34 | 0 | case BUFERR_ZLIB: |
35 | 0 | git_error_set(GIT_ERROR_ZLIB, |
36 | 0 | "Buffer error when writing out ZLib data"); |
37 | 0 | return -1; |
38 | | |
39 | 10 | default: |
40 | 10 | return 0; |
41 | 10 | } |
42 | 10 | } |
43 | | |
44 | | static int lock_file(git_filebuf *file, int flags, mode_t mode) |
45 | 9 | { |
46 | 9 | if (git_fs_path_exists(file->path_lock) == true) { |
47 | 0 | git_error_clear(); /* actual OS error code just confuses */ |
48 | 0 | git_error_set(GIT_ERROR_OS, |
49 | 0 | "failed to lock file '%s' for writing", file->path_lock); |
50 | 0 | return GIT_ELOCKED; |
51 | 0 | } |
52 | | |
53 | | /* create path to the file buffer is required */ |
54 | 9 | if (flags & GIT_FILEBUF_CREATE_LEADING_DIRS) { |
55 | | /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */ |
56 | 0 | file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode); |
57 | 9 | } else { |
58 | 9 | file->fd = git_futils_creat_locked(file->path_lock, mode); |
59 | 9 | } |
60 | | |
61 | 9 | if (file->fd < 0) |
62 | 0 | return file->fd; |
63 | | |
64 | 9 | file->fd_is_open = true; |
65 | | |
66 | 9 | if ((flags & GIT_FILEBUF_APPEND) && git_fs_path_exists(file->path_original) == true) { |
67 | 0 | git_file source; |
68 | 0 | char buffer[GIT_BUFSIZE_FILEIO]; |
69 | 0 | ssize_t read_bytes; |
70 | 0 | int error = 0; |
71 | |
|
72 | 0 | source = p_open(file->path_original, O_RDONLY); |
73 | 0 | if (source < 0) { |
74 | 0 | git_error_set(GIT_ERROR_OS, |
75 | 0 | "failed to open file '%s' for reading", |
76 | 0 | file->path_original); |
77 | 0 | return -1; |
78 | 0 | } |
79 | | |
80 | 0 | while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) { |
81 | 0 | if ((error = p_write(file->fd, buffer, read_bytes)) < 0) |
82 | 0 | break; |
83 | 0 | if (file->compute_digest) |
84 | 0 | git_hash_update(&file->digest, buffer, read_bytes); |
85 | 0 | } |
86 | |
|
87 | 0 | p_close(source); |
88 | |
|
89 | 0 | if (read_bytes < 0) { |
90 | 0 | git_error_set(GIT_ERROR_OS, "failed to read file '%s'", file->path_original); |
91 | 0 | return -1; |
92 | 0 | } else if (error < 0) { |
93 | 0 | git_error_set(GIT_ERROR_OS, "failed to write file '%s'", file->path_lock); |
94 | 0 | return -1; |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | 9 | return 0; |
99 | 9 | } |
100 | | |
101 | | void git_filebuf_cleanup(git_filebuf *file) |
102 | 17 | { |
103 | 17 | if (file->fd_is_open && file->fd >= 0) |
104 | 0 | p_close(file->fd); |
105 | | |
106 | 17 | if (file->created_lock && !file->did_rename && file->path_lock && git_fs_path_exists(file->path_lock)) |
107 | 0 | p_unlink(file->path_lock); |
108 | | |
109 | 17 | if (file->compute_digest) { |
110 | 6 | git_hash_ctx_cleanup(&file->digest); |
111 | 6 | file->compute_digest = 0; |
112 | 6 | } |
113 | | |
114 | 17 | if (file->buffer) |
115 | 9 | git__free(file->buffer); |
116 | | |
117 | | /* use the presence of z_buf to decide if we need to deflateEnd */ |
118 | 17 | if (file->z_buf) { |
119 | 0 | git__free(file->z_buf); |
120 | 0 | deflateEnd(&file->zs); |
121 | 0 | } |
122 | | |
123 | 17 | if (file->path_original) |
124 | 9 | git__free(file->path_original); |
125 | 17 | if (file->path_lock) |
126 | 9 | git__free(file->path_lock); |
127 | | |
128 | 17 | memset(file, 0x0, sizeof(git_filebuf)); |
129 | 17 | file->fd = -1; |
130 | 17 | } |
131 | | |
132 | | GIT_INLINE(int) flush_buffer(git_filebuf *file) |
133 | 10 | { |
134 | 10 | int result = file->write(file, file->buffer, file->buf_pos); |
135 | 10 | file->buf_pos = 0; |
136 | 10 | return result; |
137 | 10 | } |
138 | | |
139 | | int git_filebuf_flush(git_filebuf *file) |
140 | 0 | { |
141 | 0 | return flush_buffer(file); |
142 | 0 | } |
143 | | |
144 | | static int write_normal(git_filebuf *file, void *source, size_t len) |
145 | 10 | { |
146 | 10 | if (len > 0) { |
147 | 10 | if (p_write(file->fd, (void *)source, len) < 0) { |
148 | 0 | file->last_error = BUFERR_WRITE; |
149 | 0 | return -1; |
150 | 0 | } |
151 | | |
152 | 10 | if (file->compute_digest) |
153 | 7 | git_hash_update(&file->digest, source, len); |
154 | 10 | } |
155 | | |
156 | 10 | return 0; |
157 | 10 | } |
158 | | |
159 | | static int write_deflate(git_filebuf *file, void *source, size_t len) |
160 | 0 | { |
161 | 0 | z_stream *zs = &file->zs; |
162 | |
|
163 | 0 | if (len > 0 || file->flush_mode == Z_FINISH) { |
164 | 0 | zs->next_in = source; |
165 | 0 | zs->avail_in = (uInt)len; |
166 | |
|
167 | 0 | do { |
168 | 0 | size_t have; |
169 | |
|
170 | 0 | zs->next_out = file->z_buf; |
171 | 0 | zs->avail_out = (uInt)file->buf_size; |
172 | |
|
173 | 0 | if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) { |
174 | 0 | file->last_error = BUFERR_ZLIB; |
175 | 0 | return -1; |
176 | 0 | } |
177 | | |
178 | 0 | have = file->buf_size - (size_t)zs->avail_out; |
179 | |
|
180 | 0 | if (p_write(file->fd, file->z_buf, have) < 0) { |
181 | 0 | file->last_error = BUFERR_WRITE; |
182 | 0 | return -1; |
183 | 0 | } |
184 | |
|
185 | 0 | } while (zs->avail_out == 0); |
186 | | |
187 | 0 | GIT_ASSERT(zs->avail_in == 0); |
188 | | |
189 | 0 | if (file->compute_digest) |
190 | 0 | git_hash_update(&file->digest, source, len); |
191 | 0 | } |
192 | | |
193 | 0 | return 0; |
194 | 0 | } |
195 | | |
196 | 9 | #define MAX_SYMLINK_DEPTH 5 |
197 | | |
198 | | static int resolve_symlink(git_str *out, const char *path) |
199 | 9 | { |
200 | 9 | int i, error, root; |
201 | 9 | ssize_t ret; |
202 | 9 | struct stat st; |
203 | 9 | git_str curpath = GIT_STR_INIT, target = GIT_STR_INIT; |
204 | | |
205 | 9 | if ((error = git_str_grow(&target, GIT_PATH_MAX + 1)) < 0 || |
206 | 9 | (error = git_str_puts(&curpath, path)) < 0) |
207 | 0 | return error; |
208 | | |
209 | 9 | for (i = 0; i < MAX_SYMLINK_DEPTH; i++) { |
210 | 9 | error = p_lstat(curpath.ptr, &st); |
211 | 9 | if (error < 0 && errno == ENOENT) { |
212 | 3 | error = git_str_puts(out, curpath.ptr); |
213 | 3 | goto cleanup; |
214 | 3 | } |
215 | | |
216 | 6 | if (error < 0) { |
217 | 0 | git_error_set(GIT_ERROR_OS, "failed to stat '%s'", curpath.ptr); |
218 | 0 | error = -1; |
219 | 0 | goto cleanup; |
220 | 0 | } |
221 | | |
222 | 6 | if (!S_ISLNK(st.st_mode)) { |
223 | 6 | error = git_str_puts(out, curpath.ptr); |
224 | 6 | goto cleanup; |
225 | 6 | } |
226 | | |
227 | 0 | ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX); |
228 | 0 | if (ret < 0) { |
229 | 0 | git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", curpath.ptr); |
230 | 0 | error = -1; |
231 | 0 | goto cleanup; |
232 | 0 | } |
233 | | |
234 | 0 | if (ret == GIT_PATH_MAX) { |
235 | 0 | git_error_set(GIT_ERROR_INVALID, "symlink target too long"); |
236 | 0 | error = -1; |
237 | 0 | goto cleanup; |
238 | 0 | } |
239 | | |
240 | | /* readlink(2) won't NUL-terminate for us */ |
241 | 0 | target.ptr[ret] = '\0'; |
242 | 0 | target.size = ret; |
243 | |
|
244 | 0 | root = git_fs_path_root(target.ptr); |
245 | 0 | if (root >= 0) { |
246 | 0 | if ((error = git_str_sets(&curpath, target.ptr)) < 0) |
247 | 0 | goto cleanup; |
248 | 0 | } else { |
249 | 0 | git_str dir = GIT_STR_INIT; |
250 | |
|
251 | 0 | if ((error = git_fs_path_dirname_r(&dir, curpath.ptr)) < 0) |
252 | 0 | goto cleanup; |
253 | | |
254 | 0 | git_str_swap(&curpath, &dir); |
255 | 0 | git_str_dispose(&dir); |
256 | |
|
257 | 0 | if ((error = git_fs_path_apply_relative(&curpath, target.ptr)) < 0) |
258 | 0 | goto cleanup; |
259 | 0 | } |
260 | 0 | } |
261 | | |
262 | 0 | git_error_set(GIT_ERROR_INVALID, "maximum symlink depth reached"); |
263 | 0 | error = -1; |
264 | |
|
265 | 9 | cleanup: |
266 | 9 | git_str_dispose(&curpath); |
267 | 9 | git_str_dispose(&target); |
268 | 9 | return error; |
269 | 0 | } |
270 | | |
271 | | int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) |
272 | 9 | { |
273 | 9 | return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE); |
274 | 9 | } |
275 | | |
276 | | int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size) |
277 | 9 | { |
278 | 9 | int compression, error = -1; |
279 | 9 | size_t path_len, alloc_len; |
280 | | |
281 | 9 | GIT_ASSERT_ARG(file); |
282 | 9 | GIT_ASSERT_ARG(path); |
283 | 9 | GIT_ASSERT(file->buffer == NULL); |
284 | | |
285 | 9 | memset(file, 0x0, sizeof(git_filebuf)); |
286 | | |
287 | 9 | if (flags & GIT_FILEBUF_DO_NOT_BUFFER) |
288 | 0 | file->do_not_buffer = true; |
289 | | |
290 | 9 | if (flags & GIT_FILEBUF_FSYNC) |
291 | 0 | file->do_fsync = true; |
292 | | |
293 | 9 | file->buf_size = size; |
294 | 9 | file->buf_pos = 0; |
295 | 9 | file->fd = -1; |
296 | 9 | file->last_error = BUFERR_OK; |
297 | | |
298 | | /* Allocate the main cache buffer */ |
299 | 9 | if (!file->do_not_buffer) { |
300 | 9 | file->buffer = git__malloc(file->buf_size); |
301 | 9 | GIT_ERROR_CHECK_ALLOC(file->buffer); |
302 | 9 | } |
303 | | |
304 | | /* If we are hashing on-write, allocate a new hash context */ |
305 | 9 | if (flags & GIT_FILEBUF_HASH_SHA1) { |
306 | 1 | file->compute_digest = 1; |
307 | | |
308 | 1 | if (git_hash_ctx_init(&file->digest, GIT_HASH_ALGORITHM_SHA1) < 0) |
309 | 0 | goto cleanup; |
310 | 8 | } else if (flags & GIT_FILEBUF_HASH_SHA256) { |
311 | 6 | file->compute_digest = 1; |
312 | | |
313 | 6 | if (git_hash_ctx_init(&file->digest, GIT_HASH_ALGORITHM_SHA256) < 0) |
314 | 0 | goto cleanup; |
315 | 6 | } |
316 | | |
317 | 9 | compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; |
318 | | |
319 | | /* If we are deflating on-write, */ |
320 | 9 | if (compression != 0) { |
321 | | /* Initialize the ZLib stream */ |
322 | 0 | if (deflateInit(&file->zs, compression) != Z_OK) { |
323 | 0 | git_error_set(GIT_ERROR_ZLIB, "failed to initialize zlib"); |
324 | 0 | goto cleanup; |
325 | 0 | } |
326 | | |
327 | | /* Allocate the Zlib cache buffer */ |
328 | 0 | file->z_buf = git__malloc(file->buf_size); |
329 | 0 | GIT_ERROR_CHECK_ALLOC(file->z_buf); |
330 | | |
331 | | /* Never flush */ |
332 | 0 | file->flush_mode = Z_NO_FLUSH; |
333 | 0 | file->write = &write_deflate; |
334 | 9 | } else { |
335 | 9 | file->write = &write_normal; |
336 | 9 | } |
337 | | |
338 | | /* If we are writing to a temp file */ |
339 | 9 | if (flags & GIT_FILEBUF_TEMPORARY) { |
340 | 0 | git_str tmp_path = GIT_STR_INIT; |
341 | | |
342 | | /* Open the file as temporary for locking */ |
343 | 0 | file->fd = git_futils_mktmp(&tmp_path, path, mode); |
344 | |
|
345 | 0 | if (file->fd < 0) { |
346 | 0 | git_str_dispose(&tmp_path); |
347 | 0 | goto cleanup; |
348 | 0 | } |
349 | 0 | file->fd_is_open = true; |
350 | 0 | file->created_lock = true; |
351 | | |
352 | | /* No original path */ |
353 | 0 | file->path_original = NULL; |
354 | 0 | file->path_lock = git_str_detach(&tmp_path); |
355 | 0 | GIT_ERROR_CHECK_ALLOC(file->path_lock); |
356 | 9 | } else { |
357 | 9 | git_str resolved_path = GIT_STR_INIT; |
358 | | |
359 | 9 | if ((error = resolve_symlink(&resolved_path, path)) < 0) |
360 | 0 | goto cleanup; |
361 | | |
362 | | /* Save the original path of the file */ |
363 | 9 | path_len = resolved_path.size; |
364 | 9 | file->path_original = git_str_detach(&resolved_path); |
365 | | |
366 | | /* create the locking path by appending ".lock" to the original */ |
367 | 9 | GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH); |
368 | 9 | file->path_lock = git__malloc(alloc_len); |
369 | 9 | GIT_ERROR_CHECK_ALLOC(file->path_lock); |
370 | | |
371 | 9 | memcpy(file->path_lock, file->path_original, path_len); |
372 | 9 | memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); |
373 | | |
374 | 9 | if (git_fs_path_isdir(file->path_original)) { |
375 | 0 | git_error_set(GIT_ERROR_FILESYSTEM, "path '%s' is a directory", file->path_original); |
376 | 0 | error = GIT_EDIRECTORY; |
377 | 0 | goto cleanup; |
378 | 0 | } |
379 | | |
380 | | /* open the file for locking */ |
381 | 9 | if ((error = lock_file(file, flags, mode)) < 0) |
382 | 0 | goto cleanup; |
383 | | |
384 | 9 | file->created_lock = true; |
385 | 9 | } |
386 | | |
387 | 9 | return 0; |
388 | | |
389 | 0 | cleanup: |
390 | 0 | git_filebuf_cleanup(file); |
391 | 0 | return error; |
392 | 9 | } |
393 | | |
394 | | int git_filebuf_hash(unsigned char *out, git_filebuf *file) |
395 | 1 | { |
396 | 1 | GIT_ASSERT_ARG(out); |
397 | 1 | GIT_ASSERT_ARG(file); |
398 | 1 | GIT_ASSERT_ARG(file->compute_digest); |
399 | | |
400 | 1 | flush_buffer(file); |
401 | | |
402 | 1 | if (verify_last_error(file) < 0) |
403 | 0 | return -1; |
404 | | |
405 | 1 | git_hash_final(out, &file->digest); |
406 | 1 | git_hash_ctx_cleanup(&file->digest); |
407 | 1 | file->compute_digest = 0; |
408 | | |
409 | 1 | return 0; |
410 | 1 | } |
411 | | |
412 | | int git_filebuf_commit_at(git_filebuf *file, const char *path) |
413 | 1 | { |
414 | 1 | git__free(file->path_original); |
415 | 1 | file->path_original = git__strdup(path); |
416 | 1 | GIT_ERROR_CHECK_ALLOC(file->path_original); |
417 | | |
418 | 1 | return git_filebuf_commit(file); |
419 | 1 | } |
420 | | |
421 | | int git_filebuf_commit(git_filebuf *file) |
422 | 9 | { |
423 | | /* temporary files cannot be committed */ |
424 | 9 | GIT_ASSERT_ARG(file); |
425 | 9 | GIT_ASSERT(file->path_original); |
426 | | |
427 | 9 | file->flush_mode = Z_FINISH; |
428 | 9 | flush_buffer(file); |
429 | | |
430 | 9 | if (verify_last_error(file) < 0) |
431 | 0 | goto on_error; |
432 | | |
433 | 9 | file->fd_is_open = false; |
434 | | |
435 | 9 | if (file->do_fsync && p_fsync(file->fd) < 0) { |
436 | 0 | git_error_set(GIT_ERROR_OS, "failed to fsync '%s'", file->path_lock); |
437 | 0 | goto on_error; |
438 | 0 | } |
439 | | |
440 | 9 | if (p_close(file->fd) < 0) { |
441 | 0 | git_error_set(GIT_ERROR_OS, "failed to close file at '%s'", file->path_lock); |
442 | 0 | goto on_error; |
443 | 0 | } |
444 | | |
445 | 9 | file->fd = -1; |
446 | | |
447 | 9 | if (p_rename(file->path_lock, file->path_original) < 0) { |
448 | 0 | git_error_set(GIT_ERROR_OS, "failed to rename lockfile to '%s'", file->path_original); |
449 | 0 | goto on_error; |
450 | 0 | } |
451 | | |
452 | 9 | if (file->do_fsync && git_futils_fsync_parent(file->path_original) < 0) |
453 | 0 | goto on_error; |
454 | | |
455 | 9 | file->did_rename = true; |
456 | | |
457 | 9 | git_filebuf_cleanup(file); |
458 | 9 | return 0; |
459 | | |
460 | 0 | on_error: |
461 | 0 | git_filebuf_cleanup(file); |
462 | 0 | return -1; |
463 | 9 | } |
464 | | |
465 | | GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) |
466 | 265 | { |
467 | 265 | memcpy(file->buffer + file->buf_pos, buf, len); |
468 | 265 | file->buf_pos += len; |
469 | 265 | } |
470 | | |
471 | | int git_filebuf_write(git_filebuf *file, const void *buff, size_t len) |
472 | 265 | { |
473 | 265 | const unsigned char *buf = buff; |
474 | | |
475 | 265 | ENSURE_BUF_OK(file); |
476 | | |
477 | 265 | if (file->do_not_buffer) |
478 | 0 | return file->write(file, (void *)buff, len); |
479 | | |
480 | 265 | for (;;) { |
481 | 265 | size_t space_left = file->buf_size - file->buf_pos; |
482 | | |
483 | | /* cache if it's small */ |
484 | 265 | if (space_left > len) { |
485 | 265 | add_to_cache(file, buf, len); |
486 | 265 | return 0; |
487 | 265 | } |
488 | | |
489 | 0 | add_to_cache(file, buf, space_left); |
490 | 0 | if (flush_buffer(file) < 0) |
491 | 0 | return -1; |
492 | | |
493 | 0 | len -= space_left; |
494 | 0 | buf += space_left; |
495 | 0 | } |
496 | 265 | } |
497 | | |
498 | | int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len) |
499 | 0 | { |
500 | 0 | size_t space_left = file->buf_size - file->buf_pos; |
501 | |
|
502 | 0 | *buffer = NULL; |
503 | |
|
504 | 0 | ENSURE_BUF_OK(file); |
505 | |
|
506 | 0 | if (len > file->buf_size) { |
507 | 0 | file->last_error = BUFERR_MEM; |
508 | 0 | return -1; |
509 | 0 | } |
510 | | |
511 | 0 | if (space_left <= len) { |
512 | 0 | if (flush_buffer(file) < 0) |
513 | 0 | return -1; |
514 | 0 | } |
515 | | |
516 | 0 | *buffer = (file->buffer + file->buf_pos); |
517 | 0 | file->buf_pos += len; |
518 | |
|
519 | 0 | return 0; |
520 | 0 | } |
521 | | |
522 | | int git_filebuf_printf(git_filebuf *file, const char *format, ...) |
523 | 2 | { |
524 | 2 | va_list arglist; |
525 | 2 | size_t space_left, len, alloclen; |
526 | 2 | int written, res; |
527 | 2 | char *tmp_buffer; |
528 | | |
529 | 2 | ENSURE_BUF_OK(file); |
530 | | |
531 | 2 | space_left = file->buf_size - file->buf_pos; |
532 | | |
533 | 2 | do { |
534 | 2 | va_start(arglist, format); |
535 | 2 | written = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); |
536 | 2 | va_end(arglist); |
537 | | |
538 | 2 | if (written < 0) { |
539 | 0 | file->last_error = BUFERR_MEM; |
540 | 0 | return -1; |
541 | 0 | } |
542 | | |
543 | 2 | len = written; |
544 | 2 | if (len + 1 <= space_left) { |
545 | 2 | file->buf_pos += len; |
546 | 2 | return 0; |
547 | 2 | } |
548 | | |
549 | 0 | if (flush_buffer(file) < 0) |
550 | 0 | return -1; |
551 | | |
552 | 0 | space_left = file->buf_size - file->buf_pos; |
553 | |
|
554 | 0 | } while (len + 1 <= space_left); |
555 | | |
556 | 0 | if (GIT_ADD_SIZET_OVERFLOW(&alloclen, len, 1) || |
557 | 0 | !(tmp_buffer = git__malloc(alloclen))) { |
558 | 0 | file->last_error = BUFERR_MEM; |
559 | 0 | return -1; |
560 | 0 | } |
561 | | |
562 | 0 | va_start(arglist, format); |
563 | 0 | written = p_vsnprintf(tmp_buffer, len + 1, format, arglist); |
564 | 0 | va_end(arglist); |
565 | |
|
566 | 0 | if (written < 0) { |
567 | 0 | git__free(tmp_buffer); |
568 | 0 | file->last_error = BUFERR_MEM; |
569 | 0 | return -1; |
570 | 0 | } |
571 | | |
572 | 0 | res = git_filebuf_write(file, tmp_buffer, len); |
573 | 0 | git__free(tmp_buffer); |
574 | |
|
575 | 0 | return res; |
576 | 0 | } |
577 | | |
578 | | int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file) |
579 | 0 | { |
580 | 0 | int res; |
581 | 0 | struct stat st; |
582 | |
|
583 | 0 | if (file->fd_is_open) |
584 | 0 | res = p_fstat(file->fd, &st); |
585 | 0 | else |
586 | 0 | res = p_stat(file->path_original, &st); |
587 | |
|
588 | 0 | if (res < 0) { |
589 | 0 | git_error_set(GIT_ERROR_OS, "could not get stat info for '%s'", |
590 | 0 | file->path_original); |
591 | 0 | return res; |
592 | 0 | } |
593 | | |
594 | 0 | if (mtime) |
595 | 0 | *mtime = st.st_mtime; |
596 | 0 | if (size) |
597 | 0 | *size = (size_t)st.st_size; |
598 | |
|
599 | 0 | return 0; |
600 | 0 | } |