Blob Blame History Raw
From d58e447819e96d84560b16d7632bb7bdf88cc412 Mon Sep 17 00:00:00 2001
From: Nishanth Aravamudan <nacc@linux.vnet.ibm.com>
Date: Tue, 16 Jun 2015 10:43:21 -0700
Subject: [PATCH 04/55] grubby: properly handle mixed ' and " and nested quotes

The SLES12 grub2.cfg file on ppc64le by default contains a line like:

  submenu "Bootable snapshot #$snapshot_num" {
    menuentry "If OK, run 'snapper rollback $snapshot_num' reboot." { true; }
  }

On any grubby (tested with 8.40) invocation that updates the config
file, the combination of nested quotes and mixed quotes leads to a
generated file content like:

  submenu "Bootable snapshot #$snapshot_num" {
    menuentry 'If OK, run snapper rollback $snapshot_num' rollback $snapshot_num' and reboot." { true; }
  }

which includes both a change from " to ', but also improperly quoted
strings and trailing characters relative to the string. This actually
leads to a failure to boot from the disk by default when using grubby
(e.g., Autotest) on SLES12 ppc64le. Whether SLES12 should be adding an
entry like this by default or not is probably open to debate, but grubby
should be able to hand this input file.

To fix the issue, three changes were necessary:

1) grub2ExtractTitle needs to check that if the second element starts
with a quote, that the matching element found ends with the same
quote-type (' vs. ")

2) lineWrite needs to output the right kind of quote based upon if the
string to be outputted itself contains quotes. This is not currently
possible in the code, because quotes are stripped out normally by
readConfig, but with the change in 3), that only happens now for the
quotes that actually delineate a string.

3) readConfig needs to check that when it is extracting titles and
determining extras, it uses matching quotes.

With these changes, a simple grubby --set-default=SLES12 (for example),
now produces:

   submenu "Bootable snapshot #$snapshot_num" {
     menuentry "If OK, run 'snapper rollback $snapshot_num' and reboot." { true; }
   }

as expected.

Signed-off-by: Nishanth Aravamudan <nacc@linux.vnet.ibm.com>
---
 grubby.c | 42 +++++++++++++++++++++++++++++++++---------
 1 file changed, 33 insertions(+), 9 deletions(-)

diff --git a/grubby.c b/grubby.c
index 53fe9250e27..440c6277935 100644
--- a/grubby.c
+++ b/grubby.c
@@ -451,6 +451,8 @@ char *grub2ExtractTitle(struct singleLine * line) {
      * whose last character is also quote (assuming it's the closing one) */
     int resultMaxSize;
     char * result;
+    /* need to ensure that ' does not match " as we search */
+    char quote_char = *current;
     
     resultMaxSize = sizeOfSingleLine(line);
     result = malloc(resultMaxSize);
@@ -464,7 +466,7 @@ char *grub2ExtractTitle(struct singleLine * line) {
 	current_indent_len = strlen(current_indent);
 
 	strncat(result, current_indent, current_indent_len);
-	if (!isquote(current[current_len-1])) {
+	if (current[current_len-1] != quote_char) {
 	    strncat(result, current, current_len);
 	} else {
 	    strncat(result, current, current_len - 1);
@@ -928,10 +930,23 @@ static int lineWrite(FILE * out, struct singleLine * line,
 	/* Need to handle this, because we strip the quotes from
 	 * menuentry when read it. */
 	if (line->type == LT_MENUENTRY && i == 1) {
-	    if(!isquote(*line->elements[i].item))
-		fprintf(out, "\'%s\'", line->elements[i].item);
-	    else
+	    if(!isquote(*line->elements[i].item)) {
+		int substring = 0;
+		/* If the line contains nested quotes, we did not strip
+		 * the "interna" quotes and we must use the right quotes
+		 * again when writing the updated file. */
+		for (int j = i; j < line->numElements; j++) {
+		    if (strchr(line->elements[i].item, '\'') != NULL) {
+		       substring = 1;
+		       fprintf(out, "\"%s\"", line->elements[i].item);
+		       break;
+		    }
+		}
+		if (!substring)
+		    fprintf(out, "\'%s\'", line->elements[i].item);
+	    } else {
 		fprintf(out, "%s", line->elements[i].item);
+	    }
 	    fprintf(out, "%s", line->elements[i].indent);
 
 	    continue;
@@ -1267,6 +1282,8 @@ static struct grubConfig * readConfig(const char * inName,
 	    len = 0;
 	    char *extras;
 	    char *title;
+	    /* initially unseen value */
+	    char quote_char = '\0';
 
 	    for (int i = 1; i < line->numElements; i++) {
 		len += strlen(line->elements[i].item);
@@ -1283,13 +1300,16 @@ static struct grubConfig * readConfig(const char * inName,
 	    for (int i = 0; i < line->numElements; i++) {
 		if (!strcmp(line->elements[i].item, "menuentry"))
 		    continue;
-		if (isquote(*line->elements[i].item))
+		if (isquote(*line->elements[i].item) && quote_char == '\0') {
+		    /* ensure we properly pair off quotes */
+		    quote_char = *line->elements[i].item;
 		    title = line->elements[i].item + 1;
-		else
+		} else {
 		    title = line->elements[i].item;
+		}
 
 		len = strlen(title);
-	        if (isquote(title[len-1])) {
+	        if (title[len-1] == quote_char) {
 		    strncat(buf, title,len-1);
 		    break;
 		} else {
@@ -1300,6 +1320,7 @@ static struct grubConfig * readConfig(const char * inName,
 
 	    /* get extras */
 	    int count = 0;
+	    quote_char = '\0';
 	    for (int i = 0; i < line->numElements; i++) {
 		if (count >= 2) {
 		    strcat(extras, line->elements[i].item);
@@ -1310,12 +1331,15 @@ static struct grubConfig * readConfig(const char * inName,
 		    continue;
 
 		/* count ' or ", there should be two in menuentry line. */
-		if (isquote(*line->elements[i].item))
+		if (isquote(*line->elements[i].item) && quote_char == '\0') {
+		    /* ensure we properly pair off quotes */
+	            quote_char = *line->elements[i].item;
 		    count++;
+		}
 
 		len = strlen(line->elements[i].item);
 
-		if (isquote(line->elements[i].item[len -1]))
+		if (line->elements[i].item[len -1] == quote_char)
 		    count++;
 
 		/* ok, we get the final ' or ", others are extras. */
-- 
2.17.1