summaryrefslogtreecommitdiffstats
path: root/modules-available/serversetup-bwlp-ipxe/inc
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/serversetup-bwlp-ipxe/inc')
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php254
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php91
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php162
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php2
-rw-r--r--modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php2
5 files changed, 414 insertions, 97 deletions
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
index 130bb52b..e97d1389 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
@@ -3,17 +3,6 @@
abstract class BootEntry
{
- public function __construct($data = false)
- {
- if (is_array($data)) {
- foreach ($data as $key => $value) {
- if (property_exists($this, $key)) {
- $this->{$key} = $value;
- }
- }
- }
- }
-
public abstract function supportsMode($mode);
public abstract function toScript($failLabel, $mode);
@@ -29,18 +18,31 @@ abstract class BootEntry
/**
* 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($data)
+ 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 (isset($data['script'])) {
+ if ($module === '.script') {
return new CustomBootEntry($data);
}
- if (isset($data['executable'])) {
+ if ($module === '.exec') {
return new StandardBootEntry($data);
}
return null;
@@ -51,9 +53,9 @@ abstract class BootEntry
return new MenuBootEntry($menuId);
}
- public static function newStandardBootEntry($initData)
+ public static function newStandardBootEntry($initData, $efi = false, $arch = false)
{
- $ret = new StandardBootEntry($initData);
+ $ret = new StandardBootEntry($initData, $efi, $arch);
$list = [];
if ($ret->arch() !== StandardBootEntry::EFI) {
$list[] = StandardBootEntry::BIOS;
@@ -61,8 +63,9 @@ abstract class BootEntry
if ($ret->arch() === StandardBootEntry::EFI || $ret->arch() === StandardBootEntry::BOTH) {
$list[] = StandardBootEntry::EFI;
}
+ $data = $ret->toArray();
foreach ($list as $mode) {
- if (empty($ret->toArray()['executable'][$mode])) {
+ if (empty($data[$mode]['executable'])) {
error_log('Incomplete stdbot: ' . print_r($initData, true));
return null;
}
@@ -85,11 +88,11 @@ abstract class BootEntry
*/
public static function fromDatabaseId($id)
{
- $row = Database::queryFirst("SELECT data FROM serversetup_bootentry
+ $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['data']);
+ return self::fromJson($row['module'], $row['data']);
}
/**
@@ -103,7 +106,7 @@ abstract class BootEntry
$res = Database::simpleQuery("SELECT entryid, data FROM serversetup_bootentry");
$ret = [];
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $tmp = self::fromJson($row['data']);
+ $tmp = self::fromJson($row['module'], $row['data']);
if ($tmp === null)
continue;
$ret[$row['entryid']] = $tmp;
@@ -115,12 +118,15 @@ abstract class BootEntry
class StandardBootEntry extends BootEntry
{
- protected $executable;
- protected $initRd;
- protected $commandLine;
- protected $replace;
- protected $autoUnload;
- protected $resetConsole;
+ /**
+ * @var ExecData PCBIOS boot data
+ */
+ protected $pcbios;
+ /**
+ * @var ExecData same for EFI
+ */
+ protected $efi;
+
protected $arch; // Constants below
const BIOS = 'PCBIOS'; // Only valid for legacy BIOS boot
@@ -128,56 +134,104 @@ class StandardBootEntry extends BootEntry
const BOTH = 'PCBIOS-EFI'; // Supports both via distinct entry
const AGNOSTIC = 'agnostic'; // Supports both via same entry (PCBIOS entry)
- public function __construct($data = false)
+ 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) {
- // Gets arrayfied below
- $this->executable = $data->kernel;
- $this->initRd = [self::BIOS => $data->initrd];
- $this->commandLine = ' ' . str_replace('vga=current', '', $data->append) . ' ';
- $this->resetConsole = true;
- $this->replace = true;
- $this->autoUnload = true;
- if (strpos($this->commandLine, ' quiet ') !== false) {
- $this->commandLine .= ' loglevel=5 rd.systemd.show_status=auto';
+ // Import from PXELINUX menu
+ $this->fromPxeMenu($data);
+ } elseif ($data instanceof ExecData && is_string($arch)) {
+ if (!($efi instanceof ExecData)) {
+ $efi = new ExecData();
}
- if ($data->ipAppend & 1) {
- $this->commandLine .= ' ${ipappend1}';
+ $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 ($data->ipAppend & 2) {
- $this->commandLine .= ' ${ipappend2}';
+ if (isset($data[self::BIOS]) || isset($data[self::EFI])) {
+ // Current format
+ $this->fromCurrentFormat($data);
+ } else {
+ // Convert legacy DB format
+ $this->fromLegacyFormat($data);
}
- if ($data->ipAppend & 4) {
- $this->commandLine .= ' SYSUUID=${uuid}';
- }
- $this->commandLine = trim(preg_replace('/\s+/', ' ', $this->commandLine));
} else {
- if (isset($data['initRd']) && is_array($data['initRd'])) {
- foreach ($data['initRd'] as &$initrd) {
- if (is_string($initrd)) {
- $initrd = preg_split('/\s*,\s*/', $initrd);
- }
- }
- unset($initrd);
+ error_log('Invalid StandardBootEntry constructor call');
+ }
+ if (!in_array($this->arch, [self::BIOS, self::EFI, self::BOTH, self::AGNOSTIC])) {
+ $this->arch = self::AGNOSTIC;
+ }
+ }
+
+ private function fromLegacyFormat($data)
+ {
+ $ok = false;
+ foreach (self::KEYS as $key) {
+ if (isset($data[$key][self::BIOS])) {
+ $this->pcbios->{$key} = $data[$key][self::BIOS];
+ $ok = true;
+ }
+ if (isset($data[$key][self::EFI])) {
+ $this->efi->{$key} = $data[$key][self::EFI];
+ $ok = true;
}
- parent::__construct($data);
}
- // Convert legacy DB format
- foreach (['executable', 'initRd', 'commandLine', 'replace', 'autoUnload', 'resetConsole'] as $key) {
- if (!is_array($this->{$key})) {
- $this->{$key} = [ self::BIOS => $this->{$key}, self::EFI => '' ];
+ if (!$ok) {
+ // Very old entry
+ foreach (self::KEYS as $key) {
+ if (isset($data[$key])) {
+ $this->pcbios->{$key} = $data[$key];
+ }
}
}
- foreach ($this->initRd as &$initrd) {
- if (!is_array($initrd)) {
- $initrd = [$initrd];
+ }
+
+ private function fromCurrentFormat($data)
+ {
+ foreach (self::KEYS as $key) {
+ if (isset($data[self::BIOS][$key])) {
+ $this->pcbios->{$key} = $data[self::BIOS][$key];
+ }
+ if (isset($data[self::EFI][$key])) {
+ $this->efi->{$key} = $data[self::EFI][$key];
}
- $initrd = array_filter($initrd, function($x) { return strlen(trim($x)) !== 0; });
}
- unset($initrd);
- if ($this->arch === null) {
- $this->arch = self::AGNOSTIC;
+ }
+
+ /**
+ * @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()
@@ -201,19 +255,37 @@ class StandardBootEntry extends BootEntry
if (!$this->supportsMode($mode)) {
return "prompt Entry doesn't have an executable for mode $mode\n";
}
- if ($this->arch === self::AGNOSTIC) {
- $mode = self::BIOS;
+ if ($this->arch === self::AGNOSTIC || $mode == self::BIOS) {
+ $entry = $this->pcbios;
+ } else {
+ $entry = $this->efi;
}
$script = '';
- if ($this->resetConsole[$mode]) {
+ if ($entry->resetConsole) {
$script .= "console ||\n";
}
- // TODO: Checkbox
- $script .= "imgfree ||\n";
+ if ($entry->imageFree) {
+ $script .= "imgfree ||\n";
+ }
+ foreach ($entry->dhcpOptions as $opt) {
+ if (empty($opt['value'])) {
+ $val = '${}';
+ } else {
+ if (empty($opt['hex'])) {
+ $val = bin2hex($opt['value']);
+ } else {
+ $val = $opt['value'];
+ }
+ preg_match_all('/[0-9a-f]{2}/', $val, $out);
+ $val = implode(':', $out[0]);
+ }
+ $script .= 'set net${idx}/' . $opt['opt'] . ':hex ' . $val
+ . ' || prompt Cannot override DHCP server option ' . $opt['opt'] . ". Press any key to continue anyways.\n";
+ }
$initrds = [];
- if (!empty($this->initRd[$mode])) {
- foreach (array_values($this->initRd[$mode]) as $i => $initrd) {
+ if (!empty($entry->initRd)) {
+ foreach (array_values($entry->initRd) as $i => $initrd) {
if (empty($initrd))
continue;
$script .= "initrd --name initrd$i $initrd || goto $failLabel\n";
@@ -221,23 +293,23 @@ class StandardBootEntry extends BootEntry
}
}
$script .= "boot ";
- if ($this->autoUnload[$mode]) {
+ if ($entry->autoUnload) {
$script .= "-a ";
}
- if ($this->replace[$mode]) {
+ if ($entry->replace) {
$script .= "-r ";
}
- $script .= $this->executable[$mode];
+ $script .= $entry->executable;
if (empty($initrds)) {
$rdBase = '';
} else {
$rdBase = " initrd=" . implode(',', $initrds);
}
- if (!empty($this->commandLine[$mode])) {
- $script .= "$rdBase {$this->commandLine[$mode]}";
+ if (!empty($entry->commandLine)) {
+ $script .= "$rdBase {$entry->commandLine}";
}
$script .= " || goto $failLabel\n";
- if ($this->resetConsole[$mode]) {
+ if ($entry->resetConsole) {
$script .= "goto start ||\n";
}
return $script;
@@ -246,30 +318,16 @@ class StandardBootEntry extends BootEntry
public function addFormFields(&$array)
{
$array[$this->arch . '_selected'] = 'selected';
- foreach ([self::BIOS, self::EFI] as $mode) {
- $array['entries'][] = [
- 'is' . $mode => true,
- 'mode' => $mode,
- 'executable' => $this->executable[$mode],
- 'initRd' => implode(',', $this->initRd[$mode]),
- 'commandLine' => $this->commandLine[$mode],
- 'replace_checked' => $this->replace[$mode] ? 'checked' : '',
- 'autoUnload_checked' => $this->autoUnload[$mode] ? 'checked' : '',
- 'resetConsole_checked' => $this->resetConsole[$mode] ? 'checked' : '',
- ];
- }
+ $array['entries'][] = $this->pcbios->toFormFields(self::BIOS);
+ $array['entries'][] = $this->efi->toFormFields(self::EFI);
$array['exec_checked'] = 'checked';
}
public function toArray()
{
return [
- 'executable' => $this->executable,
- 'initRd' => $this->initRd,
- 'commandLine' => $this->commandLine,
- 'replace' => $this->replace,
- 'autoUnload' => $this->autoUnload,
- 'resetConsole' => $this->resetConsole,
+ self::BIOS => $this->pcbios->toArray(),
+ self::EFI => $this->efi->toArray(),
'arch' => $this->arch,
];
}
@@ -279,6 +337,13 @@ class CustomBootEntry extends BootEntry
{
protected $script;
+ public function __construct($data)
+ {
+ if (is_array($data) && isset($data['script'])) {
+ $this->script = $data['script'];
+ }
+ }
+
public function supportsMode($mode)
{
return true;
@@ -309,7 +374,6 @@ class MenuBootEntry extends BootEntry
public function __construct($menuId)
{
- parent::__construct(false);
$this->menuId = $menuId;
}
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php
new file mode 100644
index 00000000..2e2e5009
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/bootentryhook.inc.php
@@ -0,0 +1,91 @@
+<?php
+
+abstract class BootEntryHook
+{
+
+ /**
+ * @var string -- set by ipxe, not module implementing hook
+ */
+ public $moduleId;
+ /**
+ * @var string -- set by ipxe, not module implementing hook
+ */
+ public $checked;
+
+ private $selectedId;
+
+ public abstract function name();
+
+ /**
+ * @return HookEntryGroup[]
+ */
+ protected abstract function groupsInternal();
+
+ /**
+ * @param $id
+ * @return BootEntry|null the actual boot entry instance for given entry, null if invalid id
+ */
+ public abstract function getBootEntry($id);
+
+ /**
+ * @return HookEntryGroup[]
+ */
+ public final function groups()
+ {
+ $groups = $this->groupsInternal();
+ foreach ($groups as $group) {
+ foreach ($group->entries as $entry) {
+ if ($entry->id === $this->selectedId) {
+ $entry->selected = 'selected';
+ }
+ }
+ }
+ return $groups;
+ }
+
+ public function setSelected($id)
+ {
+ $this->selectedId = $id;
+ }
+
+}
+
+class HookEntryGroup
+{
+ /**
+ * @var string
+ */
+ public $groupName;
+ /**
+ * @var HookEntry[]
+ */
+ public $entries;
+
+ public function __construct($groupName, $entries)
+ {
+ $this->groupName = $groupName;
+ $this->entries = $entries;
+ }
+}
+
+class HookEntry
+{
+ /**
+ * @var string
+ */
+ public $id;
+ /**
+ * @var string
+ */
+ public $name;
+ /**
+ * @var string internal - to be set by ipxe module
+ */
+ public $selected;
+
+ public function __construct($id, $name)
+ {
+ $this->id = $id;
+ $this->name = $name;
+ }
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php
new file mode 100644
index 00000000..b82ce2e7
--- /dev/null
+++ b/modules-available/serversetup-bwlp-ipxe/inc/execdata.inc.php
@@ -0,0 +1,162 @@
+<?php
+
+class ExecData
+{
+
+ /**
+ * @var string The binary to launch
+ */
+ public $executable = '';
+
+ /**
+ * @var string[] List of additional images to load (initrds)
+ */
+ public $initRd = [];
+
+ /**
+ * @var string Command line to pass to executable
+ */
+ public $commandLine = '';
+
+ /**
+ * @var bool Call imgfree before loading and executing this entry
+ */
+ public $imageFree = false;
+
+ /**
+ * @var bool Whether to completely replace the currently running iPXE stack
+ */
+ public $replace = false;
+
+ /**
+ * @var bool Whether to automatically unload the binary after execution
+ */
+ public $autoUnload = false;
+
+ /**
+ * @var bool Whether to reset the console before execution
+ */
+ public $resetConsole = false;
+
+ /**
+ * @var array DHCP options to override Maps OPTIONNUM -> Value
+ */
+ public $dhcpOptions = [];
+
+ /**
+ * Supported Options
+ */
+ const DHCP_OPTIONS = [
+ 17 => [
+ 'name' => 'Root Path',
+ 'type' => 'string',
+ ],
+ 43 => [
+ 'name' => 'Vendor Specific',
+ 'type' => 'string',
+ ],
+ 66 => [
+ 'name' => 'Next Server',
+ 'type' => 'string',
+ ],
+ 67 => [
+ 'name' => 'Boot File',
+ 'type' => 'string',
+ ],
+ 209 => [
+ 'name' => 'Configuration File',
+ 'type' => 'string',
+ ],
+ 210 => [
+ 'name' => 'Path Prefix',
+ 'type' => 'string',
+ ],
+ ];
+
+ private function sanitize()
+ {
+ settype($this->executable, 'string');
+ settype($this->initRd, 'array');
+ foreach ($this->initRd as &$entry) {
+ settype($entry, 'string');
+ }
+ settype($this->commandLine, 'string');
+ settype($this->imageFree, 'bool');
+ settype($this->replace, 'bool');
+ settype($this->autoUnload, 'bool');
+ settype($this->resetConsole, 'bool');
+ settype($this->dhcpOptions, 'array');
+ foreach (array_keys($this->dhcpOptions) as $key) {
+ $val =& $this->dhcpOptions[$key];
+ if (!empty($val['override'])) {
+ unset($val['override']);
+ $val['opt'] = $key;
+ if (isset($val['hex']) && isset($val['value'])) {
+ $val['value'] = preg_replace('/[^0-9a-f]/i', '', $val['value']);
+ $val['value'] = substr($val['value'], 0, floor(strlen($val['value']) / 2) * 2);
+ $val['value'] = strtolower($val['value']);
+ }
+ }
+ if (!isset($val['opt']) || !is_numeric($val['opt']) || $val['opt'] <= 0 || $val['opt'] >= 255) {
+ unset($this->dhcpOptions[$key]);
+ continue;
+ }
+ if (!array_key_exists($val['opt'], self::DHCP_OPTIONS))
+ continue; // Not known...
+ settype($val['value'], self::DHCP_OPTIONS[$val['opt']]['type']);
+ }
+ $this->dhcpOptions = array_values($this->dhcpOptions);
+ }
+
+ public function toArray()
+ {
+ $this->sanitize();
+ return [
+ 'executable' => $this->executable,
+ 'initRd' => $this->initRd,
+ 'commandLine' => $this->commandLine,
+ 'imageFree' => $this->imageFree,
+ 'replace' => $this->replace,
+ 'autoUnload' => $this->autoUnload,
+ 'resetConsole' => $this->resetConsole,
+ 'dhcpOptions' => $this->dhcpOptions,
+ ];
+ }
+
+ public function toFormFields($arch)
+ {
+ $this->sanitize();
+ $opts = [];
+ foreach (self::DHCP_OPTIONS as $opt => $val) {
+ $opts[$opt] = [
+ 'opt' => $opt,
+ 'name' => $val['name'],
+ ];
+ }
+ foreach ($this->dhcpOptions as $val) {
+ if (!isset($opts[$val['opt']])) {
+ $opts[$val['opt']] = [];
+ }
+ $opts[$val['opt']] += [
+ 'opt' => $val['opt'],
+ 'value' => $val['value'],
+ 'override_checked' => 'checked',
+ 'hex_checked' => empty($val['hex']) ? '' : 'checked',
+ ];
+ }
+ ksort($opts);
+ return [
+ 'is' . $arch => true,
+ 'mode' => $arch,
+ 'executable' => $this->executable,
+ 'initRd' => implode(',', $this->initRd),
+ 'commandLine' => $this->commandLine,
+ 'imageFree_checked' => $this->imageFree ? 'checked' : '',
+ 'replace_checked' => $this->replace ? 'checked' : '',
+ 'autoUnload_checked' => $this->autoUnload ? 'checked' : '',
+ 'resetConsole_checked' => $this->resetConsole ? 'checked' : '',
+ 'opts' => array_values($opts),
+ ];
+ }
+
+} \ No newline at end of file
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
index 0c20e839..f87d15c2 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/ipxemenu.inc.php
@@ -26,7 +26,7 @@ class IPxeMenu
$this->title = $menu['title'];
$this->defaultEntryId = $menu['defaultentryid'];
$res = Database::simpleQuery("SELECT e.menuentryid, e.entryid, e.refmenuid, e.hotkey, e.title, e.hidden, e.sortval, e.md5pass,
- b.data AS bootentry
+ b.module, b.data AS bootentry
FROM serversetup_menuentry e
LEFT JOIN serversetup_bootentry b USING (entryid)
WHERE e.menuid = :menuid
diff --git a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
index eff7f24e..a65e9f98 100644
--- a/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
+++ b/modules-available/serversetup-bwlp-ipxe/inc/menuentry.inc.php
@@ -48,7 +48,7 @@ class MenuEntry
}
$this->hotkey = self::getKeyCode($row['hotkey']);
if (!empty($row['bootentry'])) {
- $this->bootEntry = BootEntry::fromJson($row['bootentry']);
+ $this->bootEntry = BootEntry::fromJson($row['module'], $row['bootentry']);
} elseif ($row['refmenuid'] !== null) {
$this->bootEntry = BootEntry::forMenu($row['refmenuid']);
}