--- trunk/grubby/grubby.c 2013/02/20 14:04:28 2056 +++ trunk/grubby/grubby.c 2013/10/21 14:01:48 2257 @@ -36,6 +36,8 @@ #include #include +#include "log.h" + #ifndef DEBUG #define DEBUG 0 #endif @@ -58,6 +60,8 @@ int isEfi = 0; +char *saved_command_line = NULL; + /* comments get lumped in with indention */ struct lineElement { char * item; @@ -132,16 +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; @@ -153,6 +162,7 @@ int mbInitRdIsModule; int mbConcatArgs; int mbAllowExtraInitRds; + char *envFile; }; struct keywordTypes grubKeywords[] = { @@ -226,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]; } @@ -249,6 +267,105 @@ 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; @@ -343,6 +460,8 @@ struct configFileInfo grub2ConfigType = { .findConfig = grub2FindConfig, + .getEnv = grub2GetEnv, + .setEnv = grub2SetEnv, .keywords = grub2Keywords, .defaultIsIndex = 1, .defaultSupportSaved = 1, @@ -522,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); @@ -679,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; } @@ -954,7 +1080,10 @@ 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) { @@ -1021,7 +1150,8 @@ 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; @@ -1226,7 +1356,17 @@ if (defaultLine->numElements > 2 && cfi->defaultSupportSaved && !strncmp(defaultLine->elements[2].item,"\"${saved_entry}\"", 16)) { - cfg->defaultImage = DEFAULT_SAVED_GRUB2; + 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; + entry = findEntryByTitle(cfg, defTitle, &index); + if (entry) + cfg->defaultImage = index; + } + } } else if (cfi->defaultIsVariable) { char *value = defaultLine->elements[2].item; while (*value && (*value == '"' || *value == '\'' || @@ -1267,6 +1407,14 @@ cfg->defaultImage = -1; } } + } else if (cfg->cfi->defaultIsSaved && cfg->cfi->getEnv) { + char *defTitle = cfi->getEnv(cfg->cfi, "saved_entry"); + if (defTitle) { + int index = 0; + entry = findEntryByTitle(cfg, defTitle, &index); + if (entry) + cfg->defaultImage = index; + } } else { cfg->defaultImage = 0; } @@ -1284,9 +1432,21 @@ if (cfg->defaultImage == DEFAULT_SAVED) fprintf(out, "%sdefault%ssaved\n", indent, separator); - else if (cfg->defaultImage == DEFAULT_SAVED_GRUB2) + else if (cfg->cfi->defaultIsSaved) { fprintf(out, "%sset default=\"${saved_entry}\"\n", indent); - else if (cfg->defaultImage > -1) { + 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, @@ -1389,7 +1549,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) { @@ -1534,43 +1695,67 @@ return NULL; } -void printEntry(struct singleEntry * entry) { +void printEntry(struct singleEntry * entry, FILE *f) { int i; struct singleLine * line; for (line = entry->lines; line; line = line->next) { - fprintf(stderr, "DBG: %s", line->indent); + 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)) - fprintf(stderr, "\'%s\'", line->elements[i].item); + log_message(f, "\'%s\'", line->elements[i].item); else - fprintf(stderr, "%s", line->elements[i].item); - fprintf(stderr, "%s", line->elements[i].indent); + log_message(f, "%s", line->elements[i].item); + log_message(f, "%s", line->elements[i].indent); continue; } - fprintf(stderr, "%s%s", + log_message(f, "%s%s", line->elements[i].item, line->elements[i].indent); } - fprintf(stderr, "\n"); + log_message(f, "\n"); } } -void notSuitablePrintf(struct singleEntry * entry, const char *fmt, ...) +void notSuitablePrintf(struct singleEntry * entry, int okay, const char *fmt, ...) { - va_list argp; + 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) + if (!debug) { + once = 1; + va_end(argp); return; + } - va_start(argp, fmt); + 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); + printEntry(entry, stderr); va_end(argp); } @@ -1597,22 +1782,25 @@ char * rootdev; if (skipRemoved && entry->skip) { - notSuitablePrintf(entry, "marked to skip\n"); + notSuitablePrintf(entry, 0, "marked to skip\n"); return 0; } line = getLineByType(LT_KERNEL|LT_HYPER|LT_KERNEL_EFI, entry->lines); if (!line) { - notSuitablePrintf(entry, "no line found\n"); + notSuitablePrintf(entry, 0, "no line found\n"); return 0; } if (line->numElements < 2) { - notSuitablePrintf(entry, "line has only %d elements\n", + 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); @@ -1623,7 +1811,7 @@ 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); + notSuitablePrintf(entry, 0, "access to %s failed\n", fullName); return 0; } for (i = 2; i < line->numElements; i++) @@ -1644,7 +1832,7 @@ /* failed to find one */ if (!line) { - notSuitablePrintf(entry, "no line found\n"); + notSuitablePrintf(entry, 0, "no line found\n"); return 0; } @@ -1653,7 +1841,7 @@ if (i < line->numElements) dev = line->elements[i].item + 5; else { - notSuitablePrintf(entry, "no root= entry found\n"); + notSuitablePrintf(entry, 0, "no root= entry found\n"); /* it failed too... can't find root= */ return 0; } @@ -1662,32 +1850,33 @@ dev = getpathbyspec(dev); if (!getpathbyspec(dev)) { - notSuitablePrintf(entry, "can't find blkid entry for %s\n", dev); + notSuitablePrintf(entry, 0, "can't find blkid entry for %s\n", dev); return 0; } else dev = getpathbyspec(dev); rootdev = findDiskForRoot(); if (!rootdev) { - notSuitablePrintf(entry, "can't find root device\n"); + notSuitablePrintf(entry, 0, "can't find root device\n"); return 0; } if (!getuuidbydev(rootdev) || !getuuidbydev(dev)) { - notSuitablePrintf(entry, "uuid missing: rootdev %s, dev %s\n", + 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, "uuid mismatch: rootdev %s, dev %s\n", + 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; } @@ -1816,6 +2005,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; @@ -1838,7 +2057,15 @@ 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; + entry = findEntryByTitle(cfg, defTitle, &index); + } + } + } else if (cfg->defaultImage > -1) { entry = findEntryByIndex(cfg, cfg->defaultImage); if (entry && suitableImage(entry, prefix, skipRemoved, flags)) { if (indexPtr) *indexPtr = cfg->defaultImage; @@ -3757,7 +3984,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); @@ -3792,6 +4019,7 @@ 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; @@ -3843,6 +4071,9 @@ _("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, @@ -3855,7 +4086,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, @@ -3899,6 +4130,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); @@ -3942,6 +4187,8 @@ return 1; } else if (configureGrub2) { cfi = &grub2ConfigType; + if (envPath) + cfi->envFile = envPath; } else if (configureLilo) { cfi = &liloConfigType; } else if (configureGrub) { @@ -4142,6 +4389,11 @@ return 0; } + if (grubConfig == NULL) { + printf("Could not find bootloader configuration file.\n"); + exit(1); + } + config = readConfig(grubConfig, cfi); if (!config) return 1; @@ -4151,6 +4403,9 @@ 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; @@ -4169,6 +4424,9 @@ 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; @@ -4191,7 +4449,11 @@ } 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);