[PATCH i-g-t v3 07/10] runner: Fix use of newline on arguments

Lucas De Marchi lucas.demarchi at intel.com
Fri Jan 31 15:58:14 UTC 2025


On Fri, Jan 31, 2025 at 10:21:33AM +0100, Peter Senna Tschudin wrote:
>
>
>On 30.01.2025 18:21, Lucas De Marchi wrote:
>> Currently there's no support for newlines on arguments passed to runner.
>> However it's also a silent failure:
>>
>> 	# igt_runner --test-list '/tmp/test
>> 	list2.txt' build/tests/ /tmp/results
>>
>> 	# head /tmp/results/metadata.txt
>> 	disk_usage_limit : 0
>> 	test_list : /tmp/test
>> 	list2.txt
>> 	name : results
>> 	...
>>
>> 	# ./build/runner/igt_resume /tmp/results
>> 	[9840425.334900] All tests already executed.
>> 	resume failed at generating results
>> 	Done.
>>
>> Embedding a newline like this is very dubious for test-list, but it's
>> used for e.g. hooks. In future we will add the command line to the
>> metadata and possibly migrate the hooks, so add support for
>> escaping/unescaping the string on save/restore.
>>
>> The method chosen is slightly different than the one used for hooks:
>> instead of adding a escape char and keeping the char escaped, this just
>> prefers using an hex representation of the char with a \x<HEX>h
>> sequence. This makes it easier when unescaping since the reader can
>> continue reading one line per iteration. In future this can also be
>> adopted by the hooks or even migrating the hooks to use metadata.txt.
>>
>> For compatibility with previous parsing, it still prints "(null)" for
>> NULL pointers: ideally there would be nothing printed to avoid the
>> special case for this string.
>>
>> Signed-off-by: Lucas De Marchi <lucas.demarchi at intel.com>
>> ---
>>  runner/settings.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++-
>>  1 file changed, 76 insertions(+), 1 deletion(-)
>>
>> diff --git a/runner/settings.c b/runner/settings.c
>> index 693c5484e..b527c01d9 100644
>> --- a/runner/settings.c
>> +++ b/runner/settings.c
>> @@ -1052,10 +1052,79 @@ static bool serialize_hook_strs(struct settings *settings, int dirfd)
>>  	return true;
>>  }
>>
>> +/*
>> + * Serialize @s to @f, escaping '\' and '\n'. See unescape_str()
>> + */
>> +static void escape_str(const char *s, FILE *f)
>> +{
>> +	if (!s) {
>> +		fputs("(null)", f);
>> +		return;
>> +	}
>> +
>> +	while (*s) {
>> +		size_t len = strcspn(s, "\\\n");
>> +
>> +		if (len > 0) {
>> +			fwrite(s, len, 1, f);
>> +			s += len;
>> +		}
>> +
>> +		if (*s) {
>> +			fprintf(f, "\\x%xh", *s);
>> +			s++;
>> +		}
>> +	}
>> +}
>> +
>> +/*
>> + * Unescape a '\' and '\n': undo escape_str
>
>Nit: there is a specific format for documenting new functions. I have the impression
>that the arguments are not properly documented.

For external functions for other parts of igt to use, the format is
kernel-doc. That is not the case for static functions.

>
>> + *
>> + * Exacpe chars using the form '\x<hex>h' so they don't interfere with the line
>> + * parser.
>> + *
>> + * Return the number of chars saved in buf and optionally
>> + * the number of chars scanned in @n_src if that is non-nul.
>> + */
>> +static size_t unescape_str(char *buf, size_t *n_src)
>> +{
>> +	size_t dst_len = 0;
>> +	char *s = buf;
>> +
>> +	while (*s) {
>> +		char next = *(s + 1);
>> +
>> +		if (*s != '\\') {
>> +			buf[dst_len++] = *s++;
>> +		} else if (*s == '\\' && next == 'x') {
>> +			s += 2;
>> +			buf[dst_len++] = strtoul(s, &s, 16);
>> +			if (*s != 'h') {
>> +				/* this shouldn't happen */
>> +				return 0;
>> +			}
>> +			s++;
>> +		} else {
>> +			return 0;
>> +		}
>> +	}
>> +
>> +	buf[dst_len] = '\0';
>> +
>> +	if (n_src)
>> +		*n_src = s - buf;
>> +
>> +	return dst_len;
>> +}
>
>Have you considered  json_tokener_parse() / json_object_get_string() ?
>Glib' g_strescape () / g_strcompress() ? Not a fan of Glib,
>but it is currently used anyway. Or even libcurl' curl_easy_escape() /
>curl_easy_unescape()? Meson probes for the lib, but I did not see it in use.

depending on other things I'm working on, I will evaluate moving this file
to toml so I don't like adding another dep.

>
>My point being that it would be better to reuse instead of reinventing this
>wheel. If you prefer to use your own, please add unit testing, I found at least
>one issue:
>
>Lorem ipsum dolor sit amet
>Lorem ipsum dolor sit amet (26)
>
>Lorem \xZZh ipsum dolor sit amet
>Lorem  (0)
>
>Lorem \x41h ipsum dolor sit amet
>Lorem A ipsum dolor sit amet (32)

sorry, but I'm not following the example. What string was
encoded by escape_str() and decoded wrongly by unescape_str()? Not sure
there's much value making it very fool proof.

Additional tests would check that "given a random string s, does
escape_str(s) -> unescape_str(s) produce back the same string?", but I'm
just relying on the ones already there for hooks.

unescape_str() doesn't accept random strings, only the ones escaped by
its counterpart function. I deliberately didn't bother making it super
complex, see e.g. `/* this shouldn't happen */` above.  But I will see
what additional checks I can add.


>
>I guess you want to check strtoul() output for errors(can it cause an infinite
>loop on error?), check buffer limits to prevent writing past allocated size,

output is never bigger than input when unescaping a valid string.

>and using error code such as -1 instead of failing with 0.

yep

>
>> +
>>  #define SERIALIZE_LINE(f, s, name, fmt) fprintf(f, "%s : " fmt "\n", #name, s->name)
>>  #define SERIALIZE_INT(f, s, name) SERIALIZE_LINE(f, s, name, "%d")
>>  #define SERIALIZE_UL(f, s, name) SERIALIZE_LINE(f, s, name, "%lu")
>> -#define SERIALIZE_STR(f, s, name) SERIALIZE_LINE(f, s, name, "%s")
>> +#define SERIALIZE_STR(f, s, name) do {	\
>> +		fputs(#name " : ", f);	\
>> +		escape_str(s->name, f);	\
>> +		fputc('\n', f);		\
>> +	} while (0)
>>  bool serialize_settings(struct settings *settings)
>>  {
>>  	FILE *f;
>> @@ -1171,6 +1240,12 @@ static char *parse_str(char **val)
>>  {
>>  	char *ret = *val;
>>
>> +	/*
>> +	 * Unescaping a string is guaranteed to produce a string that is
>> +	 * smaller or of the same size. Just modify it in place and leak the
>> +	 * buffer
>> +	 */
>> +	unescape_str(ret, NULL);
>>  	*val = NULL;
>>
>>  	return ret;
>


More information about the igt-dev mailing list