summaryrefslogblamecommitdiffstats
path: root/modules-available/serversetup-bwlp-ipxe/inc/bootentry.inc.php
blob: 5812c0cd1e835bfaf3440483f056613b510c56cb (plain) (tree)
1
2
3
4
5




                        








                                                                    









                                                                                              
                                                                  
 
                                                                              
 
                                                  
 
                                                                    
 




                                            






                                                                
                                                                                              
                                                    
                                                                             
           
                                                                                 
         
                                         

                                                                            
                                             







                                                                                                       


                                                 
                                            

                                                          
                                          

                                                            


                                                           


                            
                                                                  



                                                  
                                                                                                                                        
         
                                                                                  
                           

                                                 
                 

                                                                                
                 
                                        
                                          
                                                                
                                                                                            
                                            
                         

                            

         
                                                                              
         
                                                             






                                                                       
                                                                                      
           
                                                                     
         
                                                                                           

                                                                     
                                    
                                                                    

         





                                                                        
                                              
         
                                                                                                        
                          
                                        
                                                                            






                                                     



                                         







                                         
           
                                                 

                        
 

                                                                                                                                  
                                                                                                       
         
                                                 

                                               
                                                  




                                                                          
                         








                                                                                                                             
                         
                                                                                            




                                                                
                         


                                                                                
                        

                                                                                

                                                                                                                      






                                                

                                                                                     

                                           

                                                                                 
                                           
                         
                 





                                                                            

                         




                                                 

                                                                                     
                         

                                                                                 

                         

         
                                                            












                                                                                               
                 






                                                                                          

         
                                       



                                   
                                                        
         
                                                                                 
                                    

                                                                            


                                                             

         
                                                                    
         




                                                                                                                              

         
                                                          
         
                                                               

                                                                                   


                                                   



                                                                 

                        

                                                                    
                                              





                                       


                           
                             



                        
 

                                          
                                              




                                                                      


                 
                                                        



                            
                                                                    
         
                                                                                                     

                                                          

                                                          

                                                          
                          

         
                                                          

                                   
                                                



                                                     



                                        
         
                                                 

         


                                     
                       

                          
                                                
         
                                                       


                                        
                                                        



                            
                                                                    
         
                                                           
                                                     

         
                                        



                          
                                                          



         







                                                     
                                                              

         
                                                        



                            
                                                                    



                                                         
                                        



                          
                                                              
 
 
<?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';

	/**
	 * @var string Internal ID - set to your liking, e.g. the MiniLinux version identifier
	 */
	protected $internalId;

	public function __construct(string $internalId)
	{
		$this->internalId = $internalId;
	}

	public abstract function supportsMode(string $mode): bool;

	public abstract function toScript(ScriptBuilderBase $builder): string;

	public abstract function toArray(): array;

	public abstract function addFormFields(array &$array): void;

	public function internalId(): string
	{
		return $this->internalId;
	}

	/*
	 *
	 */

	/**
	 * Return a BootEntry instance from the serialized data.
	 *
	 * @param string $module module this entry belongs to, or special values .script/.exec
	 * @param string $data serialized entry data
	 * @return ?BootEntry instance representing boot entry, null on error
	 */
	public static function fromJson(string $module, string $data): ?BootEntry
	{
		if ($module[0] !== '.') {
			// Hook from other module
			$hook = Hook::loadSingle($module, 'ipxe-bootentry');
			if ($hook === null) {
				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);
		}
		$data = json_decode($data, true);
		if (!is_array($data))
			return null;
		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(int $menuId): MenuBootEntry
	{
		return new MenuBootEntry($menuId);
	}

	public static function newStandardBootEntry($initData, $efi = false, $arch = false, string $internalId = ''): ?StandardBootEntry
	{
		$ret = new StandardBootEntry($initData, $efi, $arch, $internalId);
		$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): ?CustomBootEntry
	{
		if (!is_array($initData) || empty($initData))
			return null;
		return new CustomBootEntry($initData);
	}

	/**
	 * Return a BootEntry instance from database with the given id.
	 *
	 * @return ?BootEntry null = unknown entry type, BootEntry instance on success
	 */
	public static function fromDatabaseId(string $id): ?BootEntry
	{
		$row = Database::queryFirst("SELECT module, data FROM serversetup_bootentry
			WHERE entryid = :id LIMIT 1", ['id' => $id]);
		if ($row === false)
			return null;
		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(): array
	{
		$res = Database::simpleQuery("SELECT entryid, module, data FROM serversetup_bootentry");
		$ret = [];
		foreach ($res as $row) {
			$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, ?string $arch = null, string $internalId = '')
	{
		parent::__construct($internalId);
		$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];
			}
		}
	}

	private function fromPxeMenu(PxeSection $data): void
	{
		$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(): ?string
	{
		return $this->arch;
	}

	public function supportsMode(string $mode): bool
	{
		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(ScriptBuilderBase $builder): string
	{
		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): void
	{
		$array[$this->arch . '_selected'] = 'selected';
		$array['entries'][] = $this->pcbios->toFormFields(BootEntry::BIOS);
		$array['entries'][] = $this->efi->toFormFields(BootEntry::EFI);
		$array['exec_checked'] = 'checked';
	}

	/**
	 * @return array{PCBIOS: array, EFI: array, arch: string}
	 */
	public function toArray(): array
	{
		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)
	{
		parent::__construct('custom');
		if (is_array($data)) {
			$this->ipxe = $data['script'] ?? ''; // LEGACY
			foreach (['bash', 'grub'] as $key) {
				$this->{$key} = $data[$key] ?? '';
			}
		}
	}

	public function supportsMode(string $mode): bool
	{
		return true;
	}

	public function toScript(ScriptBuilderBase $builder): string
	{
		// TODO: A (very) simple translator for oneliners like "poweroff || goto fail" maybe?
		if ($builder instanceof ScriptBuilderIpxe)
			return $this->ipxe;
		if ($builder instanceof ScriptBuilderBash)
			return $this->bash;
		if ($builder instanceof ScriptBuilderGrub)
			return $this->grub;
		return '';
	}

	public function addFormFields(array &$array): void
	{
		$array['entry'] = [
			'script' => $this->ipxe,
		];
		$array['script_checked'] = 'checked';
	}

	/**
	 * @return array{script: string}
	 */
	public function toArray(): array
	{
		return ['script' => $this->ipxe];
	}
}

class MenuBootEntry extends BootEntry
{
	/** @var int */
	protected $menuId;

	public function __construct(int $menuId)
	{
		parent::__construct('menu-' . $menuId);
		$this->menuId = $menuId;
	}

	public function supportsMode(string $mode): bool
	{
		return true;
	}

	public function toScript(ScriptBuilderBase $builder): string
	{
		$menu = IPxeMenu::get($this->menuId, true);
		return $builder->menuToScript($menu);
	}

	public function toArray(): array
	{
		return [];
	}

	public function addFormFields(array &$array): void
	{
	}
}

class SpecialBootEntry extends BootEntry
{

	private $type;

	public function __construct($type)
	{
		$this->type = $type['type'] ?? $type;
		parent::__construct('special-' . $this->type);
	}

	public function supportsMode(string $mode): bool
	{
		return true;
	}

	public function toScript(ScriptBuilderBase $builder): string
	{
		return $builder->getSpecial($this->type);
	}

	public function toArray(): array
	{
		return [];
	}

	public function addFormFields(array &$array): void { }

}