diff mbox series

[v3,1/2] discover: Allow for empty paths

Message ID 20210120192658.27930-1-klaus@linux.vnet.ibm.com
State New
Headers show
Series [v3,1/2] discover: Allow for empty paths | expand

Commit Message

Klaus Heinrich Kiwi Jan. 20, 2021, 7:26 p.m. UTC
Some grub tests involve a (device)/path structure, where it is
actually legal to have an empty path. Adjust join_path() and
dependant functions to allow for empty pathnames.

Signed-off-by: Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com>
---
 discover/grub2/builtins.c        | 10 ++++++----
 discover/grub2/grub2.c           | 29 +++++++++++++++++++----------
 discover/paths.c                 | 16 +++++++++++-----
 discover/paths.h                 |  6 +++---
 test/parser/test-grub2-devpath.c | 28 +++++++++++++++++++---------
 test/parser/utils.c              |  2 +-
 6 files changed, 59 insertions(+), 32 deletions(-)

Comments

Jeremy Kerr Jan. 21, 2021, 1:31 a.m. UTC | #1
Hi Klaus,

Looks good! Just some minor things:

> @@ -130,24 +130,33 @@ struct grub2_file *grub2_parse_file(struct grub2_script *script,
>                 /* just a path - no device, return path as-is */
>                 file->path = talloc_strdup(file, str);
>  
> -       } else {
> +       }
> +       else {

Not sure we want this change.

>                 /* device plus path - split into components */
> -
>                 pos = strchr(str, ')');
>  
> -               /* no closing bracket, or zero-length path? */
> -               if (!pos || *(pos+1) == '\0') {
> +               /* no closing bracket is not legal */
> +               if (!pos) {
>                         talloc_free(file);
>                         return NULL;
>                 }
>  
> -               file->path = talloc_strdup(file, pos + 1);
> +               /* path is non-empty - copy it (otherwise keep it
> +                * NULL as it should be legal) */
> +               if ( *(pos+1) != '\0' )
> +                       file->path = talloc_strdup(file, pos + 1);

The formatting of the if-statements doesn't match the rest of the
file, you have spaces within the brackets. Not a huge issue, and I'm
happy to tweak this during the merge if you prefer.


> -               dev_len = pos - str - 1;
> -               if (dev_len)
> -                       file->dev = talloc_strndup(file, str + 1, dev_len);
> +               /* same as above for device string */
> +               if ( pos > (str+1) )
> +                       file->dev = talloc_strndup(file, str + 1,
> +                                      (size_t) (pos - str - 1));

This change looks unnecessary now that you have the case above, or am I
missing something?

>  
> +       /* if both dev and path are empty, this is probably an error
> */
> +       if (!file->dev && !file->path) {
> +               talloc_free(file);
> +               return NULL;
> +       }
>         return file;
>  }

OK, that looks good now, and seems to correctly handle either an empty
path or empty device.

The change looks good to me - could you add some tests to cover the
empty path cases? With this patch, there are not (yet) any legal cases
for an empty path, so we'd want to ensure that the path handling code
is correct for those. eg source, resources, and the test builtins.

For the case where an empty path becomes legal (ie, with the 'test -e'
command), then those particular tests could deferred to 2/2.

Cheers,


Jeremy
Jeremy Kerr Jan. 21, 2021, 3:10 a.m. UTC | #2
Hi Klaus,

> The change looks good to me - could you add some tests to cover the
> empty path cases? With this patch, there are not (yet) any legal cases
> for an empty path, so we'd want to ensure that the path handling code
> is correct for those. eg source, resources, and the test builtins.

... and if you like, I'm happy to help out with adding these tests. I
understand that this has grown somewhat from just fixing the RHEL boot
config!  :)

Cheers,


Jeremy
Klaus Heinrich Kiwi Jan. 21, 2021, 5:38 p.m. UTC | #3
Hi Jeremy,

On 1/20/2021 10:31 PM, Jeremy Kerr wrote:
> Hi Klaus,
> 
> Looks good! Just some minor things:

>> -       } else {
>> +       }
>> +       else {
> 
> Not sure we want this change.

fixed


>> +                * NULL as it should be legal) */
>> +               if ( *(pos+1) != '\0' )
>> +                       file->path = talloc_strdup(file, pos + 1);
> 
> The formatting of the if-statements doesn't match the rest of the
> file, you have spaces within the brackets. Not a huge issue, and I'm
> happy to tweak this during the merge if you prefer.

Fixed as well

> 
>> -               dev_len = pos - str - 1;
>> -               if (dev_len)
>> -                       file->dev = talloc_strndup(file, str + 1, dev_len);
>> +               /* same as above for device string */
>> +               if ( pos > (str+1) )
>> +                       file->dev = talloc_strndup(file, str + 1,
>> +                                      (size_t) (pos - str - 1));
> 
> This change looks unnecessary now that you have the case above, or am I
> missing something?

You're right. I was trying to make it clearer, but the code should be
equivalent. I'll remove this change.

>>   
>> +       /* if both dev and path are empty, this is probably an error
>> */
>> +       if (!file->dev && !file->path) {
>> +               talloc_free(file);
>> +               return NULL;
>> +       }
>>          return file;
>>   }
> 
> OK, that looks good now, and seems to correctly handle either an empty
> path or empty device.
> 
> The change looks good to me - could you add some tests to cover the
> empty path cases? With this patch, there are not (yet) any legal cases
> for an empty path, so we'd want to ensure that the path handling code
> is correct for those. eg source, resources, and the test builtins.

I've added one test to test-grub2-devpath.c that checks for an empty
device string 'linux ()/vmlinux' but I'm not sure how we could test
for an empty path without something like 'test -e (device)'.

resources without a path such as
menuentry a {
	linux (device)
}
are already covered under the same test-grub2-devpath.c and I'm not
sure it's useful to add empty path tests for 'test -f', 'test -s' or
'test -d' since an empty path will always be replaced by '/' in those
cases.

But I'll look into it and send a v4 soon.

> For the case where an empty path becomes legal (ie, with the 'test -e'
> command), then those particular tests could deferred to 2/2.

Will try to add them as part of v4 too.

> Cheers,
> 
> 
> Jeremy
>

  -Klaus
diff mbox series

Patch

diff --git a/discover/grub2/builtins.c b/discover/grub2/builtins.c
index ab1407a..4b94f99 100644
--- a/discover/grub2/builtins.c
+++ b/discover/grub2/builtins.c
@@ -247,13 +247,13 @@  static bool builtin_test_op_file(struct grub2_script *script, char op,
 	switch (op) {
 	case 's':
 		/* -s: return true if file exists and has non-zero size */
-		result = statbuf.st_size > 0;
+		result = !path ? false : statbuf.st_size > 0;
 		break;
 	case 'f':
 		/* -f: return true if file exists and is not a directory. This is
 		 * different than the behavior of "test", but is what GRUB does
 		 * (though note as above that we follow symlinks unlike GRUB). */
-		result = !S_ISDIR(statbuf.st_mode);
+		result = !path ? false : !S_ISDIR(statbuf.st_mode);
 		break;
 	default:
 		result = false;
@@ -284,7 +284,7 @@  static bool builtin_test_op_dir(struct grub2_script *script, char op,
 	if (rc)
 		return false;
 
-	return S_ISDIR(statbuf.st_mode);
+	return !path ? false : S_ISDIR(statbuf.st_mode);
 }
 
 static bool builtin_test_op(struct grub2_script *script,
@@ -419,7 +419,9 @@  static int builtin_source(struct grub2_script *script,
 		return false;
 
 	rc = parse_to_device_path(script, argv[1], &dev, &path);
-	if (rc)
+
+	/* We need to have a valid (non-empty) path for sources */
+	if (rc || !path)
 		return false;
 
 	rc = parser_request_file(script->ctx, dev, path, &buf, &len);
diff --git a/discover/grub2/grub2.c b/discover/grub2/grub2.c
index b176ce2..7fb6f62 100644
--- a/discover/grub2/grub2.c
+++ b/discover/grub2/grub2.c
@@ -70,7 +70,8 @@  struct resource *create_grub2_resource(struct grub2_script *script,
 	}
 
 	file = grub2_parse_file(script, path);
-	if (!file)
+	/* We need a non-empty path for resources */
+	if (!file || !file->path)
 		return NULL;
 
 	res = talloc(opt, struct resource);
@@ -118,7 +119,6 @@  struct grub2_file *grub2_parse_file(struct grub2_script *script,
 		const char *str)
 {
 	struct grub2_file *file;
-	size_t dev_len;
 	char *pos;
 
 	if (!str)
@@ -130,24 +130,33 @@  struct grub2_file *grub2_parse_file(struct grub2_script *script,
 		/* just a path - no device, return path as-is */
 		file->path = talloc_strdup(file, str);
 
-	} else {
+	}
+	else {
 		/* device plus path - split into components */
-
 		pos = strchr(str, ')');
 
-		/* no closing bracket, or zero-length path? */
-		if (!pos || *(pos+1) == '\0') {
+		/* no closing bracket is not legal */
+		if (!pos) {
 			talloc_free(file);
 			return NULL;
 		}
 
-		file->path = talloc_strdup(file, pos + 1);
+		/* path is non-empty - copy it (otherwise keep it
+		 * NULL as it should be legal) */
+		if ( *(pos+1) != '\0' )
+			file->path = talloc_strdup(file, pos + 1);
 
-		dev_len = pos - str - 1;
-		if (dev_len)
-			file->dev = talloc_strndup(file, str + 1, dev_len);
+		/* same as above for device string */
+		if ( pos > (str+1) )
+			file->dev = talloc_strndup(file, str + 1,
+				       (size_t) (pos - str - 1));
 	}
 
+	/* if both dev and path are empty, this is probably an error */
+	if (!file->dev && !file->path) {
+		talloc_free(file);
+		return NULL;
+	}
 	return file;
 }
 
diff --git a/discover/paths.c b/discover/paths.c
index 16fdd59..3c43bf6 100644
--- a/discover/paths.c
+++ b/discover/paths.c
@@ -51,13 +51,19 @@  const char *mount_base(void)
 char *join_paths(void *alloc_ctx, const char *a, const char *b)
 {
 	char *full_path;
+	size_t a_len = a ? strlen(a) : 0;
+	size_t b_len = b ? strlen(b) : 0;
 
-	full_path = talloc_array(alloc_ctx, char, strlen(a) + strlen(b) + 2);
+	full_path = talloc_zero_array(alloc_ctx, char, a_len + b_len + 2);
 
-	strcpy(full_path, a);
-	if (b[0] != '/' && a[strlen(a) - 1] != '/')
-		strcat(full_path, "/");
-	strcat(full_path, b);
+	if (a_len)
+		strcpy(full_path, a);
+
+	if (b_len) {
+		if (a_len && a[a_len - 1] != '/' && b[0] != '/')
+			strcat(full_path, "/");
+		strcat(full_path, b);
+	}
 
 	return full_path;
 }
diff --git a/discover/paths.h b/discover/paths.h
index 67fe8a3..6694877 100644
--- a/discover/paths.h
+++ b/discover/paths.h
@@ -6,10 +6,10 @@ 
 #include <process/process.h>
 
 /**
- * Utility function for joining two paths. Adds a / between a and b if
- * required.
+ * Utility function for joining two paths. Allows either or
+ * both paths to be NULL. Adds a / between a and b if required.
  *
- * Returns a newly-allocated string.
+ * Returns a newly-allocated string (empty if both paths NULL)
  */
 char *join_paths(void *alloc_ctx, const char *a, const char *b);
 
diff --git a/test/parser/test-grub2-devpath.c b/test/parser/test-grub2-devpath.c
index d1d00f1..2571078 100644
--- a/test/parser/test-grub2-devpath.c
+++ b/test/parser/test-grub2-devpath.c
@@ -9,35 +9,40 @@  menuentry a {
 	linux /vmlinux
 }
 
+# local, with an empty device-string
+menuentry b {
+	linux ()/vmlinux
+}
+
 # local, specified by root env var
 root=00000000-0000-0000-0000-000000000001
-menuentry b {
+menuentry c {
 	linux /vmlinux
 }
 
 # remote, specified by root env var
 root=00000000-0000-0000-0000-000000000002
-menuentry c {
+menuentry d {
 	linux /vmlinux
 }
 
 # local, full dev+path spec
-menuentry d {
+menuentry e {
 	linux (00000000-0000-0000-0000-000000000001)/vmlinux
 }
 
 # remote, full dev+path spec
-menuentry e {
+menuentry f {
 	linux (00000000-0000-0000-0000-000000000002)/vmlinux
 }
 
 # invalid: incomplete dev+path spec
-menuentry f {
+menuentry g {
 	linux (00000000-0000-0000-0000-000000000001
 }
 
 # invalid: no path
-menuentry g {
+menuentry h {
 	linux (00000000-0000-0000-0000-000000000001)
 }
 
@@ -64,7 +69,7 @@  void run_test(struct parser_test *test)
 
 	test_run_parser(test, "grub2");
 
-	check_boot_option_count(ctx, 5);
+	check_boot_option_count(ctx, 6);
 
 	opt = get_boot_option(ctx, 0);
 	check_name(opt, "a");
@@ -76,13 +81,18 @@  void run_test(struct parser_test *test)
 
 	opt = get_boot_option(ctx, 2);
 	check_name(opt, "c");
-	check_resolved_local_resource(opt->boot_image, dev2, "/vmlinux");
+	check_resolved_local_resource(opt->boot_image, dev1, "/vmlinux");
 
 	opt = get_boot_option(ctx, 3);
 	check_name(opt, "d");
-	check_resolved_local_resource(opt->boot_image, dev1, "/vmlinux");
+	check_resolved_local_resource(opt->boot_image, dev2, "/vmlinux");
 
 	opt = get_boot_option(ctx, 4);
 	check_name(opt, "e");
+	check_resolved_local_resource(opt->boot_image, dev1, "/vmlinux");
+
+	opt = get_boot_option(ctx, 5);
+	check_name(opt, "f");
 	check_resolved_local_resource(opt->boot_image, dev2, "/vmlinux");
+
 }
diff --git a/test/parser/utils.c b/test/parser/utils.c
index d8499a4..2705b9a 100644
--- a/test/parser/utils.c
+++ b/test/parser/utils.c
@@ -257,7 +257,7 @@  int parser_stat_path(struct discover_context *ctx,
 	list_for_each_entry(&test->files, file, list) {
 		if (file->dev != dev)
 			continue;
-		if (strcmp(file->name, path))
+		if (path && strcmp(file->name, path))
 			continue;
 
 		statbuf->st_size = (off_t)file->size;