@@ -11,24 +11,40 @@ static const char *const builtin_config_batch_usage[] = {
1111 NULL
1212};
1313
14+ static int zformat = 0 ;
15+
1416#define UNKNOWN_COMMAND "unknown_command"
1517#define HELP_COMMAND "help"
1618#define GET_COMMAND "get"
1719#define COMMAND_PARSE_ERROR "command_parse_error"
1820
21+ static void print_word (const char * word , int start )
22+ {
23+ if (zformat ) {
24+ printf ("%" PRIuMAX ":%s" , strlen (word ), word );
25+ fputc (0 , stdout );
26+ } else if (start )
27+ printf ("%s" , word );
28+ else
29+ printf (" %s" , word );
30+ }
31+
1932static int emit_response (const char * response , ...)
2033{
2134 va_list params ;
2235 const char * token ;
2336
24- printf ( "%s" , response );
37+ print_word ( response , 1 );
2538
2639 va_start (params , response );
2740 while ((token = va_arg (params , const char * )))
28- printf ( " %s" , token );
41+ print_word ( token , 0 );
2942 va_end (params );
3043
31- printf ("\n" );
44+ if (zformat )
45+ fputc (0 , stdout );
46+ else
47+ printf ("\n" );
3248 fflush (stdout );
3349 return 0 ;
3450}
@@ -59,6 +75,52 @@ static int unknown_command(struct repository *repo UNUSED,
5975 return emit_response (UNKNOWN_COMMAND , NULL );
6076}
6177
78+ /*
79+ * Parse the next token using the NUL-byte format.
80+ */
81+ static size_t parse_ztoken (char * * data , size_t * data_len ,
82+ char * * token , int * err )
83+ {
84+ size_t i = 0 , token_len ;
85+
86+ while (i < * data_len && (* data )[i ] != ':' ) {
87+ if ((* data )[i ] < '0' || (* data )[i ] > '9' ) {
88+ goto parse_error ;
89+ }
90+ i ++ ;
91+ }
92+
93+ if (i >= * data_len || (* data )[i ] != ':' || i > 5 )
94+ goto parse_error ;
95+
96+ (* data )[i ] = 0 ;
97+ token_len = atoi (* data );
98+
99+ if (token_len + i + 1 >= * data_len )
100+ goto parse_error ;
101+
102+ * token = * data + i + 1 ;
103+ * data_len = * data_len - (i + 1 );
104+
105+ /* check for early NULs. */
106+ for (i = 0 ; i < token_len ; i ++ ) {
107+ if (!(* token )[i ])
108+ goto parse_error ;
109+ }
110+ /* check for matching NUL. */
111+ if ((* token )[token_len ])
112+ goto parse_error ;
113+
114+ * data = * token + token_len + 1 ;
115+ * data_len = * data_len - (token_len + 1 );
116+ return token_len ;
117+
118+ parse_error :
119+ * err = 1 ;
120+ * token = NULL ;
121+ return 0 ;
122+ }
123+
62124static size_t parse_whitespace_token (char * * data , size_t * data_len ,
63125 char * * token , int * err UNUSED )
64126{
@@ -93,15 +155,23 @@ static size_t parse_whitespace_token(char **data, size_t *data_len,
93155 * The returned value is the length of the token that was
94156 * discovered.
95157 *
96- * 'err' is ignored for now, but will be filled in in a future
97- * change.
158+ * The 'token' pointer is used to set the start of the token.
159+ * In the whitespace format, this is always the input value of
160+ * 'data' but in the NUL-terminated format this follows an "<N>:"
161+ * prefix.
162+ *
163+ * In the case of the NUL-terminated format, a bad parse of the
164+ * decimal length or a mismatch of the decimal length and the
165+ * length of the following NUL-terminated string will result in
166+ * the value pointed at by 'err' to be set to 1.
98167 */
99168static size_t parse_token (char * * data , size_t * data_len ,
100169 char * * token , int * err )
101170{
102171 if (!* data_len )
103172 return 0 ;
104-
173+ if (zformat )
174+ return parse_ztoken (data , data_len , token , err );
105175 return parse_whitespace_token (data , data_len , token , err );
106176}
107177
@@ -255,7 +325,13 @@ static int get_command_1(struct repository *repo,
255325 goto parse_error ; /* unknown arg. */
256326
257327 /* Use the remaining data as the value string. */
258- gc_data .value = data ;
328+ if (!zformat )
329+ gc_data .value = data ;
330+ else {
331+ parse_token (& data , & data_len , & gc_data .value , & err );
332+ if (err )
333+ goto parse_error ;
334+ }
259335
260336 if (gc_data .mode == MATCH_REGEX ) {
261337 CALLOC_ARRAY (gc_data .value_pattern , 1 );
@@ -348,17 +424,74 @@ static int help_command_1(struct repository *repo UNUSED,
348424 return 0 ;
349425}
350426
351- /**
352- * Process a single line from stdin and process the command.
353- *
354- * Returns 0 on successful processing of command, including the
355- * unknown_command output.
356- *
357- * Returns 1 on natural exit due to exist signal of empty line.
358- *
359- * Returns negative value on other catastrophic error.
360- */
361- static int process_command (struct repository * repo )
427+ static int process_command_nul (struct repository * repo )
428+ {
429+ static struct strbuf line = STRBUF_INIT ;
430+ char * data , * command , * versionstr ;
431+ size_t data_len , token_len ;
432+ int res = 0 , err = 0 , version = 0 , getc ;
433+ char c ;
434+
435+ /* If we start with EOF it's not an error. */
436+ getc = fgetc (stdin );
437+ if (getc == EOF )
438+ return 1 ;
439+
440+ do {
441+ c = (char )getc ;
442+ strbuf_addch (& line , c );
443+
444+ if (!c && line .len > 1 && !line .buf [line .len - 2 ])
445+ break ;
446+
447+ getc = fgetc (stdin );
448+
449+ /* It's an error if we reach EOF while parsing a command. */
450+ if (getc == EOF )
451+ goto parse_error ;
452+ } while (1 );
453+
454+ data = line .buf ;
455+ data_len = line .len - 1 ;
456+
457+ token_len = parse_ztoken (& data , & data_len , & command , & err );
458+ if (!token_len || err )
459+ goto parse_error ;
460+
461+ token_len = parse_ztoken (& data , & data_len , & versionstr , & err );
462+ if (!token_len || err )
463+ goto parse_error ;
464+
465+ if (!git_parse_int (versionstr , & version )) {
466+ res = error (_ ("unable to parse '%s' to integer" ),
467+ versionstr );
468+ goto parse_error ;
469+ }
470+
471+ for (size_t i = 0 ; i < COMMAND_COUNT ; i ++ ) {
472+ /*
473+ * Run the ith command if we have hit the unknown
474+ * command or if the name and version match.
475+ */
476+ if (!commands [i ].name [0 ] ||
477+ (!strcmp (command , commands [i ].name ) &&
478+ commands [i ].version == version )) {
479+ res = commands [i ].fn (repo , data , data_len );
480+ goto cleanup ;
481+ }
482+ }
483+
484+ BUG (_ ("scanned to end of command list, including 'unknown_command'" ));
485+
486+ parse_error :
487+ res = unknown_command (repo , NULL , 0 );
488+
489+ cleanup :
490+ strbuf_release (& line );
491+ return res ;
492+ }
493+
494+ static int process_command_whitespace (struct repository * repo )
362495{
363496 static struct strbuf line = STRBUF_INIT ;
364497 struct string_list tokens = STRING_LIST_INIT_NODUP ;
@@ -415,13 +548,32 @@ static int process_command(struct repository *repo)
415548 return res ;
416549}
417550
551+ /**
552+ * Process a single line from stdin and process the command.
553+ *
554+ * Returns 0 on successful processing of command, including the
555+ * unknown_command output.
556+ *
557+ * Returns 1 on natural exit due to exist signal of empty line.
558+ *
559+ * Returns negative value on other catastrophic error.
560+ */
561+ static int process_command (struct repository * repo )
562+ {
563+ if (zformat )
564+ return process_command_nul (repo );
565+ return process_command_whitespace (repo );
566+ }
567+
418568int cmd_config_batch (int argc ,
419569 const char * * argv ,
420570 const char * prefix ,
421571 struct repository * repo )
422572{
423573 int res = 0 ;
424574 struct option options [] = {
575+ OPT_BOOL ('z' , NULL , & zformat ,
576+ N_ ("stdin and stdout is NUL-terminated" )),
425577 OPT_END (),
426578 };
427579
0 commit comments