Git fork

Merge branch 'kn/reftable-consistency-checks'

The reftable backend learned to sanity check its on-disk data more
carefully.

* kn/reftable-consistency-checks:
refs/reftable: add fsck check for checking the table name
reftable: add code to facilitate consistency checks
fsck: order 'fsck_msg_type' alphabetically
Documentation/fsck-msgids: remove duplicate msg id
reftable: check for trailing newline in 'tables.list'
refs: move consistency check msg to generic layer
refs: remove unused headers

+330 -59
+3 -3
Documentation/fsck-msgids.adoc
··· 38 38 `badReferentName`:: 39 39 (ERROR) The referent name of a symref is invalid. 40 40 41 + `badReftableTableName`:: 42 + (WARN) A reftable table has an invalid name. 43 + 41 44 `badTagName`:: 42 45 (INFO) A tag has an invalid format. 43 46 ··· 103 106 104 107 `gitmodulesParse`:: 105 108 (INFO) Could not parse `.gitmodules` blob. 106 - 107 - `gitmodulesLarge`; 108 - (ERROR) `.gitmodules` blob is too large to parse. 109 109 110 110 `gitmodulesPath`:: 111 111 (ERROR) `.gitmodules` path is invalid.
+2 -1
Makefile
··· 2767 2767 xdiff-objs: $(XDIFF_OBJS) 2768 2768 2769 2769 REFTABLE_OBJS += reftable/basics.o 2770 - REFTABLE_OBJS += reftable/error.o 2771 2770 REFTABLE_OBJS += reftable/block.o 2772 2771 REFTABLE_OBJS += reftable/blocksource.o 2772 + REFTABLE_OBJS += reftable/error.o 2773 + REFTABLE_OBJS += reftable/fsck.o 2773 2774 REFTABLE_OBJS += reftable/iter.o 2774 2775 REFTABLE_OBJS += reftable/merged.o 2775 2776 REFTABLE_OBJS += reftable/pq.o
+20 -19
fsck.h
··· 33 33 FUNC(BAD_PACKED_REF_ENTRY, ERROR) \ 34 34 FUNC(BAD_PACKED_REF_HEADER, ERROR) \ 35 35 FUNC(BAD_PARENT_SHA1, ERROR) \ 36 + FUNC(BAD_REFERENT_NAME, ERROR) \ 36 37 FUNC(BAD_REF_CONTENT, ERROR) \ 37 38 FUNC(BAD_REF_FILETYPE, ERROR) \ 38 39 FUNC(BAD_REF_NAME, ERROR) \ 39 - FUNC(BAD_REFERENT_NAME, ERROR) \ 40 40 FUNC(BAD_TIMEZONE, ERROR) \ 41 41 FUNC(BAD_TREE, ERROR) \ 42 42 FUNC(BAD_TREE_SHA1, ERROR) \ 43 43 FUNC(BAD_TYPE, ERROR) \ 44 44 FUNC(DUPLICATE_ENTRIES, ERROR) \ 45 + FUNC(GITATTRIBUTES_BLOB, ERROR) \ 46 + FUNC(GITATTRIBUTES_LARGE, ERROR) \ 47 + FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \ 48 + FUNC(GITATTRIBUTES_MISSING, ERROR) \ 49 + FUNC(GITMODULES_BLOB, ERROR) \ 50 + FUNC(GITMODULES_LARGE, ERROR) \ 51 + FUNC(GITMODULES_MISSING, ERROR) \ 52 + FUNC(GITMODULES_NAME, ERROR) \ 53 + FUNC(GITMODULES_PATH, ERROR) \ 54 + FUNC(GITMODULES_SYMLINK, ERROR) \ 55 + FUNC(GITMODULES_UPDATE, ERROR) \ 56 + FUNC(GITMODULES_URL, ERROR) \ 45 57 FUNC(MISSING_AUTHOR, ERROR) \ 46 58 FUNC(MISSING_COMMITTER, ERROR) \ 47 59 FUNC(MISSING_EMAIL, ERROR) \ ··· 60 72 FUNC(TREE_NOT_SORTED, ERROR) \ 61 73 FUNC(UNKNOWN_TYPE, ERROR) \ 62 74 FUNC(ZERO_PADDED_DATE, ERROR) \ 63 - FUNC(GITMODULES_MISSING, ERROR) \ 64 - FUNC(GITMODULES_BLOB, ERROR) \ 65 - FUNC(GITMODULES_LARGE, ERROR) \ 66 - FUNC(GITMODULES_NAME, ERROR) \ 67 - FUNC(GITMODULES_SYMLINK, ERROR) \ 68 - FUNC(GITMODULES_URL, ERROR) \ 69 - FUNC(GITMODULES_PATH, ERROR) \ 70 - FUNC(GITMODULES_UPDATE, ERROR) \ 71 - FUNC(GITATTRIBUTES_MISSING, ERROR) \ 72 - FUNC(GITATTRIBUTES_LARGE, ERROR) \ 73 - FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \ 74 - FUNC(GITATTRIBUTES_BLOB, ERROR) \ 75 75 /* warnings */ \ 76 + FUNC(BAD_REFTABLE_TABLE_NAME, WARN) \ 76 77 FUNC(EMPTY_NAME, WARN) \ 77 78 FUNC(FULL_PATHNAME, WARN) \ 78 79 FUNC(HAS_DOT, WARN) \ 79 80 FUNC(HAS_DOTDOT, WARN) \ 80 81 FUNC(HAS_DOTGIT, WARN) \ 82 + FUNC(LARGE_PATHNAME, WARN) \ 81 83 FUNC(NULL_SHA1, WARN) \ 82 - FUNC(ZERO_PADDED_FILEMODE, WARN) \ 83 84 FUNC(NUL_IN_COMMIT, WARN) \ 84 - FUNC(LARGE_PATHNAME, WARN) \ 85 + FUNC(ZERO_PADDED_FILEMODE, WARN) \ 85 86 /* infos (reported as warnings, but ignored by default) */ \ 86 87 FUNC(BAD_FILEMODE, INFO) \ 88 + FUNC(BAD_TAG_NAME, INFO) \ 87 89 FUNC(EMPTY_PACKED_REFS_FILE, INFO) \ 88 - FUNC(GITMODULES_PARSE, INFO) \ 90 + FUNC(GITATTRIBUTES_SYMLINK, INFO) \ 89 91 FUNC(GITIGNORE_SYMLINK, INFO) \ 90 - FUNC(GITATTRIBUTES_SYMLINK, INFO) \ 92 + FUNC(GITMODULES_PARSE, INFO) \ 91 93 FUNC(MAILMAP_SYMLINK, INFO) \ 92 - FUNC(BAD_TAG_NAME, INFO) \ 93 94 FUNC(MISSING_TAGGER_ENTRY, INFO) \ 94 - FUNC(SYMLINK_REF, INFO) \ 95 95 FUNC(REF_MISSING_NEWLINE, INFO) \ 96 + FUNC(SYMLINK_REF, INFO) \ 96 97 FUNC(SYMREF_TARGET_IS_NOT_A_REF, INFO) \ 97 98 FUNC(TRAILING_REF_CONTENT, INFO) \ 98 99 /* ignored (elevated when requested) */ \
+1
meson.build
··· 452 452 'reftable/error.c', 453 453 'reftable/block.c', 454 454 'reftable/blocksource.c', 455 + 'reftable/fsck.c', 455 456 'reftable/iter.c', 456 457 'reftable/merged.c', 457 458 'reftable/pq.c',
+4
refs.c
··· 32 32 #include "commit.h" 33 33 #include "wildmatch.h" 34 34 #include "ident.h" 35 + #include "fsck.h" 35 36 36 37 /* 37 38 * List of all available backends ··· 323 324 int refs_fsck(struct ref_store *refs, struct fsck_options *o, 324 325 struct worktree *wt) 325 326 { 327 + if (o->verbose) 328 + fprintf_ln(stderr, _("Checking references consistency")); 329 + 326 330 return refs->be->fsck(refs, o, wt); 327 331 } 328 332
-1
refs/debug.c
··· 1 1 #include "git-compat-util.h" 2 2 #include "hex.h" 3 3 #include "refs-internal.h" 4 - #include "string-list.h" 5 4 #include "trace.h" 6 5 7 6 static struct trace_key trace_refs = TRACE_KEY_INIT(REFS);
-3
refs/files-backend.c
··· 20 20 #include "../dir-iterator.h" 21 21 #include "../lockfile.h" 22 22 #include "../object.h" 23 - #include "../object-file.h" 24 23 #include "../path.h" 25 24 #include "../dir.h" 26 25 #include "../chdir-notify.h" ··· 3970 3969 NULL, 3971 3970 }; 3972 3971 3973 - if (o->verbose) 3974 - fprintf_ln(stderr, _("Checking references consistency")); 3975 3972 return files_fsck_refs_dir(ref_store, o, "refs", wt, fsck_refs_fn); 3976 3973 } 3977 3974
+52 -6
refs/reftable-backend.c
··· 6 6 #include "../config.h" 7 7 #include "../dir.h" 8 8 #include "../environment.h" 9 + #include "../fsck.h" 9 10 #include "../gettext.h" 10 11 #include "../hash.h" 11 12 #include "../hex.h" 12 13 #include "../iterator.h" 13 14 #include "../ident.h" 14 - #include "../lockfile.h" 15 15 #include "../object.h" 16 16 #include "../path.h" 17 17 #include "../refs.h" 18 18 #include "../reftable/reftable-basics.h" 19 - #include "../reftable/reftable-stack.h" 20 - #include "../reftable/reftable-record.h" 21 19 #include "../reftable/reftable-error.h" 20 + #include "../reftable/reftable-fsck.h" 22 21 #include "../reftable/reftable-iterator.h" 22 + #include "../reftable/reftable-record.h" 23 + #include "../reftable/reftable-stack.h" 23 24 #include "../repo-settings.h" 24 25 #include "../setup.h" 25 26 #include "../strmap.h" ··· 2714 2715 return ret; 2715 2716 } 2716 2717 2717 - static int reftable_be_fsck(struct ref_store *ref_store UNUSED, 2718 - struct fsck_options *o UNUSED, 2718 + static void reftable_fsck_verbose_handler(const char *msg, void *cb_data) 2719 + { 2720 + struct fsck_options *o = cb_data; 2721 + 2722 + if (o->verbose) 2723 + fprintf_ln(stderr, "%s", msg); 2724 + } 2725 + 2726 + static const enum fsck_msg_id fsck_msg_id_map[] = { 2727 + [REFTABLE_FSCK_ERROR_TABLE_NAME] = FSCK_MSG_BAD_REFTABLE_TABLE_NAME, 2728 + }; 2729 + 2730 + static int reftable_fsck_error_handler(struct reftable_fsck_info *info, 2731 + void *cb_data) 2732 + { 2733 + struct fsck_ref_report report = { .path = info->path }; 2734 + struct fsck_options *o = cb_data; 2735 + enum fsck_msg_id msg_id; 2736 + 2737 + if (info->error < 0 || info->error >= REFTABLE_FSCK_MAX_VALUE) 2738 + BUG("unknown fsck error: %d", (int)info->error); 2739 + 2740 + msg_id = fsck_msg_id_map[info->error]; 2741 + 2742 + if (!msg_id) 2743 + BUG("fsck_msg_id value missing for reftable error: %d", (int)info->error); 2744 + 2745 + return fsck_report_ref(o, &report, msg_id, "%s", info->msg); 2746 + } 2747 + 2748 + static int reftable_be_fsck(struct ref_store *ref_store, struct fsck_options *o, 2719 2749 struct worktree *wt UNUSED) 2720 2750 { 2721 - return 0; 2751 + struct reftable_ref_store *refs; 2752 + struct strmap_entry *entry; 2753 + struct hashmap_iter iter; 2754 + int ret = 0; 2755 + 2756 + refs = reftable_be_downcast(ref_store, REF_STORE_READ, "fsck"); 2757 + 2758 + ret |= reftable_fsck_check(refs->main_backend.stack, reftable_fsck_error_handler, 2759 + reftable_fsck_verbose_handler, o); 2760 + 2761 + strmap_for_each_entry(&refs->worktree_backends, &iter, entry) { 2762 + struct reftable_backend *b = (struct reftable_backend *)entry->value; 2763 + ret |= reftable_fsck_check(b->stack, reftable_fsck_error_handler, 2764 + reftable_fsck_verbose_handler, o); 2765 + } 2766 + 2767 + return ret; 2722 2768 } 2723 2769 2724 2770 struct ref_storage_be refs_be_reftable = {
+24 -13
reftable/basics.c
··· 195 195 return p - names; 196 196 } 197 197 198 - char **parse_names(char *buf, int size) 198 + int parse_names(char *buf, int size, char ***out) 199 199 { 200 200 char **names = NULL; 201 201 size_t names_cap = 0; 202 202 size_t names_len = 0; 203 203 char *p = buf; 204 204 char *end = buf + size; 205 + int err = 0; 205 206 206 207 while (p < end) { 207 208 char *next = strchr(p, '\n'); 208 - if (next && next < end) { 209 - *next = 0; 209 + if (!next) { 210 + err = REFTABLE_FORMAT_ERROR; 211 + goto done; 212 + } else if (next < end) { 213 + *next = '\0'; 210 214 } else { 211 215 next = end; 212 216 } 217 + 213 218 if (p < next) { 214 219 if (REFTABLE_ALLOC_GROW(names, names_len + 1, 215 - names_cap)) 216 - goto err; 220 + names_cap)) { 221 + err = REFTABLE_OUT_OF_MEMORY_ERROR; 222 + goto done; 223 + } 217 224 218 225 names[names_len] = reftable_strdup(p); 219 - if (!names[names_len++]) 220 - goto err; 226 + if (!names[names_len++]) { 227 + err = REFTABLE_OUT_OF_MEMORY_ERROR; 228 + goto done; 229 + } 221 230 } 222 231 p = next + 1; 223 232 } 224 233 225 - if (REFTABLE_ALLOC_GROW(names, names_len + 1, names_cap)) 226 - goto err; 234 + if (REFTABLE_ALLOC_GROW(names, names_len + 1, names_cap)) { 235 + err = REFTABLE_OUT_OF_MEMORY_ERROR; 236 + goto done; 237 + } 227 238 names[names_len] = NULL; 228 239 229 - return names; 230 - 231 - err: 240 + *out = names; 241 + return 0; 242 + done: 232 243 for (size_t i = 0; i < names_len; i++) 233 244 reftable_free(names[i]); 234 245 reftable_free(names); 235 - return NULL; 246 + return err; 236 247 } 237 248 238 249 int names_equal(const char **a, const char **b)
+4 -3
reftable/basics.h
··· 167 167 168 168 /* 169 169 * Parse a newline separated list of names. `size` is the length of the buffer, 170 - * without terminating '\0'. Empty names are discarded. Returns a `NULL` 171 - * pointer when allocations fail. 170 + * without terminating '\0'. Empty names are discarded. 171 + * 172 + * Returns 0 on success, a reftable error code on error. 172 173 */ 173 - char **parse_names(char *buf, int size); 174 + int parse_names(char *buf, int size, char ***out); 174 175 175 176 /* compares two NULL-terminated arrays of strings. */ 176 177 int names_equal(const char **a, const char **b);
+100
reftable/fsck.c
··· 1 + #include "basics.h" 2 + #include "reftable-fsck.h" 3 + #include "reftable-table.h" 4 + #include "stack.h" 5 + 6 + static bool table_has_valid_name(const char *name) 7 + { 8 + const char *ptr = name; 9 + char *endptr; 10 + 11 + /* strtoull doesn't set errno on success */ 12 + errno = 0; 13 + 14 + strtoull(ptr, &endptr, 16); 15 + if (errno) 16 + return false; 17 + ptr = endptr; 18 + 19 + if (*ptr != '-') 20 + return false; 21 + ptr++; 22 + 23 + strtoull(ptr, &endptr, 16); 24 + if (errno) 25 + return false; 26 + ptr = endptr; 27 + 28 + if (*ptr != '-') 29 + return false; 30 + ptr++; 31 + 32 + strtoul(ptr, &endptr, 16); 33 + if (errno) 34 + return false; 35 + ptr = endptr; 36 + 37 + if (strcmp(ptr, ".ref") && strcmp(ptr, ".log")) 38 + return false; 39 + 40 + return true; 41 + } 42 + 43 + typedef int (*table_check_fn)(struct reftable_table *table, 44 + reftable_fsck_report_fn report_fn, 45 + void *cb_data); 46 + 47 + static int table_check_name(struct reftable_table *table, 48 + reftable_fsck_report_fn report_fn, 49 + void *cb_data) 50 + { 51 + if (!table_has_valid_name(table->name)) { 52 + struct reftable_fsck_info info; 53 + 54 + info.error = REFTABLE_FSCK_ERROR_TABLE_NAME; 55 + info.msg = "invalid reftable table name"; 56 + info.path = table->name; 57 + 58 + return report_fn(&info, cb_data); 59 + } 60 + 61 + return 0; 62 + } 63 + 64 + static int table_checks(struct reftable_table *table, 65 + reftable_fsck_report_fn report_fn, 66 + reftable_fsck_verbose_fn verbose_fn UNUSED, 67 + void *cb_data) 68 + { 69 + table_check_fn table_check_fns[] = { 70 + table_check_name, 71 + NULL, 72 + }; 73 + int err = 0; 74 + 75 + for (size_t i = 0; table_check_fns[i]; i++) 76 + err |= table_check_fns[i](table, report_fn, cb_data); 77 + 78 + return err; 79 + } 80 + 81 + int reftable_fsck_check(struct reftable_stack *stack, 82 + reftable_fsck_report_fn report_fn, 83 + reftable_fsck_verbose_fn verbose_fn, 84 + void *cb_data) 85 + { 86 + struct reftable_buf msg = REFTABLE_BUF_INIT; 87 + int err = 0; 88 + 89 + for (size_t i = 0; i < stack->tables_len; i++) { 90 + reftable_buf_reset(&msg); 91 + reftable_buf_addstr(&msg, "Checking table: "); 92 + reftable_buf_addstr(&msg, stack->tables[i]->name); 93 + verbose_fn(msg.buf, cb_data); 94 + 95 + err |= table_checks(stack->tables[i], report_fn, verbose_fn, cb_data); 96 + } 97 + 98 + reftable_buf_release(&msg); 99 + return err; 100 + }
+40
reftable/reftable-fsck.h
··· 1 + #ifndef REFTABLE_FSCK_H 2 + #define REFTABLE_FSCK_H 3 + 4 + #include "reftable-stack.h" 5 + 6 + enum reftable_fsck_error { 7 + /* Invalid table name */ 8 + REFTABLE_FSCK_ERROR_TABLE_NAME = 0, 9 + /* Used for bounds checking, must be last */ 10 + REFTABLE_FSCK_MAX_VALUE, 11 + }; 12 + 13 + /* Represents an individual error encountered during the FSCK checks. */ 14 + struct reftable_fsck_info { 15 + enum reftable_fsck_error error; 16 + const char *msg; 17 + const char *path; 18 + }; 19 + 20 + typedef int reftable_fsck_report_fn(struct reftable_fsck_info *info, 21 + void *cb_data); 22 + typedef void reftable_fsck_verbose_fn(const char *msg, void *cb_data); 23 + 24 + /* 25 + * Given a reftable stack, perform consistency checks on the stack. 26 + * 27 + * If an issue is encountered, the issue is reported to the callee via the 28 + * provided 'report_fn'. If the issue is non-recoverable the flow will not 29 + * continue. If it is recoverable, the flow will continue and further issues 30 + * will be reported as identified. 31 + * 32 + * The 'verbose_fn' will be invoked to provide verbose information about 33 + * the progress and state of the consistency checks. 34 + */ 35 + int reftable_fsck_check(struct reftable_stack *stack, 36 + reftable_fsck_report_fn report_fn, 37 + reftable_fsck_verbose_fn verbose_fn, 38 + void *cb_data); 39 + 40 + #endif /* REFTABLE_FSCK_H */
+1 -6
reftable/stack.c
··· 109 109 } 110 110 buf[size] = 0; 111 111 112 - *namesp = parse_names(buf, size); 113 - if (!*namesp) { 114 - err = REFTABLE_OUT_OF_MEMORY_ERROR; 115 - goto done; 116 - } 117 - 112 + err = parse_names(buf, size, namesp); 118 113 done: 119 114 reftable_free(buf); 120 115 return err;
+1
t/meson.build
··· 146 146 't0611-reftable-httpd.sh', 147 147 't0612-reftable-jgit-compatibility.sh', 148 148 't0613-reftable-write-options.sh', 149 + 't0614-reftable-fsck.sh', 149 150 't1000-read-tree-m-3way.sh', 150 151 't1001-read-tree-m-2way.sh', 151 152 't1002-read-tree-m-u-2way.sh',
+58
t/t0614-reftable-fsck.sh
··· 1 + #!/bin/sh 2 + 3 + test_description='Test reftable backend consistency check' 4 + 5 + GIT_TEST_DEFAULT_REF_FORMAT=reftable 6 + export GIT_TEST_DEFAULT_REF_FORMAT 7 + 8 + . ./test-lib.sh 9 + 10 + test_expect_success "no errors reported on a well formed repository" ' 11 + test_when_finished "rm -rf repo" && 12 + git init repo && 13 + ( 14 + cd repo && 15 + git commit --allow-empty -m initial && 16 + 17 + for i in $(test_seq 20) 18 + do 19 + git update-ref refs/heads/branch-$i HEAD || return 1 20 + done && 21 + 22 + # The repository should end up with multiple tables. 23 + test_line_count ">" 1 .git/reftable/tables.list && 24 + 25 + git refs verify 2>err && 26 + test_must_be_empty err 27 + ) 28 + ' 29 + 30 + for TABLE_NAME in "foo-bar-e4d12d59.ref" \ 31 + "0x00000000zzzz-0x00000000zzzz-e4d12d59.ref" \ 32 + "0x000000000001-0x000000000002-e4d12d59.abc" \ 33 + "0x000000000001-0x000000000002-e4d12d59.refabc"; do 34 + test_expect_success "table name $TABLE_NAME should be checked" ' 35 + test_when_finished "rm -rf repo" && 36 + git init repo && 37 + ( 38 + cd repo && 39 + git commit --allow-empty -m initial && 40 + 41 + git refs verify 2>err && 42 + test_must_be_empty err && 43 + 44 + EXISTING_TABLE=$(head -n1 .git/reftable/tables.list) && 45 + mv ".git/reftable/$EXISTING_TABLE" ".git/reftable/$TABLE_NAME" && 46 + sed "s/${EXISTING_TABLE}/${TABLE_NAME}/g" .git/reftable/tables.list > tables.list && 47 + mv tables.list .git/reftable/tables.list && 48 + 49 + git refs verify 2>err && 50 + cat >expect <<-EOF && 51 + warning: ${TABLE_NAME}: badReftableTableName: invalid reftable table name 52 + EOF 53 + test_cmp expect err 54 + ) 55 + ' 56 + done 57 + 58 + test_done
+20 -4
t/unit-tests/u-reftable-basics.c
··· 9 9 #include "unit-test.h" 10 10 #include "lib-reftable.h" 11 11 #include "reftable/basics.h" 12 + #include "reftable/reftable-error.h" 12 13 13 14 struct integer_needle_lesseq_args { 14 15 int needle; ··· 79 80 void test_reftable_basics__parse_names(void) 80 81 { 81 82 char in1[] = "line\n"; 82 - char in2[] = "a\nb\nc"; 83 - char **out = parse_names(in1, strlen(in1)); 83 + char in2[] = "a\nb\nc\n"; 84 + char **out = NULL; 85 + int err = parse_names(in1, strlen(in1), &out); 86 + cl_assert(err == 0); 84 87 cl_assert(out != NULL); 85 88 cl_assert_equal_s(out[0], "line"); 86 89 cl_assert(!out[1]); 87 90 free_names(out); 88 91 89 - out = parse_names(in2, strlen(in2)); 92 + out = NULL; 93 + err = parse_names(in2, strlen(in2), &out); 94 + cl_assert(err == 0); 90 95 cl_assert(out != NULL); 91 96 cl_assert_equal_s(out[0], "a"); 92 97 cl_assert_equal_s(out[1], "b"); ··· 95 100 free_names(out); 96 101 } 97 102 103 + void test_reftable_basics__parse_names_missing_newline(void) 104 + { 105 + char in1[] = "line\nline2"; 106 + char **out = NULL; 107 + int err = parse_names(in1, strlen(in1), &out); 108 + cl_assert(err == REFTABLE_FORMAT_ERROR); 109 + cl_assert(out == NULL); 110 + } 111 + 98 112 void test_reftable_basics__parse_names_drop_empty_string(void) 99 113 { 100 114 char in[] = "a\n\nb\n"; 101 - char **out = parse_names(in, strlen(in)); 115 + char **out = NULL; 116 + int err = parse_names(in, strlen(in), &out); 117 + cl_assert(err == 0); 102 118 cl_assert(out != NULL); 103 119 cl_assert_equal_s(out[0], "a"); 104 120 /* simply '\n' should be dropped as empty string */