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 "filter.h"
9
10 #include "common.h"
11 #include "fileops.h"
12 #include "hash.h"
13 #include "repository.h"
14 #include "global.h"
15 #include "git2/sys/filter.h"
16 #include "git2/config.h"
17 #include "blob.h"
18 #include "attr_file.h"
19 #include "array.h"
20
21 struct git_filter_source {
22 git_repository *repo;
23 const char *path;
24 git_oid oid; /* zero if unknown (which is likely) */
25 uint16_t filemode; /* zero if unknown */
26 git_filter_mode_t mode;
27 uint32_t flags;
28 };
29
30 typedef struct {
31 const char *filter_name;
32 git_filter *filter;
33 void *payload;
34 } git_filter_entry;
35
36 struct git_filter_list {
37 git_array_t(git_filter_entry) filters;
38 git_filter_source source;
39 git_buf *temp_buf;
40 char path[GIT_FLEX_ARRAY];
41 };
42
43 typedef struct {
44 char *filter_name;
45 git_filter *filter;
46 int priority;
47 int initialized;
48 size_t nattrs, nmatches;
49 char *attrdata;
50 const char *attrs[GIT_FLEX_ARRAY];
51 } git_filter_def;
52
filter_def_priority_cmp(const void * a,const void * b)53 static int filter_def_priority_cmp(const void *a, const void *b)
54 {
55 int pa = ((const git_filter_def *)a)->priority;
56 int pb = ((const git_filter_def *)b)->priority;
57 return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
58 }
59
60 struct git_filter_registry {
61 git_rwlock lock;
62 git_vector filters;
63 };
64
65 static struct git_filter_registry filter_registry;
66
67 static void git_filter_global_shutdown(void);
68
69
filter_def_scan_attrs(git_buf * attrs,size_t * nattr,size_t * nmatch,const char * attr_str)70 static int filter_def_scan_attrs(
71 git_buf *attrs, size_t *nattr, size_t *nmatch, const char *attr_str)
72 {
73 const char *start, *scan = attr_str;
74 int has_eq;
75
76 *nattr = *nmatch = 0;
77
78 if (!scan)
79 return 0;
80
81 while (*scan) {
82 while (git__isspace(*scan)) scan++;
83
84 for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) {
85 if (*scan == '=')
86 has_eq = 1;
87 }
88
89 if (scan > start) {
90 (*nattr)++;
91 if (has_eq || *start == '-' || *start == '+' || *start == '!')
92 (*nmatch)++;
93
94 if (has_eq)
95 git_buf_putc(attrs, '=');
96 git_buf_put(attrs, start, scan - start);
97 git_buf_putc(attrs, '\0');
98 }
99 }
100
101 return 0;
102 }
103
filter_def_set_attrs(git_filter_def * fdef)104 static void filter_def_set_attrs(git_filter_def *fdef)
105 {
106 char *scan = fdef->attrdata;
107 size_t i;
108
109 for (i = 0; i < fdef->nattrs; ++i) {
110 const char *name, *value;
111
112 switch (*scan) {
113 case '=':
114 name = scan + 1;
115 for (scan++; *scan != '='; scan++) /* find '=' */;
116 *scan++ = '\0';
117 value = scan;
118 break;
119 case '-':
120 name = scan + 1; value = git_attr__false; break;
121 case '+':
122 name = scan + 1; value = git_attr__true; break;
123 case '!':
124 name = scan + 1; value = git_attr__unset; break;
125 default:
126 name = scan; value = NULL; break;
127 }
128
129 fdef->attrs[i] = name;
130 fdef->attrs[i + fdef->nattrs] = value;
131
132 scan += strlen(scan) + 1;
133 }
134 }
135
filter_def_name_key_check(const void * key,const void * fdef)136 static int filter_def_name_key_check(const void *key, const void *fdef)
137 {
138 const char *name =
139 fdef ? ((const git_filter_def *)fdef)->filter_name : NULL;
140 return name ? git__strcmp(key, name) : -1;
141 }
142
filter_def_filter_key_check(const void * key,const void * fdef)143 static int filter_def_filter_key_check(const void *key, const void *fdef)
144 {
145 const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL;
146 return (key == filter) ? 0 : -1;
147 }
148
149 /* Note: callers must lock the registry before calling this function */
filter_registry_insert(const char * name,git_filter * filter,int priority)150 static int filter_registry_insert(
151 const char *name, git_filter *filter, int priority)
152 {
153 git_filter_def *fdef;
154 size_t nattr = 0, nmatch = 0, alloc_len;
155 git_buf attrs = GIT_BUF_INIT;
156
157 if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0)
158 return -1;
159
160 GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, nattr, 2);
161 GIT_ERROR_CHECK_ALLOC_MULTIPLY(&alloc_len, alloc_len, sizeof(char *));
162 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, sizeof(git_filter_def));
163
164 fdef = git__calloc(1, alloc_len);
165 GIT_ERROR_CHECK_ALLOC(fdef);
166
167 fdef->filter_name = git__strdup(name);
168 GIT_ERROR_CHECK_ALLOC(fdef->filter_name);
169
170 fdef->filter = filter;
171 fdef->priority = priority;
172 fdef->nattrs = nattr;
173 fdef->nmatches = nmatch;
174 fdef->attrdata = git_buf_detach(&attrs);
175
176 filter_def_set_attrs(fdef);
177
178 if (git_vector_insert(&filter_registry.filters, fdef) < 0) {
179 git__free(fdef->filter_name);
180 git__free(fdef->attrdata);
181 git__free(fdef);
182 return -1;
183 }
184
185 git_vector_sort(&filter_registry.filters);
186 return 0;
187 }
188
git_filter_global_init(void)189 int git_filter_global_init(void)
190 {
191 git_filter *crlf = NULL, *ident = NULL;
192 int error = 0;
193
194 if (git_rwlock_init(&filter_registry.lock) < 0)
195 return -1;
196
197 if ((error = git_vector_init(&filter_registry.filters, 2,
198 filter_def_priority_cmp)) < 0)
199 goto done;
200
201 if ((crlf = git_crlf_filter_new()) == NULL ||
202 filter_registry_insert(
203 GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0 ||
204 (ident = git_ident_filter_new()) == NULL ||
205 filter_registry_insert(
206 GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0)
207 error = -1;
208
209 git__on_shutdown(git_filter_global_shutdown);
210
211 done:
212 if (error) {
213 git_filter_free(crlf);
214 git_filter_free(ident);
215 }
216
217 return error;
218 }
219
git_filter_global_shutdown(void)220 static void git_filter_global_shutdown(void)
221 {
222 size_t pos;
223 git_filter_def *fdef;
224
225 if (git_rwlock_wrlock(&filter_registry.lock) < 0)
226 return;
227
228 git_vector_foreach(&filter_registry.filters, pos, fdef) {
229 if (fdef->filter && fdef->filter->shutdown) {
230 fdef->filter->shutdown(fdef->filter);
231 fdef->initialized = false;
232 }
233
234 git__free(fdef->filter_name);
235 git__free(fdef->attrdata);
236 git__free(fdef);
237 }
238
239 git_vector_free(&filter_registry.filters);
240
241 git_rwlock_wrunlock(&filter_registry.lock);
242 git_rwlock_free(&filter_registry.lock);
243 }
244
245 /* Note: callers must lock the registry before calling this function */
filter_registry_find(size_t * pos,const char * name)246 static int filter_registry_find(size_t *pos, const char *name)
247 {
248 return git_vector_search2(
249 pos, &filter_registry.filters, filter_def_name_key_check, name);
250 }
251
252 /* Note: callers must lock the registry before calling this function */
filter_registry_lookup(size_t * pos,const char * name)253 static git_filter_def *filter_registry_lookup(size_t *pos, const char *name)
254 {
255 git_filter_def *fdef = NULL;
256
257 if (!filter_registry_find(pos, name))
258 fdef = git_vector_get(&filter_registry.filters, *pos);
259
260 return fdef;
261 }
262
263
git_filter_register(const char * name,git_filter * filter,int priority)264 int git_filter_register(
265 const char *name, git_filter *filter, int priority)
266 {
267 int error;
268
269 assert(name && filter);
270
271 if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
272 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
273 return -1;
274 }
275
276 if (!filter_registry_find(NULL, name)) {
277 git_error_set(
278 GIT_ERROR_FILTER, "attempt to reregister existing filter '%s'", name);
279 error = GIT_EEXISTS;
280 goto done;
281 }
282
283 error = filter_registry_insert(name, filter, priority);
284
285 done:
286 git_rwlock_wrunlock(&filter_registry.lock);
287 return error;
288 }
289
git_filter_unregister(const char * name)290 int git_filter_unregister(const char *name)
291 {
292 size_t pos;
293 git_filter_def *fdef;
294 int error = 0;
295
296 assert(name);
297
298 /* cannot unregister default filters */
299 if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) {
300 git_error_set(GIT_ERROR_FILTER, "cannot unregister filter '%s'", name);
301 return -1;
302 }
303
304 if (git_rwlock_wrlock(&filter_registry.lock) < 0) {
305 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
306 return -1;
307 }
308
309 if ((fdef = filter_registry_lookup(&pos, name)) == NULL) {
310 git_error_set(GIT_ERROR_FILTER, "cannot find filter '%s' to unregister", name);
311 error = GIT_ENOTFOUND;
312 goto done;
313 }
314
315 git_vector_remove(&filter_registry.filters, pos);
316
317 if (fdef->initialized && fdef->filter && fdef->filter->shutdown) {
318 fdef->filter->shutdown(fdef->filter);
319 fdef->initialized = false;
320 }
321
322 git__free(fdef->filter_name);
323 git__free(fdef->attrdata);
324 git__free(fdef);
325
326 done:
327 git_rwlock_wrunlock(&filter_registry.lock);
328 return error;
329 }
330
filter_initialize(git_filter_def * fdef)331 static int filter_initialize(git_filter_def *fdef)
332 {
333 int error = 0;
334
335 if (!fdef->initialized && fdef->filter && fdef->filter->initialize) {
336 if ((error = fdef->filter->initialize(fdef->filter)) < 0)
337 return error;
338 }
339
340 fdef->initialized = true;
341 return 0;
342 }
343
git_filter_lookup(const char * name)344 git_filter *git_filter_lookup(const char *name)
345 {
346 size_t pos;
347 git_filter_def *fdef;
348 git_filter *filter = NULL;
349
350 if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
351 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
352 return NULL;
353 }
354
355 if ((fdef = filter_registry_lookup(&pos, name)) == NULL ||
356 (!fdef->initialized && filter_initialize(fdef) < 0))
357 goto done;
358
359 filter = fdef->filter;
360
361 done:
362 git_rwlock_rdunlock(&filter_registry.lock);
363 return filter;
364 }
365
git_filter_free(git_filter * filter)366 void git_filter_free(git_filter *filter)
367 {
368 git__free(filter);
369 }
370
git_filter_source_repo(const git_filter_source * src)371 git_repository *git_filter_source_repo(const git_filter_source *src)
372 {
373 return src->repo;
374 }
375
git_filter_source_path(const git_filter_source * src)376 const char *git_filter_source_path(const git_filter_source *src)
377 {
378 return src->path;
379 }
380
git_filter_source_filemode(const git_filter_source * src)381 uint16_t git_filter_source_filemode(const git_filter_source *src)
382 {
383 return src->filemode;
384 }
385
git_filter_source_id(const git_filter_source * src)386 const git_oid *git_filter_source_id(const git_filter_source *src)
387 {
388 return git_oid_iszero(&src->oid) ? NULL : &src->oid;
389 }
390
git_filter_source_mode(const git_filter_source * src)391 git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
392 {
393 return src->mode;
394 }
395
git_filter_source_flags(const git_filter_source * src)396 uint32_t git_filter_source_flags(const git_filter_source *src)
397 {
398 return src->flags;
399 }
400
filter_list_new(git_filter_list ** out,const git_filter_source * src)401 static int filter_list_new(
402 git_filter_list **out, const git_filter_source *src)
403 {
404 git_filter_list *fl = NULL;
405 size_t pathlen = src->path ? strlen(src->path) : 0, alloclen;
406
407 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, sizeof(git_filter_list), pathlen);
408 GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
409
410 fl = git__calloc(1, alloclen);
411 GIT_ERROR_CHECK_ALLOC(fl);
412
413 if (src->path)
414 memcpy(fl->path, src->path, pathlen);
415 fl->source.repo = src->repo;
416 fl->source.path = fl->path;
417 fl->source.mode = src->mode;
418 fl->source.flags = src->flags;
419
420 *out = fl;
421 return 0;
422 }
423
filter_list_check_attributes(const char *** out,git_repository * repo,git_attr_session * attr_session,git_filter_def * fdef,const git_filter_source * src)424 static int filter_list_check_attributes(
425 const char ***out,
426 git_repository *repo,
427 git_attr_session *attr_session,
428 git_filter_def *fdef,
429 const git_filter_source *src)
430 {
431 int error;
432 size_t i;
433 const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
434 GIT_ERROR_CHECK_ALLOC(strs);
435
436 error = git_attr_get_many_with_session(
437 strs, repo, attr_session, 0, src->path, fdef->nattrs, fdef->attrs);
438
439 /* if no values were found but no matches are needed, it's okay! */
440 if (error == GIT_ENOTFOUND && !fdef->nmatches) {
441 git_error_clear();
442 git__free((void *)strs);
443 return 0;
444 }
445
446 for (i = 0; !error && i < fdef->nattrs; ++i) {
447 const char *want = fdef->attrs[fdef->nattrs + i];
448 git_attr_t want_type, found_type;
449
450 if (!want)
451 continue;
452
453 want_type = git_attr_value(want);
454 found_type = git_attr_value(strs[i]);
455
456 if (want_type != found_type)
457 error = GIT_ENOTFOUND;
458 else if (want_type == GIT_ATTR_VALUE_T &&
459 strcmp(want, strs[i]) &&
460 strcmp(want, "*"))
461 error = GIT_ENOTFOUND;
462 }
463
464 if (error)
465 git__free((void *)strs);
466 else
467 *out = strs;
468
469 return error;
470 }
471
git_filter_list_new(git_filter_list ** out,git_repository * repo,git_filter_mode_t mode,uint32_t flags)472 int git_filter_list_new(
473 git_filter_list **out,
474 git_repository *repo,
475 git_filter_mode_t mode,
476 uint32_t flags)
477 {
478 git_filter_source src = { 0 };
479 src.repo = repo;
480 src.path = NULL;
481 src.mode = mode;
482 src.flags = flags;
483 return filter_list_new(out, &src);
484 }
485
git_filter_list__load_ext(git_filter_list ** filters,git_repository * repo,git_blob * blob,const char * path,git_filter_mode_t mode,git_filter_options * filter_opts)486 int git_filter_list__load_ext(
487 git_filter_list **filters,
488 git_repository *repo,
489 git_blob *blob, /* can be NULL */
490 const char *path,
491 git_filter_mode_t mode,
492 git_filter_options *filter_opts)
493 {
494 int error = 0;
495 git_filter_list *fl = NULL;
496 git_filter_source src = { 0 };
497 git_filter_entry *fe;
498 size_t idx;
499 git_filter_def *fdef;
500
501 if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
502 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
503 return -1;
504 }
505
506 src.repo = repo;
507 src.path = path;
508 src.mode = mode;
509 src.flags = filter_opts->flags;
510
511 if (blob)
512 git_oid_cpy(&src.oid, git_blob_id(blob));
513
514 git_vector_foreach(&filter_registry.filters, idx, fdef) {
515 const char **values = NULL;
516 void *payload = NULL;
517
518 if (!fdef || !fdef->filter)
519 continue;
520
521 if (fdef->nattrs > 0) {
522 error = filter_list_check_attributes(
523 &values, repo, filter_opts->attr_session, fdef, &src);
524
525 if (error == GIT_ENOTFOUND) {
526 error = 0;
527 continue;
528 } else if (error < 0)
529 break;
530 }
531
532 if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
533 break;
534
535 if (fdef->filter->check)
536 error = fdef->filter->check(
537 fdef->filter, &payload, &src, values);
538
539 git__free((void *)values);
540
541 if (error == GIT_PASSTHROUGH)
542 error = 0;
543 else if (error < 0)
544 break;
545 else {
546 if (!fl) {
547 if ((error = filter_list_new(&fl, &src)) < 0)
548 break;
549
550 fl->temp_buf = filter_opts->temp_buf;
551 }
552
553 fe = git_array_alloc(fl->filters);
554 GIT_ERROR_CHECK_ALLOC(fe);
555
556 fe->filter = fdef->filter;
557 fe->filter_name = fdef->filter_name;
558 fe->payload = payload;
559 }
560 }
561
562 git_rwlock_rdunlock(&filter_registry.lock);
563
564 if (error && fl != NULL) {
565 git_array_clear(fl->filters);
566 git__free(fl);
567 fl = NULL;
568 }
569
570 *filters = fl;
571 return error;
572 }
573
git_filter_list_load(git_filter_list ** filters,git_repository * repo,git_blob * blob,const char * path,git_filter_mode_t mode,uint32_t flags)574 int git_filter_list_load(
575 git_filter_list **filters,
576 git_repository *repo,
577 git_blob *blob, /* can be NULL */
578 const char *path,
579 git_filter_mode_t mode,
580 uint32_t flags)
581 {
582 git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT;
583
584 filter_opts.flags = flags;
585
586 return git_filter_list__load_ext(
587 filters, repo, blob, path, mode, &filter_opts);
588 }
589
git_filter_list_free(git_filter_list * fl)590 void git_filter_list_free(git_filter_list *fl)
591 {
592 uint32_t i;
593
594 if (!fl)
595 return;
596
597 for (i = 0; i < git_array_size(fl->filters); ++i) {
598 git_filter_entry *fe = git_array_get(fl->filters, i);
599 if (fe->filter->cleanup)
600 fe->filter->cleanup(fe->filter, fe->payload);
601 }
602
603 git_array_clear(fl->filters);
604 git__free(fl);
605 }
606
git_filter_list_contains(git_filter_list * fl,const char * name)607 int git_filter_list_contains(
608 git_filter_list *fl,
609 const char *name)
610 {
611 size_t i;
612
613 assert(name);
614
615 if (!fl)
616 return 0;
617
618 for (i = 0; i < fl->filters.size; i++) {
619 if (strcmp(fl->filters.ptr[i].filter_name, name) == 0)
620 return 1;
621 }
622
623 return 0;
624 }
625
git_filter_list_push(git_filter_list * fl,git_filter * filter,void * payload)626 int git_filter_list_push(
627 git_filter_list *fl, git_filter *filter, void *payload)
628 {
629 int error = 0;
630 size_t pos;
631 git_filter_def *fdef = NULL;
632 git_filter_entry *fe;
633
634 assert(fl && filter);
635
636 if (git_rwlock_rdlock(&filter_registry.lock) < 0) {
637 git_error_set(GIT_ERROR_OS, "failed to lock filter registry");
638 return -1;
639 }
640
641 if (git_vector_search2(
642 &pos, &filter_registry.filters,
643 filter_def_filter_key_check, filter) == 0)
644 fdef = git_vector_get(&filter_registry.filters, pos);
645
646 git_rwlock_rdunlock(&filter_registry.lock);
647
648 if (fdef == NULL) {
649 git_error_set(GIT_ERROR_FILTER, "cannot use an unregistered filter");
650 return -1;
651 }
652
653 if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
654 return error;
655
656 fe = git_array_alloc(fl->filters);
657 GIT_ERROR_CHECK_ALLOC(fe);
658 fe->filter = filter;
659 fe->payload = payload;
660
661 return 0;
662 }
663
git_filter_list_length(const git_filter_list * fl)664 size_t git_filter_list_length(const git_filter_list *fl)
665 {
666 return fl ? git_array_size(fl->filters) : 0;
667 }
668
669 struct buf_stream {
670 git_writestream parent;
671 git_buf *target;
672 bool complete;
673 };
674
buf_stream_write(git_writestream * s,const char * buffer,size_t len)675 static int buf_stream_write(
676 git_writestream *s, const char *buffer, size_t len)
677 {
678 struct buf_stream *buf_stream = (struct buf_stream *)s;
679 assert(buf_stream);
680
681 assert(buf_stream->complete == 0);
682
683 return git_buf_put(buf_stream->target, buffer, len);
684 }
685
buf_stream_close(git_writestream * s)686 static int buf_stream_close(git_writestream *s)
687 {
688 struct buf_stream *buf_stream = (struct buf_stream *)s;
689 assert(buf_stream);
690
691 assert(buf_stream->complete == 0);
692 buf_stream->complete = 1;
693
694 return 0;
695 }
696
buf_stream_free(git_writestream * s)697 static void buf_stream_free(git_writestream *s)
698 {
699 GIT_UNUSED(s);
700 }
701
buf_stream_init(struct buf_stream * writer,git_buf * target)702 static void buf_stream_init(struct buf_stream *writer, git_buf *target)
703 {
704 memset(writer, 0, sizeof(struct buf_stream));
705
706 writer->parent.write = buf_stream_write;
707 writer->parent.close = buf_stream_close;
708 writer->parent.free = buf_stream_free;
709 writer->target = target;
710
711 git_buf_clear(target);
712 }
713
git_filter_list_apply_to_data(git_buf * tgt,git_filter_list * filters,git_buf * src)714 int git_filter_list_apply_to_data(
715 git_buf *tgt, git_filter_list *filters, git_buf *src)
716 {
717 struct buf_stream writer;
718 int error;
719
720 git_buf_sanitize(tgt);
721 git_buf_sanitize(src);
722
723 if (!filters) {
724 git_buf_attach_notowned(tgt, src->ptr, src->size);
725 return 0;
726 }
727
728 buf_stream_init(&writer, tgt);
729
730 if ((error = git_filter_list_stream_data(filters, src,
731 &writer.parent)) < 0)
732 return error;
733
734 assert(writer.complete);
735 return error;
736 }
737
git_filter_list_apply_to_file(git_buf * out,git_filter_list * filters,git_repository * repo,const char * path)738 int git_filter_list_apply_to_file(
739 git_buf *out,
740 git_filter_list *filters,
741 git_repository *repo,
742 const char *path)
743 {
744 struct buf_stream writer;
745 int error;
746
747 buf_stream_init(&writer, out);
748
749 if ((error = git_filter_list_stream_file(
750 filters, repo, path, &writer.parent)) < 0)
751 return error;
752
753 assert(writer.complete);
754 return error;
755 }
756
buf_from_blob(git_buf * out,git_blob * blob)757 static int buf_from_blob(git_buf *out, git_blob *blob)
758 {
759 git_off_t rawsize = git_blob_rawsize(blob);
760
761 if (!git__is_sizet(rawsize)) {
762 git_error_set(GIT_ERROR_OS, "blob is too large to filter");
763 return -1;
764 }
765
766 git_buf_attach_notowned(out, git_blob_rawcontent(blob), (size_t)rawsize);
767 return 0;
768 }
769
git_filter_list_apply_to_blob(git_buf * out,git_filter_list * filters,git_blob * blob)770 int git_filter_list_apply_to_blob(
771 git_buf *out,
772 git_filter_list *filters,
773 git_blob *blob)
774 {
775 struct buf_stream writer;
776 int error;
777
778 buf_stream_init(&writer, out);
779
780 if ((error = git_filter_list_stream_blob(
781 filters, blob, &writer.parent)) < 0)
782 return error;
783
784 assert(writer.complete);
785 return error;
786 }
787
788 struct proxy_stream {
789 git_writestream parent;
790 git_filter *filter;
791 const git_filter_source *source;
792 void **payload;
793 git_buf input;
794 git_buf temp_buf;
795 git_buf *output;
796 git_writestream *target;
797 };
798
proxy_stream_write(git_writestream * s,const char * buffer,size_t len)799 static int proxy_stream_write(
800 git_writestream *s, const char *buffer, size_t len)
801 {
802 struct proxy_stream *proxy_stream = (struct proxy_stream *)s;
803 assert(proxy_stream);
804
805 return git_buf_put(&proxy_stream->input, buffer, len);
806 }
807
proxy_stream_close(git_writestream * s)808 static int proxy_stream_close(git_writestream *s)
809 {
810 struct proxy_stream *proxy_stream = (struct proxy_stream *)s;
811 git_buf *writebuf;
812 git_error_state error_state = {0};
813 int error;
814
815 assert(proxy_stream);
816
817 error = proxy_stream->filter->apply(
818 proxy_stream->filter,
819 proxy_stream->payload,
820 proxy_stream->output,
821 &proxy_stream->input,
822 proxy_stream->source);
823
824 if (error == GIT_PASSTHROUGH) {
825 writebuf = &proxy_stream->input;
826 } else if (error == 0) {
827 git_buf_sanitize(proxy_stream->output);
828 writebuf = proxy_stream->output;
829 } else {
830 /* close stream before erroring out taking care
831 * to preserve the original error */
832 git_error_state_capture(&error_state, error);
833 proxy_stream->target->close(proxy_stream->target);
834 git_error_state_restore(&error_state);
835 return error;
836 }
837
838 if ((error = proxy_stream->target->write(
839 proxy_stream->target, writebuf->ptr, writebuf->size)) == 0)
840 error = proxy_stream->target->close(proxy_stream->target);
841
842 return error;
843 }
844
proxy_stream_free(git_writestream * s)845 static void proxy_stream_free(git_writestream *s)
846 {
847 struct proxy_stream *proxy_stream = (struct proxy_stream *)s;
848 assert(proxy_stream);
849
850 git_buf_dispose(&proxy_stream->input);
851 git_buf_dispose(&proxy_stream->temp_buf);
852 git__free(proxy_stream);
853 }
854
proxy_stream_init(git_writestream ** out,git_filter * filter,git_buf * temp_buf,void ** payload,const git_filter_source * source,git_writestream * target)855 static int proxy_stream_init(
856 git_writestream **out,
857 git_filter *filter,
858 git_buf *temp_buf,
859 void **payload,
860 const git_filter_source *source,
861 git_writestream *target)
862 {
863 struct proxy_stream *proxy_stream = git__calloc(1, sizeof(struct proxy_stream));
864 GIT_ERROR_CHECK_ALLOC(proxy_stream);
865
866 proxy_stream->parent.write = proxy_stream_write;
867 proxy_stream->parent.close = proxy_stream_close;
868 proxy_stream->parent.free = proxy_stream_free;
869 proxy_stream->filter = filter;
870 proxy_stream->payload = payload;
871 proxy_stream->source = source;
872 proxy_stream->target = target;
873 proxy_stream->output = temp_buf ? temp_buf : &proxy_stream->temp_buf;
874
875 if (temp_buf)
876 git_buf_clear(temp_buf);
877
878 *out = (git_writestream *)proxy_stream;
879 return 0;
880 }
881
stream_list_init(git_writestream ** out,git_vector * streams,git_filter_list * filters,git_writestream * target)882 static int stream_list_init(
883 git_writestream **out,
884 git_vector *streams,
885 git_filter_list *filters,
886 git_writestream *target)
887 {
888 git_writestream *last_stream = target;
889 size_t i;
890 int error = 0;
891
892 *out = NULL;
893
894 if (!filters) {
895 *out = target;
896 return 0;
897 }
898
899 /* Create filters last to first to get the chaining direction */
900 for (i = 0; i < git_array_size(filters->filters); ++i) {
901 size_t filter_idx = (filters->source.mode == GIT_FILTER_TO_WORKTREE) ?
902 git_array_size(filters->filters) - 1 - i : i;
903 git_filter_entry *fe = git_array_get(filters->filters, filter_idx);
904 git_writestream *filter_stream;
905
906 assert(fe->filter->stream || fe->filter->apply);
907
908 /* If necessary, create a stream that proxies the traditional
909 * application.
910 */
911 if (fe->filter->stream)
912 error = fe->filter->stream(&filter_stream, fe->filter,
913 &fe->payload, &filters->source, last_stream);
914 else
915 /* Create a stream that proxies the one-shot apply */
916 error = proxy_stream_init(&filter_stream, fe->filter,
917 filters->temp_buf, &fe->payload, &filters->source,
918 last_stream);
919
920 if (error < 0)
921 goto out;
922
923 git_vector_insert(streams, filter_stream);
924 last_stream = filter_stream;
925 }
926
927 out:
928 if (error)
929 last_stream->close(last_stream);
930 else
931 *out = last_stream;
932
933 return error;
934 }
935
stream_list_free(git_vector * streams)936 void stream_list_free(git_vector *streams)
937 {
938 git_writestream *stream;
939 size_t i;
940
941 git_vector_foreach(streams, i, stream)
942 stream->free(stream);
943 git_vector_free(streams);
944 }
945
git_filter_list_stream_file(git_filter_list * filters,git_repository * repo,const char * path,git_writestream * target)946 int git_filter_list_stream_file(
947 git_filter_list *filters,
948 git_repository *repo,
949 const char *path,
950 git_writestream *target)
951 {
952 char buf[FILTERIO_BUFSIZE];
953 git_buf abspath = GIT_BUF_INIT;
954 const char *base = repo ? git_repository_workdir(repo) : NULL;
955 git_vector filter_streams = GIT_VECTOR_INIT;
956 git_writestream *stream_start;
957 ssize_t readlen;
958 int fd = -1, error, initialized = 0;
959
960 if ((error = stream_list_init(
961 &stream_start, &filter_streams, filters, target)) < 0 ||
962 (error = git_path_join_unrooted(&abspath, path, base, NULL)) < 0)
963 goto done;
964 initialized = 1;
965
966 if ((fd = git_futils_open_ro(abspath.ptr)) < 0) {
967 error = fd;
968 goto done;
969 }
970
971 while ((readlen = p_read(fd, buf, sizeof(buf))) > 0) {
972 if ((error = stream_start->write(stream_start, buf, readlen)) < 0)
973 goto done;
974 }
975
976 if (readlen < 0)
977 error = -1;
978
979 done:
980 if (initialized)
981 error |= stream_start->close(stream_start);
982
983 if (fd >= 0)
984 p_close(fd);
985 stream_list_free(&filter_streams);
986 git_buf_dispose(&abspath);
987 return error;
988 }
989
git_filter_list_stream_data(git_filter_list * filters,git_buf * data,git_writestream * target)990 int git_filter_list_stream_data(
991 git_filter_list *filters,
992 git_buf *data,
993 git_writestream *target)
994 {
995 git_vector filter_streams = GIT_VECTOR_INIT;
996 git_writestream *stream_start;
997 int error, initialized = 0;
998
999 git_buf_sanitize(data);
1000
1001 if ((error = stream_list_init(&stream_start, &filter_streams, filters, target)) < 0)
1002 goto out;
1003 initialized = 1;
1004
1005 if ((error = stream_start->write(
1006 stream_start, data->ptr, data->size)) < 0)
1007 goto out;
1008
1009 out:
1010 if (initialized)
1011 error |= stream_start->close(stream_start);
1012
1013 stream_list_free(&filter_streams);
1014 return error;
1015 }
1016
git_filter_list_stream_blob(git_filter_list * filters,git_blob * blob,git_writestream * target)1017 int git_filter_list_stream_blob(
1018 git_filter_list *filters,
1019 git_blob *blob,
1020 git_writestream *target)
1021 {
1022 git_buf in = GIT_BUF_INIT;
1023
1024 if (buf_from_blob(&in, blob) < 0)
1025 return -1;
1026
1027 if (filters)
1028 git_oid_cpy(&filters->source.oid, git_blob_id(blob));
1029
1030 return git_filter_list_stream_data(filters, &in, target);
1031 }
1032
git_filter_init(git_filter * filter,unsigned int version)1033 int git_filter_init(git_filter *filter, unsigned int version)
1034 {
1035 GIT_INIT_STRUCTURE_FROM_TEMPLATE(filter, version, git_filter, GIT_FILTER_INIT);
1036 return 0;
1037 }
1038