]> git.proxmox.com Git - libgit2.git/blob - src/filebuf.c
pkt-line: parse other-ref lines
[libgit2.git] / src / filebuf.c
1 /*
2 * This file is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2,
4 * as published by the Free Software Foundation.
5 *
6 * In addition to the permissions in the GNU General Public License,
7 * the authors give you unlimited permission to link the compiled
8 * version of this file into combinations with other programs,
9 * and to distribute those combinations without any restriction
10 * coming from the use of this file. (The General Public License
11 * restrictions do apply in other respects; for example, they cover
12 * modification of the file, and distribution when not linked into
13 * a combined executable.)
14 *
15 * This file is distributed in the hope that it will be useful, but
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; see the file COPYING. If not, write to
22 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25 #include <stdarg.h>
26
27 #include "common.h"
28 #include "filebuf.h"
29 #include "fileops.h"
30
31 static const size_t WRITE_BUFFER_SIZE = (4096 * 2);
32
33 static int lock_file(git_filebuf *file, int flags)
34 {
35 if (gitfo_exists(file->path_lock) == 0) {
36 if (flags & GIT_FILEBUF_FORCE)
37 gitfo_unlink(file->path_lock);
38 else
39 return git__throw(GIT_EOSERR, "Failed to lock file");
40 }
41
42 /* create path to the file buffer is required */
43 if (flags & GIT_FILEBUF_FORCE) {
44 file->fd = gitfo_creat_locked_force(file->path_lock, 0644);
45 } else {
46 file->fd = gitfo_creat_locked(file->path_lock, 0644);
47 }
48
49 if (file->fd < 0)
50 return git__throw(GIT_EOSERR, "Failed to create lock");
51
52 if ((flags & GIT_FILEBUF_APPEND) && gitfo_exists(file->path_original) == 0) {
53 git_file source;
54 char buffer[2048];
55 size_t read_bytes;
56
57 source = gitfo_open(file->path_original, O_RDONLY);
58 if (source < 0)
59 return git__throw(GIT_EOSERR, "Failed to lock file. Could not open %s", file->path_original);
60
61 while ((read_bytes = gitfo_read(source, buffer, 2048)) > 0) {
62 gitfo_write(file->fd, buffer, read_bytes);
63 if (file->digest)
64 git_hash_update(file->digest, buffer, read_bytes);
65 }
66
67 gitfo_close(source);
68 }
69
70 return GIT_SUCCESS;
71 }
72
73 void git_filebuf_cleanup(git_filebuf *file)
74 {
75 if (file->fd >= 0)
76 gitfo_close(file->fd);
77
78 if (file->fd >= 0 && file->path_lock && gitfo_exists(file->path_lock) == GIT_SUCCESS)
79 gitfo_unlink(file->path_lock);
80
81 if (file->digest)
82 git_hash_free_ctx(file->digest);
83
84 free(file->buffer);
85 free(file->z_buf);
86
87 deflateEnd(&file->zs);
88
89 free(file->path_original);
90 free(file->path_lock);
91 }
92
93 GIT_INLINE(int) flush_buffer(git_filebuf *file)
94 {
95 int result = file->write(file, file->buffer, file->buf_pos);
96 file->buf_pos = 0;
97 return result;
98 }
99
100 static int write_normal(git_filebuf *file, const void *source, size_t len)
101 {
102 int result = 0;
103
104 if (len > 0) {
105 result = gitfo_write(file->fd, (void *)source, len);
106 if (file->digest)
107 git_hash_update(file->digest, source, len);
108 }
109
110 return result;
111 }
112
113 static int write_deflate(git_filebuf *file, const void *source, size_t len)
114 {
115 int result = Z_OK;
116 z_stream *zs = &file->zs;
117
118 if (len > 0 || file->flush_mode == Z_FINISH) {
119 zs->next_in = (void *)source;
120 zs->avail_in = len;
121
122 do {
123 int have;
124
125 zs->next_out = file->z_buf;
126 zs->avail_out = file->buf_size;
127
128 result = deflate(zs, file->flush_mode);
129 assert(result != Z_STREAM_ERROR);
130
131 have = file->buf_size - zs->avail_out;
132
133 if (gitfo_write(file->fd, file->z_buf, have) < GIT_SUCCESS)
134 return git__throw(GIT_EOSERR, "Failed to write to file");
135
136 } while (zs->avail_out == 0);
137
138 assert(zs->avail_in == 0);
139
140 if (file->digest)
141 git_hash_update(file->digest, source, len);
142 }
143
144 return GIT_SUCCESS;
145 }
146
147 int git_filebuf_open(git_filebuf *file, const char *path, int flags)
148 {
149 int error;
150 size_t path_len;
151
152 assert(file && path);
153
154 memset(file, 0x0, sizeof(git_filebuf));
155
156 file->buf_size = WRITE_BUFFER_SIZE;
157 file->buf_pos = 0;
158 file->fd = -1;
159
160 /* Allocate the main cache buffer */
161 file->buffer = git__malloc(file->buf_size);
162 if (file->buffer == NULL){
163 error = GIT_ENOMEM;
164 goto cleanup;
165 }
166
167 /* If we are hashing on-write, allocate a new hash context */
168 if (flags & GIT_FILEBUF_HASH_CONTENTS) {
169 if ((file->digest = git_hash_new_ctx()) == NULL) {
170 error = GIT_ENOMEM;
171 goto cleanup;
172 }
173 }
174
175 /* If we are deflating on-write, */
176 if (flags & GIT_FILEBUF_DEFLATE_CONTENTS) {
177
178 /* Initialize the ZLib stream */
179 if (deflateInit(&file->zs, Z_BEST_SPEED) != Z_OK) {
180 error = git__throw(GIT_EZLIB, "Failed to initialize zlib");
181 goto cleanup;
182 }
183
184 /* Allocate the Zlib cache buffer */
185 file->z_buf = git__malloc(file->buf_size);
186 if (file->z_buf == NULL){
187 error = GIT_ENOMEM;
188 goto cleanup;
189 }
190
191 /* Never flush */
192 file->flush_mode = Z_NO_FLUSH;
193 file->write = &write_deflate;
194 } else {
195 file->write = &write_normal;
196 }
197
198 /* If we are writing to a temp file */
199 if (flags & GIT_FILEBUF_TEMPORARY) {
200 char tmp_path[GIT_PATH_MAX];
201
202 /* Open the file as temporary for locking */
203 file->fd = gitfo_mktemp(tmp_path, path);
204 if (file->fd < 0) {
205 error = GIT_EOSERR;
206 goto cleanup;
207 }
208
209 /* No original path */
210 file->path_original = NULL;
211 file->path_lock = git__strdup(tmp_path);
212
213 if (file->path_lock == NULL) {
214 error = GIT_ENOMEM;
215 goto cleanup;
216 }
217 } else {
218 path_len = strlen(path);
219
220 /* Save the original path of the file */
221 file->path_original = git__strdup(path);
222 if (file->path_original == NULL) {
223 error = GIT_ENOMEM;
224 goto cleanup;
225 }
226
227 /* create the locking path by appending ".lock" to the original */
228 file->path_lock = git__malloc(path_len + GIT_FILELOCK_EXTLENGTH);
229 if (file->path_lock == NULL) {
230 error = GIT_ENOMEM;
231 goto cleanup;
232 }
233
234 memcpy(file->path_lock, file->path_original, path_len);
235 memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH);
236
237 /* open the file for locking */
238 if ((error = lock_file(file, flags)) < GIT_SUCCESS)
239 goto cleanup;
240 }
241
242 return GIT_SUCCESS;
243
244 cleanup:
245 git_filebuf_cleanup(file);
246 return git__rethrow(error, "Failed to open file buffer for '%s'", path);
247 }
248
249 int git_filebuf_hash(git_oid *oid, git_filebuf *file)
250 {
251 int error;
252
253 assert(oid && file && file->digest);
254
255 if ((error = flush_buffer(file)) < GIT_SUCCESS)
256 return git__rethrow(error, "Failed to get hash for file");
257
258 git_hash_final(oid, file->digest);
259 git_hash_free_ctx(file->digest);
260 file->digest = NULL;
261
262 return GIT_SUCCESS;
263 }
264
265 int git_filebuf_commit_at(git_filebuf *file, const char *path)
266 {
267 free(file->path_original);
268 file->path_original = git__strdup(path);
269 if (file->path_original == NULL)
270 return GIT_ENOMEM;
271
272 return git_filebuf_commit(file);
273 }
274
275 int git_filebuf_commit(git_filebuf *file)
276 {
277 int error;
278
279 /* temporary files cannot be committed */
280 assert(file && file->path_original);
281
282 file->flush_mode = Z_FINISH;
283 if ((error = flush_buffer(file)) < GIT_SUCCESS)
284 goto cleanup;
285
286 gitfo_close(file->fd);
287 file->fd = -1;
288
289 error = gitfo_mv(file->path_lock, file->path_original);
290
291 cleanup:
292 git_filebuf_cleanup(file);
293 if (error < GIT_SUCCESS)
294 return git__rethrow(error, "Failed to commit locked file from buffer");
295 return GIT_SUCCESS;
296 }
297
298 GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len)
299 {
300 memcpy(file->buffer + file->buf_pos, buf, len);
301 file->buf_pos += len;
302 }
303
304 int git_filebuf_write(git_filebuf *file, const void *buff, size_t len)
305 {
306 int error;
307 const unsigned char *buf = buff;
308
309 for (;;) {
310 size_t space_left = file->buf_size - file->buf_pos;
311
312 /* cache if it's small */
313 if (space_left > len) {
314 add_to_cache(file, buf, len);
315 return GIT_SUCCESS;
316 }
317
318 /* flush the cache if it doesn't fit */
319 if (file->buf_pos > 0) {
320 add_to_cache(file, buf, space_left);
321
322 if ((error = flush_buffer(file)) < GIT_SUCCESS)
323 return git__rethrow(error, "Failed to write to buffer");
324
325 len -= space_left;
326 buf += space_left;
327 }
328
329 /* write too-large chunks immediately */
330 if (len > file->buf_size) {
331 error = file->write(file, buf, len);
332 if (error < GIT_SUCCESS)
333 return git__rethrow(error, "Failed to write to buffer");
334 }
335 }
336 }
337
338 int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len)
339 {
340 int error;
341 size_t space_left = file->buf_size - file->buf_pos;
342
343 *buffer = NULL;
344
345 if (len > file->buf_size)
346 return GIT_ENOMEM;
347
348 if (space_left <= len) {
349 if ((error = flush_buffer(file)) < GIT_SUCCESS)
350 return git__rethrow(error, "Failed to reserve buffer");
351 }
352
353 *buffer = (file->buffer + file->buf_pos);
354 file->buf_pos += len;
355
356 return GIT_SUCCESS;
357 }
358
359 int git_filebuf_printf(git_filebuf *file, const char *format, ...)
360 {
361 va_list arglist;
362 size_t space_left = file->buf_size - file->buf_pos;
363 int len, error;
364
365 va_start(arglist, format);
366 len = vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist);
367 va_end(arglist);
368
369 if (len < 0 || (size_t)len >= space_left) {
370 if ((error = flush_buffer(file)) < GIT_SUCCESS)
371 return git__rethrow(error, "Failed to output to buffer");
372
373 space_left = file->buf_size - file->buf_pos;
374
375 va_start(arglist, format);
376 len = vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist);
377 va_end(arglist);
378
379 if (len < 0 || (size_t)len > file->buf_size)
380 return GIT_ENOMEM;
381 }
382
383 file->buf_pos += len;
384 return GIT_SUCCESS;
385
386 }
387