--- trunk/grubby/grubby.c 2012/02/17 23:21:08 1693 +++ trunk/grubby/grubby.c 2012/07/02 12:59:07 1844 @@ -36,7 +36,9 @@ #include #include +#ifndef DEBUG #define DEBUG 0 +#endif #if DEBUG #define dbgPrintf(format, args...) fprintf(stderr, format , ## args) @@ -44,11 +46,16 @@ #define dbgPrintf(format, args...) #endif +int debug = 0; /* Currently just for template debugging */ + #define _(A) (A) #define MAX_EXTRA_INITRDS 16 /* code segment checked by --bootloader-probe */ #define CODE_SEG_SIZE 128 /* code segment checked by --bootloader-probe */ +#define NOOP_OPCODE 0x90 +#define JMP_SHORT_OPCODE 0xeb + /* comments get lumped in with indention */ struct lineElement { char * item; @@ -56,22 +63,26 @@ }; enum lineType_e { - LT_WHITESPACE = 1 << 0, - LT_TITLE = 1 << 1, - LT_KERNEL = 1 << 2, - LT_INITRD = 1 << 3, - LT_HYPER = 1 << 4, - LT_DEFAULT = 1 << 5, - LT_MBMODULE = 1 << 6, - LT_ROOT = 1 << 7, - LT_FALLBACK = 1 << 8, - LT_KERNELARGS = 1 << 9, - LT_BOOT = 1 << 10, - LT_BOOTROOT = 1 << 11, - LT_LBA = 1 << 12, - LT_OTHER = 1 << 13, - LT_GENERIC = 1 << 14, - LT_UNKNOWN = 1 << 15, + LT_WHITESPACE = 1 << 0, + LT_TITLE = 1 << 1, + LT_KERNEL = 1 << 2, + LT_INITRD = 1 << 3, + LT_HYPER = 1 << 4, + LT_DEFAULT = 1 << 5, + LT_MBMODULE = 1 << 6, + LT_ROOT = 1 << 7, + LT_FALLBACK = 1 << 8, + LT_KERNELARGS = 1 << 9, + LT_BOOT = 1 << 10, + LT_BOOTROOT = 1 << 11, + LT_LBA = 1 << 12, + LT_OTHER = 1 << 13, + LT_GENERIC = 1 << 14, + LT_ECHO = 1 << 16, + LT_MENUENTRY = 1 << 17, + LT_ENTRY_END = 1 << 18, + LT_SET_VARIABLE = 1 << 19, + LT_UNKNOWN = 1 << 20, }; struct singleLine { @@ -99,9 +110,11 @@ #define NEED_TITLE (1 << 2) #define NEED_ARGS (1 << 3) #define NEED_MB (1 << 4) +#define NEED_END (1 << 5) #define MAIN_DEFAULT (1 << 0) #define DEFAULT_SAVED -2 +#define DEFAULT_SAVED_GRUB2 -3 struct keywordTypes { char * key; @@ -110,16 +123,27 @@ char separatorChar; }; +struct configFileInfo; + +typedef const char *(*findConfigFunc)(struct configFileInfo *); +typedef const int (*writeLineFunc)(struct configFileInfo *, + struct singleLine *line); + struct configFileInfo { char * defaultConfig; + findConfigFunc findConfig; + writeLineFunc writeLine; struct keywordTypes * keywords; int defaultIsIndex; + int defaultIsVariable; int defaultSupportSaved; enum lineType_e entryStart; + enum lineType_e entryEnd; int needsBootPrefix; int argsInQuotes; int maxTitleLength; int titleBracketed; + int titlePosition; int mbHyperFirst; int mbInitRdIsModule; int mbConcatArgs; @@ -138,8 +162,30 @@ { NULL, 0, 0 }, }; +const char *grubFindConfig(struct configFileInfo *cfi) { + static const char *configFiles[] = { + "/etc/grub.conf", + "/boot/grub/grub.conf", + "/boot/grub/menu.lst", + NULL + }; + static int i = -1; + + if (i == -1) { + for (i = 0; configFiles[i] != NULL; i++) { + dbgPrintf("Checking \"%s\": ", configFiles[i]); + if (!access(configFiles[i], R_OK)) { + dbgPrintf("found\n"); + return configFiles[i]; + } + dbgPrintf("not found\n"); + } + } + return configFiles[i]; +} + struct configFileInfo grubConfigType = { - .defaultConfig = "/boot/grub/grub.conf", + .findConfig = grubFindConfig, .keywords = grubKeywords, .defaultIsIndex = 1, .defaultSupportSaved = 1, @@ -150,6 +196,152 @@ .mbAllowExtraInitRds = 1, }; +struct keywordTypes grub2Keywords[] = { + { "menuentry", LT_MENUENTRY, ' ' }, + { "}", LT_ENTRY_END, ' ' }, + { "echo", LT_ECHO, ' ' }, + { "set", LT_SET_VARIABLE,' ', '=' }, + { "root", LT_BOOTROOT, ' ' }, + { "default", LT_DEFAULT, ' ' }, + { "fallback", LT_FALLBACK, ' ' }, + { "linux", LT_KERNEL, ' ' }, + { "initrd", LT_INITRD, ' ', ' ' }, + { "module", LT_MBMODULE, ' ' }, + { "kernel", LT_HYPER, ' ' }, + { NULL, 0, 0 }, +}; + +const char *grub2FindConfig(struct configFileInfo *cfi) { + static const char *configFiles[] = { + "/boot/grub/grub-efi.cfg", + "/boot/grub/grub.cfg", + NULL + }; + static int i = -1; + static const char *grub_cfg = "/boot/grub/grub.cfg"; + + if (i == -1) { + for (i = 0; configFiles[i] != NULL; i++) { + dbgPrintf("Checking \"%s\": ", configFiles[i]); + if (!access(configFiles[i], R_OK)) { + dbgPrintf("found\n"); + return configFiles[i]; + } + } + } + + /* Ubuntu renames grub2 to grub, so check for the grub.d directory + * that isn't in grub1, and if it exists, return the config file path + * that they use. */ + if (configFiles[i] == NULL && !access("/etc/grub.d/", R_OK)) { + dbgPrintf("found\n"); + return grub_cfg; + } + + dbgPrintf("not found\n"); + return configFiles[i]; +} + +int sizeOfSingleLine(struct singleLine * line) { + int i; + int count = 0; + + for (i = 0; i < line->numElements; i++) { + int indentSize = 0; + + count = count + strlen(line->elements[i].item); + + indentSize = strlen(line->elements[i].indent); + if (indentSize > 0) + count = count + indentSize; + else + /* be extra safe and add room for whitespaces */ + count = count + 1; + } + + /* room for trailing terminator */ + count = count + 1; + + return count; +} + +static int isquote(char q) +{ + if (q == '\'' || q == '\"') + return 1; + return 0; +} + +char *grub2ExtractTitle(struct singleLine * line) { + char * current; + char * current_indent; + int current_len; + int current_indent_len; + int i; + + /* bail out if line does not start with menuentry */ + if (strcmp(line->elements[0].item, "menuentry")) + return NULL; + + i = 1; + current = line->elements[i].item; + current_len = strlen(current); + + /* if second word is quoted, strip the quotes and return single word */ + if (isquote(*current) && isquote(current[current_len - 1])) { + char *tmp; + + tmp = strdup(current); + *(tmp + current_len - 1) = '\0'; + return ++tmp; + } + + /* if no quotes, return second word verbatim */ + if (!isquote(*current)) + return current; + + /* second element start with a quote, so we have to find the element + * whose last character is also quote (assuming it's the closing one) */ + int resultMaxSize; + char * result; + + resultMaxSize = sizeOfSingleLine(line); + result = malloc(resultMaxSize); + snprintf(result, resultMaxSize, "%s", ++current); + + i++; + for (; i < line->numElements; ++i) { + current = line->elements[i].item; + current_len = strlen(current); + current_indent = line->elements[i].indent; + current_indent_len = strlen(current_indent); + + strncat(result, current_indent, current_indent_len); + if (!isquote(current[current_len-1])) { + strncat(result, current, current_len); + } else { + strncat(result, current, current_len - 1); + break; + } + } + return result; +} + +struct configFileInfo grub2ConfigType = { + .findConfig = grub2FindConfig, + .keywords = grub2Keywords, + .defaultIsIndex = 1, + .defaultSupportSaved = 1, + .defaultIsVariable = 1, + .entryStart = LT_MENUENTRY, + .entryEnd = LT_ENTRY_END, + .titlePosition = 1, + .needsBootPrefix = 1, + .mbHyperFirst = 1, + .mbInitRdIsModule = 1, + .mbAllowExtraInitRds = 1, +}; + struct keywordTypes yabootKeywords[] = { { "label", LT_TITLE, '=' }, { "root", LT_ROOT, '=' }, @@ -324,6 +516,7 @@ static int getNextLine(char ** bufPtr, struct singleLine * line, struct configFileInfo * cfi); static char * getRootSpecifier(char * str); +static void requote(struct singleLine *line, struct configFileInfo * cfi); static void insertElement(struct singleLine * line, const char * item, int insertHere, struct configFileInfo * cfi); @@ -388,6 +581,13 @@ return NULL; } +static char *getKeyByType(enum lineType_e type, struct configFileInfo * cfi) { + struct keywordTypes *kt = getKeywordByType(type, cfi); + if (kt) + return kt->key; + return "unknown"; +} + static char * getpathbyspec(char *device) { if (!blkid) blkid_get_cache(&blkid, NULL); @@ -531,6 +731,18 @@ if (fprintf(out, "%s", line->indent) == -1) return -1; for (i = 0; i < line->numElements; i++) { + /* 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 + fprintf(out, "%s", line->elements[i].item); + fprintf(out, "%s", line->elements[i].indent); + + continue; + } + if (i == 1 && line->type == LT_KERNELARGS && cfi->argsInQuotes) if (fputc('"', out) == EOF) return -1; @@ -689,13 +901,11 @@ break; } - free(line->elements[i].indent); + line->elements[i + 1].indent = line->elements[i].indent; line->elements[i].indent = strdup(indent); *p++ = '\0'; i++; line->elements[i].item = strdup(p); - line->elements[i].indent = strdup(""); - p = line->elements[i].item; } } } @@ -762,7 +972,7 @@ cfg->secondaryIndent = strdup(line->indent); } - if (isEntryStart(line, cfi)) { + if (isEntryStart(line, cfi) || (cfg->entries && !sawEntry)) { sawEntry = 1; if (!entry) { cfg->entries = malloc(sizeof(*entry)); @@ -778,7 +988,21 @@ entry->next = NULL; } - if (line->type == LT_DEFAULT && line->numElements == 2) { + if (line->type == LT_SET_VARIABLE) { + int i; + dbgPrintf("found 'set' command (%d elements): ", line->numElements); + dbgPrintf("%s", line->indent); + for (i = 0; i < line->numElements; i++) + dbgPrintf("\"%s\"%s", line->elements[i].item, line->elements[i].indent); + dbgPrintf("\n"); + struct keywordTypes *kwType = getKeywordByType(LT_DEFAULT, cfi); + if (kwType && line->numElements == 3 && + !strcmp(line->elements[1].item, kwType->key)) { + dbgPrintf("Line sets default config\n"); + cfg->flags &= ~GRUB_CONFIG_NO_DEFAULT; + defaultLine = line; + } + } else if (line->type == LT_DEFAULT && line->numElements == 2) { cfg->flags &= ~GRUB_CONFIG_NO_DEFAULT; defaultLine = line; @@ -838,7 +1062,71 @@ line->elements[line->numElements - 1].indent; line->elements[1].item = buf; line->numElements = 2; + } else if (line->type == LT_MENUENTRY && line->numElements > 3) { + /* let --remove-kernel="TITLE=what" work */ + len = 0; + char *extras; + char *title; + + for (i = 1; i < line->numElements; i++) { + len += strlen(line->elements[i].item); + len += strlen(line->elements[i].indent); + } + buf = malloc(len + 1); + *buf = '\0'; + + /* allocate mem for extra flags. */ + extras = malloc(len + 1); + *extras = '\0'; + + /* get title. */ + for (i = 0; i < line->numElements; i++) { + if (!strcmp(line->elements[i].item, "menuentry")) + continue; + if (isquote(*line->elements[i].item)) + title = line->elements[i].item + 1; + else + title = line->elements[i].item; + + len = strlen(title); + if (isquote(title[len-1])) { + strncat(buf, title,len-1); + break; + } else { + strcat(buf, title); + strcat(buf, line->elements[i].indent); + } + } + + /* get extras */ + int count = 0; + for (i = 0; i < line->numElements; i++) { + if (count >= 2) { + strcat(extras, line->elements[i].item); + strcat(extras, line->elements[i].indent); + } + + if (!strcmp(line->elements[i].item, "menuentry")) + continue; + + /* count ' or ", there should be two in menuentry line. */ + if (isquote(*line->elements[i].item)) + count++; + + len = strlen(line->elements[i].item); + if (isquote(line->elements[i].item[len -1])) + count++; + + /* ok, we get the final ' or ", others are extras. */ + } + line->elements[1].indent = + line->elements[line->numElements - 2].indent; + line->elements[1].item = buf; + line->elements[2].indent = + line->elements[line->numElements - 2].indent; + line->elements[2].item = extras; + line->numElements = 3; } else if (line->type == LT_KERNELARGS && cfi->argsInQuotes) { /* Strip off any " which may be present; they'll be put back on write. This is one of the few (the only?) places that grubby @@ -847,13 +1135,13 @@ if (line->numElements >= 2) { int last, len; - if (*line->elements[1].item == '"') + if (isquote(*line->elements[1].item)) memmove(line->elements[1].item, line->elements[1].item + 1, strlen(line->elements[1].item + 1) + 1); last = line->numElements - 1; len = strlen(line->elements[last].item) - 1; - if (line->elements[last].item[len] == '"') + if (isquote(line->elements[last].item[len])) line->elements[last].item[len] = '\0'; } } @@ -891,13 +1179,18 @@ entry->lines = line; else last->next = line; - dbgPrintf("readConfig added %d to %p\n", line->type, entry); + dbgPrintf("readConfig added %s to %p\n", getKeyByType(line->type, cfi), entry); + + /* we could have seen this outside of an entry... if so, we + * ignore it like any other line we don't grok */ + if (line->type == LT_ENTRY_END && sawEntry) + sawEntry = 0; } else { if (!cfg->theLines) cfg->theLines = line; else last->next = line; - dbgPrintf("readConfig added %d to cfg\n", line->type); + dbgPrintf("readConfig added %s to cfg\n", getKeyByType(line->type, cfi)); } last = line; @@ -905,8 +1198,23 @@ free(incoming); + dbgPrintf("defaultLine is %s\n", defaultLine ? "set" : "unset"); if (defaultLine) { - if (cfi->defaultSupportSaved && + if (defaultLine->numElements > 2 && + cfi->defaultSupportSaved && + !strncmp(defaultLine->elements[2].item,"\"${saved_entry}\"", 16)) { + cfg->defaultImage = DEFAULT_SAVED_GRUB2; + } else if (cfi->defaultIsVariable) { + char *value = defaultLine->elements[2].item; + while (*value && (*value == '"' || *value == '\'' || + *value == ' ' || *value == '\t')) + value++; + cfg->defaultImage = strtol(value, &end, 10); + while (*end && (*end == '"' || *end == '\'' || + *end == ' ' || *end == '\t')) + end++; + if (*end) cfg->defaultImage = -1; + } else if (cfi->defaultSupportSaved && !strncmp(defaultLine->elements[1].item, "saved", 5)) { cfg->defaultImage = DEFAULT_SAVED; } else if (cfi->defaultIsIndex) { @@ -953,10 +1261,17 @@ if (cfg->defaultImage == DEFAULT_SAVED) fprintf(out, "%sdefault%ssaved\n", indent, separator); + else if (cfg->defaultImage == DEFAULT_SAVED_GRUB2) + fprintf(out, "%sset default=\"${saved_entry}\"\n", indent); else if (cfg->defaultImage > -1) { if (cfg->cfi->defaultIsIndex) { - fprintf(out, "%sdefault%s%d\n", indent, separator, - cfg->defaultImage); + if (cfg->cfi->defaultIsVariable) { + fprintf(out, "%sset default=\"%d\"\n", indent, + cfg->defaultImage); + } else { + fprintf(out, "%sdefault%s%d\n", indent, separator, + cfg->defaultImage); + } } else { int image = cfg->defaultImage; @@ -1046,8 +1361,14 @@ } line = cfg->theLines; + struct keywordTypes *defaultKw = getKeywordByType(LT_DEFAULT, cfg->cfi); while (line) { - if (line->type == LT_DEFAULT) { + if (line->type == LT_SET_VARIABLE && defaultKw && + line->numElements == 3 && + !strcmp(line->elements[1].item, defaultKw->key)) { + writeDefault(out, line->indent, line->elements[0].indent, cfg); + needs &= ~MAIN_DEFAULT; + } else if (line->type == LT_DEFAULT) { writeDefault(out, line->indent, line->elements[0].indent, cfg); needs &= ~MAIN_DEFAULT; } else if (line->type == LT_FALLBACK) { @@ -1140,6 +1461,8 @@ buf[rc] = '\0'; chptr = buf; + char *foundanswer = NULL; + while (chptr && chptr != buf+rc) { devname = chptr; @@ -1167,13 +1490,8 @@ * for '/' obviously. */ if (*(++chptr) == '/' && *(++chptr) == ' ') { - /* - * Move back 2, which is the first space after the device name, set - * it to \0 so strdup will just get the devicename. - */ - chptr -= 2; - *chptr = '\0'; - return strdup(devname); + /* remember the last / entry in mtab */ + foundanswer = devname; } /* Next line */ @@ -1182,9 +1500,69 @@ chptr++; } + /* Return the last / entry found */ + if (foundanswer) { + chptr = strchr(foundanswer, ' '); + *chptr = '\0'; + return strdup(foundanswer); + } + return NULL; } +void printEntry(struct singleEntry * entry) { + int i; + struct singleLine * line; + + for (line = entry->lines; line; line = line->next) { + fprintf(stderr, "DBG: %s", line->indent); + for (i = 0; i < line->numElements; i++) { + /* 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(stderr, "\'%s\'", line->elements[i].item); + else + fprintf(stderr, "%s", line->elements[i].item); + fprintf(stderr, "%s", line->elements[i].indent); + + continue; + } + + fprintf(stderr, "%s%s", + line->elements[i].item, line->elements[i].indent); + } + fprintf(stderr, "\n"); + } +} + +void notSuitablePrintf(struct singleEntry * entry, const char *fmt, ...) +{ + va_list argp; + + if (!debug) + return; + + va_start(argp, fmt); + fprintf(stderr, "DBG: Image entry failed: "); + vfprintf(stderr, fmt, argp); + printEntry(entry); + va_end(argp); +} + +#define beginswith(s, c) ((s) && (s)[0] == (c)) + +static int endswith(const char *s, char c) +{ + int slen; + + if (!s || !s[0]) + return 0; + slen = strlen(s) - 1; + + return s[slen] == c; +} + int suitableImage(struct singleEntry * entry, const char * bootPrefix, int skipRemoved, int flags) { struct singleLine * line; @@ -1194,20 +1572,36 @@ char * rootspec; char * rootdev; - if (skipRemoved && entry->skip) return 0; + if (skipRemoved && entry->skip) { + notSuitablePrintf(entry, "marked to skip\n"); + return 0; + } line = getLineByType(LT_KERNEL|LT_HYPER, entry->lines); - if (!line || line->numElements < 2) return 0; + if (!line) { + notSuitablePrintf(entry, "no line found\n"); + return 0; + } + if (line->numElements < 2) { + notSuitablePrintf(entry, "line has only %d elements\n", + line->numElements); + return 0; + } if (flags & GRUBBY_BADIMAGE_OKAY) return 1; fullName = alloca(strlen(bootPrefix) + strlen(line->elements[1].item) + 1); rootspec = getRootSpecifier(line->elements[1].item); - sprintf(fullName, "%s%s", bootPrefix, - line->elements[1].item + (rootspec ? strlen(rootspec) : 0)); - if (access(fullName, R_OK)) return 0; - + int rootspec_offset = rootspec ? strlen(rootspec) : 0; + int hasslash = endswith(bootPrefix, '/') || + beginswith(line->elements[1].item + rootspec_offset, '/'); + sprintf(fullName, "%s%s%s", bootPrefix, hasslash ? "" : "/", + line->elements[1].item + rootspec_offset); + if (access(fullName, R_OK)) { + notSuitablePrintf(entry, "access to %s failed\n", fullName); + return 0; + } for (i = 2; i < line->numElements; i++) if (!strncasecmp(line->elements[i].item, "root=", 5)) break; if (i < line->numElements) { @@ -1225,13 +1619,17 @@ line = getLineByType(LT_KERNELARGS|LT_MBMODULE, entry->lines); /* failed to find one */ - if (!line) return 0; + if (!line) { + notSuitablePrintf(entry, "no line found\n"); + return 0; + } for (i = 1; i < line->numElements; i++) if (!strncasecmp(line->elements[i].item, "root=", 5)) break; if (i < line->numElements) dev = line->elements[i].item + 5; else { + notSuitablePrintf(entry, "no root= entry found\n"); /* it failed too... can't find root= */ return 0; } @@ -1239,19 +1637,28 @@ } dev = getpathbyspec(dev); - if (!dev) + if (!getpathbyspec(dev)) { + notSuitablePrintf(entry, "can't find blkid entry for %s\n", dev); return 0; + } else + dev = getpathbyspec(dev); rootdev = findDiskForRoot(); - if (!rootdev) + if (!rootdev) { + notSuitablePrintf(entry, "can't find root device\n"); return 0; + } if (!getuuidbydev(rootdev) || !getuuidbydev(dev)) { + notSuitablePrintf(entry, "uuid missing: rootdev %s, dev %s\n", + getuuidbydev(rootdev), getuuidbydev(dev)); free(rootdev); return 0; } if (strcmp(getuuidbydev(rootdev), getuuidbydev(dev))) { + notSuitablePrintf(entry, "uuid mismatch: rootdev %s, dev %s\n", + getuuidbydev(rootdev), getuuidbydev(dev)); free(rootdev); return 0; } @@ -1338,7 +1745,7 @@ if (!strncmp(kernel, "TITLE=", 6)) { prefix = ""; - checkType = LT_TITLE; + checkType = LT_TITLE|LT_MENUENTRY; kernel += 6; } @@ -1354,13 +1761,17 @@ checkType, line); if (!line) break; /* not found in this entry */ - if (line && line->numElements >= 2) { + if (line && line->type != LT_MENUENTRY && + line->numElements >= 2) { rootspec = getRootSpecifier(line->elements[1].item); if (!strcmp(line->elements[1].item + ((rootspec != NULL) ? strlen(rootspec) : 0), kernel + strlen(prefix))) break; } + if(line->type == LT_MENUENTRY && + !strcmp(line->elements[1].item, kernel)) + break; } /* make sure this entry has a kernel identifier; this skips @@ -1452,7 +1863,16 @@ const char * prefix) { struct singleEntry * entry; - if (!image) return; + if (!image) + return; + + /* check and see if we're removing the default image */ + if (isdigit(*image)) { + entry = findEntryByPath(cfg, image, prefix, NULL); + if(entry) + entry->skip = 1; + return; + } while ((entry = findEntryByPath(cfg, image, prefix, NULL))) entry->skip = 1; @@ -1479,7 +1899,8 @@ /* defaultImage now points to what we'd like to use, but before any order changes */ - if (config->defaultImage == DEFAULT_SAVED) + if ((config->defaultImage == DEFAULT_SAVED) || + (config->defaultImage == DEFAULT_SAVED_GRUB2)) /* default is set to saved, we don't want to change it */ return; @@ -1546,7 +1967,7 @@ return; } - printf("kernel=%s\n", line->elements[1].item); + printf("kernel=%s%s\n", prefix, line->elements[1].item); if (line->numElements >= 3) { printf("args=\""); @@ -1610,6 +2031,17 @@ printf("%s%s", line->elements[i].item, line->elements[i].indent); printf("\n"); } + + line = getLineByType(LT_TITLE, entry->lines); + if (line) { + printf("title=%s\n", line->elements[1].item); + } else { + char * title; + line = getLineByType(LT_MENUENTRY, entry->lines); + title = grub2ExtractTitle(line); + if (title) + printf("title=%s\n", title); + } } int parseSysconfigGrub(int * lbaPtr, char ** bootPtr) { @@ -1770,9 +2202,36 @@ sprintf(tmpl.elements[0].item, "[%s]", val); tmpl.elements[0].indent = ""; val = NULL; + } else if (type == LT_MENUENTRY) { + char *lineend = "--class gnu-linux --class gnu --class os {"; + if (!val) { + fprintf(stderr, "Line type LT_MENUENTRY requires a value\n"); + abort(); + } + kw = getKeywordByType(type, cfi); + if (!kw) { + fprintf(stderr, "Looking up keyword for unknown type %d\n", type); + abort(); + } + tmpl.indent = ""; + tmpl.type = type; + tmpl.numElements = 3; + tmpl.elements = alloca(sizeof(*tmpl.elements) * tmpl.numElements); + tmpl.elements[0].item = kw->key; + tmpl.elements[0].indent = alloca(2); + sprintf(tmpl.elements[0].indent, "%c", kw->nextChar); + tmpl.elements[1].item = (char *)val; + tmpl.elements[1].indent = alloca(2); + sprintf(tmpl.elements[1].indent, "%c", kw->nextChar); + tmpl.elements[2].item = alloca(strlen(lineend)+1); + strcpy(tmpl.elements[2].item, lineend); + tmpl.elements[2].indent = ""; } else { kw = getKeywordByType(type, cfi); - if (!kw) abort(); + if (!kw) { + fprintf(stderr, "Looking up keyword for unknown type %d\n", type); + abort(); + } tmpl.type = type; tmpl.numElements = val ? 2 : 1; tmpl.elements = alloca(sizeof(*tmpl.elements) * tmpl.numElements); @@ -1796,10 +2255,21 @@ if (!line->next && !prev) prev = line; } - if (prev == entry->lines) - tmpl.indent = defaultIndent ?: ""; - else - tmpl.indent = prev->indent; + struct singleLine *menuEntry; + menuEntry = getLineByType(LT_MENUENTRY, entry->lines); + if (tmpl.type == LT_ENTRY_END) { + if (menuEntry) + tmpl.indent = menuEntry->indent; + else + tmpl.indent = defaultIndent ?: ""; + } else if (tmpl.type != LT_MENUENTRY) { + if (menuEntry) + tmpl.indent = "\t"; + else if (prev == entry->lines) + tmpl.indent = defaultIndent ?: ""; + else + tmpl.indent = prev->indent; + } return addLineTmpl(entry, &tmpl, prev, val, cfi); } @@ -1826,6 +2296,68 @@ free(line); } +static void requote(struct singleLine *tmplLine, struct configFileInfo * cfi) +{ + struct singleLine newLine = { + .indent = tmplLine->indent, + .type = tmplLine->type, + .next = tmplLine->next, + }; + int firstQuotedItem = -1; + int quoteLen = 0; + int j; + int element = 0; + char *c; + + c = malloc(strlen(tmplLine->elements[0].item) + 1); + strcpy(c, tmplLine->elements[0].item); + insertElement(&newLine, c, element++, cfi); + free(c); + c = NULL; + + for (j = 1; j < tmplLine->numElements; j++) { + if (firstQuotedItem == -1) { + quoteLen += strlen(tmplLine->elements[j].item); + + if (isquote(tmplLine->elements[j].item[0])) { + firstQuotedItem = j; + quoteLen += strlen(tmplLine->elements[j].indent); + } else { + c = malloc(quoteLen + 1); + strcpy(c, tmplLine->elements[j].item); + insertElement(&newLine, c, element++, cfi); + free(c); + quoteLen = 0; + } + } else { + int itemlen = strlen(tmplLine->elements[j].item); + quoteLen += itemlen; + quoteLen += strlen(tmplLine->elements[j].indent); + + if (isquote(tmplLine->elements[j].item[itemlen - 1])) { + c = malloc(quoteLen + 1); + c[0] = '\0'; + for (int i = firstQuotedItem; i < j+1; i++) { + strcat(c, tmplLine->elements[i].item); + strcat(c, tmplLine->elements[i].indent); + } + insertElement(&newLine, c, element++, cfi); + free(c); + + firstQuotedItem = -1; + quoteLen = 0; + } + } + } + while (tmplLine->numElements) + removeElement(tmplLine, 0); + if (tmplLine->elements) + free(tmplLine->elements); + + tmplLine->numElements = newLine.numElements; + tmplLine->elements = newLine.elements; +} + static void insertElement(struct singleLine * line, const char * item, int insertHere, struct configFileInfo * cfi) @@ -2153,7 +2685,7 @@ int updateInitrd(struct grubConfig * cfg, const char * image, const char * prefix, const char * initrd) { struct singleEntry * entry; - struct singleLine * line, * kernelLine; + struct singleLine * line, * kernelLine, *endLine = NULL; int index = 0; if (!image) return 0; @@ -2170,8 +2702,18 @@ if (!strncmp(initrd, prefix, prefixLen)) initrd += prefixLen; } + endLine = getLineByType(LT_ENTRY_END, entry->lines); + if (endLine) + removeLine(entry, endLine); line = addLine(entry, cfg->cfi, LT_INITRD, kernelLine->indent, initrd); - if (!line) return 1; + if (!line) + return 1; + if (endLine) { + line = addLine(entry, cfg->cfi, LT_ENTRY_END, "", NULL); + if (!line) + return 1; + } + break; } @@ -2201,12 +2743,22 @@ if (memcmp(boot, bootSect, 3)) return 0; - if (boot[1] == 0xeb) { + if (boot[1] == JMP_SHORT_OPCODE) { offset = boot[2] + 2; } else if (boot[1] == 0xe8 || boot[1] == 0xe9) { offset = (boot[3] << 8) + boot[2] + 2; - } else if (boot[0] == 0xeb) { - offset = boot[1] + 2; + } else if (boot[0] == JMP_SHORT_OPCODE) { + offset = boot[1] + 2; + /* + * it looks like grub, when copying stage1 into the mbr, patches stage1 + * right after the JMP location, replacing other instructions such as + * JMPs for NOOPs. So, relax the check a little bit by skipping those + * different bytes. + */ + if ((bootSect[offset + 1] == NOOP_OPCODE) + && (bootSect[offset + 2] == NOOP_OPCODE)) { + offset = offset + 3; + } } else if (boot[0] == 0xe8 || boot[0] == 0xe9) { offset = (boot[2] << 8) + boot[1] + 2; } else { @@ -2348,6 +2900,13 @@ return checkDeviceBootloader(line->elements[1].item, boot); } +int checkForGrub2(struct grubConfig * config) { + if (!access("/etc/grub.d/", R_OK)) + return 2; + + return 1; +} + int checkForGrub(struct grubConfig * config) { int fd; unsigned char bootSect[512]; @@ -2420,7 +2979,7 @@ static char * getInitrdVal(struct grubConfig * config, const char * prefix, struct singleLine *tmplLine, const char * newKernelInitrd, - char ** extraInitrds, int extraInitrdCount) + const char ** extraInitrds, int extraInitrdCount) { char *initrdVal, *end; int i; @@ -2465,10 +3024,10 @@ int addNewKernel(struct grubConfig * config, struct singleEntry * template, const char * prefix, - char * newKernelPath, char * newKernelTitle, - char * newKernelArgs, char * newKernelInitrd, - char ** extraInitrds, int extraInitrdCount, - char * newMBKernel, char * newMBKernelArgs) { + const char * newKernelPath, const char * newKernelTitle, + const char * newKernelArgs, const char * newKernelInitrd, + const char ** extraInitrds, int extraInitrdCount, + const char * newMBKernel, const char * newMBKernelArgs) { struct singleEntry * new; struct singleLine * newLine = NULL, * tmplLine = NULL, * masterLine = NULL; int needs; @@ -2523,7 +3082,7 @@ if (*chptr == '#') continue; if (tmplLine->type == LT_KERNEL && - tmplLine->numElements >= 2) { + tmplLine->numElements >= 2) { if (!template->multiboot && (needs & NEED_MB)) { /* it's not a multiboot template and this is the kernel * line. Try to be intelligent about inserting the @@ -2649,6 +3208,16 @@ needs &= ~NEED_INITRD; } + } else if (tmplLine->type == LT_MENUENTRY && + (needs & NEED_TITLE)) { + requote(tmplLine, config->cfi); + char *nkt = malloc(strlen(newKernelTitle)+3); + strcpy(nkt, "'"); + strcat(nkt, newKernelTitle); + strcat(nkt, "'"); + newLine = addLineTmpl(new, tmplLine, newLine, nkt, config->cfi); + free(nkt); + needs &= ~NEED_TITLE; } else if (tmplLine->type == LT_TITLE && (needs & NEED_TITLE)) { if (tmplLine->numElements >= 2) { @@ -2662,7 +3231,26 @@ tmplLine->indent, newKernelTitle); needs &= ~NEED_TITLE; } - + } else if (tmplLine->type == LT_ECHO) { + requote(tmplLine, config->cfi); + static const char *prefix = "'Loading "; + if (tmplLine->numElements > 1 && + strstr(tmplLine->elements[1].item, prefix) && + masterLine->next && masterLine->next->type == LT_KERNEL) { + char *newTitle = malloc(strlen(prefix) + + strlen(newKernelTitle) + 2); + + strcpy(newTitle, prefix); + strcat(newTitle, newKernelTitle); + strcat(newTitle, "'"); + newLine = addLine(new, config->cfi, LT_ECHO, + tmplLine->indent, newTitle); + free(newTitle); + } else { + /* pass through other lines from the template */ + newLine = addLineTmpl(new, tmplLine, newLine, NULL, + config->cfi); + } } else { /* pass through other lines from the template */ newLine = addLineTmpl(new, tmplLine, newLine, NULL, config->cfi); @@ -2692,6 +3280,18 @@ needs &= ~NEED_MB; break; + case LT_MENUENTRY: { + char *nkt = malloc(strlen(newKernelTitle)+3); + strcpy(nkt, "'"); + strcat(nkt, newKernelTitle); + strcat(nkt, "'"); + newLine = addLine(new, config->cfi, LT_MENUENTRY, + config->primaryIndent, nkt); + free(nkt); + needs &= ~NEED_TITLE; + needs |= NEED_END; + break; + } case LT_TITLE: if( useextlinuxmenu != 0 ){ // We just need useextlinuxmenu to not be zero (set above) char * templabel; @@ -2766,6 +3366,11 @@ free(initrdVal); needs &= ~NEED_INITRD; } + if (needs & NEED_END) { + newLine = addLine(new, config->cfi, LT_ENTRY_END, + config->secondaryIndent, NULL); + needs &= ~NEED_END; + } if (needs) { printf(_("grubby: needs=%d, aborting\n"), needs); @@ -2795,11 +3400,12 @@ int main(int argc, const char ** argv) { poptContext optCon; - char * grubConfig = NULL; + const char * grubConfig = NULL; char * outputFile = NULL; int arg = 0; int flags = 0; int badImageOkay = 0; + int configureGrub2 = 0; int configureLilo = 0, configureELilo = 0, configureGrub = 0; int configureYaboot = 0, configureSilo = 0, configureZipl = 0; int configureExtLinux = 0; @@ -2827,6 +3433,8 @@ struct singleEntry * template = NULL; int copyDefault = 0, makeDefault = 0; int displayDefault = 0; + int displayDefaultIndex = 0; + int displayDefaultTitle = 0; struct poptOption options[] = { { "add-kernel", 0, POPT_ARG_STRING, &newKernelPath, 0, _("add an entry for the specified kernel"), _("kernel-path") }, @@ -2857,14 +3465,22 @@ "the kernel referenced by the default image does not exist, " "the first linux entry whose kernel does exist is used as the " "template"), NULL }, + { "debug", 0, 0, &debug, 0, + _("print debugging information for failures") }, { "default-kernel", 0, 0, &displayDefault, 0, _("display the path of the default kernel") }, + { "default-index", 0, 0, &displayDefaultIndex, 0, + _("display the index of the default kernel") }, + { "default-title", 0, 0, &displayDefaultTitle, 0, + _("display the title of the default kernel") }, { "elilo", 0, POPT_ARG_NONE, &configureELilo, 0, _("configure elilo bootloader") }, { "extlinux", 0, POPT_ARG_NONE, &configureExtLinux, 0, _("configure extlinux bootloader (from syslinux)") }, { "grub", 0, POPT_ARG_NONE, &configureGrub, 0, _("configure grub bootloader") }, + { "grub2", 0, POPT_ARG_NONE, &configureGrub2, 0, + _("configure grub2 bootloader") }, { "info", 0, POPT_ARG_STRING, &kernelInfo, 0, _("display boot information for specified kernel"), _("kernel-path") }, @@ -2944,7 +3560,7 @@ return 1; } - if ((configureLilo + configureGrub + configureELilo + + if ((configureLilo + configureGrub2 + configureGrub + configureELilo + configureYaboot + configureSilo + configureZipl + configureExtLinux ) > 1) { fprintf(stderr, _("grubby: cannot specify multiple bootloaders\n")); @@ -2953,6 +3569,8 @@ fprintf(stderr, _("grubby: cannot specify config file with --bootloader-probe\n")); return 1; + } else if (configureGrub2) { + cfi = &grub2ConfigType; } else if (configureLilo) { cfi = &liloConfigType; } else if (configureGrub) { @@ -2971,27 +3589,34 @@ } if (!cfi) { + if (grub2FindConfig(&grub2ConfigType)) + cfi = &grub2ConfigType; + else #ifdef __ia64__ - cfi = &eliloConfigType; + cfi = &eliloConfigType; #elif __powerpc__ - cfi = &yabootConfigType; + cfi = &yabootConfigType; #elif __sparc__ - cfi = &siloConfigType; + cfi = &siloConfigType; #elif __s390__ - cfi = &ziplConfigType; + cfi = &ziplConfigType; #elif __s390x__ - cfi = &ziplConfigtype; + cfi = &ziplConfigtype; #else - cfi = &grubConfigType; + cfi = &grubConfigType; #endif } - if (!grubConfig) - grubConfig = cfi->defaultConfig; + if (!grubConfig) { + if (cfi->findConfig) + grubConfig = cfi->findConfig(cfi); + if (!grubConfig) + grubConfig = cfi->defaultConfig; + } if (bootloaderProbe && (displayDefault || kernelInfo || newKernelVersion || newKernelPath || removeKernelPath || makeDefault || - defaultKernel)) { + defaultKernel || displayDefaultIndex || displayDefaultTitle)) { fprintf(stderr, _("grubby: --bootloader-probe may not be used with " "specified option")); return 1; @@ -3034,7 +3659,7 @@ defaultKernel = NULL; } - if (!strcmp(grubConfig, "-") && !outputFile) { + if (grubConfig && !strcmp(grubConfig, "-") && !outputFile) { fprintf(stderr, _("grubby: output file must be specified if stdin " "is used\n")); return 1; @@ -3042,7 +3667,7 @@ if (!removeKernelPath && !newKernelPath && !displayDefault && !defaultKernel && !kernelInfo && !bootloaderProbe && !updateKernelPath - && !removeMBKernel) { + && !removeMBKernel && !displayDefaultIndex && !displayDefaultTitle) { fprintf(stderr, _("grubby: no action specified\n")); return 1; } @@ -3069,11 +3694,21 @@ } if (bootloaderProbe) { - int lrc = 0, grc = 0, erc = 0; + int lrc = 0, grc = 0, gr2c = 0, erc = 0; struct grubConfig * lconfig, * gconfig; - if (!access(grubConfigType.defaultConfig, F_OK)) { - gconfig = readConfig(grubConfigType.defaultConfig, &grubConfigType); + const char *grub2config = grub2FindConfig(&grub2ConfigType); + if (grub2config) { + gconfig = readConfig(grub2config, &grub2ConfigType); + if (!gconfig) + gr2c = 1; + else + gr2c = checkForGrub2(gconfig); + } + + const char *grubconfig = grubFindConfig(&grubConfigType); + if (!access(grubconfig, F_OK)) { + gconfig = readConfig(grubconfig, &grubConfigType); if (!gconfig) grc = 1; else @@ -3096,9 +3731,10 @@ erc = checkForExtLinux(lconfig); } - if (lrc == 1 || grc == 1) return 1; + if (lrc == 1 || grc == 1 || gr2c == 1) return 1; if (lrc == 2) printf("lilo\n"); + if (gr2c == 2) printf("grub2\n"); if (grc == 2) printf("grub\n"); if (erc == 2) printf("extlinux\n"); @@ -3126,6 +3762,36 @@ ((rootspec != NULL) ? strlen(rootspec) : 0)); return 0; + + } else if (displayDefaultTitle) { + struct singleLine * line; + struct singleEntry * entry; + + if (config->defaultImage == -1) return 0; + entry = findEntryByIndex(config, config->defaultImage); + if (!entry) return 0; + + if (!configureGrub2) { + line = getLineByType(LT_TITLE, entry->lines); + if (!line) return 0; + printf("%s\n", line->elements[1].item); + + } else { + char * title; + + dbgPrintf("This is GRUB2, default title is embeded in menuentry\n"); + line = getLineByType(LT_MENUENTRY, entry->lines); + if (!line) return 0; + title = grub2ExtractTitle(line); + if (title) + printf("%s\n", title); + } + return 0; + + } else if (displayDefaultIndex) { + if (config->defaultImage == -1) return 0; + printf("%i\n", config->defaultImage); + } else if (kernelInfo) return displayInfo(config, kernelInfo, bootPrefix); @@ -3147,7 +3813,7 @@ } if (addNewKernel(config, template, bootPrefix, newKernelPath, newKernelTitle, newKernelArgs, newKernelInitrd, - extraInitrds, extraInitrdCount, + (const char **)extraInitrds, extraInitrdCount, newMBKernel, newMBKernelArgs)) return 1; @@ -3158,7 +3824,7 @@ } if (!outputFile) - outputFile = grubConfig; + outputFile = (char *)grubConfig; return writeConfig(config, outputFile, bootPrefix); }