package grub import ( "errors" "fmt" "path/filepath" "regexp" "strings" file "edge-infra.dev/pkg/sds/lib/os/file" ) type CfgFile interface { WriteGrubCfg(fileHandler file.File, cfgPath string) error SetProperty(property, value string) GetMenuEntries() []MenuEntry GetMenuEntry(name string) (*MenuEntry, error) AddMenuEntry(menuEntry MenuEntry) DeleteMenuEntry(entryName string) WriteGrubCfgReadWrite(fileHandler file.File, grubVolumeMountPath, cfgPath string) error UpdateRequired() bool } type grubFile struct { Contents string OriginalContents string } type MenuEntry struct { Name string Options string Contents []string } func GetGrubConfigPath(fileHandler file.File, grubVolumeMountPath string, grubConfigFileName string) (string, error) { efi := filepath.Join(grubVolumeMountPath, "efi", "EFI", "grub", grubConfigFileName) legacy := filepath.Join(grubVolumeMountPath, "grub", grubConfigFileName) if fileHandler.Exists(efi) { return efi, nil } else if fileHandler.Exists(legacy) { return legacy, nil } return "", fmt.Errorf("grub config path not found. Efi: %s Legacy: %s", efi, legacy) } func ReadGrubCfg(fileHandler file.File, cfgPath string) (CfgFile, error) { grubContentBytes, err := fileHandler.Read(cfgPath) if err != nil { return nil, err } grubContent := &grubFile{ Contents: string(grubContentBytes), OriginalContents: string(grubContentBytes), } return grubContent, nil } func (grubFile *grubFile) UpdateRequired() bool { return grubFile.OriginalContents != grubFile.Contents } func (grubFile *grubFile) WriteGrubCfg(fileHandler file.File, cfgPath string) error { err := fileHandler.Write(cfgPath+".swp", []byte(grubFile.Contents), 0400) if err != nil { return err } return fileHandler.Rename(cfgPath+".swp", cfgPath) } func (grubFile *grubFile) WriteGrubCfgReadWrite(fileHandler file.File, grubVolumeMountPath, cfgPath string) error { return fileHandler.WriteWithFsReadWrite(grubVolumeMountPath, cfgPath, []byte(grubFile.Contents), 0400) } func (grubFile *grubFile) SetProperty(property, value string) { setStr := "set " + property newLine := fmt.Sprintf("%s=%s", setStr, value) if strings.Contains(grubFile.Contents, setStr) { re := regexp.MustCompile(setStr + "=.*") grubFile.Contents = re.ReplaceAllLiteralString(grubFile.Contents, newLine) } else { grubFile.Contents = grubFile.Contents + "\n" + newLine + "\n" } } func (grubFile *grubFile) GetMenuEntries() []MenuEntry { menuEntries := []MenuEntry{} Contents := grubFile.Contents index := strings.Index(Contents, "\nmenuentry") for index >= 0 { Contents = Contents[index+len("\nmenuentry"):] startIdx := strings.Index(Contents, "{\n") endIdx := strings.Index(Contents, "\n}") split := strings.Fields(Contents[:startIdx]) newEntry := MenuEntry{ Name: split[0], Options: strings.TrimSpace(Contents[strings.Index(Contents, split[0])+len(split[0]) : startIdx]), Contents: strings.Split(Contents[startIdx+2:endIdx], "\n"), } Contents = Contents[endIdx:] menuEntries = append(menuEntries, newEntry) index = strings.Index(Contents, "\nmenuentry") } return menuEntries } func (grubFile *grubFile) GetMenuEntry(name string) (*MenuEntry, error) { for _, entry := range grubFile.GetMenuEntries() { if strings.HasPrefix(entry.Name, `"`+name) { return &entry, nil } } return nil, errors.New("Menu entry not found") } func (grubFile *grubFile) AddMenuEntry(menuEntry MenuEntry) { grubFile.Contents += "\n\n" + menuEntry.String() } func (grubFile *grubFile) DeleteMenuEntry(entryName string) { re := regexp.MustCompile(`\n*menuentry\s+.?` + entryName + `.?\s+(.|\n)*\n}`) grubFile.Contents = re.ReplaceAllString(grubFile.Contents, "") } func (menuEntry *MenuEntry) String() string { str := fmt.Sprintf("menuentry %s %s {", menuEntry.Name, menuEntry.Options) for _, x := range menuEntry.Contents { str += "\n" + x } str += "\n}" return str }