Skip to content

Commit 73df75d

Browse files
committed
config_file: refactor include handling
Current code for configuration files uses the `reader` structure to parse configuration files and store additional metadata like the file's path and checksum. These structures are stored within an array in the backend itself, which causes multiple problems. First, it does not make sense to keep around the file's contents with the backend itself. While this data is usually free'd before being added to the backend, this brings along somewhat intricate lifecycle problems. A better solution would be to store only the file paths as well as the checksum of the currently parsed content only. The second problem is that the `reader` structures are stored inside an array. When re-parsing configuration files due to changed contents, we may cause this array to be reallocated, requiring us to update pointers hold by callers. Furthermore, we do not keep track of includes which are already associated to a reader inside of this array. This causes us to add readers multiple times to the backend, e.g. in the scenario of refreshing configurations. This commit fixes these shortcomings. We introduce a split between the parsing data and the configuration file's metadata. The `reader` will now only hold the file's contents and the parser state and the new `config_file` structure holds the file's path and checksum. Furthermore, the new structure is a recursive structure in that it will also hold references to the files it directly includes. The diskfile is changed to only store the top-level configuration file. These changes allow us further refactorings and greatly simplify understanding the code.
1 parent 6f7aab0 commit 73df75d

File tree

1 file changed

+116
-98
lines changed

1 file changed

+116
-98
lines changed

src/config_file.c

Lines changed: 116 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,14 @@ typedef struct git_config_file_iter {
7474
(iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
7575
(iter) = (tmp))
7676

77-
struct reader {
77+
struct config_file {
7878
git_oid checksum;
79-
char *file_path;
79+
char *path;
80+
git_array_t(struct config_file) includes;
81+
};
82+
83+
struct reader {
84+
struct config_file *file;
8085
git_buf buffer;
8186
char *read_ptr;
8287
int line_number;
@@ -100,13 +105,11 @@ typedef struct {
100105

101106
git_config_level_t level;
102107

103-
git_array_t(struct reader) readers;
104-
105108
bool locked;
106109
git_filebuf locked_buf;
107110
git_buf locked_content;
108111

109-
char *file_path;
112+
struct config_file file;
110113
} diskfile_backend;
111114

112115
typedef struct {
@@ -125,7 +128,7 @@ static int config_snapshot(git_config_backend **out, git_config_backend *in);
125128
static void set_parse_error(struct reader *reader, int col, const char *error_str)
126129
{
127130
giterr_set(GITERR_CONFIG, "failed to parse config file: %s (in %s:%d, column %d)",
128-
error_str, reader->file_path, reader->line_number, col);
131+
error_str, reader->file->path, reader->line_number, col);
129132
}
130133

131134
static int config_error_readonly(void)
@@ -261,123 +264,140 @@ static int refcounted_strmap_alloc(refcounted_strmap **out)
261264
return error;
262265
}
263266

267+
static void reader_init(struct reader *reader, struct config_file *file)
268+
{
269+
memset(reader, 0, sizeof(*reader));
270+
git_buf_init(&reader->buffer, 0);
271+
reader->file = file;
272+
}
273+
274+
static void config_file_clear(struct config_file *file)
275+
{
276+
struct config_file *include;
277+
uint32_t i;
278+
279+
if (file == NULL)
280+
return;
281+
282+
git_array_foreach(file->includes, i, include) {
283+
config_file_clear(include);
284+
}
285+
git_array_clear(file->includes);
286+
287+
git__free(file->path);
288+
}
289+
264290
static int config_open(git_config_backend *cfg, git_config_level_t level)
265291
{
266292
int res;
267-
struct reader *reader;
293+
struct reader reader;
268294
diskfile_backend *b = (diskfile_backend *)cfg;
269295

270296
b->level = level;
271297

272298
if ((res = refcounted_strmap_alloc(&b->header.values)) < 0)
273299
return res;
274300

275-
git_array_init(b->readers);
276-
reader = git_array_alloc(b->readers);
277-
if (!reader) {
278-
refcounted_strmap_free(b->header.values);
279-
return -1;
280-
}
281-
memset(reader, 0, sizeof(struct reader));
282-
283-
reader->file_path = git__strdup(b->file_path);
284-
GITERR_CHECK_ALLOC(reader->file_path);
301+
reader_init(&reader, &b->file);
285302

286-
git_buf_init(&reader->buffer, 0);
287303
res = git_futils_readbuffer_updated(
288-
&reader->buffer, b->file_path, &reader->checksum, NULL);
304+
&reader.buffer, b->file.path, &b->file.checksum, NULL);
289305

290306
/* It's fine if the file doesn't exist */
291307
if (res == GIT_ENOTFOUND)
292308
return 0;
293309

294-
if (res < 0 || (res = config_read(b->header.values->values, b, reader, level, 0)) < 0) {
310+
if (res < 0 || (res = config_read(b->header.values->values, b, &reader, level, 0)) < 0) {
295311
refcounted_strmap_free(b->header.values);
296312
b->header.values = NULL;
297313
}
298314

299-
reader = git_array_get(b->readers, 0);
300-
git_buf_free(&reader->buffer);
315+
git_buf_free(&reader.buffer);
301316

302317
return res;
303318
}
304319

305320
/* The meat of the refresh, as we want to use it in different places */
306-
static int config__refresh(git_config_backend *cfg)
321+
static int config__refresh(diskfile_backend *b, struct reader *reader, refcounted_strmap *values)
307322
{
308-
refcounted_strmap *values = NULL, *tmp;
309-
diskfile_backend *b = (diskfile_backend *)cfg;
310-
struct reader *reader = NULL;
311323
int error = 0;
312324

313-
if ((error = refcounted_strmap_alloc(&values)) < 0)
314-
goto out;
315-
316-
reader = git_array_get(b->readers, git_array_size(b->readers) - 1);
317-
GITERR_CHECK_ALLOC(reader);
318-
319-
if ((error = config_read(values->values, b, reader, b->level, 0)) < 0)
320-
goto out;
321-
322325
if ((error = git_mutex_lock(&b->header.values_mutex)) < 0) {
323326
giterr_set(GITERR_OS, "failed to lock config backend");
324327
goto out;
325328
}
326329

327-
tmp = b->header.values;
328-
b->header.values = values;
329-
values = tmp;
330+
if ((error = config_read(values->values, b, reader, b->level, 0)) < 0)
331+
goto out;
330332

331333
git_mutex_unlock(&b->header.values_mutex);
332334

333335
out:
334-
refcounted_strmap_free(values);
335-
if (reader)
336-
git_buf_free(&reader->buffer);
337336
return error;
338337
}
339338

340339
static int config_refresh(git_config_backend *cfg)
341340
{
342-
int error = 0, updated = 0, any_updated = 0;
343341
diskfile_backend *b = (diskfile_backend *)cfg;
344-
struct reader *reader = NULL;
342+
refcounted_strmap *values = NULL, *tmp;
343+
struct config_file *include;
344+
struct reader reader;
345+
int error, updated;
345346
uint32_t i;
346347

347-
for (i = 0; i < git_array_size(b->readers); i++) {
348-
reader = git_array_get(b->readers, i);
349-
error = git_futils_readbuffer_updated(
350-
&reader->buffer, reader->file_path,
351-
&reader->checksum, &updated);
348+
reader_init(&reader, &b->file);
349+
350+
error = git_futils_readbuffer_updated(
351+
&reader.buffer, b->file.path, &b->file.checksum, &updated);
352+
353+
if (error < 0 && error != GIT_ENOTFOUND)
354+
goto out;
355+
356+
if (updated) {
357+
if ((error = refcounted_strmap_alloc(&values)) < 0)
358+
goto out;
359+
360+
/* Reparse the current configuration */
361+
git_array_foreach(b->file.includes, i, include) {
362+
config_file_clear(include);
363+
}
364+
365+
git_array_clear(b->file.includes);
352366

353-
if (error < 0 && error != GIT_ENOTFOUND)
354-
return error;
367+
if ((error = config__refresh(b, &reader, values)) < 0)
368+
goto out;
355369

356-
if (updated)
357-
any_updated = 1;
370+
tmp = b->header.values;
371+
b->header.values = values;
372+
values = tmp;
373+
} else {
374+
/* Refresh included configuration files */
375+
git_array_foreach(b->file.includes, i, include) {
376+
git_buf_free(&reader.buffer);
377+
reader_init(&reader, include);
378+
error = git_futils_readbuffer_updated(&reader.buffer, b->file.path,
379+
&b->file.checksum, NULL);
380+
381+
if ((error = config__refresh(b, &reader, b->header.values)) < 0)
382+
goto out;
383+
}
358384
}
359385

360-
if (!any_updated)
361-
return (error == GIT_ENOTFOUND) ? 0 : error;
386+
out:
387+
refcounted_strmap_free(values);
388+
git_buf_free(&reader.buffer);
362389

363-
return config__refresh(cfg);
390+
return (error == GIT_ENOTFOUND) ? 0 : error;
364391
}
365392

366393
static void backend_free(git_config_backend *_backend)
367394
{
368395
diskfile_backend *backend = (diskfile_backend *)_backend;
369-
uint32_t i;
370396

371397
if (backend == NULL)
372398
return;
373399

374-
for (i = 0; i < git_array_size(backend->readers); i++) {
375-
struct reader *r = git_array_get(backend->readers, i);
376-
git__free(r->file_path);
377-
}
378-
git_array_clear(backend->readers);
379-
380-
git__free(backend->file_path);
400+
config_file_clear(&backend->file);
381401
refcounted_strmap_free(backend->header.values);
382402
git_mutex_free(&backend->header.values_mutex);
383403
git__free(backend);
@@ -685,10 +705,10 @@ static int config_lock(git_config_backend *_cfg)
685705
diskfile_backend *cfg = (diskfile_backend *) _cfg;
686706
int error;
687707

688-
if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0)
708+
if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file.path, 0, GIT_CONFIG_FILE_MODE)) < 0)
689709
return error;
690710

691-
error = git_futils_readbuffer(&cfg->locked_content, cfg->file_path);
711+
error = git_futils_readbuffer(&cfg->locked_content, cfg->file.path);
692712
if (error < 0 && error != GIT_ENOTFOUND) {
693713
git_filebuf_cleanup(&cfg->locked_buf);
694714
return error;
@@ -726,8 +746,9 @@ int git_config_file__ondisk(git_config_backend **out, const char *path)
726746
backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
727747
git_mutex_init(&backend->header.values_mutex);
728748

729-
backend->file_path = git__strdup(path);
730-
GITERR_CHECK_ALLOC(backend->file_path);
749+
backend->file.path = git__strdup(path);
750+
GITERR_CHECK_ALLOC(backend->file.path);
751+
git_array_init(backend->file.includes);
731752

732753
backend->header.parent.open = config_open;
733754
backend->header.parent.get = config_get;
@@ -1549,7 +1570,6 @@ static int config_parse(
15491570
struct parse_data {
15501571
git_strmap *values;
15511572
diskfile_backend *cfg_file;
1552-
uint32_t reader_idx;
15531573
git_config_level_t level;
15541574
int depth;
15551575
};
@@ -1597,44 +1617,41 @@ static int read_on_variable(
15971617

15981618
/* Add or append the new config option */
15991619
if (!git__strcmp(var->entry->name, "include.path")) {
1600-
struct reader *r;
1620+
struct config_file *include;
1621+
struct reader r;
16011622
git_buf path = GIT_BUF_INIT;
16021623
char *dir;
1603-
uint32_t index;
1604-
1605-
r = git_array_alloc(parse_data->cfg_file->readers);
1606-
/* The reader may have been reallocated */
1607-
*reader = git_array_get(parse_data->cfg_file->readers, parse_data->reader_idx);
1608-
memset(r, 0, sizeof(struct reader));
16091624

1610-
if ((result = git_path_dirname_r(&path, (*reader)->file_path)) < 0)
1625+
if ((result = git_path_dirname_r(&path, (*reader)->file->path)) < 0)
16111626
return result;
16121627

1613-
/* We need to know our index in the array, as the next config_parse call may realloc */
1614-
index = git_array_size(parse_data->cfg_file->readers) - 1;
16151628
dir = git_buf_detach(&path);
16161629
result = included_path(&path, dir, var->entry->value);
16171630
git__free(dir);
16181631

16191632
if (result < 0)
16201633
return result;
16211634

1622-
r->file_path = git_buf_detach(&path);
1623-
git_buf_init(&r->buffer, 0);
1635+
include = git_array_alloc((*reader)->file->includes);
1636+
memset(include, 0, sizeof(*include));
1637+
git_array_init(include->includes);
1638+
include->path = git_buf_detach(&path);
1639+
1640+
git_buf_init(&r.buffer, 0);
1641+
memset(&r, 0, sizeof(r));
1642+
r.file = include;
16241643

16251644
result = git_futils_readbuffer_updated(
1626-
&r->buffer, r->file_path, &r->checksum, NULL);
1645+
&r.buffer, include->path, &include->checksum, NULL);
16271646

16281647
if (result == 0) {
1629-
result = config_read(parse_data->values, parse_data->cfg_file, r, parse_data->level, parse_data->depth+1);
1630-
r = git_array_get(parse_data->cfg_file->readers, index);
1631-
*reader = git_array_get(parse_data->cfg_file->readers, parse_data->reader_idx);
1648+
result = config_read(parse_data->values, parse_data->cfg_file, &r, parse_data->level, parse_data->depth+1);
16321649
} else if (result == GIT_ENOTFOUND) {
16331650
giterr_clear();
16341651
result = 0;
16351652
}
16361653

1637-
git_buf_free(&r->buffer);
1654+
git_buf_free(&r.buffer);
16381655
}
16391656

16401657
return result;
@@ -1659,7 +1676,6 @@ static int config_read(git_strmap *values, diskfile_backend *cfg_file, struct re
16591676

16601677
parse_data.values = values;
16611678
parse_data.cfg_file = cfg_file;
1662-
parse_data.reader_idx = git_array_size(cfg_file->readers) - 1;
16631679
parse_data.level = level;
16641680
parse_data.depth = depth;
16651681

@@ -1896,31 +1912,33 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
18961912
char *section, *name, *ldot;
18971913
git_filebuf file = GIT_FILEBUF_INIT;
18981914
git_buf buf = GIT_BUF_INIT;
1899-
struct reader *reader = git_array_get(cfg->readers, 0);
1915+
struct reader reader;
19001916
struct write_data write_data;
19011917

1918+
reader_init(&reader, &cfg->file);
1919+
19021920
if (cfg->locked) {
1903-
result = git_buf_puts(&reader->buffer, git_buf_cstr(&cfg->locked_content));
1921+
result = git_buf_puts(&reader.buffer, git_buf_cstr(&cfg->locked_content));
19041922
} else {
19051923
/* Lock the file */
19061924
if ((result = git_filebuf_open(
1907-
&file, cfg->file_path, GIT_FILEBUF_HASH_CONTENTS, GIT_CONFIG_FILE_MODE)) < 0) {
1908-
git_buf_free(&reader->buffer);
1925+
&file, cfg->file.path, GIT_FILEBUF_HASH_CONTENTS, GIT_CONFIG_FILE_MODE)) < 0) {
1926+
git_buf_free(&reader.buffer);
19091927
return result;
19101928
}
19111929

19121930
/* We need to read in our own config file */
1913-
result = git_futils_readbuffer(&reader->buffer, cfg->file_path);
1931+
result = git_futils_readbuffer(&reader.buffer, cfg->file.path);
19141932
}
19151933

19161934
/* Initialise the reading position */
19171935
if (result == GIT_ENOTFOUND) {
1918-
reader->read_ptr = NULL;
1919-
reader->eof = 1;
1920-
git_buf_clear(&reader->buffer);
1936+
reader.read_ptr = NULL;
1937+
reader.eof = 1;
1938+
git_buf_clear(&reader.buffer);
19211939
} else if (result == 0) {
1922-
reader->read_ptr = reader->buffer.ptr;
1923-
reader->eof = 0;
1940+
reader.read_ptr = reader.buffer.ptr;
1941+
reader.eof = 0;
19241942
} else {
19251943
git_filebuf_cleanup(&file);
19261944
return -1; /* OS error when reading the file */
@@ -1939,7 +1957,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
19391957
write_data.preg = preg;
19401958
write_data.value = value;
19411959

1942-
result = config_parse(reader, write_on_section, write_on_variable, write_on_comment, write_on_eof, &write_data);
1960+
result = config_parse(&reader, write_on_section, write_on_variable, write_on_comment, write_on_eof, &write_data);
19431961
git__free(section);
19441962
git_buf_free(&write_data.buffered_comment);
19451963

@@ -1960,6 +1978,6 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
19601978

19611979
done:
19621980
git_buf_free(&buf);
1963-
git_buf_free(&reader->buffer);
1981+
git_buf_free(&reader.buffer);
19641982
return result;
19651983
}

0 commit comments

Comments
 (0)