github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/boot/systemboot/main.go (about)

     1  // Copyright 2017-2019 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"log"
    11  	"os"
    12  	"os/exec"
    13  	"os/signal"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/mvdan/u-root-coreutils/pkg/boot/systembooter"
    18  	"github.com/mvdan/u-root-coreutils/pkg/ipmi"
    19  	"github.com/mvdan/u-root-coreutils/pkg/ipmi/ocp"
    20  	"github.com/mvdan/u-root-coreutils/pkg/smbios"
    21  	"github.com/mvdan/u-root-coreutils/pkg/vpd"
    22  )
    23  
    24  var (
    25  	allowInteractive = flag.Bool("i", true, "Allow user to interrupt boot process and run commands")
    26  	doQuiet          = flag.Bool("q", false, fmt.Sprintf("Disable verbose output. If not specified, read it from VPD var '%s'. Default false", vpdSystembootLogLevel))
    27  	interval         = flag.Int("I", 1, "Interval in seconds before looping to the next boot command")
    28  	noDefaultBoot    = flag.Bool("nodefault", false, "Do not attempt default boot entries if regular ones fail")
    29  )
    30  
    31  const (
    32  	// vpdSystembootLogLevel is the name of the VPD variable used to set the log level.
    33  	vpdSystembootLogLevel = "systemboot_log_level"
    34  )
    35  
    36  // isFlagPassed checks whether a flag was explicitly passed on the command line
    37  func isFlagPassed(name string) bool {
    38  	found := false
    39  	flag.Visit(func(f *flag.Flag) {
    40  		if f.Name == name {
    41  			found = true
    42  		}
    43  	})
    44  	return found
    45  }
    46  
    47  var defaultBootsequence = [][]string{
    48  	{"fbnetboot", "-userclass", "linuxboot"},
    49  	{"localboot", "-grub"},
    50  }
    51  
    52  // VPD variable for enabling IPMI BMC overriding boot order, default is not set
    53  const VpdBmcBootOrderOverride = "bmc_bootorder_override"
    54  
    55  var bmcBootOverride bool
    56  
    57  // Product list for running IPMI OEM commands
    58  var productList = [5]string{"Tioga Pass", "Mono Lake", "Delta Lake", "Crater Lake", "S9S"}
    59  
    60  var selRecorded bool
    61  
    62  func isMatched(productName string) bool {
    63  	for _, v := range productList {
    64  		if strings.HasPrefix(productName, v) {
    65  			return true
    66  		}
    67  	}
    68  	return false
    69  }
    70  
    71  func getBaseboardProductName(si *smbios.Info) (string, error) {
    72  	t2, err := si.GetBaseboardInfo()
    73  	if err != nil {
    74  		log.Printf("Error getting Baseboard Information: %v", err)
    75  		return "", err
    76  	}
    77  	return t2[0].Product, nil
    78  }
    79  
    80  func getSystemFWVersion(si *smbios.Info) (string, error) {
    81  	t0, err := si.GetBIOSInfo()
    82  	if err != nil {
    83  		log.Printf("Error getting BIOS Information: %v", err)
    84  		return "", err
    85  	}
    86  	return t0.Version, nil
    87  }
    88  
    89  func checkCMOSClear(ipmi *ipmi.IPMI) error {
    90  	if cmosclear, bootorder, err := ocp.IsCMOSClearSet(ipmi); cmosclear {
    91  		log.Printf("CMOS clear starts")
    92  		if err = cmosClear(); err != nil {
    93  			return err
    94  		}
    95  		if err = vpd.ClearRwVpd(); err != nil {
    96  			return err
    97  		}
    98  
    99  		if err = ocp.ClearCMOSClearValidBits(ipmi, bootorder); err != nil {
   100  			return err
   101  		}
   102  		addSEL("cmosclear")
   103  		if err = reboot(); err != nil {
   104  			return err
   105  		}
   106  	} else if err != nil {
   107  		return err
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  func runIPMICommands() {
   114  	i, err := ipmi.Open(0)
   115  	if err != nil {
   116  		log.Printf("Failed to open ipmi device %v, watchdog may still be running", err)
   117  		return
   118  	}
   119  	defer i.Close()
   120  
   121  	if err = i.ShutoffWatchdog(); err != nil {
   122  		log.Printf("Failed to stop watchdog %v.", err)
   123  	} else {
   124  		log.Printf("Watchdog is stopped.")
   125  	}
   126  	// Try RW_VPD first
   127  	value, err := systembooter.Get(VpdBmcBootOrderOverride, false)
   128  	if err != nil {
   129  		// Try RO_VPD
   130  		value, err = systembooter.Get(VpdBmcBootOrderOverride, true)
   131  	}
   132  	if err == nil && string(value) == "1" {
   133  		bmcBootOverride = true
   134  	}
   135  	log.Printf("VPD %s is %v", VpdBmcBootOrderOverride, string(value))
   136  	// Below IPMI commands would require SMBIOS data
   137  	si, err := smbios.FromSysfs()
   138  	if err != nil {
   139  		log.Printf("Error reading SMBIOS info: %v", err)
   140  		return
   141  	}
   142  
   143  	if fwVersion, err := getSystemFWVersion(si); err == nil {
   144  		log.Printf("System firmware version: %s", fwVersion)
   145  		if err = i.SetSystemFWVersion(fwVersion); err != nil {
   146  			log.Printf("Failed to set system firmware version to BMC %v.", err)
   147  		}
   148  	}
   149  
   150  	if productName, err := getBaseboardProductName(si); err == nil {
   151  		if isMatched(productName) {
   152  			log.Printf("Running OEM IPMI commands.")
   153  			if err = checkCMOSClear(i); err != nil {
   154  				log.Printf("IPMI CMOS clear err: %v", err)
   155  			}
   156  			if err = ocp.CheckBMCBootOrder(i, bmcBootOverride); err != nil {
   157  				log.Printf("Failed to sync BMC Boot Order %v.", err)
   158  			}
   159  			dimmInfo, err := ocp.GetOemIpmiDimmInfo(si)
   160  			if err == nil {
   161  				if err = ocp.SendOemIpmiDimmInfo(i, dimmInfo); err == nil {
   162  					log.Printf("Send the information of DIMMs to BMC.")
   163  				} else {
   164  					log.Printf("Failed to send the information of DIMMs to BMC: %v.", err)
   165  				}
   166  			} else {
   167  				log.Printf("Failed to get the information of DIMMs: %v.", err)
   168  			}
   169  
   170  			processorInfo, err := ocp.GetOemIpmiProcessorInfo(si)
   171  			if err == nil {
   172  				if err = ocp.SendOemIpmiProcessorInfo(i, processorInfo); err == nil {
   173  					log.Printf("Send the information of processors to BMC.")
   174  				} else {
   175  					log.Printf("Failed to send the information of processors to BMC: %v.", err)
   176  				}
   177  			} else {
   178  				log.Printf("Failed to get the information of Processors: %v.", err)
   179  			}
   180  
   181  			BootDriveInfo, err := ocp.GetOemIpmiBootDriveInfo(si)
   182  			if err == nil {
   183  				if BootDriveInfo != nil {
   184  					if err = ocp.SendOemIpmiBootDriveInfo(i, BootDriveInfo); err == nil {
   185  						log.Printf("Send the information of boot drive to BMC.")
   186  					} else {
   187  						log.Printf("Failed to send the information of boot drive to BMC: %v.", err)
   188  					}
   189  				} else {
   190  					log.Printf("The information of boot drive is not found.")
   191  				}
   192  			} else {
   193  				log.Printf("Failed to get the information of boot drive: %v.", err)
   194  			}
   195  
   196  			if err = ocp.SetOemIpmiPostEnd(i); err == nil {
   197  				log.Printf("Send IPMI POST end to BMC")
   198  			} else {
   199  				log.Printf("Failed to send IPMI POST end to BMC: %v.", err)
   200  			}
   201  
   202  		} else {
   203  			log.Printf("No product name is matched for OEM commands.")
   204  		}
   205  	}
   206  }
   207  
   208  func addSEL(sequence string) {
   209  	var bootErr ipmi.Event
   210  
   211  	i, err := ipmi.Open(0)
   212  	if err != nil {
   213  		log.Printf("Failed to open ipmi device to send SEL %v", err)
   214  		return
   215  	}
   216  	defer i.Close()
   217  
   218  	switch sequence {
   219  	case "netboot":
   220  		fallthrough
   221  	case "fbnetboot":
   222  		bootErr.RecordID = 0
   223  		bootErr.RecordType = ipmi.OEM_NTS_TYPE
   224  		bootErr.OEMNontsDefinedData[0] = 0x28
   225  		bootErr.OEMNontsDefinedData[5] = 0xf0
   226  		for idx := 6; idx < 13; idx++ {
   227  			bootErr.OEMNontsDefinedData[idx] = 0xff
   228  		}
   229  		if err := i.LogSystemEvent(&bootErr); err != nil {
   230  			log.Printf("SEL recorded: %s fail\n", sequence)
   231  		}
   232  	case "cmosclear":
   233  		bootErr.RecordID = 0
   234  		bootErr.RecordType = ipmi.OEM_NTS_TYPE
   235  		bootErr.OEMNontsDefinedData[0] = 0x28
   236  		bootErr.OEMNontsDefinedData[5] = 0xf1
   237  		for idx := 6; idx < 13; idx++ {
   238  			bootErr.OEMNontsDefinedData[idx] = 0xff
   239  		}
   240  		if err := i.LogSystemEvent(&bootErr); err != nil {
   241  			log.Printf("SEL recorded: %s fail\n", sequence)
   242  		}
   243  	default:
   244  	}
   245  }
   246  
   247  // getDebugEnabled checks whether debug output is requested, either via command line or via VPD
   248  // variables.
   249  // If -q was explicitly passed on the command line, will use that value, otherwise will look for
   250  // the VPD variable "systemboot_log_level".
   251  // Valid values are coreboot loglevels https://review.coreboot.org/cgit/coreboot.git/tree/src/commonlib/include/commonlib/loglevel.h,
   252  // either as integer (1, 2) or string ("debug").
   253  // If the VPD variable is missing or it is set to an invalid value, it will use the default.
   254  func getDebugEnabled() bool {
   255  	if isFlagPassed("q") {
   256  		return !*doQuiet
   257  	}
   258  
   259  	// -q was not passed, so `doQuiet` contains the default value
   260  	defaultDebugEnabled := !*doQuiet
   261  	// check for the VPD variable "systemboot_log_level". First the read-write, then the read-only
   262  	v, err := vpd.Get(vpdSystembootLogLevel, false)
   263  	if err != nil {
   264  		// TODO do not print warning if file is not found
   265  		log.Printf("Warning: failed to read read-write VPD variable '%s', will try the read-only one. Error was: %v", vpdSystembootLogLevel, err)
   266  		v, err = vpd.Get(vpdSystembootLogLevel, true)
   267  		if err != nil {
   268  			// TODO do not print warning if file is not found
   269  			log.Printf("Warning: failed to read read-only VPD variable '%s', will use the default value. Error was: %v", vpdSystembootLogLevel, err)
   270  			return defaultDebugEnabled
   271  		}
   272  	}
   273  	level := strings.ToLower(strings.TrimSpace(string(v)))
   274  	switch level {
   275  	case "0", "emerg", "1", "alert", "2", "crit", "3", "err", "4", "warning", "9", "never":
   276  		// treat as quiet
   277  		return false
   278  	case "5", "notice", "6", "info", "7", "debug", "8", "spew":
   279  		// treat as debug
   280  		return true
   281  	default:
   282  		log.Printf("Invalid value '%s' for VPD variable '%s', using default", level, vpdSystembootLogLevel)
   283  		return defaultDebugEnabled
   284  	}
   285  }
   286  
   287  func main() {
   288  	flag.Parse()
   289  
   290  	debugEnabled := getDebugEnabled()
   291  
   292  	log.Print(`
   293                       ____            _                 _                 _
   294                      / ___| _   _ ___| |_ ___ _ __ ___ | |__   ___   ___ | |_
   295                      \___ \| | | / __| __/ _ \ '_ ` + "`" + ` _ \| '_ \ / _ \ / _ \| __|
   296                       ___) | |_| \__ \ ||  __/ | | | | | |_) | (_) | (_) | |_
   297                      |____/ \__, |___/\__\___|_| |_| |_|_.__/ \___/ \___/ \__|
   298                             |___/
   299  `)
   300  	runIPMICommands()
   301  	sleepInterval := time.Duration(*interval) * time.Second
   302  	if *allowInteractive {
   303  		log.Printf("**************************************************************************")
   304  		log.Print("Starting boot sequence, press CTRL-C within 5 seconds to drop into a shell")
   305  		log.Printf("**************************************************************************")
   306  		time.Sleep(5 * time.Second)
   307  	} else {
   308  		signal.Ignore()
   309  	}
   310  
   311  	// Get and show boot entries
   312  	var bootEntries []systembooter.BootEntry
   313  	if bmcBootOverride && ocp.BmcUpdatedBootorder {
   314  		bootEntries = ocp.BootEntries
   315  	} else {
   316  		bootEntries = systembooter.GetBootEntries()
   317  	}
   318  	log.Printf("BOOT ENTRIES:")
   319  	for _, entry := range bootEntries {
   320  		log.Printf("    %v) %+v", entry.Name, string(entry.Config))
   321  	}
   322  	for _, entry := range bootEntries {
   323  		log.Printf("Trying boot entry %s: %s", entry.Name, string(entry.Config))
   324  		if err := entry.Booter.Boot(debugEnabled); err != nil {
   325  			log.Printf("Warning: failed to boot with configuration: %+v", entry)
   326  			addSEL(entry.Booter.TypeName())
   327  		}
   328  		if debugEnabled {
   329  			log.Printf("Sleeping %v before attempting next boot command", sleepInterval)
   330  		}
   331  		time.Sleep(sleepInterval)
   332  	}
   333  
   334  	// if boot entries failed, use the default boot sequence
   335  	log.Printf("Boot entries failed")
   336  
   337  	if !*noDefaultBoot {
   338  		log.Print("Falling back to the default boot sequence")
   339  		for {
   340  			for _, bootcmd := range defaultBootsequence {
   341  				if debugEnabled {
   342  					bootcmd = append(bootcmd, "-d")
   343  				}
   344  				log.Printf("Running boot command: %v", bootcmd)
   345  				cmd := exec.Command(bootcmd[0], bootcmd[1:]...)
   346  				cmd.Stdout = os.Stdout
   347  				cmd.Stderr = os.Stderr
   348  				if err := cmd.Run(); err != nil {
   349  					log.Printf("Error executing %v: %v", cmd, err)
   350  					if !selRecorded {
   351  						addSEL(bootcmd[0])
   352  					}
   353  				}
   354  			}
   355  			selRecorded = true
   356  
   357  			if debugEnabled {
   358  				log.Printf("Sleeping %v before attempting next boot command", sleepInterval)
   359  			}
   360  			time.Sleep(sleepInterval)
   361  		}
   362  	}
   363  }