--- trunk/grubby/grubby.c 2012/02/18 00:47:17 1717 +++ trunk/grubby/grubby.c 2013/10/21 14:02:25 2258 @@ -36,6 +36,8 @@ #include #include +#include "log.h" + #ifndef DEBUG #define DEBUG 0 #endif @@ -46,11 +48,20 @@ #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 + +int isEfi = 0; + +char *saved_command_line = NULL; + /* comments get lumped in with indention */ struct lineElement { char * item; @@ -77,7 +88,9 @@ LT_MENUENTRY = 1 << 17, LT_ENTRY_END = 1 << 18, LT_SET_VARIABLE = 1 << 19, - LT_UNKNOWN = 1 << 20, + LT_KERNEL_EFI = 1 << 20, + LT_INITRD_EFI = 1 << 21, + LT_UNKNOWN = 1 << 22, }; struct singleLine { @@ -109,6 +122,7 @@ #define MAIN_DEFAULT (1 << 0) #define DEFAULT_SAVED -2 +#define DEFAULT_SAVED_GRUB2 -3 struct keywordTypes { char * key; @@ -122,15 +136,21 @@ typedef const char *(*findConfigFunc)(struct configFileInfo *); typedef const int (*writeLineFunc)(struct configFileInfo *, struct singleLine *line); +typedef char *(*getEnvFunc)(struct configFileInfo *, char *name); +typedef int (*setEnvFunc)(struct configFileInfo *, char *name, char *value); struct configFileInfo { char * defaultConfig; findConfigFunc findConfig; writeLineFunc writeLine; + getEnvFunc getEnv; + setEnvFunc setEnv; struct keywordTypes * keywords; + int caseInsensitive; int defaultIsIndex; int defaultIsVariable; int defaultSupportSaved; + int defaultIsSaved; enum lineType_e entryStart; enum lineType_e entryEnd; int needsBootPrefix; @@ -142,6 +162,7 @@ int mbInitRdIsModule; int mbConcatArgs; int mbAllowExtraInitRds; + char *envFile; }; struct keywordTypes grubKeywords[] = { @@ -158,9 +179,9 @@ const char *grubFindConfig(struct configFileInfo *cfi) { static const char *configFiles[] = { - "/etc/grub.conf", "/boot/grub/grub.conf", "/boot/grub/menu.lst", + "/etc/grub.conf", NULL }; static int i = -1; @@ -199,7 +220,9 @@ { "default", LT_DEFAULT, ' ' }, { "fallback", LT_FALLBACK, ' ' }, { "linux", LT_KERNEL, ' ' }, + { "linuxefi", LT_KERNEL_EFI, ' ' }, { "initrd", LT_INITRD, ' ', ' ' }, + { "initrdefi", LT_INITRD_EFI, ' ', ' ' }, { "module", LT_MBMODULE, ' ' }, { "kernel", LT_HYPER, ' ' }, { NULL, 0, 0 }, @@ -213,11 +236,19 @@ }; static int i = -1; static const char *grub_cfg = "/boot/grub/grub.cfg"; + int rc = -1; if (i == -1) { for (i = 0; configFiles[i] != NULL; i++) { dbgPrintf("Checking \"%s\": ", configFiles[i]); - if (!access(configFiles[i], R_OK)) { + if ((rc = access(configFiles[i], R_OK))) { + if (errno == EACCES) { + printf("Unable to access bootloader configuration file " + "\"%s\": %m\n", configFiles[i]); + exit(1); + } + continue; + } else { dbgPrintf("found\n"); return configFiles[i]; } @@ -236,11 +267,204 @@ return configFiles[i]; } +/* kind of hacky. It'll give the first 1024 bytes, ish. */ +static char *grub2GetEnv(struct configFileInfo *info, char *name) +{ + static char buf[1025]; + char *s = NULL; + char *ret = NULL; + char *envFile = info->envFile ? info->envFile : "/boot/grub2/grubenv"; + int rc = asprintf(&s, "grub2-editenv %s list | grep '^%s='", envFile, name); + + if (rc < 0) + return NULL; + + FILE *f = popen(s, "r"); + if (!f) + goto out; + + memset(buf, '\0', sizeof (buf)); + ret = fgets(buf, 1024, f); + pclose(f); + + if (ret) { + ret += strlen(name) + 1; + ret[strlen(ret) - 1] = '\0'; + } + dbgPrintf("grub2GetEnv(%s): %s\n", name, ret); +out: + free(s); + return ret; +} + +static int sPopCount(const char *s, const char *c) +{ + int ret = 0; + if (!s) + return -1; + for (int i = 0; s[i] != '\0'; i++) + for (int j = 0; c[j] != '\0'; j++) + if (s[i] == c[j]) + ret++; + return ret; +} + +static char *shellEscape(const char *s) +{ + int l = strlen(s) + sPopCount(s, "'") * 2; + + char *ret = calloc(l+1, sizeof (*ret)); + if (!ret) + return NULL; + for (int i = 0, j = 0; s[i] != '\0'; i++, j++) { + if (s[i] == '\'') + ret[j++] = '\\'; + ret[j] = s[i]; + } + return ret; +} + +static void unquote(char *s) +{ + int l = strlen(s); + + if ((s[l-1] == '\'' && s[0] == '\'') || (s[l-1] == '"' && s[0] == '"')) { + memmove(s, s+1, l-2); + s[l-2] = '\0'; + } +} + +static int grub2SetEnv(struct configFileInfo *info, char *name, char *value) +{ + char *s = NULL; + int rc = 0; + char *envFile = info->envFile ? info->envFile : "/boot/grub2/grubenv"; + + unquote(value); + value = shellEscape(value); + if (!value) + return -1; + + rc = asprintf(&s, "grub2-editenv %s set '%s=%s'", envFile, name, value); + free(value); + if (rc <0) + return -1; + + dbgPrintf("grub2SetEnv(%s): %s\n", name, s); + rc = system(s); + free(s); + return rc; +} + +/* this is a gigantic hack to avoid clobbering grub2 variables... */ +static int is_special_grub2_variable(const char *name) +{ + if (!strcmp(name,"\"${next_entry}\"")) + return 1; + if (!strcmp(name,"\"${prev_saved_entry}\"")) + return 1; + return 0; +} + +int sizeOfSingleLine(struct singleLine * line) { + int count = 0; + + for (int 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; +} + +static int iskernel(enum lineType_e type) { + return (type == LT_KERNEL || type == LT_KERNEL_EFI); +} + +static int isinitrd(enum lineType_e type) { + return (type == LT_INITRD || type == LT_INITRD_EFI); +} + +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, + .getEnv = grub2GetEnv, + .setEnv = grub2SetEnv, .keywords = grub2Keywords, .defaultIsIndex = 1, - .defaultSupportSaved = 0, + .defaultSupportSaved = 1, .defaultIsVariable = 1, .entryStart = LT_MENUENTRY, .entryEnd = LT_ENTRY_END, @@ -392,6 +616,7 @@ struct configFileInfo extlinuxConfigType = { .defaultConfig = "/boot/extlinux/extlinux.conf", .keywords = extlinuxKeywords, + .caseInsensitive = 1, .entryStart = LT_TITLE, .needsBootPrefix = 1, .maxTitleLength = 255, @@ -416,6 +641,8 @@ struct singleEntry * findEntryByPath(struct grubConfig * cfg, const char * path, const char * prefix, int * index); +struct singleEntry * findEntryByTitle(struct grubConfig * cfg, char *title, + int * index); static int readFile(int fd, char ** bufPtr); static void lineInit(struct singleLine * line); struct singleLine * lineDup(struct singleLine * line); @@ -480,10 +707,24 @@ return buf; } +static enum lineType_e preferredLineType(enum lineType_e type, + struct configFileInfo *cfi) { + if (isEfi && cfi == &grub2ConfigType) { + switch (type) { + case LT_KERNEL: + return LT_KERNEL_EFI; + case LT_INITRD: + return LT_INITRD_EFI; + default: + return type; + } + } + return type; +} + static struct keywordTypes * getKeywordByType(enum lineType_e type, struct configFileInfo * cfi) { - struct keywordTypes * kw; - for (kw = cfi->keywords; kw->key; kw++) { + for (struct keywordTypes *kw = cfi->keywords; kw->key; kw++) { if (kw->type == type) return kw; } @@ -513,10 +754,14 @@ static enum lineType_e getTypeByKeyword(char * keyword, struct configFileInfo * cfi) { - struct keywordTypes * kw; - for (kw = cfi->keywords; kw->key; kw++) { - if (!strcmp(keyword, kw->key)) - return kw->type; + for (struct keywordTypes *kw = cfi->keywords; kw->key; kw++) { + if (cfi->caseInsensitive) { + if (!strcasecmp(keyword, kw->key)) + return kw->type; + } else { + if (!strcmp(keyword, kw->key)) + return kw->type; + } } return LT_UNKNOWN; } @@ -555,10 +800,15 @@ /* extract the title from within brackets (for zipl) */ static char * extractTitle(struct singleLine * line) { /* bracketed title... let's extract it (leaks a byte) */ - char * title; - title = strdup(line->elements[0].item); - title++; - *(title + strlen(title) - 1) = '\0'; + char * title = NULL; + if (line->type == LT_TITLE) { + title = strdup(line->elements[0].item); + title++; + *(title + strlen(title) - 1) = '\0'; + } else if (line->type == LT_MENUENTRY) + title = strdup(line->elements[1].item); + else + return NULL; return title; } @@ -601,7 +851,6 @@ } struct singleLine * lineDup(struct singleLine * line) { - int i; struct singleLine * newLine = malloc(sizeof(*newLine)); newLine->indent = strdup(line->indent); @@ -611,7 +860,7 @@ newLine->elements = malloc(sizeof(*newLine->elements) * newLine->numElements); - for (i = 0; i < newLine->numElements; i++) { + for (int i = 0; i < newLine->numElements; i++) { newLine->elements[i].indent = strdup(line->elements[i].indent); newLine->elements[i].item = strdup(line->elements[i].item); } @@ -620,11 +869,9 @@ } static void lineFree(struct singleLine * line) { - int i; - if (line->indent) free(line->indent); - for (i = 0; i < line->numElements; i++) { + for (int i = 0; i < line->numElements; i++) { free(line->elements[i].item); free(line->elements[i].indent); } @@ -635,11 +882,21 @@ static int lineWrite(FILE * out, struct singleLine * line, struct configFileInfo * cfi) { - int i; - if (fprintf(out, "%s", line->indent) == -1) return -1; - for (i = 0; i < line->numElements; i++) { + for (int 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; @@ -733,10 +990,9 @@ if (*line->elements[0].item == '#') { char * fullLine; int len; - int i; len = strlen(line->indent); - for (i = 0; i < line->numElements; i++) + for (int i = 0; i < line->numElements; i++) len += strlen(line->elements[i].item) + strlen(line->elements[i].indent); @@ -745,7 +1001,7 @@ free(line->indent); line->indent = fullLine; - for (i = 0; i < line->numElements; i++) { + for (int i = 0; i < line->numElements; i++) { strcat(fullLine, line->elements[i].item); strcat(fullLine, line->elements[i].indent); free(line->elements[i].item); @@ -764,12 +1020,10 @@ * elements up more */ if (!isspace(kw->separatorChar)) { - int i; char indent[2] = ""; indent[0] = kw->separatorChar; - for (i = 1; i < line->numElements; i++) { + for (int i = 1; i < line->numElements; i++) { char *p; - int j; int numNewElements; numNewElements = 0; @@ -785,7 +1039,7 @@ sizeof(*line->elements) * elementsAlloced); } - for (j = line->numElements; j > i; j--) { + for (int j = line->numElements; j > i; j--) { line->elements[j + numNewElements] = line->elements[j]; } line->numElements += numNewElements; @@ -798,13 +1052,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; } } } @@ -814,6 +1066,15 @@ return 0; } +static int isnumber(const char *s) +{ + int i; + for (i = 0; s[i] != '\0'; i++) + if (s[i] < '0' || s[i] > '9') + return 0; + return i; +} + static struct grubConfig * readConfig(const char * inName, struct configFileInfo * cfi) { int in; @@ -825,10 +1086,13 @@ struct singleLine * last = NULL, * line, * defaultLine = NULL; char * end; struct singleEntry * entry = NULL; - int i, len; + int len; char * buf; - if (!strcmp(inName, "-")) { + if (inName == NULL) { + printf("Could not find bootloader configuration\n"); + exit(1); + } else if (!strcmp(inName, "-")) { in = 0; } else { if ((in = open(inName, O_RDONLY)) < 0) { @@ -871,7 +1135,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)); @@ -888,15 +1152,15 @@ } 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].indent, line->elements[i].item); + for (int 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)) { + !strcmp(line->elements[1].item, kwType->key) && + !is_special_grub2_variable(line->elements[2].item)) { dbgPrintf("Line sets default config\n"); cfg->flags &= ~GRUB_CONFIG_NO_DEFAULT; defaultLine = line; @@ -905,7 +1169,7 @@ cfg->flags &= ~GRUB_CONFIG_NO_DEFAULT; defaultLine = line; - } else if (line->type == LT_KERNEL) { + } else if (iskernel(line->type)) { /* if by some freak chance this is multiboot and the "module" * lines came earlier in the template, make sure to use LT_HYPER * instead of LT_KERNEL now @@ -919,11 +1183,10 @@ * This only applies to grub, but that's the only place we * should find LT_MBMODULE lines anyway. */ - struct singleLine * l; - for (l = entry->lines; l; l = l->next) { + for (struct singleLine *l = entry->lines; l; l = l->next) { if (l->type == LT_HYPER) break; - else if (l->type == LT_KERNEL) { + else if (iskernel(l->type)) { l->type = LT_HYPER; break; } @@ -940,14 +1203,14 @@ } else if (line->type == LT_TITLE && line->numElements > 1) { /* make the title a single argument (undoing our parsing) */ len = 0; - for (i = 1; i < line->numElements; i++) { + for (int i = 1; i < line->numElements; i++) { len += strlen(line->elements[i].item); len += strlen(line->elements[i].indent); } buf = malloc(len + 1); *buf = '\0'; - for (i = 1; i < line->numElements; i++) { + for (int i = 1; i < line->numElements; i++) { strcat(buf, line->elements[i].item); free(line->elements[i].item); @@ -961,7 +1224,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 (int 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 (int 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 (int 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 @@ -970,13 +1297,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'; } } @@ -1035,7 +1362,26 @@ dbgPrintf("defaultLine is %s\n", defaultLine ? "set" : "unset"); if (defaultLine) { - if (cfi->defaultIsVariable) { + if (defaultLine->numElements > 2 && + cfi->defaultSupportSaved && + !strncmp(defaultLine->elements[2].item,"\"${saved_entry}\"", 16)) { + cfg->cfi->defaultIsSaved = 1; + cfg->defaultImage = DEFAULT_SAVED_GRUB2; + if (cfg->cfi->getEnv) { + char *defTitle = cfi->getEnv(cfg->cfi, "saved_entry"); + if (defTitle) { + int index = 0; + if (isnumber(defTitle)) { + index = atoi(defTitle); + entry = findEntryByIndex(cfg, index); + } else { + entry = findEntryByTitle(cfg, defTitle, &index); + } + if (entry) + cfg->defaultImage = index; + } + } + } else if (cfi->defaultIsVariable) { char *value = defaultLine->elements[2].item; while (*value && (*value == '"' || *value == '\'' || *value == ' ' || *value == '\t')) @@ -1052,7 +1398,7 @@ cfg->defaultImage = strtol(defaultLine->elements[1].item, &end, 10); if (*end) cfg->defaultImage = -1; } else if (defaultLine->numElements >= 2) { - i = 0; + int i = 0; while ((entry = findEntryByIndex(cfg, i))) { for (line = entry->lines; line; line = line->next) if (line->type == LT_TITLE) break; @@ -1075,6 +1421,19 @@ cfg->defaultImage = -1; } } + } else if (cfg->cfi->defaultIsSaved && cfg->cfi->getEnv) { + char *defTitle = cfi->getEnv(cfg->cfi, "saved_entry"); + if (defTitle) { + int index = 0; + if (isnumber(defTitle)) { + index = atoi(defTitle); + entry = findEntryByIndex(cfg, index); + } else { + entry = findEntryByTitle(cfg, defTitle, &index); + } + if (entry) + cfg->defaultImage = index; + } } else { cfg->defaultImage = 0; } @@ -1092,7 +1451,21 @@ if (cfg->defaultImage == DEFAULT_SAVED) fprintf(out, "%sdefault%ssaved\n", indent, separator); - else if (cfg->defaultImage > -1) { + else if (cfg->cfi->defaultIsSaved) { + fprintf(out, "%sset default=\"${saved_entry}\"\n", indent); + if (cfg->defaultImage >= 0 && cfg->cfi->setEnv) { + char *title; + entry = findEntryByIndex(cfg, cfg->defaultImage); + line = getLineByType(LT_MENUENTRY, entry->lines); + if (!line) + line = getLineByType(LT_TITLE, entry->lines); + if (line) { + title = extractTitle(line); + if (title) + cfg->cfi->setEnv(cfg->cfi, "saved_entry", title); + } + } + } else if (cfg->defaultImage > -1) { if (cfg->cfi->defaultIsIndex) { if (cfg->cfi->defaultIsVariable) { fprintf(out, "%sset default=\"%d\"\n", indent, @@ -1152,7 +1525,8 @@ /* most likely the symlink is relative, so change our directory to the dir of the symlink */ - rc = chdir(dirname(strdupa(outName))); + char *dir = strdupa(outName); + rc = chdir(dirname(dir)); do { buf = alloca(len + 1); rc = readlink(basename(outName), buf, len); @@ -1194,7 +1568,8 @@ while (line) { if (line->type == LT_SET_VARIABLE && defaultKw && line->numElements == 3 && - !strcmp(line->elements[1].item, defaultKw->key)) { + !strcmp(line->elements[1].item, defaultKw->key) && + !is_special_grub2_variable(line->elements[2].item)) { writeDefault(out, line->indent, line->elements[0].indent, cfg); needs &= ~MAIN_DEFAULT; } else if (line->type == LT_DEFAULT) { @@ -1290,6 +1665,8 @@ buf[rc] = '\0'; chptr = buf; + char *foundanswer = NULL; + while (chptr && chptr != buf+rc) { devname = chptr; @@ -1317,13 +1694,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 */ @@ -1332,9 +1704,93 @@ chptr++; } + /* Return the last / entry found */ + if (foundanswer) { + chptr = strchr(foundanswer, ' '); + *chptr = '\0'; + return strdup(foundanswer); + } + return NULL; } +void printEntry(struct singleEntry * entry, FILE *f) { + int i; + struct singleLine * line; + + for (line = entry->lines; line; line = line->next) { + log_message(f, "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)) + log_message(f, "\'%s\'", line->elements[i].item); + else + log_message(f, "%s", line->elements[i].item); + log_message(f, "%s", line->elements[i].indent); + + continue; + } + + log_message(f, "%s%s", + line->elements[i].item, line->elements[i].indent); + } + log_message(f, "\n"); + } +} + +void notSuitablePrintf(struct singleEntry * entry, int okay, const char *fmt, ...) +{ + static int once; + va_list argp, argq; + + va_start(argp, fmt); + + va_copy(argq, argp); + if (!once) { + log_time(NULL); + log_message(NULL, "command line: %s\n", saved_command_line); + } + log_message(NULL, "DBG: Image entry %s: ", okay ? "succeeded" : "failed"); + log_vmessage(NULL, fmt, argq); + + printEntry(entry, NULL); + va_end(argq); + + if (!debug) { + once = 1; + va_end(argp); + return; + } + + if (okay) { + va_end(argp); + return; + } + + if (!once) + log_message(stderr, "DBG: command line: %s\n", saved_command_line); + once = 1; + fprintf(stderr, "DBG: Image entry failed: "); + vfprintf(stderr, fmt, argp); + printEntry(entry, stderr); + 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; @@ -1344,20 +1800,39 @@ char * rootspec; char * rootdev; - if (skipRemoved && entry->skip) return 0; + if (skipRemoved && entry->skip) { + notSuitablePrintf(entry, 0, "marked to skip\n"); + return 0; + } - line = getLineByType(LT_KERNEL|LT_HYPER, entry->lines); - if (!line || line->numElements < 2) return 0; + line = getLineByType(LT_KERNEL|LT_HYPER|LT_KERNEL_EFI, entry->lines); + if (!line) { + notSuitablePrintf(entry, 0, "no line found\n"); + return 0; + } + if (line->numElements < 2) { + notSuitablePrintf(entry, 0, "line has only %d elements\n", + line->numElements); + return 0; + } - if (flags & GRUBBY_BADIMAGE_OKAY) return 1; + if (flags & GRUBBY_BADIMAGE_OKAY) { + notSuitablePrintf(entry, 1, "\n"); + 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, 0, "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) { @@ -1375,13 +1850,17 @@ line = getLineByType(LT_KERNELARGS|LT_MBMODULE, entry->lines); /* failed to find one */ - if (!line) return 0; + if (!line) { + notSuitablePrintf(entry, 0, "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, 0, "no root= entry found\n"); /* it failed too... can't find root= */ return 0; } @@ -1389,24 +1868,34 @@ } dev = getpathbyspec(dev); - if (!dev) + if (!getpathbyspec(dev)) { + notSuitablePrintf(entry, 0, "can't find blkid entry for %s\n", dev); return 0; + } else + dev = getpathbyspec(dev); rootdev = findDiskForRoot(); - if (!rootdev) + if (!rootdev) { + notSuitablePrintf(entry, 0, "can't find root device\n"); return 0; + } if (!getuuidbydev(rootdev) || !getuuidbydev(dev)) { + notSuitablePrintf(entry, 0, "uuid missing: rootdev %s, dev %s\n", + getuuidbydev(rootdev), getuuidbydev(dev)); free(rootdev); return 0; } if (strcmp(getuuidbydev(rootdev), getuuidbydev(dev))) { + notSuitablePrintf(entry, 0, "uuid mismatch: rootdev %s, dev %s\n", + getuuidbydev(rootdev), getuuidbydev(dev)); free(rootdev); return 0; } free(rootdev); + notSuitablePrintf(entry, 1, "\n"); return 1; } @@ -1450,7 +1939,7 @@ entry = findEntryByIndex(config, indexVars[i]); if (!entry) return NULL; - line = getLineByType(LT_KERNEL|LT_HYPER, entry->lines); + line = getLineByType(LT_KERNEL|LT_HYPER|LT_KERNEL_EFI, entry->lines); if (!line) return NULL; if (index) *index = indexVars[i]; @@ -1488,7 +1977,7 @@ if (!strncmp(kernel, "TITLE=", 6)) { prefix = ""; - checkType = LT_TITLE; + checkType = LT_TITLE|LT_MENUENTRY; kernel += 6; } @@ -1499,25 +1988,33 @@ /* check all the lines matching checkType */ for (line = entry->lines; line; line = line->next) { - line = getLineByType(entry->multiboot && checkType == LT_KERNEL ? - LT_KERNEL|LT_MBMODULE|LT_HYPER : - checkType, line); - if (!line) break; /* not found in this entry */ + enum lineType_e ct = checkType; + if (entry->multiboot && checkType == LT_KERNEL) + ct = LT_KERNEL|LT_KERNEL_EFI|LT_MBMODULE|LT_HYPER; + else if (checkType & LT_KERNEL) + ct = checkType | LT_KERNEL_EFI; + line = getLineByType(ct, 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 * non-Linux boot entries (could find netbsd etc, though, which is * unfortunate) */ - if (line && getLineByType(LT_KERNEL|LT_HYPER, entry->lines)) + if (line && getLineByType(LT_KERNEL|LT_HYPER|LT_KERNEL_EFI, entry->lines)) break; /* found 'im! */ } @@ -1527,6 +2024,36 @@ return entry; } +struct singleEntry * findEntryByTitle(struct grubConfig * cfg, char *title, + int * index) { + struct singleEntry * entry; + struct singleLine * line; + int i; + char * newtitle; + + for (i = 0, entry = cfg->entries; entry; entry = entry->next, i++) { + if (index && i < *index) + continue; + line = getLineByType(LT_TITLE, entry->lines); + if (!line) + line = getLineByType(LT_MENUENTRY, entry->lines); + if (!line) + continue; + newtitle = grub2ExtractTitle(line); + if (!newtitle) + continue; + if (!strcmp(title, newtitle)) + break; + } + + if (!entry) + return NULL; + + if (index) + *index = i; + return entry; +} + struct singleEntry * findEntryByIndex(struct grubConfig * cfg, int index) { struct singleEntry * entry; @@ -1549,7 +2076,22 @@ struct singleEntry * entry, * entry2; int index; - if (cfg->defaultImage > -1) { + if (cfg->cfi->defaultIsSaved) { + if (cfg->cfi->getEnv) { + char *defTitle = cfg->cfi->getEnv(cfg->cfi, "saved_entry"); + if (defTitle) { + int index = 0; + if (isnumber(defTitle)) { + index = atoi(defTitle); + entry = findEntryByIndex(cfg, index); + } else { + entry = findEntryByTitle(cfg, defTitle, &index); + } + if (entry) + cfg->defaultImage = index; + } + } + } else if (cfg->defaultImage > -1) { entry = findEntryByIndex(cfg, cfg->defaultImage); if (entry && suitableImage(entry, prefix, skipRemoved, flags)) { if (indexPtr) *indexPtr = cfg->defaultImage; @@ -1602,7 +2144,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; @@ -1610,13 +2161,19 @@ void setDefaultImage(struct grubConfig * config, int hasNew, const char * defaultKernelPath, int newIsDefault, - const char * prefix, int flags) { + const char * prefix, int flags, int index) { struct singleEntry * entry, * entry2, * newDefault; int i, j; if (newIsDefault) { config->defaultImage = 0; return; + } else if ((index >= 0) && config->cfi->defaultIsIndex) { + if (findEntryByIndex(config, index)) + config->defaultImage = index; + else + config->defaultImage = -1; + return; } else if (defaultKernelPath) { i = 0; if (findEntryByPath(config, defaultKernelPath, prefix, &i)) { @@ -1629,7 +2186,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; @@ -1690,13 +2248,16 @@ printf("index=%d\n", index); - line = getLineByType(LT_KERNEL|LT_HYPER, entry->lines); + line = getLineByType(LT_KERNEL|LT_HYPER|LT_KERNEL_EFI, entry->lines); if (!line) { printf("non linux entry\n"); return; } - printf("kernel=%s\n", line->elements[1].item); + if (!strncmp(prefix, line->elements[1].item, strlen(prefix))) + printf("kernel=%s\n", line->elements[1].item); + else + printf("kernel=%s%s\n", prefix, line->elements[1].item); if (line->numElements >= 3) { printf("args=\""); @@ -1752,14 +2313,290 @@ printf("root=%s\n", s); } - line = getLineByType(LT_INITRD, entry->lines); + line = getLineByType(LT_INITRD|LT_INITRD_EFI, entry->lines); if (line && line->numElements >= 2) { - printf("initrd=%s", prefix); + if (!strncmp(prefix, line->elements[1].item, strlen(prefix))) + printf("initrd="); + else + printf("initrd=%s", prefix); + for (i = 1; i < line->numElements; i++) 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 isSuseSystem(void) { + const char * path; + const static char default_path[] = "/etc/SuSE-release"; + + if ((path = getenv("GRUBBY_SUSE_RELEASE")) == NULL) + path = default_path; + + if (!access(path, R_OK)) + return 1; + return 0; +} + +int isSuseGrubConf(const char * path) { + FILE * grubConf; + char * line = NULL; + size_t len = 0, res = 0; + + grubConf = fopen(path, "r"); + if (!grubConf) { + dbgPrintf("Could not open SuSE configuration file '%s'\n", path); + return 0; + } + + while ((res = getline(&line, &len, grubConf)) != -1) { + if (!strncmp(line, "setup", 5)) { + fclose(grubConf); + free(line); + return 1; + } + } + + dbgPrintf("SuSE configuration file '%s' does not appear to be valid\n", + path); + + fclose(grubConf); + free(line); + return 0; +} + +int suseGrubConfGetLba(const char * path, int * lbaPtr) { + FILE * grubConf; + char * line = NULL; + size_t res = 0, len = 0; + + if (!path) return 1; + if (!lbaPtr) return 1; + + grubConf = fopen(path, "r"); + if (!grubConf) return 1; + + while ((res = getline(&line, &len, grubConf)) != -1) { + if (line[res - 1] == '\n') + line[res - 1] = '\0'; + else if (len > res) + line[res] = '\0'; + else { + line = realloc(line, res + 1); + line[res] = '\0'; + } + + if (!strncmp(line, "setup", 5)) { + if (strstr(line, "--force-lba")) { + *lbaPtr = 1; + } else { + *lbaPtr = 0; + } + dbgPrintf("lba: %i\n", *lbaPtr); + break; + } + } + + free(line); + fclose(grubConf); + return 0; +} + +int suseGrubConfGetInstallDevice(const char * path, char ** devicePtr) { + FILE * grubConf; + char * line = NULL; + size_t res = 0, len = 0; + char * lastParamPtr = NULL; + char * secLastParamPtr = NULL; + char installDeviceNumber = '\0'; + char * bounds = NULL; + + if (!path) return 1; + if (!devicePtr) return 1; + + grubConf = fopen(path, "r"); + if (!grubConf) return 1; + + while ((res = getline(&line, &len, grubConf)) != -1) { + if (strncmp(line, "setup", 5)) + continue; + + if (line[res - 1] == '\n') + line[res - 1] = '\0'; + else if (len > res) + line[res] = '\0'; + else { + line = realloc(line, res + 1); + line[res] = '\0'; + } + + lastParamPtr = bounds = line + res; + + /* Last parameter in grub may be an optional IMAGE_DEVICE */ + while (!isspace(*lastParamPtr)) + lastParamPtr--; + lastParamPtr++; + + secLastParamPtr = lastParamPtr - 2; + dbgPrintf("lastParamPtr: %s\n", lastParamPtr); + + if (lastParamPtr + 3 > bounds) { + dbgPrintf("lastParamPtr going over boundary"); + fclose(grubConf); + free(line); + return 1; + } + if (!strncmp(lastParamPtr, "(hd", 3)) + lastParamPtr += 3; + dbgPrintf("lastParamPtr: %c\n", *lastParamPtr); + + /* + * Second last parameter will decide wether last parameter is + * an IMAGE_DEVICE or INSTALL_DEVICE + */ + while (!isspace(*secLastParamPtr)) + secLastParamPtr--; + secLastParamPtr++; + + if (secLastParamPtr + 3 > bounds) { + dbgPrintf("secLastParamPtr going over boundary"); + fclose(grubConf); + free(line); + return 1; + } + dbgPrintf("secLastParamPtr: %s\n", secLastParamPtr); + if (!strncmp(secLastParamPtr, "(hd", 3)) { + secLastParamPtr += 3; + dbgPrintf("secLastParamPtr: %c\n", *secLastParamPtr); + installDeviceNumber = *secLastParamPtr; + } else { + installDeviceNumber = *lastParamPtr; + } + + *devicePtr = malloc(6); + snprintf(*devicePtr, 6, "(hd%c)", installDeviceNumber); + dbgPrintf("installDeviceNumber: %c\n", installDeviceNumber); + fclose(grubConf); + free(line); + return 0; + } + + free(line); + fclose(grubConf); + return 1; +} + +int grubGetBootFromDeviceMap(const char * device, + char ** bootPtr) { + FILE * deviceMap; + char * line = NULL; + size_t res = 0, len = 0; + char * devicePtr; + char * bounds = NULL; + const char * path; + const static char default_path[] = "/boot/grub/device.map"; + + if (!device) return 1; + if (!bootPtr) return 1; + + if ((path = getenv("GRUBBY_GRUB_DEVICE_MAP")) == NULL) + path = default_path; + + dbgPrintf("opening grub device.map file from: %s\n", path); + deviceMap = fopen(path, "r"); + if (!deviceMap) + return 1; + + while ((res = getline(&line, &len, deviceMap)) != -1) { + if (!strncmp(line, "#", 1)) + continue; + + if (line[res - 1] == '\n') + line[res - 1] = '\0'; + else if (len > res) + line[res] = '\0'; + else { + line = realloc(line, res + 1); + line[res] = '\0'; + } + + devicePtr = line; + bounds = line + res; + + while ((isspace(*line) && ((devicePtr + 1) <= bounds))) + devicePtr++; + dbgPrintf("device: %s\n", devicePtr); + + if (!strncmp(devicePtr, device, strlen(device))) { + devicePtr += strlen(device); + while (isspace(*devicePtr) && ((devicePtr + 1) <= bounds)) + devicePtr++; + + *bootPtr = strdup(devicePtr); + break; + } + } + + free(line); + fclose(deviceMap); + return 0; +} + +int suseGrubConfGetBoot(const char * path, char ** bootPtr) { + char * grubDevice; + + if (suseGrubConfGetInstallDevice(path, &grubDevice)) + dbgPrintf("error looking for grub installation device\n"); + else + dbgPrintf("grubby installation device: %s\n", grubDevice); + + if (grubGetBootFromDeviceMap(grubDevice, bootPtr)) + dbgPrintf("error looking for grub boot device\n"); + else + dbgPrintf("grubby boot device: %s\n", *bootPtr); + + free(grubDevice); + return 0; +} + +int parseSuseGrubConf(int * lbaPtr, char ** bootPtr) { + /* + * This SuSE grub configuration file at this location is not your average + * grub configuration file, but instead the grub commands used to setup + * grub on that system. + */ + const char * path; + const static char default_path[] = "/etc/grub.conf"; + + if ((path = getenv("GRUBBY_SUSE_GRUB_CONF")) == NULL) + path = default_path; + + if (!isSuseGrubConf(path)) return 1; + + if (lbaPtr) { + *lbaPtr = 0; + if (suseGrubConfGetLba(path, lbaPtr)) + return 1; + } + + if (bootPtr) { + *bootPtr = NULL; + suseGrubConfGetBoot(path, bootPtr); + } + + return 0; } int parseSysconfigGrub(int * lbaPtr, char ** bootPtr) { @@ -1810,12 +2647,25 @@ } void dumpSysconfigGrub(void) { - char * boot; + char * boot = NULL; int lba; - if (!parseSysconfigGrub(&lba, &boot)) { - if (lba) printf("lba\n"); - if (boot) printf("boot=%s\n", boot); + if (isSuseSystem()) { + if (parseSuseGrubConf(&lba, &boot)) { + free(boot); + return; + } + } else { + if (parseSysconfigGrub(&lba, &boot)) { + free(boot); + return; + } + } + + if (lba) printf("lba\n"); + if (boot) { + printf("boot=%s\n", boot); + free(boot); } } @@ -1864,6 +2714,13 @@ { struct singleLine * newLine = lineDup(tmplLine); + if (isEfi && cfi == &grub2ConfigType) { + enum lineType_e old = newLine->type; + newLine->type = preferredLineType(newLine->type, cfi); + if (old != newLine->type) + newLine->elements[0].item = getKeyByType(newLine->type, cfi); + } + if (val) { /* override the inherited value with our own. * This is a little weak because it only applies to elements[1] @@ -1873,7 +2730,7 @@ insertElement(newLine, val, 1, cfi); /* but try to keep the rootspec from the template... sigh */ - if (tmplLine->type & (LT_HYPER|LT_KERNEL|LT_MBMODULE|LT_INITRD)) { + if (tmplLine->type & (LT_HYPER|LT_KERNEL|LT_MBMODULE|LT_INITRD|LT_KERNEL_EFI|LT_INITRD_EFI)) { char * rootspec = getRootSpecifier(tmplLine->elements[1].item); if (rootspec != NULL) { free(newLine->elements[1].item); @@ -1910,7 +2767,6 @@ /* NB: This function shouldn't allocate items on the heap, rather on the * stack since it calls addLineTmpl which will make copies. */ - if (type == LT_TITLE && cfi->titleBracketed) { /* we're doing a bracketed title (zipl) */ tmpl.type = type; @@ -2014,13 +2870,6 @@ free(line); } -static int isquote(char q) -{ - if (q == '\'' || q == '\"') - return 1; - return 0; -} - static void requote(struct singleLine *tmplLine, struct configFileInfo * cfi) { struct singleLine newLine = { @@ -2251,7 +3100,7 @@ firstElement = 2; } else { - line = getLineByType(LT_KERNEL|LT_MBMODULE, entry->lines); + line = getLineByType(LT_KERNEL|LT_MBMODULE|LT_KERNEL_EFI, entry->lines); if (!line) { /* no LT_KERNEL or LT_MBMODULE in this entry? */ continue; @@ -2416,10 +3265,10 @@ if (!image) return 0; for (; (entry = findEntryByPath(cfg, image, prefix, &index)); index++) { - kernelLine = getLineByType(LT_KERNEL, entry->lines); + kernelLine = getLineByType(LT_KERNEL|LT_KERNEL_EFI, entry->lines); if (!kernelLine) continue; - line = getLineByType(LT_INITRD, entry->lines); + line = getLineByType(LT_INITRD|LT_INITRD_EFI, entry->lines); if (line) removeLine(entry, line); if (prefix) { @@ -2430,7 +3279,8 @@ endLine = getLineByType(LT_ENTRY_END, entry->lines); if (endLine) removeLine(entry, endLine); - line = addLine(entry, cfg->cfi, LT_INITRD, kernelLine->indent, initrd); + line = addLine(entry, cfg->cfi, preferredLineType(LT_INITRD, cfg->cfi), + kernelLine->indent, initrd); if (!line) return 1; if (endLine) { @@ -2468,12 +3318,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 { @@ -2626,9 +3486,16 @@ int fd; unsigned char bootSect[512]; char * boot; + int onSuse = isSuseSystem(); - if (parseSysconfigGrub(NULL, &boot)) - return 0; + + if (onSuse) { + if (parseSuseGrubConf(NULL, &boot)) + return 0; + } else { + if (parseSysconfigGrub(NULL, &boot)) + return 0; + } /* assume grub is not installed -- not an error condition */ if (!boot) @@ -2647,6 +3514,12 @@ } close(fd); + /* The more elaborate checks do not work on SuSE. The checks done + * seem to be reasonble (at least for now), so just return success + */ + if (onSuse) + return 2; + return checkDeviceBootloader(boot, bootSect); } @@ -2680,6 +3553,30 @@ return checkDeviceBootloader(boot, bootSect); } +int checkForYaboot(struct grubConfig * config) { + /* + * This is a simplistic check that we consider good enough for own puporses + * + * If we were to properly check if yaboot is *installed* we'd need to: + * 1) get the system boot device (LT_BOOT) + * 2) considering it's a raw filesystem, check if the yaboot binary matches + * the content on the boot device + * 3) if not, copy the binary to a temporary file and run "addnote" on it + * 4) check again if binary and boot device contents match + */ + if (!access("/etc/yaboot.conf", R_OK)) + return 2; + + return 1; +} + +int checkForElilo(struct grubConfig * config) { + if (!access("/etc/elilo.conf", R_OK)) + return 2; + + return 1; +} + static char * getRootSpecifier(char * str) { char * idx, * rootspec = NULL; @@ -2694,7 +3591,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; @@ -2739,10 +3636,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; @@ -2796,8 +3693,7 @@ while (*chptr && isspace(*chptr)) chptr++; if (*chptr == '#') continue; - if (tmplLine->type == LT_KERNEL && - tmplLine->numElements >= 2) { + if (iskernel(tmplLine->type) && 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 @@ -2874,30 +3770,32 @@ /* template is multi but new is not, * insert the kernel in the first module slot */ - tmplLine->type = LT_KERNEL; + tmplLine->type = preferredLineType(LT_KERNEL, config->cfi); free(tmplLine->elements[0].item); tmplLine->elements[0].item = - strdup(getKeywordByType(LT_KERNEL, config->cfi)->key); + strdup(getKeywordByType(tmplLine->type, + config->cfi)->key); newLine = addLineTmpl(new, tmplLine, newLine, - newKernelPath + strlen(prefix), config->cfi); + newKernelPath + strlen(prefix), + config->cfi); needs &= ~NEED_KERNEL; } else if (needs & NEED_INITRD) { char *initrdVal; /* template is multi but new is not, * insert the initrd in the second module slot */ - tmplLine->type = LT_INITRD; + tmplLine->type = preferredLineType(LT_INITRD, config->cfi); free(tmplLine->elements[0].item); tmplLine->elements[0].item = - strdup(getKeywordByType(LT_INITRD, config->cfi)->key); + strdup(getKeywordByType(tmplLine->type, + config->cfi)->key); initrdVal = getInitrdVal(config, prefix, tmplLine, newKernelInitrd, extraInitrds, extraInitrdCount); newLine = addLineTmpl(new, tmplLine, newLine, initrdVal, config->cfi); free(initrdVal); needs &= ~NEED_INITRD; } - } else if (tmplLine->type == LT_INITRD && - tmplLine->numElements >= 2) { + } else if (isinitrd(tmplLine->type) && tmplLine->numElements >= 2) { if (needs & NEED_INITRD && new->multiboot && !template->multiboot && config->cfi->mbInitRdIsModule) { @@ -2948,9 +3846,11 @@ } } else if (tmplLine->type == LT_ECHO) { requote(tmplLine, config->cfi); + static const char *prefix = "'Loading "; if (tmplLine->numElements > 1 && - strstr(tmplLine->elements[1].item, "'Loading Linux ")) { - char *prefix = "'Loading "; + strstr(tmplLine->elements[1].item, prefix) && + masterLine->next && + iskernel(masterLine->next->type)) { char *newTitle = malloc(strlen(prefix) + strlen(newKernelTitle) + 2); @@ -2977,10 +3877,12 @@ */ switch (config->cfi->entryStart) { case LT_KERNEL: + case LT_KERNEL_EFI: if (new->multiboot && config->cfi->mbHyperFirst) { /* fall through to LT_HYPER */ } else { - newLine = addLine(new, config->cfi, LT_KERNEL, + newLine = addLine(new, config->cfi, + preferredLineType(LT_KERNEL, config->cfi), config->primaryIndent, newKernelPath + strlen(prefix)); needs &= ~NEED_KERNEL; @@ -3056,8 +3958,9 @@ if (needs & NEED_KERNEL) { newLine = addLine(new, config->cfi, (new->multiboot && getKeywordByType(LT_MBMODULE, - config->cfi)) ? - LT_MBMODULE : LT_KERNEL, + config->cfi)) + ? LT_MBMODULE + : preferredLineType(LT_KERNEL, config->cfi), config->secondaryIndent, newKernelPath + strlen(prefix)); needs &= ~NEED_KERNEL; @@ -3073,8 +3976,9 @@ initrdVal = getInitrdVal(config, prefix, NULL, newKernelInitrd, extraInitrds, extraInitrdCount); newLine = addLine(new, config->cfi, (new->multiboot && getKeywordByType(LT_MBMODULE, - config->cfi)) ? - LT_MBMODULE : LT_INITRD, + config->cfi)) + ? LT_MBMODULE + : preferredLineType(LT_INITRD, config->cfi), config->secondaryIndent, initrdVal); free(initrdVal); @@ -3106,7 +4010,7 @@ memset(array, '\0', sizeof (array)); size = backtrace(array, 40); - fprintf(stderr, "grubby recieved SIGSEGV! Backtrace (%ld):\n", + fprintf(stderr, "grubby received SIGSEGV! Backtrace (%ld):\n", (unsigned long)size); backtrace_symbols_fd(array, size, STDERR_FILENO); exit(1); @@ -3141,12 +4045,16 @@ char * removeArgs = NULL; char * kernelInfo = NULL; char * extraInitrds[MAX_EXTRA_INITRDS] = { NULL }; + char * envPath = NULL; const char * chptr = NULL; struct configFileInfo * cfi = NULL; struct grubConfig * config; struct singleEntry * template = NULL; int copyDefault = 0, makeDefault = 0; int displayDefault = 0; + int displayDefaultIndex = 0; + int displayDefaultTitle = 0; + int defaultIndex = -1; struct poptOption options[] = { { "add-kernel", 0, POPT_ARG_STRING, &newKernelPath, 0, _("add an entry for the specified kernel"), _("kernel-path") }, @@ -3164,9 +4072,9 @@ { "boot-filesystem", 0, POPT_ARG_STRING, &bootPrefix, 0, _("filestystem which contains /boot directory (for testing only)"), _("bootfs") }, -#if defined(__i386__) || defined(__x86_64__) +#if defined(__i386__) || defined(__x86_64__) || defined (__powerpc64__) || defined (__ia64__) { "bootloader-probe", 0, POPT_ARG_NONE, &bootloaderProbe, 0, - _("check if lilo is installed on lilo.conf boot sector") }, + _("check which bootloader is installed on boot sector") }, #endif { "config-file", 'c', POPT_ARG_STRING, &grubConfig, 0, _("path to grub config file to update (\"-\" for stdin)"), @@ -3177,10 +4085,21 @@ "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") }, + { "efi", 0, POPT_ARG_NONE, &isEfi, 0, + _("force grub2 stanzas to use efi") }, + { "env", 0, POPT_ARG_STRING, &envPath, 0, + _("path for environment data"), + _("path") }, { "extlinux", 0, POPT_ARG_NONE, &configureExtLinux, 0, _("configure extlinux bootloader (from syslinux)") }, { "grub", 0, POPT_ARG_NONE, &configureGrub, 0, @@ -3193,7 +4112,7 @@ { "initrd", 0, POPT_ARG_STRING, &newKernelInitrd, 0, _("initrd image for the new kernel"), _("initrd-path") }, { "extra-initrd", 'i', POPT_ARG_STRING, NULL, 'i', - _("auxilliary initrd image for things other than the new kernel"), _("initrd-path") }, + _("auxiliary initrd image for things other than the new kernel"), _("initrd-path") }, { "lilo", 0, POPT_ARG_NONE, &configureLilo, 0, _("configure lilo bootloader") }, { "make-default", 0, 0, &makeDefault, 0, @@ -3213,6 +4132,9 @@ { "set-default", 0, POPT_ARG_STRING, &defaultKernel, 0, _("make the first entry referencing the specified kernel " "the default"), _("kernel-path") }, + { "set-default-index", 0, POPT_ARG_INT, &defaultIndex, 0, + _("make the given entry index the default entry"), + _("entry-index") }, { "silo", 0, POPT_ARG_NONE, &configureSilo, 0, _("configure silo bootloader") }, { "title", 0, POPT_ARG_STRING, &newKernelTitle, 0, @@ -3234,6 +4156,20 @@ signal(SIGSEGV, traceback); + int i = 0; + for (int j = 1; j < argc; j++) + i += strlen(argv[j]) + 1; + saved_command_line = malloc(i); + if (!saved_command_line) { + fprintf(stderr, "grubby: %m\n"); + exit(1); + } + saved_command_line[0] = '\0'; + for (int j = 1; j < argc; j++) { + strcat(saved_command_line, argv[j]); + strncat(saved_command_line, j == argc -1 ? "" : " ", 1); + } + optCon = poptGetContext("grubby", argc, argv, options, 0); poptReadDefaultConfig(optCon, 1); @@ -3277,6 +4213,8 @@ return 1; } else if (configureGrub2) { cfi = &grub2ConfigType; + if (envPath) + cfi->envFile = envPath; } else if (configureLilo) { cfi = &liloConfigType; } else if (configureGrub) { @@ -3295,20 +4233,20 @@ } 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 - if (grub2FindConfig(&grub2ConfigType)) - cfi = &grub2ConfigType; - else cfi = &grubConfigType; #endif } @@ -3321,8 +4259,9 @@ } if (bootloaderProbe && (displayDefault || kernelInfo || newKernelVersion || - newKernelPath || removeKernelPath || makeDefault || - defaultKernel)) { + newKernelPath || removeKernelPath || makeDefault || + defaultKernel || displayDefaultIndex || displayDefaultTitle || + (defaultIndex >= 0))) { fprintf(stderr, _("grubby: --bootloader-probe may not be used with " "specified option")); return 1; @@ -3364,6 +4303,11 @@ makeDefault = 1; defaultKernel = NULL; } + else if (defaultKernel && (defaultIndex >= 0)) { + fprintf(stderr, _("grubby: --set-default and --set-default-index " + "may not be used together\n")); + return 1; + } if (grubConfig && !strcmp(grubConfig, "-") && !outputFile) { fprintf(stderr, _("grubby: output file must be specified if stdin " @@ -3372,8 +4316,9 @@ } if (!removeKernelPath && !newKernelPath && !displayDefault && !defaultKernel - && !kernelInfo && !bootloaderProbe && !updateKernelPath - && !removeMBKernel) { + && !kernelInfo && !bootloaderProbe && !updateKernelPath + && !removeMBKernel && !displayDefaultIndex && !displayDefaultTitle + && (defaultIndex == -1)) { fprintf(stderr, _("grubby: no action specified\n")); return 1; } @@ -3400,8 +4345,8 @@ } if (bootloaderProbe) { - int lrc = 0, grc = 0, gr2c = 0, erc = 0; - struct grubConfig * lconfig, * gconfig; + int lrc = 0, grc = 0, gr2c = 0, extrc = 0, yrc = 0, erc = 0; + struct grubConfig * lconfig, * gconfig, * yconfig, * econfig; const char *grub2config = grub2FindConfig(&grub2ConfigType); if (grub2config) { @@ -3429,24 +4374,52 @@ lrc = checkForLilo(lconfig); } + if (!access(eliloConfigType.defaultConfig, F_OK)) { + econfig = readConfig(eliloConfigType.defaultConfig, + &eliloConfigType); + if (!econfig) + erc = 1; + else + erc = checkForElilo(econfig); + } + if (!access(extlinuxConfigType.defaultConfig, F_OK)) { lconfig = readConfig(extlinuxConfigType.defaultConfig, &extlinuxConfigType); if (!lconfig) - erc = 1; + extrc = 1; else - erc = checkForExtLinux(lconfig); + extrc = checkForExtLinux(lconfig); } - if (lrc == 1 || grc == 1 || gr2c == 1) return 1; + + if (!access(yabootConfigType.defaultConfig, F_OK)) { + yconfig = readConfig(yabootConfigType.defaultConfig, + &yabootConfigType); + if (!yconfig) + yrc = 1; + else + yrc = checkForYaboot(yconfig); + } + + if (lrc == 1 || grc == 1 || gr2c == 1 || extrc == 1 || yrc == 1 || + erc == 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"); + if (extrc == 2) printf("extlinux\n"); + if (yrc == 2) printf("yaboot\n"); + if (erc == 2) printf("elilo\n"); return 0; } + if (grubConfig == NULL) { + printf("Could not find bootloader configuration file.\n"); + exit(1); + } + config = readConfig(grubConfig, cfi); if (!config) return 1; @@ -3456,11 +4429,14 @@ char * rootspec; if (config->defaultImage == -1) return 0; + if (config->defaultImage == DEFAULT_SAVED_GRUB2 && + cfi->defaultIsSaved) + config->defaultImage = 0; entry = findEntryByIndex(config, config->defaultImage); if (!entry) return 0; if (!suitableImage(entry, bootPrefix, 0, flags)) return 0; - line = getLineByType(LT_KERNEL|LT_HYPER, entry->lines); + line = getLineByType(LT_KERNEL|LT_HYPER|LT_KERNEL_EFI, entry->lines); if (!line) return 0; rootspec = getRootSpecifier(line->elements[1].item); @@ -3468,6 +4444,43 @@ ((rootspec != NULL) ? strlen(rootspec) : 0)); return 0; + + } else if (displayDefaultTitle) { + struct singleLine * line; + struct singleEntry * entry; + + if (config->defaultImage == -1) return 0; + if (config->defaultImage == DEFAULT_SAVED_GRUB2 && + cfi->defaultIsSaved) + config->defaultImage = 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; + if (config->defaultImage == DEFAULT_SAVED_GRUB2 && + cfi->defaultIsSaved) + config->defaultImage = 0; + printf("%i\n", config->defaultImage); + return 0; + } else if (kernelInfo) return displayInfo(config, kernelInfo, bootPrefix); @@ -3479,7 +4492,7 @@ markRemovedImage(config, removeKernelPath, bootPrefix); markRemovedImage(config, removeMBKernel, bootPrefix); setDefaultImage(config, newKernelPath != NULL, defaultKernel, makeDefault, - bootPrefix, flags); + bootPrefix, flags, defaultIndex); setFallbackImage(config, newKernelPath != NULL); if (updateImage(config, updateKernelPath, bootPrefix, newKernelArgs, removeArgs, newMBKernelArgs, removeMBKernelArgs)) return 1; @@ -3489,7 +4502,7 @@ } if (addNewKernel(config, template, bootPrefix, newKernelPath, newKernelTitle, newKernelArgs, newKernelInitrd, - extraInitrds, extraInitrdCount, + (const char **)extraInitrds, extraInitrdCount, newMBKernel, newMBKernelArgs)) return 1;