Merge branch 'jt/perf-updates' into maint
[git] / streaming.c
1 /*
2  * Copyright (c) 2011, Google Inc.
3  */
4 #include "cache.h"
5 #include "streaming.h"
6
7 enum input_source {
8         stream_error = -1,
9         incore = 0,
10         loose = 1,
11         pack_non_delta = 2
12 };
13
14 typedef int (*open_istream_fn)(struct git_istream *,
15                                struct object_info *,
16                                const unsigned char *,
17                                enum object_type *);
18 typedef int (*close_istream_fn)(struct git_istream *);
19 typedef ssize_t (*read_istream_fn)(struct git_istream *, char *, size_t);
20
21 struct stream_vtbl {
22         close_istream_fn close;
23         read_istream_fn read;
24 };
25
26 #define open_method_decl(name) \
27         int open_istream_ ##name \
28         (struct git_istream *st, struct object_info *oi, \
29          const unsigned char *sha1, \
30          enum object_type *type)
31
32 #define close_method_decl(name) \
33         int close_istream_ ##name \
34         (struct git_istream *st)
35
36 #define read_method_decl(name) \
37         ssize_t read_istream_ ##name \
38         (struct git_istream *st, char *buf, size_t sz)
39
40 /* forward declaration */
41 static open_method_decl(incore);
42 static open_method_decl(loose);
43 static open_method_decl(pack_non_delta);
44 static struct git_istream *attach_stream_filter(struct git_istream *st,
45                                                 struct stream_filter *filter);
46
47
48 static open_istream_fn open_istream_tbl[] = {
49         open_istream_incore,
50         open_istream_loose,
51         open_istream_pack_non_delta,
52 };
53
54 #define FILTER_BUFFER (1024*16)
55
56 struct filtered_istream {
57         struct git_istream *upstream;
58         struct stream_filter *filter;
59         char ibuf[FILTER_BUFFER];
60         char obuf[FILTER_BUFFER];
61         int i_end, i_ptr;
62         int o_end, o_ptr;
63         int input_finished;
64 };
65
66 struct git_istream {
67         const struct stream_vtbl *vtbl;
68         unsigned long size; /* inflated size of full object */
69         git_zstream z;
70         enum { z_unused, z_used, z_done, z_error } z_state;
71
72         union {
73                 struct {
74                         char *buf; /* from read_object() */
75                         unsigned long read_ptr;
76                 } incore;
77
78                 struct {
79                         void *mapped;
80                         unsigned long mapsize;
81                         char hdr[32];
82                         int hdr_avail;
83                         int hdr_used;
84                 } loose;
85
86                 struct {
87                         struct packed_git *pack;
88                         off_t pos;
89                 } in_pack;
90
91                 struct filtered_istream filtered;
92         } u;
93 };
94
95 int close_istream(struct git_istream *st)
96 {
97         int r = st->vtbl->close(st);
98         free(st);
99         return r;
100 }
101
102 ssize_t read_istream(struct git_istream *st, void *buf, size_t sz)
103 {
104         return st->vtbl->read(st, buf, sz);
105 }
106
107 static enum input_source istream_source(const unsigned char *sha1,
108                                         enum object_type *type,
109                                         struct object_info *oi)
110 {
111         unsigned long size;
112         int status;
113
114         oi->typep = type;
115         oi->sizep = &size;
116         status = sha1_object_info_extended(sha1, oi, 0);
117         if (status < 0)
118                 return stream_error;
119
120         switch (oi->whence) {
121         case OI_LOOSE:
122                 return loose;
123         case OI_PACKED:
124                 if (!oi->u.packed.is_delta && big_file_threshold < size)
125                         return pack_non_delta;
126                 /* fallthru */
127         default:
128                 return incore;
129         }
130 }
131
132 struct git_istream *open_istream(const unsigned char *sha1,
133                                  enum object_type *type,
134                                  unsigned long *size,
135                                  struct stream_filter *filter)
136 {
137         struct git_istream *st;
138         struct object_info oi = OBJECT_INFO_INIT;
139         const unsigned char *real = lookup_replace_object(sha1);
140         enum input_source src = istream_source(real, type, &oi);
141
142         if (src < 0)
143                 return NULL;
144
145         st = xmalloc(sizeof(*st));
146         if (open_istream_tbl[src](st, &oi, real, type)) {
147                 if (open_istream_incore(st, &oi, real, type)) {
148                         free(st);
149                         return NULL;
150                 }
151         }
152         if (filter) {
153                 /* Add "&& !is_null_stream_filter(filter)" for performance */
154                 struct git_istream *nst = attach_stream_filter(st, filter);
155                 if (!nst) {
156                         close_istream(st);
157                         return NULL;
158                 }
159                 st = nst;
160         }
161
162         *size = st->size;
163         return st;
164 }
165
166
167 /*****************************************************************
168  *
169  * Common helpers
170  *
171  *****************************************************************/
172
173 static void close_deflated_stream(struct git_istream *st)
174 {
175         if (st->z_state == z_used)
176                 git_inflate_end(&st->z);
177 }
178
179
180 /*****************************************************************
181  *
182  * Filtered stream
183  *
184  *****************************************************************/
185
186 static close_method_decl(filtered)
187 {
188         free_stream_filter(st->u.filtered.filter);
189         return close_istream(st->u.filtered.upstream);
190 }
191
192 static read_method_decl(filtered)
193 {
194         struct filtered_istream *fs = &(st->u.filtered);
195         size_t filled = 0;
196
197         while (sz) {
198                 /* do we already have filtered output? */
199                 if (fs->o_ptr < fs->o_end) {
200                         size_t to_move = fs->o_end - fs->o_ptr;
201                         if (sz < to_move)
202                                 to_move = sz;
203                         memcpy(buf + filled, fs->obuf + fs->o_ptr, to_move);
204                         fs->o_ptr += to_move;
205                         sz -= to_move;
206                         filled += to_move;
207                         continue;
208                 }
209                 fs->o_end = fs->o_ptr = 0;
210
211                 /* do we have anything to feed the filter with? */
212                 if (fs->i_ptr < fs->i_end) {
213                         size_t to_feed = fs->i_end - fs->i_ptr;
214                         size_t to_receive = FILTER_BUFFER;
215                         if (stream_filter(fs->filter,
216                                           fs->ibuf + fs->i_ptr, &to_feed,
217                                           fs->obuf, &to_receive))
218                                 return -1;
219                         fs->i_ptr = fs->i_end - to_feed;
220                         fs->o_end = FILTER_BUFFER - to_receive;
221                         continue;
222                 }
223
224                 /* tell the filter to drain upon no more input */
225                 if (fs->input_finished) {
226                         size_t to_receive = FILTER_BUFFER;
227                         if (stream_filter(fs->filter,
228                                           NULL, NULL,
229                                           fs->obuf, &to_receive))
230                                 return -1;
231                         fs->o_end = FILTER_BUFFER - to_receive;
232                         if (!fs->o_end)
233                                 break;
234                         continue;
235                 }
236                 fs->i_end = fs->i_ptr = 0;
237
238                 /* refill the input from the upstream */
239                 if (!fs->input_finished) {
240                         fs->i_end = read_istream(fs->upstream, fs->ibuf, FILTER_BUFFER);
241                         if (fs->i_end < 0)
242                                 return -1;
243                         if (fs->i_end)
244                                 continue;
245                 }
246                 fs->input_finished = 1;
247         }
248         return filled;
249 }
250
251 static struct stream_vtbl filtered_vtbl = {
252         close_istream_filtered,
253         read_istream_filtered,
254 };
255
256 static struct git_istream *attach_stream_filter(struct git_istream *st,
257                                                 struct stream_filter *filter)
258 {
259         struct git_istream *ifs = xmalloc(sizeof(*ifs));
260         struct filtered_istream *fs = &(ifs->u.filtered);
261
262         ifs->vtbl = &filtered_vtbl;
263         fs->upstream = st;
264         fs->filter = filter;
265         fs->i_end = fs->i_ptr = 0;
266         fs->o_end = fs->o_ptr = 0;
267         fs->input_finished = 0;
268         ifs->size = -1; /* unknown */
269         return ifs;
270 }
271
272 /*****************************************************************
273  *
274  * Loose object stream
275  *
276  *****************************************************************/
277
278 static read_method_decl(loose)
279 {
280         size_t total_read = 0;
281
282         switch (st->z_state) {
283         case z_done:
284                 return 0;
285         case z_error:
286                 return -1;
287         default:
288                 break;
289         }
290
291         if (st->u.loose.hdr_used < st->u.loose.hdr_avail) {
292                 size_t to_copy = st->u.loose.hdr_avail - st->u.loose.hdr_used;
293                 if (sz < to_copy)
294                         to_copy = sz;
295                 memcpy(buf, st->u.loose.hdr + st->u.loose.hdr_used, to_copy);
296                 st->u.loose.hdr_used += to_copy;
297                 total_read += to_copy;
298         }
299
300         while (total_read < sz) {
301                 int status;
302
303                 st->z.next_out = (unsigned char *)buf + total_read;
304                 st->z.avail_out = sz - total_read;
305                 status = git_inflate(&st->z, Z_FINISH);
306
307                 total_read = st->z.next_out - (unsigned char *)buf;
308
309                 if (status == Z_STREAM_END) {
310                         git_inflate_end(&st->z);
311                         st->z_state = z_done;
312                         break;
313                 }
314                 if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) {
315                         git_inflate_end(&st->z);
316                         st->z_state = z_error;
317                         return -1;
318                 }
319         }
320         return total_read;
321 }
322
323 static close_method_decl(loose)
324 {
325         close_deflated_stream(st);
326         munmap(st->u.loose.mapped, st->u.loose.mapsize);
327         return 0;
328 }
329
330 static struct stream_vtbl loose_vtbl = {
331         close_istream_loose,
332         read_istream_loose,
333 };
334
335 static open_method_decl(loose)
336 {
337         st->u.loose.mapped = map_sha1_file(sha1, &st->u.loose.mapsize);
338         if (!st->u.loose.mapped)
339                 return -1;
340         if ((unpack_sha1_header(&st->z,
341                                 st->u.loose.mapped,
342                                 st->u.loose.mapsize,
343                                 st->u.loose.hdr,
344                                 sizeof(st->u.loose.hdr)) < 0) ||
345             (parse_sha1_header(st->u.loose.hdr, &st->size) < 0)) {
346                 git_inflate_end(&st->z);
347                 munmap(st->u.loose.mapped, st->u.loose.mapsize);
348                 return -1;
349         }
350
351         st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1;
352         st->u.loose.hdr_avail = st->z.total_out;
353         st->z_state = z_used;
354
355         st->vtbl = &loose_vtbl;
356         return 0;
357 }
358
359
360 /*****************************************************************
361  *
362  * Non-delta packed object stream
363  *
364  *****************************************************************/
365
366 static read_method_decl(pack_non_delta)
367 {
368         size_t total_read = 0;
369
370         switch (st->z_state) {
371         case z_unused:
372                 memset(&st->z, 0, sizeof(st->z));
373                 git_inflate_init(&st->z);
374                 st->z_state = z_used;
375                 break;
376         case z_done:
377                 return 0;
378         case z_error:
379                 return -1;
380         case z_used:
381                 break;
382         }
383
384         while (total_read < sz) {
385                 int status;
386                 struct pack_window *window = NULL;
387                 unsigned char *mapped;
388
389                 mapped = use_pack(st->u.in_pack.pack, &window,
390                                   st->u.in_pack.pos, &st->z.avail_in);
391
392                 st->z.next_out = (unsigned char *)buf + total_read;
393                 st->z.avail_out = sz - total_read;
394                 st->z.next_in = mapped;
395                 status = git_inflate(&st->z, Z_FINISH);
396
397                 st->u.in_pack.pos += st->z.next_in - mapped;
398                 total_read = st->z.next_out - (unsigned char *)buf;
399                 unuse_pack(&window);
400
401                 if (status == Z_STREAM_END) {
402                         git_inflate_end(&st->z);
403                         st->z_state = z_done;
404                         break;
405                 }
406                 if (status != Z_OK && status != Z_BUF_ERROR) {
407                         git_inflate_end(&st->z);
408                         st->z_state = z_error;
409                         return -1;
410                 }
411         }
412         return total_read;
413 }
414
415 static close_method_decl(pack_non_delta)
416 {
417         close_deflated_stream(st);
418         return 0;
419 }
420
421 static struct stream_vtbl pack_non_delta_vtbl = {
422         close_istream_pack_non_delta,
423         read_istream_pack_non_delta,
424 };
425
426 static open_method_decl(pack_non_delta)
427 {
428         struct pack_window *window;
429         enum object_type in_pack_type;
430
431         st->u.in_pack.pack = oi->u.packed.pack;
432         st->u.in_pack.pos = oi->u.packed.offset;
433         window = NULL;
434
435         in_pack_type = unpack_object_header(st->u.in_pack.pack,
436                                             &window,
437                                             &st->u.in_pack.pos,
438                                             &st->size);
439         unuse_pack(&window);
440         switch (in_pack_type) {
441         default:
442                 return -1; /* we do not do deltas for now */
443         case OBJ_COMMIT:
444         case OBJ_TREE:
445         case OBJ_BLOB:
446         case OBJ_TAG:
447                 break;
448         }
449         st->z_state = z_unused;
450         st->vtbl = &pack_non_delta_vtbl;
451         return 0;
452 }
453
454
455 /*****************************************************************
456  *
457  * In-core stream
458  *
459  *****************************************************************/
460
461 static close_method_decl(incore)
462 {
463         free(st->u.incore.buf);
464         return 0;
465 }
466
467 static read_method_decl(incore)
468 {
469         size_t read_size = sz;
470         size_t remainder = st->size - st->u.incore.read_ptr;
471
472         if (remainder <= read_size)
473                 read_size = remainder;
474         if (read_size) {
475                 memcpy(buf, st->u.incore.buf + st->u.incore.read_ptr, read_size);
476                 st->u.incore.read_ptr += read_size;
477         }
478         return read_size;
479 }
480
481 static struct stream_vtbl incore_vtbl = {
482         close_istream_incore,
483         read_istream_incore,
484 };
485
486 static open_method_decl(incore)
487 {
488         st->u.incore.buf = read_sha1_file_extended(sha1, type, &st->size, 0);
489         st->u.incore.read_ptr = 0;
490         st->vtbl = &incore_vtbl;
491
492         return st->u.incore.buf ? 0 : -1;
493 }
494
495
496 /****************************************************************
497  * Users of streaming interface
498  ****************************************************************/
499
500 int stream_blob_to_fd(int fd, const struct object_id *oid, struct stream_filter *filter,
501                       int can_seek)
502 {
503         struct git_istream *st;
504         enum object_type type;
505         unsigned long sz;
506         ssize_t kept = 0;
507         int result = -1;
508
509         st = open_istream(oid->hash, &type, &sz, filter);
510         if (!st) {
511                 if (filter)
512                         free_stream_filter(filter);
513                 return result;
514         }
515         if (type != OBJ_BLOB)
516                 goto close_and_exit;
517         for (;;) {
518                 char buf[1024 * 16];
519                 ssize_t wrote, holeto;
520                 ssize_t readlen = read_istream(st, buf, sizeof(buf));
521
522                 if (readlen < 0)
523                         goto close_and_exit;
524                 if (!readlen)
525                         break;
526                 if (can_seek && sizeof(buf) == readlen) {
527                         for (holeto = 0; holeto < readlen; holeto++)
528                                 if (buf[holeto])
529                                         break;
530                         if (readlen == holeto) {
531                                 kept += holeto;
532                                 continue;
533                         }
534                 }
535
536                 if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
537                         goto close_and_exit;
538                 else
539                         kept = 0;
540                 wrote = write_in_full(fd, buf, readlen);
541
542                 if (wrote != readlen)
543                         goto close_and_exit;
544         }
545         if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
546                      xwrite(fd, "", 1) != 1))
547                 goto close_and_exit;
548         result = 0;
549
550  close_and_exit:
551         close_istream(st);
552         return result;
553 }