reftable: check for trailing newline in 'tables.list'

In the reftable format, the 'tables.list' file contains a
newline separated list of tables. While we parse this file, we do not
check or care about the last newline. Tighten the parser in
`parse_names()` to return an appropriate error if the last newline is
missing.

This requires modification to `parse_names()` to now return the error
while accepting the output as a third argument.

Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Karthik Nayak
2025-10-07 14:11:27 +02:00
committed by Junio C Hamano
parent 1ef32f0989
commit f644206377
4 changed files with 49 additions and 26 deletions

View File

@@ -195,44 +195,55 @@ size_t names_length(const char **names)
return p - names; return p - names;
} }
char **parse_names(char *buf, int size) int parse_names(char *buf, int size, char ***out)
{ {
char **names = NULL; char **names = NULL;
size_t names_cap = 0; size_t names_cap = 0;
size_t names_len = 0; size_t names_len = 0;
char *p = buf; char *p = buf;
char *end = buf + size; char *end = buf + size;
int err = 0;
while (p < end) { while (p < end) {
char *next = strchr(p, '\n'); char *next = strchr(p, '\n');
if (next && next < end) { if (!next) {
*next = 0; err = REFTABLE_FORMAT_ERROR;
goto done;
} else if (next < end) {
*next = '\0';
} else { } else {
next = end; next = end;
} }
if (p < next) { if (p < next) {
if (REFTABLE_ALLOC_GROW(names, names_len + 1, if (REFTABLE_ALLOC_GROW(names, names_len + 1,
names_cap)) names_cap)) {
goto err; err = REFTABLE_OUT_OF_MEMORY_ERROR;
goto done;
}
names[names_len] = reftable_strdup(p); names[names_len] = reftable_strdup(p);
if (!names[names_len++]) if (!names[names_len++]) {
goto err; err = REFTABLE_OUT_OF_MEMORY_ERROR;
goto done;
}
} }
p = next + 1; p = next + 1;
} }
if (REFTABLE_ALLOC_GROW(names, names_len + 1, names_cap)) if (REFTABLE_ALLOC_GROW(names, names_len + 1, names_cap)) {
goto err; err = REFTABLE_OUT_OF_MEMORY_ERROR;
goto done;
}
names[names_len] = NULL; names[names_len] = NULL;
return names; *out = names;
return 0;
err: done:
for (size_t i = 0; i < names_len; i++) for (size_t i = 0; i < names_len; i++)
reftable_free(names[i]); reftable_free(names[i]);
reftable_free(names); reftable_free(names);
return NULL; return err;
} }
int names_equal(const char **a, const char **b) int names_equal(const char **a, const char **b)

View File

@@ -167,10 +167,11 @@ void free_names(char **a);
/* /*
* Parse a newline separated list of names. `size` is the length of the buffer, * Parse a newline separated list of names. `size` is the length of the buffer,
* without terminating '\0'. Empty names are discarded. Returns a `NULL` * without terminating '\0'. Empty names are discarded.
* pointer when allocations fail. *
* Returns 0 on success, a reftable error code on error.
*/ */
char **parse_names(char *buf, int size); int parse_names(char *buf, int size, char ***out);
/* compares two NULL-terminated arrays of strings. */ /* compares two NULL-terminated arrays of strings. */
int names_equal(const char **a, const char **b); int names_equal(const char **a, const char **b);

View File

@@ -169,12 +169,7 @@ static int fd_read_lines(int fd, char ***namesp)
} }
buf[size] = 0; buf[size] = 0;
*namesp = parse_names(buf, size); err = parse_names(buf, size, namesp);
if (!*namesp) {
err = REFTABLE_OUT_OF_MEMORY_ERROR;
goto done;
}
done: done:
reftable_free(buf); reftable_free(buf);
return err; return err;

View File

@@ -9,6 +9,7 @@ https://developers.google.com/open-source/licenses/bsd
#include "unit-test.h" #include "unit-test.h"
#include "lib-reftable.h" #include "lib-reftable.h"
#include "reftable/basics.h" #include "reftable/basics.h"
#include "reftable/reftable-error.h"
struct integer_needle_lesseq_args { struct integer_needle_lesseq_args {
int needle; int needle;
@@ -79,14 +80,18 @@ void test_reftable_basics__names_equal(void)
void test_reftable_basics__parse_names(void) void test_reftable_basics__parse_names(void)
{ {
char in1[] = "line\n"; char in1[] = "line\n";
char in2[] = "a\nb\nc"; char in2[] = "a\nb\nc\n";
char **out = parse_names(in1, strlen(in1)); char **out = NULL;
int err = parse_names(in1, strlen(in1), &out);
cl_assert(err == 0);
cl_assert(out != NULL); cl_assert(out != NULL);
cl_assert_equal_s(out[0], "line"); cl_assert_equal_s(out[0], "line");
cl_assert(!out[1]); cl_assert(!out[1]);
free_names(out); free_names(out);
out = parse_names(in2, strlen(in2)); out = NULL;
err = parse_names(in2, strlen(in2), &out);
cl_assert(err == 0);
cl_assert(out != NULL); cl_assert(out != NULL);
cl_assert_equal_s(out[0], "a"); cl_assert_equal_s(out[0], "a");
cl_assert_equal_s(out[1], "b"); cl_assert_equal_s(out[1], "b");
@@ -95,10 +100,21 @@ void test_reftable_basics__parse_names(void)
free_names(out); free_names(out);
} }
void test_reftable_basics__parse_names_missing_newline(void)
{
char in1[] = "line\nline2";
char **out = NULL;
int err = parse_names(in1, strlen(in1), &out);
cl_assert(err == REFTABLE_FORMAT_ERROR);
cl_assert(out == NULL);
}
void test_reftable_basics__parse_names_drop_empty_string(void) void test_reftable_basics__parse_names_drop_empty_string(void)
{ {
char in[] = "a\n\nb\n"; char in[] = "a\n\nb\n";
char **out = parse_names(in, strlen(in)); char **out = NULL;
int err = parse_names(in, strlen(in), &out);
cl_assert(err == 0);
cl_assert(out != NULL); cl_assert(out != NULL);
cl_assert_equal_s(out[0], "a"); cl_assert_equal_s(out[0], "a");
/* simply '\n' should be dropped as empty string */ /* simply '\n' should be dropped as empty string */