]>
Commit | Line | Data |
---|---|---|
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 |
13 | static const size_t WRITE_BUFFER_SIZE = (4096 * 2); |
14 | ||
15 | static 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 | ||
56 | void 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 | 76 | GIT_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 | 83 | static 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 | 96 | static 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 |
131 | int 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 | ||
229 | cleanup: | |
230 | git_filebuf_cleanup(file); | |
f6328611 | 231 | return git__rethrow(error, "Failed to open file buffer for '%s'", path); |
817c2820 VM |
232 | } |
233 | ||
234 | int 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 |
250 | int 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 |
260 | int 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 | |
276 | cleanup: | |
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 | 283 | GIT_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 | 289 | int 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 | ||
313 | int 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 |
334 | int 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 |