mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-08 19:51:31 +00:00

This renames git_vector_free_all to the better git_vector_free_deep and also contains a couple of memory leak fixes based on valgrind checks. The fixes are specifically: failure to free global dir path variables when not compiled with threading on and failure to free filters from the filter registry that had not be initialized fully.
677 lines
14 KiB
C
677 lines
14 KiB
C
/*
|
|
* Copyright (C) the libgit2 contributors. All rights reserved.
|
|
*
|
|
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
|
* a Linking Exception. For full terms see the included COPYING file.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "fileops.h"
|
|
#include "hash.h"
|
|
#include "filter.h"
|
|
#include "repository.h"
|
|
#include "global.h"
|
|
#include "git2/sys/filter.h"
|
|
#include "git2/config.h"
|
|
#include "blob.h"
|
|
#include "attr_file.h"
|
|
#include "array.h"
|
|
|
|
struct git_filter_source {
|
|
git_repository *repo;
|
|
const char *path;
|
|
git_oid oid; /* zero if unknown (which is likely) */
|
|
uint16_t filemode; /* zero if unknown */
|
|
git_filter_mode_t mode;
|
|
};
|
|
|
|
typedef struct {
|
|
git_filter *filter;
|
|
void *payload;
|
|
} git_filter_entry;
|
|
|
|
struct git_filter_list {
|
|
git_array_t(git_filter_entry) filters;
|
|
git_filter_source source;
|
|
char path[GIT_FLEX_ARRAY];
|
|
};
|
|
|
|
typedef struct {
|
|
const char *filter_name;
|
|
git_filter *filter;
|
|
int priority;
|
|
int initialized;
|
|
size_t nattrs, nmatches;
|
|
char *attrdata;
|
|
const char *attrs[GIT_FLEX_ARRAY];
|
|
} git_filter_def;
|
|
|
|
static int filter_def_priority_cmp(const void *a, const void *b)
|
|
{
|
|
int pa = ((const git_filter_def *)a)->priority;
|
|
int pb = ((const git_filter_def *)b)->priority;
|
|
return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
|
|
}
|
|
|
|
struct filter_registry {
|
|
git_vector filters;
|
|
};
|
|
|
|
static struct filter_registry *git__filter_registry = NULL;
|
|
|
|
static void filter_registry_shutdown(void)
|
|
{
|
|
struct filter_registry *reg = NULL;
|
|
size_t pos;
|
|
git_filter_def *fdef;
|
|
|
|
if ((reg = git__swap(git__filter_registry, NULL)) == NULL)
|
|
return;
|
|
|
|
git_vector_foreach(®->filters, pos, fdef) {
|
|
if (fdef->filter && fdef->filter->shutdown) {
|
|
fdef->filter->shutdown(fdef->filter);
|
|
fdef->initialized = false;
|
|
}
|
|
|
|
git__free(fdef->attrdata);
|
|
git__free(fdef);
|
|
}
|
|
|
|
git_vector_free(®->filters);
|
|
git__free(reg);
|
|
}
|
|
|
|
static int filter_registry_initialize(void)
|
|
{
|
|
int error = 0;
|
|
struct filter_registry *reg;
|
|
|
|
if (git__filter_registry)
|
|
return 0;
|
|
|
|
reg = git__calloc(1, sizeof(struct filter_registry));
|
|
GITERR_CHECK_ALLOC(reg);
|
|
|
|
if ((error = git_vector_init(
|
|
®->filters, 2, filter_def_priority_cmp)) < 0)
|
|
goto cleanup;
|
|
|
|
reg = git__compare_and_swap(&git__filter_registry, NULL, reg);
|
|
if (reg != NULL)
|
|
goto cleanup;
|
|
|
|
git__on_shutdown(filter_registry_shutdown);
|
|
|
|
/* try to register both default filters */
|
|
{
|
|
git_filter *crlf = git_crlf_filter_new();
|
|
git_filter *ident = git_ident_filter_new();
|
|
|
|
if (crlf && git_filter_register(
|
|
GIT_FILTER_CRLF, crlf, GIT_FILTER_CRLF_PRIORITY) < 0)
|
|
crlf = NULL;
|
|
if (ident && git_filter_register(
|
|
GIT_FILTER_IDENT, ident, GIT_FILTER_IDENT_PRIORITY) < 0)
|
|
ident = NULL;
|
|
|
|
if (!crlf || !ident)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
cleanup:
|
|
git_vector_free(®->filters);
|
|
git__free(reg);
|
|
return error;
|
|
}
|
|
|
|
static int filter_def_scan_attrs(
|
|
git_buf *attrs, size_t *nattr, size_t *nmatch, const char *attr_str)
|
|
{
|
|
const char *start, *scan = attr_str;
|
|
int has_eq;
|
|
|
|
*nattr = *nmatch = 0;
|
|
|
|
if (!scan)
|
|
return 0;
|
|
|
|
while (*scan) {
|
|
while (git__isspace(*scan)) scan++;
|
|
|
|
for (start = scan, has_eq = 0; *scan && !git__isspace(*scan); ++scan) {
|
|
if (*scan == '=')
|
|
has_eq = 1;
|
|
}
|
|
|
|
if (scan > start) {
|
|
(*nattr)++;
|
|
if (has_eq || *start == '-' || *start == '+' || *start == '!')
|
|
(*nmatch)++;
|
|
|
|
if (has_eq)
|
|
git_buf_putc(attrs, '=');
|
|
git_buf_put(attrs, start, scan - start);
|
|
git_buf_putc(attrs, '\0');
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void filter_def_set_attrs(git_filter_def *fdef)
|
|
{
|
|
char *scan = fdef->attrdata;
|
|
size_t i;
|
|
|
|
for (i = 0; i < fdef->nattrs; ++i) {
|
|
const char *name, *value;
|
|
|
|
switch (*scan) {
|
|
case '=':
|
|
name = scan + 1;
|
|
for (scan++; *scan != '='; scan++) /* find '=' */;
|
|
*scan++ = '\0';
|
|
value = scan;
|
|
break;
|
|
case '-':
|
|
name = scan + 1; value = git_attr__false; break;
|
|
case '+':
|
|
name = scan + 1; value = git_attr__true; break;
|
|
case '!':
|
|
name = scan + 1; value = git_attr__unset; break;
|
|
default:
|
|
name = scan; value = NULL; break;
|
|
}
|
|
|
|
fdef->attrs[i] = name;
|
|
fdef->attrs[i + fdef->nattrs] = value;
|
|
|
|
scan += strlen(scan) + 1;
|
|
}
|
|
}
|
|
|
|
static int filter_def_name_key_check(const void *key, const void *fdef)
|
|
{
|
|
const char *name =
|
|
fdef ? ((const git_filter_def *)fdef)->filter_name : NULL;
|
|
return name ? git__strcmp(key, name) : -1;
|
|
}
|
|
|
|
static int filter_def_filter_key_check(const void *key, const void *fdef)
|
|
{
|
|
const void *filter = fdef ? ((const git_filter_def *)fdef)->filter : NULL;
|
|
return (key == filter) ? 0 : -1;
|
|
}
|
|
|
|
static int filter_registry_find(size_t *pos, const char *name)
|
|
{
|
|
return git_vector_search2(
|
|
pos, &git__filter_registry->filters, filter_def_name_key_check, name);
|
|
}
|
|
|
|
static git_filter_def *filter_registry_lookup(size_t *pos, const char *name)
|
|
{
|
|
git_filter_def *fdef = NULL;
|
|
|
|
if (!filter_registry_find(pos, name))
|
|
fdef = git_vector_get(&git__filter_registry->filters, *pos);
|
|
|
|
return fdef;
|
|
}
|
|
|
|
int git_filter_register(
|
|
const char *name, git_filter *filter, int priority)
|
|
{
|
|
git_filter_def *fdef;
|
|
size_t nattr = 0, nmatch = 0;
|
|
git_buf attrs = GIT_BUF_INIT;
|
|
|
|
if (filter_registry_initialize() < 0)
|
|
return -1;
|
|
|
|
if (!filter_registry_find(NULL, name)) {
|
|
giterr_set(
|
|
GITERR_FILTER, "Attempt to reregister existing filter '%s'", name);
|
|
return GIT_EEXISTS;
|
|
}
|
|
|
|
if (filter_def_scan_attrs(&attrs, &nattr, &nmatch, filter->attributes) < 0)
|
|
return -1;
|
|
|
|
fdef = git__calloc(
|
|
sizeof(git_filter_def) + 2 * nattr * sizeof(char *), 1);
|
|
GITERR_CHECK_ALLOC(fdef);
|
|
|
|
fdef->filter_name = name;
|
|
fdef->filter = filter;
|
|
fdef->priority = priority;
|
|
fdef->nattrs = nattr;
|
|
fdef->nmatches = nmatch;
|
|
fdef->attrdata = git_buf_detach(&attrs);
|
|
|
|
filter_def_set_attrs(fdef);
|
|
|
|
if (git_vector_insert(&git__filter_registry->filters, fdef) < 0) {
|
|
git__free(fdef->attrdata);
|
|
git__free(fdef);
|
|
return -1;
|
|
}
|
|
|
|
git_vector_sort(&git__filter_registry->filters);
|
|
return 0;
|
|
}
|
|
|
|
int git_filter_unregister(const char *name)
|
|
{
|
|
size_t pos;
|
|
git_filter_def *fdef;
|
|
|
|
/* cannot unregister default filters */
|
|
if (!strcmp(GIT_FILTER_CRLF, name) || !strcmp(GIT_FILTER_IDENT, name)) {
|
|
giterr_set(GITERR_FILTER, "Cannot unregister filter '%s'", name);
|
|
return -1;
|
|
}
|
|
|
|
if ((fdef = filter_registry_lookup(&pos, name)) == NULL) {
|
|
giterr_set(GITERR_FILTER, "Cannot find filter '%s' to unregister", name);
|
|
return GIT_ENOTFOUND;
|
|
}
|
|
|
|
(void)git_vector_remove(&git__filter_registry->filters, pos);
|
|
|
|
if (fdef->initialized && fdef->filter && fdef->filter->shutdown) {
|
|
fdef->filter->shutdown(fdef->filter);
|
|
fdef->initialized = false;
|
|
}
|
|
|
|
git__free(fdef->attrdata);
|
|
git__free(fdef);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int filter_initialize(git_filter_def *fdef)
|
|
{
|
|
int error = 0;
|
|
|
|
if (!fdef->initialized &&
|
|
fdef->filter &&
|
|
fdef->filter->initialize &&
|
|
(error = fdef->filter->initialize(fdef->filter)) < 0)
|
|
{
|
|
/* auto-unregister if initialize fails */
|
|
git_filter_unregister(fdef->filter_name);
|
|
return error;
|
|
}
|
|
|
|
fdef->initialized = true;
|
|
return 0;
|
|
}
|
|
|
|
git_filter *git_filter_lookup(const char *name)
|
|
{
|
|
size_t pos;
|
|
git_filter_def *fdef;
|
|
|
|
if (filter_registry_initialize() < 0)
|
|
return NULL;
|
|
|
|
if ((fdef = filter_registry_lookup(&pos, name)) == NULL)
|
|
return NULL;
|
|
|
|
if (!fdef->initialized && filter_initialize(fdef) < 0)
|
|
return NULL;
|
|
|
|
return fdef->filter;
|
|
}
|
|
|
|
void git_filter_free(git_filter *filter)
|
|
{
|
|
git__free(filter);
|
|
}
|
|
|
|
git_repository *git_filter_source_repo(const git_filter_source *src)
|
|
{
|
|
return src->repo;
|
|
}
|
|
|
|
const char *git_filter_source_path(const git_filter_source *src)
|
|
{
|
|
return src->path;
|
|
}
|
|
|
|
uint16_t git_filter_source_filemode(const git_filter_source *src)
|
|
{
|
|
return src->filemode;
|
|
}
|
|
|
|
const git_oid *git_filter_source_id(const git_filter_source *src)
|
|
{
|
|
return git_oid_iszero(&src->oid) ? NULL : &src->oid;
|
|
}
|
|
|
|
git_filter_mode_t git_filter_source_mode(const git_filter_source *src)
|
|
{
|
|
return src->mode;
|
|
}
|
|
|
|
static int filter_list_new(
|
|
git_filter_list **out, const git_filter_source *src)
|
|
{
|
|
git_filter_list *fl = NULL;
|
|
size_t pathlen = src->path ? strlen(src->path) : 0;
|
|
|
|
fl = git__calloc(1, sizeof(git_filter_list) + pathlen + 1);
|
|
GITERR_CHECK_ALLOC(fl);
|
|
|
|
if (src->path)
|
|
memcpy(fl->path, src->path, pathlen);
|
|
fl->source.repo = src->repo;
|
|
fl->source.path = fl->path;
|
|
fl->source.mode = src->mode;
|
|
|
|
*out = fl;
|
|
return 0;
|
|
}
|
|
|
|
static int filter_list_check_attributes(
|
|
const char ***out, git_filter_def *fdef, const git_filter_source *src)
|
|
{
|
|
int error;
|
|
size_t i;
|
|
const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
|
|
GITERR_CHECK_ALLOC(strs);
|
|
|
|
error = git_attr_get_many(
|
|
strs, src->repo, 0, src->path, fdef->nattrs, fdef->attrs);
|
|
|
|
/* if no values were found but no matches are needed, it's okay! */
|
|
if (error == GIT_ENOTFOUND && !fdef->nmatches) {
|
|
giterr_clear();
|
|
git__free((void *)strs);
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; !error && i < fdef->nattrs; ++i) {
|
|
const char *want = fdef->attrs[fdef->nattrs + i];
|
|
git_attr_t want_type, found_type;
|
|
|
|
if (!want)
|
|
continue;
|
|
|
|
want_type = git_attr_value(want);
|
|
found_type = git_attr_value(strs[i]);
|
|
|
|
if (want_type != found_type ||
|
|
(want_type == GIT_ATTR_VALUE_T && strcmp(want, strs[i])))
|
|
error = GIT_ENOTFOUND;
|
|
}
|
|
|
|
if (error)
|
|
git__free((void *)strs);
|
|
else
|
|
*out = strs;
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_filter_list_new(
|
|
git_filter_list **out, git_repository *repo, git_filter_mode_t mode)
|
|
{
|
|
git_filter_source src = { 0 };
|
|
src.repo = repo;
|
|
src.path = NULL;
|
|
src.mode = mode;
|
|
return filter_list_new(out, &src);
|
|
}
|
|
|
|
int git_filter_list_load(
|
|
git_filter_list **filters,
|
|
git_repository *repo,
|
|
git_blob *blob, /* can be NULL */
|
|
const char *path,
|
|
git_filter_mode_t mode)
|
|
{
|
|
int error = 0;
|
|
git_filter_list *fl = NULL;
|
|
git_filter_source src = { 0 };
|
|
git_filter_entry *fe;
|
|
size_t idx;
|
|
git_filter_def *fdef;
|
|
|
|
if (filter_registry_initialize() < 0)
|
|
return -1;
|
|
|
|
src.repo = repo;
|
|
src.path = path;
|
|
src.mode = mode;
|
|
if (blob)
|
|
git_oid_cpy(&src.oid, git_blob_id(blob));
|
|
|
|
git_vector_foreach(&git__filter_registry->filters, idx, fdef) {
|
|
const char **values = NULL;
|
|
void *payload = NULL;
|
|
|
|
if (!fdef || !fdef->filter)
|
|
continue;
|
|
|
|
if (fdef->nattrs > 0) {
|
|
error = filter_list_check_attributes(&values, fdef, &src);
|
|
if (error == GIT_ENOTFOUND) {
|
|
error = 0;
|
|
continue;
|
|
} else if (error < 0)
|
|
break;
|
|
}
|
|
|
|
if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
|
|
break;
|
|
|
|
if (fdef->filter->check)
|
|
error = fdef->filter->check(
|
|
fdef->filter, &payload, &src, values);
|
|
|
|
git__free((void *)values);
|
|
|
|
if (error == GIT_PASSTHROUGH)
|
|
error = 0;
|
|
else if (error < 0)
|
|
break;
|
|
else {
|
|
if (!fl && (error = filter_list_new(&fl, &src)) < 0)
|
|
return error;
|
|
|
|
fe = git_array_alloc(fl->filters);
|
|
GITERR_CHECK_ALLOC(fe);
|
|
fe->filter = fdef->filter;
|
|
fe->payload = payload;
|
|
}
|
|
}
|
|
|
|
if (error && fl != NULL) {
|
|
git_array_clear(fl->filters);
|
|
git__free(fl);
|
|
fl = NULL;
|
|
}
|
|
|
|
*filters = fl;
|
|
return error;
|
|
}
|
|
|
|
void git_filter_list_free(git_filter_list *fl)
|
|
{
|
|
uint32_t i;
|
|
|
|
if (!fl)
|
|
return;
|
|
|
|
for (i = 0; i < git_array_size(fl->filters); ++i) {
|
|
git_filter_entry *fe = git_array_get(fl->filters, i);
|
|
if (fe->filter->cleanup)
|
|
fe->filter->cleanup(fe->filter, fe->payload);
|
|
}
|
|
|
|
git_array_clear(fl->filters);
|
|
git__free(fl);
|
|
}
|
|
|
|
int git_filter_list_push(
|
|
git_filter_list *fl, git_filter *filter, void *payload)
|
|
{
|
|
int error = 0;
|
|
size_t pos;
|
|
git_filter_def *fdef;
|
|
git_filter_entry *fe;
|
|
|
|
assert(fl && filter);
|
|
|
|
if (git_vector_search2(
|
|
&pos, &git__filter_registry->filters,
|
|
filter_def_filter_key_check, filter) < 0) {
|
|
giterr_set(GITERR_FILTER, "Cannot use an unregistered filter");
|
|
return -1;
|
|
}
|
|
|
|
fdef = git_vector_get(&git__filter_registry->filters, pos);
|
|
|
|
if (!fdef->initialized && (error = filter_initialize(fdef)) < 0)
|
|
return error;
|
|
|
|
fe = git_array_alloc(fl->filters);
|
|
GITERR_CHECK_ALLOC(fe);
|
|
fe->filter = filter;
|
|
fe->payload = payload;
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t git_filter_list_length(const git_filter_list *fl)
|
|
{
|
|
return fl ? git_array_size(fl->filters) : 0;
|
|
}
|
|
|
|
static int filter_list_out_buffer_from_raw(
|
|
git_buf *out, const void *ptr, size_t size)
|
|
{
|
|
if (git_buf_is_allocated(out))
|
|
git_buf_free(out);
|
|
|
|
if (!size) {
|
|
git_buf_init(out, 0);
|
|
} else {
|
|
out->ptr = (char *)ptr;
|
|
out->asize = 0;
|
|
out->size = size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int git_filter_list_apply_to_data(
|
|
git_buf *tgt, git_filter_list *fl, git_buf *src)
|
|
{
|
|
int error = 0;
|
|
uint32_t i;
|
|
git_buf *dbuffer[2], local = GIT_BUF_INIT;
|
|
unsigned int si = 0;
|
|
|
|
if (!fl)
|
|
return filter_list_out_buffer_from_raw(tgt, src->ptr, src->size);
|
|
|
|
dbuffer[0] = src;
|
|
dbuffer[1] = tgt;
|
|
|
|
/* if `src` buffer is reallocable, then use it, otherwise copy it */
|
|
if (!git_buf_is_allocated(src)) {
|
|
if (git_buf_set(&local, src->ptr, src->size) < 0)
|
|
return -1;
|
|
dbuffer[0] = &local;
|
|
}
|
|
|
|
for (i = 0; i < git_array_size(fl->filters); ++i) {
|
|
unsigned int di = 1 - si;
|
|
uint32_t fidx = (fl->source.mode == GIT_FILTER_TO_WORKTREE) ?
|
|
i : git_array_size(fl->filters) - 1 - i;
|
|
git_filter_entry *fe = git_array_get(fl->filters, fidx);
|
|
|
|
dbuffer[di]->size = 0;
|
|
|
|
/* Apply the filter from dbuffer[src] to the other buffer;
|
|
* if the filtering is canceled by the user mid-filter,
|
|
* we skip to the next filter without changing the source
|
|
* of the double buffering (so that the text goes through
|
|
* cleanly).
|
|
*/
|
|
|
|
error = fe->filter->apply(
|
|
fe->filter, &fe->payload, dbuffer[di], dbuffer[si], &fl->source);
|
|
|
|
if (error == GIT_PASSTHROUGH) {
|
|
/* PASSTHROUGH means filter decided not to process the buffer */
|
|
error = 0;
|
|
} else if (!error) {
|
|
git_buf_shorten(dbuffer[di], 0); /* force NUL termination */
|
|
si = di; /* swap buffers */
|
|
} else {
|
|
tgt->size = 0;
|
|
return error;
|
|
}
|
|
}
|
|
|
|
/* Ensure that the output ends up in dbuffer[1] (i.e. the dest) */
|
|
if (si != 1)
|
|
git_buf_swap(dbuffer[0], dbuffer[1]);
|
|
|
|
git_buf_free(&local); /* don't leak if we allocated locally */
|
|
|
|
return 0;
|
|
}
|
|
|
|
int git_filter_list_apply_to_file(
|
|
git_buf *out,
|
|
git_filter_list *filters,
|
|
git_repository *repo,
|
|
const char *path)
|
|
{
|
|
int error;
|
|
const char *base = repo ? git_repository_workdir(repo) : NULL;
|
|
git_buf abspath = GIT_BUF_INIT, raw = GIT_BUF_INIT;
|
|
|
|
if (!(error = git_path_join_unrooted(&abspath, path, base, NULL)) &&
|
|
!(error = git_futils_readbuffer(&raw, abspath.ptr)))
|
|
{
|
|
error = git_filter_list_apply_to_data(out, filters, &raw);
|
|
|
|
git_buf_free(&raw);
|
|
}
|
|
|
|
git_buf_free(&abspath);
|
|
return error;
|
|
}
|
|
|
|
int git_filter_list_apply_to_blob(
|
|
git_buf *out,
|
|
git_filter_list *filters,
|
|
git_blob *blob)
|
|
{
|
|
git_buf in = GIT_BUF_INIT;
|
|
git_off_t rawsize = git_blob_rawsize(blob);
|
|
|
|
if (!git__is_sizet(rawsize)) {
|
|
giterr_set(GITERR_OS, "Blob is too large to filter");
|
|
return -1;
|
|
}
|
|
|
|
in.ptr = (char *)git_blob_rawcontent(blob);
|
|
in.asize = 0;
|
|
in.size = (size_t)rawsize;
|
|
|
|
if (filters)
|
|
git_oid_cpy(&filters->source.oid, git_blob_id(blob));
|
|
|
|
return git_filter_list_apply_to_data(out, filters, &in);
|
|
}
|