<?php
abstract class BootEntry
{
/** Supports both via same entry (stored in PCBIOS entry) */
const AGNOSTIC = 'agnostic';
/** Only valid for legacy BIOS boot */
const BIOS = 'PCBIOS';
/** Only valid for EFI boot */
const EFI = 'EFI';
/** Supports both via distinct entry */
const BOTH = 'PCBIOS-EFI';
public abstract function supportsMode($mode);
/**
* @param ScriptBuilderBase $builder
* @return string
*/
public abstract function toScript($builder);
public abstract function toArray();
public abstract function addFormFields(&$array);
/*
*
*/
/**
* Return a BootEntry instance from the serialized data.
*
* @param string $module module this entry belongs to, or special values .script/.exec
* @param string $jsonString serialized entry data
* @return BootEntry|null instance representing boot entry, null on error
*/
public static function fromJson($module, $data)
{
if ($module{0} !== '.') {
// Hook from other module
$hook = Hook::loadSingle($module, 'ipxe-bootentry');
if ($hook === false) {
error_log('Module ' . $module . ' doesnt have an ipxe-bootentry hook');
return null;
}
$ret = $hook->run();
if (!($ret instanceof BootEntryHook))
return null;
return $ret->getBootEntry($data);
}
if (is_string($data)) {
$data = json_decode($data, true);
}
if ($module === '.script') {
return new CustomBootEntry($data);
}
if ($module === '.exec') {
return new StandardBootEntry($data);
}
if ($module === '.special') {
return new SpecialBootEntry($data);
}
return null;
}
public static function forMenu($menuId)
{
return new MenuBootEntry($menuId);
}
public static function newStandardBootEntry($initData, $efi = false, $arch = false)
{
$ret = new StandardBootEntry($initData, $efi, $arch);
$list = [];
if ($ret->arch() !== self::EFI) {
$list[] = self::BIOS;
}
if ($ret->arch() === self::EFI || $ret->arch() === self::BOTH) {
$list[] = self::EFI;
}
$data = $ret->toArray();
foreach ($list as $mode) {
if (empty($data[$mode]['executable'])) {
error_log('Incomplete stdbot: ' . print_r($initData, true));
return null;
}
}
return $ret;
}
public static function newCustomBootEntry($initData)
{
if (!is_array($initData) || empty($initData))
return null;
return new CustomBootEntry($initData);
}
/**
* Return a BootEntry instance from database with the given id.
*
* @param string $id
* @return BootEntry|null|false false == unknown id, null = unknown entry type, BootEntry instance on success
*/
public static function fromDatabaseId($id)
{
$row = Database::queryFirst("SELECT module, data FROM serversetup_bootentry
WHERE entryid = :id LIMIT 1", ['id' => $id]);
if ($row === false)
return false;
return self::fromJson($row['module'], $row['data']);
}
/**
* Get all existing BootEntries from database, skipping those of
* unknown type. Returned array is assoc, key is entryid
*
* @return BootEntry[] all existing BootEntries
*/
public static function getAll()
{
$res = Database::simpleQuery("SELECT entryid, data FROM serversetup_bootentry");
$ret = [];
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
$tmp = self::fromJson($row['module'], $row['data']);
if ($tmp === null)
continue;
$ret[$row['entryid']] = $tmp;
}
return $ret;
}
}
class StandardBootEntry extends BootEntry
{
/**
* @var ExecData PCBIOS boot data
*/
protected $pcbios;
/**
* @var ExecData same for EFI
*/
protected $efi;
/**
* @var string BootEntry Constants above
*/
protected $arch;
const KEYS = ['executable', 'initRd', 'commandLine', 'replace', 'imageFree', 'autoUnload', 'resetConsole', 'dhcpOptions'];
public function __construct($data, $efi = false, $arch = false)
{
$this->pcbios = new ExecData();
$this->efi = new ExecData();
if ($data instanceof PxeSection) {
// Import from PXELINUX menu
$this->fromPxeMenu($data);
} elseif ($data instanceof ExecData && is_string($arch)) {
if (!($efi instanceof ExecData)) {
$efi = new ExecData();
}
$this->pcbios = $data;
$this->efi = $efi;
$this->arch = $arch;
} elseif (is_array($data)) {
// Serialized data
if (!isset($data['arch'])) {
error_log('Serialized data to StandardBootEntry doesnt contain arch: ' . json_encode($data));
} else {
$this->arch = $data['arch'];
}
if (isset($data[BootEntry::BIOS]) || isset($data[BootEntry::EFI])) {
// Current format
$this->fromCurrentFormat($data);
} else {
// Convert legacy DB format
$this->fromLegacyFormat($data);
}
} elseif ($arch == BootEntry::EFI && $efi instanceof ExecData) {
$this->efi = $efi;
$this->arch = $arch;
} else {
error_log('Invalid StandardBootEntry constructor call');
}
if (!in_array($this->arch, [BootEntry::BIOS, BootEntry::EFI, BootEntry::BOTH, BootEntry::AGNOSTIC])) {
$this->arch = BootEntry::AGNOSTIC;
}
}
private function fromLegacyFormat($data)
{
$ok = false;
foreach (self::KEYS as $key) {
if (isset($data[$key][BootEntry::BIOS])) {
$this->pcbios->{$key} = $data[$key][BootEntry::BIOS];
$ok = true;
}
if (isset($data[$key][BootEntry::EFI])) {
$this->efi->{$key} = $data[$key][BootEntry::EFI];
$ok = true;
}
}
if (!$ok) {
// Very old entry
foreach (self::KEYS as $key) {
if (isset($data[$key])) {
$this->pcbios->{$key} = $data[$key];
}
}
}
}
private function fromCurrentFormat($data)
{
foreach (self::KEYS as $key) {
if (isset($data[BootEntry::BIOS][$key])) {
$this->pcbios->{$key} = $data[BootEntry::BIOS][$key];
}
if (isset($data[BootEntry::EFI][$key])) {
$this->efi->{$key} = $data[BootEntry::EFI][$key];
}
}
}
/**
* @param PxeSection $data
*/
private function fromPxeMenu($data)
{
$bios = $this->pcbios;
$bios->executable = $data->kernel;
$bios->initRd = $data->initrd;
$bios->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' ';
$bios->resetConsole = true;
$bios->replace = true;
$bios->autoUnload = true;
if (strpos($bios->commandLine, ' quiet ') !== false) {
$bios->commandLine .= ' loglevel=5 rd.systemd.show_status=auto';
}
if ($data->ipAppend & 1) {
$bios->commandLine .= ' ${ipappend1}';
}
if ($data->ipAppend & 2) {
$bios->commandLine .= ' ${ipappend2}';
}
if ($data->ipAppend & 4) {
$bios->commandLine .= ' SYSUUID=${uuid}';
}
$bios->commandLine = trim(preg_replace('/\s+/', ' ', $bios->commandLine));
}
public function arch()
{
return $this->arch;
}
public function supportsMode($mode)
{
if ($mode === $this->arch || $this->arch === BootEntry::AGNOSTIC)
return true;
if ($mode === BootEntry::BIOS || $mode === BootEntry::EFI) {
return $this->arch === BootEntry::BOTH;
}
error_log('Unknown iPXE platform: ' . $mode);
return false;
}
public function toScript($builder)
{
if ($this->arch === BootEntry::AGNOSTIC) // Same as below, could construct fall-through but this is more clear
return $builder->execDataToScript($this->pcbios, null, null);
return $builder->execDataToScript(null,
$this->supportsMode(BootEntry::BIOS) ? $this->pcbios : null,
$this->supportsMode(BootEntry::EFI) ? $this->efi : null);
}
public function addFormFields(&$array)
{
$array[$this->arch . '_selected'] = 'selected';
$array['entries'][] = $this->pcbios->toFormFields(BootEntry::BIOS);
$array['entries'][] = $this->efi->toFormFields(BootEntry::EFI);
$array['exec_checked'] = 'checked';
}
public function toArray()
{
return [
BootEntry::BIOS => $this->pcbios->toArray(),
BootEntry::EFI => $this->efi->toArray(),
'arch' => $this->arch,
];
}
}
class CustomBootEntry extends BootEntry
{
/**
* @var string iPXE
*/
protected $ipxe;
protected $bash;
protected $grub;
public function __construct($data)
{
if (is_array($data)) {
$this->ipxe = $data['script'] ?? ''; // LEGACY
foreach (['bash', 'grub'] as $key) {
$this->{$key} = $data[$key] ?? '';
}
}
}
public function supportsMode($mode)
{
return true;
}
public function toScript($builder)
{
if ($builder instanceof ScriptBuilderIpxe)
return $this->ipxe;
if ($builder instanceof ScriptBuilderBash)
return $this->bash;
return '';
}
public function addFormFields(&$array)
{
$array['entry'] = [
'script' => $this->ipxe,
];
$array['script_checked'] = 'checked';
}
public function toArray()
{
return ['script' => $this->ipxe];
}
}
class MenuBootEntry extends BootEntry
{
protected $menuId;
public function __construct($menuId)
{
$this->menuId = $menuId;
}
public function supportsMode($mode)
{
return true;
}
public function toScript($builder)
{
$menu = IPxeMenu::get($this->menuId);
return $builder->menuToScript($menu);
}
public function toArray()
{
return [];
}
public function addFormFields(&$array)
{
}
}
class SpecialBootEntry extends BootEntry
{
private $type;
public function __construct($type)
{
$this->type = $type['type'] ?? $type;
}
public function supportsMode($mode)
{
return true;
}
public function toScript($builder)
{
return $builder->getSpecial($this->type);
}
public function toArray()
{
return [];
}
public function addFormFields(&$array) { }
}