]> git.proxmox.com Git - libgit2.git/blame - src/filebuf.c
fileops/repository: create (most) directories with 0777 permissions
[libgit2.git] / src / filebuf.c
CommitLineData
817c2820 1/*
bb742ede 2 * Copyright (C) 2009-2011 the libgit2 contributors
817c2820 3 *
bb742ede
VM
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.
817c2820 6 */
5591ea15 7#include <stdarg.h>
817c2820
VM
8
9#include "common.h"
10#include "filebuf.h"
11#include "fileops.h"
12
817c2820
VM
13static const size_t WRITE_BUFFER_SIZE = (4096 * 2);
14
15static int lock_file(git_filebuf *file, int flags)
16{
f79026b4 17 if (git_futils_exists(file->path_lock) == 0) {
817c2820 18 if (flags & GIT_FILEBUF_FORCE)
f79026b4 19 p_unlink(file->path_lock);
817c2820 20 else
374db5f9 21 return git__throw(GIT_EOSERR, "Failed to lock file");
817c2820
VM
22 }
23
55ffebe3
VM
24 /* create path to the file buffer is required */
25 if (flags & GIT_FILEBUF_FORCE) {
ce8cd006
BR
26 /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */
27 file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, 0644);
55ffebe3 28 } else {
f79026b4 29 file->fd = git_futils_creat_locked(file->path_lock, 0644);
55ffebe3 30 }
817c2820
VM
31
32 if (file->fd < 0)
374db5f9 33 return git__throw(GIT_EOSERR, "Failed to create lock");
817c2820 34
f79026b4 35 if ((flags & GIT_FILEBUF_APPEND) && git_futils_exists(file->path_original) == 0) {
817c2820
VM
36 git_file source;
37 char buffer[2048];
38 size_t read_bytes;
39
f79026b4 40 source = p_open(file->path_original, O_RDONLY);
817c2820 41 if (source < 0)
374db5f9 42 return git__throw(GIT_EOSERR, "Failed to lock file. Could not open %s", file->path_original);
817c2820 43
f79026b4
VM
44 while ((read_bytes = p_read(source, buffer, 2048)) > 0) {
45 p_write(file->fd, buffer, read_bytes);
817c2820
VM
46 if (file->digest)
47 git_hash_update(file->digest, buffer, read_bytes);
48 }
49
f79026b4 50 p_close(source);
817c2820
VM
51 }
52
53 return GIT_SUCCESS;
54}
55
56void git_filebuf_cleanup(git_filebuf *file)
57{
c3be1468 58 if (file->fd >= 0)
f79026b4 59 p_close(file->fd);
c3be1468 60
f79026b4
VM
61 if (file->fd >= 0 && file->path_lock && git_futils_exists(file->path_lock) == GIT_SUCCESS)
62 p_unlink(file->path_lock);
817c2820
VM
63
64 if (file->digest)
65 git_hash_free_ctx(file->digest);
66
67 free(file->buffer);
72a3fe42 68 free(file->z_buf);
817c2820 69
72a3fe42 70 deflateEnd(&file->zs);
817c2820
VM
71
72 free(file->path_original);
73 free(file->path_lock);
74}
75
72a3fe42 76GIT_INLINE(int) flush_buffer(git_filebuf *file)
817c2820 77{
72a3fe42
VM
78 int result = file->write(file, file->buffer, file->buf_pos);
79 file->buf_pos = 0;
80 return result;
81}
817c2820 82
05a62d1a 83static int write_normal(git_filebuf *file, void *source, size_t len)
72a3fe42
VM
84{
85 int result = 0;
817c2820 86
72a3fe42 87 if (len > 0) {
f79026b4 88 result = p_write(file->fd, (void *)source, len);
72a3fe42
VM
89 if (file->digest)
90 git_hash_update(file->digest, source, len);
817c2820
VM
91 }
92
93 return result;
94}
95
05a62d1a 96static int write_deflate(git_filebuf *file, void *source, size_t len)
72a3fe42
VM
97{
98 int result = Z_OK;
99 z_stream *zs = &file->zs;
100
101 if (len > 0 || file->flush_mode == Z_FINISH) {
353560b4 102 zs->next_in = source;
1c3fac4d 103 zs->avail_in = (uInt)len;
72a3fe42
VM
104
105 do {
1c3fac4d 106 size_t have;
72a3fe42
VM
107
108 zs->next_out = file->z_buf;
1c3fac4d 109 zs->avail_out = (uInt)file->buf_size;
72a3fe42 110
45e93ef3 111 result = deflate(zs, file->flush_mode);
c8f16bfe
MS
112 if (result == Z_STREAM_ERROR)
113 return git__throw(GIT_ERROR, "Failed to deflate input");
72a3fe42 114
1c3fac4d 115 have = file->buf_size - (size_t)zs->avail_out;
72a3fe42 116
f79026b4 117 if (p_write(file->fd, file->z_buf, have) < GIT_SUCCESS)
374db5f9 118 return git__throw(GIT_EOSERR, "Failed to write to file");
72a3fe42 119
45e93ef3 120 } while (zs->avail_out == 0);
72a3fe42
VM
121
122 assert(zs->avail_in == 0);
123
124 if (file->digest)
125 git_hash_update(file->digest, source, len);
126 }
127
128 return GIT_SUCCESS;
129}
130
817c2820
VM
131int git_filebuf_open(git_filebuf *file, const char *path, int flags)
132{
c103d7b4 133 int error, compression;
817c2820
VM
134 size_t path_len;
135
f6f72d7e 136 assert(file && path);
817c2820
VM
137
138 memset(file, 0x0, sizeof(git_filebuf));
139
140 file->buf_size = WRITE_BUFFER_SIZE;
141 file->buf_pos = 0;
142 file->fd = -1;
143
72a3fe42
VM
144 /* Allocate the main cache buffer */
145 file->buffer = git__malloc(file->buf_size);
146 if (file->buffer == NULL){
817c2820
VM
147 error = GIT_ENOMEM;
148 goto cleanup;
149 }
150
72a3fe42
VM
151 /* If we are hashing on-write, allocate a new hash context */
152 if (flags & GIT_FILEBUF_HASH_CONTENTS) {
153 if ((file->digest = git_hash_new_ctx()) == NULL) {
154 error = GIT_ENOMEM;
155 goto cleanup;
156 }
817c2820
VM
157 }
158
c103d7b4 159 compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT;
817c2820 160
c103d7b4
VM
161 /* If we are deflating on-write, */
162 if (compression != 0) {
72a3fe42 163 /* Initialize the ZLib stream */
c103d7b4 164 if (deflateInit(&file->zs, compression) != Z_OK) {
f6328611 165 error = git__throw(GIT_EZLIB, "Failed to initialize zlib");
72a3fe42
VM
166 goto cleanup;
167 }
817c2820 168
72a3fe42
VM
169 /* Allocate the Zlib cache buffer */
170 file->z_buf = git__malloc(file->buf_size);
171 if (file->z_buf == NULL){
172 error = GIT_ENOMEM;
173 goto cleanup;
174 }
175
176 /* Never flush */
177 file->flush_mode = Z_NO_FLUSH;
178 file->write = &write_deflate;
179 } else {
180 file->write = &write_normal;
817c2820 181 }
817c2820 182
72a3fe42
VM
183 /* If we are writing to a temp file */
184 if (flags & GIT_FILEBUF_TEMPORARY) {
185 char tmp_path[GIT_PATH_MAX];
186
187 /* Open the file as temporary for locking */
f79026b4 188 file->fd = git_futils_mktmp(tmp_path, path);
72a3fe42
VM
189 if (file->fd < 0) {
190 error = GIT_EOSERR;
191 goto cleanup;
192 }
193
194 /* No original path */
195 file->path_original = NULL;
196 file->path_lock = git__strdup(tmp_path);
197
198 if (file->path_lock == NULL) {
817c2820
VM
199 error = GIT_ENOMEM;
200 goto cleanup;
201 }
72a3fe42 202 } else {
72a3fe42
VM
203 path_len = strlen(path);
204
205 /* Save the original path of the file */
206 file->path_original = git__strdup(path);
207 if (file->path_original == NULL) {
208 error = GIT_ENOMEM;
209 goto cleanup;
210 }
211
212 /* create the locking path by appending ".lock" to the original */
213 file->path_lock = git__malloc(path_len + GIT_FILELOCK_EXTLENGTH);
214 if (file->path_lock == NULL) {
215 error = GIT_ENOMEM;
216 goto cleanup;
217 }
218
219 memcpy(file->path_lock, file->path_original, path_len);
220 memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH);
221
222 /* open the file for locking */
223 if ((error = lock_file(file, flags)) < GIT_SUCCESS)
224 goto cleanup;
225 }
817c2820
VM
226
227 return GIT_SUCCESS;
228
229cleanup:
230 git_filebuf_cleanup(file);
f6328611 231 return git__rethrow(error, "Failed to open file buffer for '%s'", path);
817c2820
VM
232}
233
234int git_filebuf_hash(git_oid *oid, git_filebuf *file)
235{
236 int error;
237
f6328611 238 assert(oid && file && file->digest);
817c2820
VM
239
240 if ((error = flush_buffer(file)) < GIT_SUCCESS)
374db5f9 241 return git__rethrow(error, "Failed to get hash for file");
817c2820
VM
242
243 git_hash_final(oid, file->digest);
244 git_hash_free_ctx(file->digest);
245 file->digest = NULL;
246
247 return GIT_SUCCESS;
248}
249
72a3fe42
VM
250int git_filebuf_commit_at(git_filebuf *file, const char *path)
251{
252 free(file->path_original);
253 file->path_original = git__strdup(path);
254 if (file->path_original == NULL)
255 return GIT_ENOMEM;
256
257 return git_filebuf_commit(file);
258}
259
817c2820
VM
260int git_filebuf_commit(git_filebuf *file)
261{
262 int error;
263
f6328611
VM
264 /* temporary files cannot be committed */
265 assert(file && file->path_original);
72a3fe42
VM
266
267 file->flush_mode = Z_FINISH;
817c2820
VM
268 if ((error = flush_buffer(file)) < GIT_SUCCESS)
269 goto cleanup;
270
f79026b4 271 p_close(file->fd);
c3be1468
VM
272 file->fd = -1;
273
f79026b4 274 error = git_futils_mv_atomic(file->path_lock, file->path_original);
817c2820
VM
275
276cleanup:
277 git_filebuf_cleanup(file);
374db5f9 278 if (error < GIT_SUCCESS)
f6328611
VM
279 return git__rethrow(error, "Failed to commit locked file from buffer");
280 return GIT_SUCCESS;
817c2820
VM
281}
282
72a3fe42 283GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len)
817c2820
VM
284{
285 memcpy(file->buffer + file->buf_pos, buf, len);
286 file->buf_pos += len;
287}
288
72a3fe42 289int git_filebuf_write(git_filebuf *file, const void *buff, size_t len)
817c2820
VM
290{
291 int error;
72a3fe42 292 const unsigned char *buf = buff;
817c2820
VM
293
294 for (;;) {
295 size_t space_left = file->buf_size - file->buf_pos;
296
297 /* cache if it's small */
298 if (space_left > len) {
299 add_to_cache(file, buf, len);
300 return GIT_SUCCESS;
301 }
302
05a62d1a 303 add_to_cache(file, buf, space_left);
817c2820 304
05a62d1a
KS
305 if ((error = flush_buffer(file)) < GIT_SUCCESS)
306 return git__rethrow(error, "Failed to write to buffer");
817c2820 307
05a62d1a
KS
308 len -= space_left;
309 buf += space_left;
817c2820
VM
310 }
311}
312
313int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len)
314{
315 int error;
316 size_t space_left = file->buf_size - file->buf_pos;
317
318 *buffer = NULL;
319
320 if (len > file->buf_size)
321 return GIT_ENOMEM;
322
323 if (space_left <= len) {
324 if ((error = flush_buffer(file)) < GIT_SUCCESS)
374db5f9 325 return git__rethrow(error, "Failed to reserve buffer");
817c2820
VM
326 }
327
328 *buffer = (file->buffer + file->buf_pos);
329 file->buf_pos += len;
330
331 return GIT_SUCCESS;
332}
333
5591ea15
VM
334int git_filebuf_printf(git_filebuf *file, const char *format, ...)
335{
336 va_list arglist;
2fc78e70 337 size_t space_left;
5591ea15 338 int len, error;
2fc78e70 339 char *tmp_buffer;
5591ea15 340
2fc78e70
VM
341 space_left = file->buf_size - file->buf_pos;
342
343 do {
344 va_start(arglist, format);
345 len = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist);
346 va_end(arglist);
347
348 if (len < 0)
349 return git__throw(GIT_EOSERR, "Failed to format string");
350
afeecf4f 351 if ((size_t)len + 1 <= space_left) {
2fc78e70
VM
352 file->buf_pos += len;
353 return GIT_SUCCESS;
354 }
5591ea15 355
5591ea15 356 if ((error = flush_buffer(file)) < GIT_SUCCESS)
f6328611 357 return git__rethrow(error, "Failed to output to buffer");
5591ea15 358
f9213015
VM
359 space_left = file->buf_size - file->buf_pos;
360
afeecf4f 361 } while ((size_t)len + 1 <= space_left);
f9213015 362
2fc78e70
VM
363 tmp_buffer = git__malloc(len + 1);
364 if (!tmp_buffer)
365 return GIT_ENOMEM;
366
367 va_start(arglist, format);
368 len = p_vsnprintf(tmp_buffer, len + 1, format, arglist);
369 va_end(arglist);
370
371 if (len < 0) {
372 free(tmp_buffer);
373 return git__throw(GIT_EOSERR, "Failed to format string");
5591ea15
VM
374 }
375
2fc78e70
VM
376 error = git_filebuf_write(file, tmp_buffer, len);
377 free(tmp_buffer);
5591ea15 378
2fc78e70 379 return error;
5591ea15
VM
380}
381