summaryrefslogtreecommitdiffstats
path: root/modules-available/serversetup-bwlp-ipxe/inc/pxelinux.inc.php
blob: 1d022fef7f9fe38329512c3b1c8767912f59b34a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
<?php


class PxeLinux
{

	/**
	 * Takes a (partial) pxelinux menu and parses it into
	 * a PxeMenu object.
	 * @param string $input The pxelinux menu to parse
	 * @return PxeMenu the parsed menu
	 */
	public static function parsePxeLinux($input)
	{
		/*
		LABEL openslx-debug
			MENU LABEL ^bwLehrpool-Umgebung starten (nosplash, debug)
			KERNEL http://IPADDR/boot/default/kernel
			INITRD http://IPADDR/boot/default/initramfs-stage31
			APPEND slxbase=boot/default
			IPAPPEND 3
		*/
		$menu = new PxeMenu;
		$sectionPropMap = [
			'menu label' => ['string', 'title'],
			'menu default' => ['true', 'isDefault'],
			'menu hide' => ['true', 'isHidden'],
			'menu disabled' => ['true', 'isDisabled'],
			'menu indent' => ['int', 'indent'],
			'kernel' => ['string', 'kernel'],
			'com32' => ['string', 'kernel'],
			'pxe' => ['string', 'kernel'],
			'initrd' => ['string', 'initrd'],
			'append' => ['string', 'append'],
			'ipappend' => ['int', 'ipAppend'],
			'sysappend' => ['int', 'ipAppend'],
			'localboot' => ['int', 'localBoot'],
		];
		$globalPropMap = [
			'timeout' => ['int', 'timeoutMs', 100],
			'totaltimeout' => ['int', 'totalTimeoutMs', 100],
			'menu title' => ['string', 'title'],
			'menu clear' => ['true', 'menuClear'],
			'menu immediate' => ['true', 'immediateHotkeys'],
			'ontimeout' => ['string', 'timeoutLabel'],
		];
		$lines = preg_split('/[\r\n]+/', $input);
		$section = null;
		$count = count($lines);
		for ($li = 0; $li < $count; ++$li) {
			$line =& $lines[$li];
			if (!preg_match('/^\s*([^m]\S*|menu\s+\S+)(\s+.*?|)\s*$/i', $line, $out))
				continue;
			$val = trim($out[2]);
			$key = trim($out[1]);
			$key = strtolower($key);
			$key = preg_replace('/\s+/', ' ', $key);
			if ($key === 'label') {
				if ($section !== null) {
					$menu->sections[] = $section;
				}
				$section = new PxeSection($val);
			} elseif ($key === 'menu separator') {
				if ($section !== null) {
					$menu->sections[] = $section;
					$section = null;
				}
				$menu->sections[] = new PxeSection(null);
			} elseif (self::handleKeyword($key, $val, $globalPropMap, $menu)) {
				continue;
			} elseif ($section === null) {
				continue;
			} elseif ($key === 'text' && strtolower($val) === 'help') {
				$text = '';
				while (++$li < $count) {
					$line =& $lines[$li];
					if (strtolower(trim($line)) === 'endtext')
						break;
					$text .= $line . "\n";
				}
				$section->helpText = $text;
			} elseif (self::handleKeyword($key, $val, $sectionPropMap, $section)) {
				continue;
			}
		}
		if ($section !== null) {
			$menu->sections[] = $section;
		}
		foreach ($menu->sections as $section) {
			$section->mangle();
		}
		return $menu;
	}

	/**
	 * Check if keyword is valid and if so, add its interpreted value
	 * to the given object. The map to look up the keyword has to be passed
	 * as well as the object to set the value in. Map and object should
	 * obviously match.
	 * @param string $key keyword of parsed line
	 * @param string $val raw value of currently parsed line (empty if not present)
	 * @param array $map Map in which $key is looked up as key
	 * @param PxeMenu|PxeSection The object to set the parsed and sanitized value in
	 * @return bool true if the value was found in the map (and set in the object), false otherwise
	 */
	private static function handleKeyword($key, $val, $map, $object)
	{
		if (!isset($map[$key]))
			return false;
		$opt = $map[$key];
		// opt[0] is the type the value should be cast to; special case "true" means
		// this is a bool option that will be set as soon as the keyword is present,
		// as it doesn't have any parameters
		if ($opt[0] === 'true') {
			$val = true;
		} else {
			settype($val, $opt[0]);
		}
		// If opt[2] is present it's a multiplier for the value
		if (isset($opt[2])) {
			$val *= $opt[2];
		}
		$object->{$opt[1]} = $val;
		return true;
	}

}

/**
 * Class representing a parsed pxelinux menu. Members
 * will be set to their annotated type if present or
 * be null otherwise, except for present-only boolean
 * options, which will default to false.
 */
class PxeMenu
{

	/**
	 * @var string menu title, shown at the top of the menu
	 */
	public $title;
	/**
	 * @var int initial timeout after which $timeoutLabel would be executed
	 */
	public $timeoutMs;
	/**
	 * @var int if the user canceled the timeout by pressing a key, this timeout would still eventually
	 *          trigger and launch the $timeoutLabel section
	 */
	public $totalTimeoutMs;
	/**
	 * @var string label of section which will execute if the timeout expires
	 */
	public $timeoutLabel;
	/**
	 * @var bool hide menu and just show background after triggering an entry
	 */
	public $menuClear = false;
	/**
	 * @var bool boot the associated entry directly if its corresponding hotkey is pressed instead of just highlighting
	 */
	public $immediateHotkeys = false;
	/**
	 * @var PxeSection[] list of sections the menu contains
	 */
	public $sections = [];

	public function hash($fuzzy)
	{
		$ctx = hash_init('md5');
		if (!$fuzzy) {
			hash_update($ctx, $this->title);
			hash_update($ctx, $this->timeoutLabel);
		}
		hash_update($ctx, $this->timeoutMs);
		foreach ($this->sections as $section) {
			if ($fuzzy) {
				hash_update($ctx, mb_strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $section->title)));
			} else {
				hash_update($ctx, $section->label);
				hash_update($ctx, $section->title);
				hash_update($ctx, $section->indent);
				hash_update($ctx, $section->helpText);
				hash_update($ctx, $section->isDefault);
				hash_update($ctx, $section->hotkey);
			}
			hash_update($ctx, $section->kernel);
			hash_update($ctx, $section->append);
			hash_update($ctx, $section->ipAppend);
			hash_update($ctx, $section->passwd);
			hash_update($ctx, $section->isHidden);
			hash_update($ctx, $section->isDisabled);
			hash_update($ctx, $section->localBoot);
			foreach ($section->initrd as $initrd) {
				hash_update($ctx, $initrd);
			}
		}
		return hash_final($ctx, false);
	}

}

/**
 * Class representing a parsed pxelinux menu entry. Members
 * will be set to their annotated type if present or
 * be null otherwise, except for present-only boolean
 * options, which will default to false.
 */
class PxeSection
{

	/**
	 * @var string label used internally in PXEMENU definition to address this entry
	 */
	public $label;
	/**
	 * @var string MENU LABEL of PXEMENU - title of entry displayed to the user
	 */
	public $title;
	/**
	 * @var int Number of spaces to prefix the title with
	 */
	public $indent;
	/**
	 * @var string help text to display when the entry is highlighted
	 */
	public $helpText;
	/**
	 * @var string Kernel to load
	 */
	public $kernel;
	/**
	 * @var string|string[] initrd to load for the kernel.
	 *   If mangle() has been called this will be an array,
	 *   otherwise it's a comma separated list.
	 */
	public $initrd;
	/**
	 * @var string command line options to pass to the kernel
	 */
	public $append;
	/**
	 * @var int IPAPPEND from PXEMENU. Bitmask of valid options 1 and 2.
	 */
	public $ipAppend;
	/**
	 * @var string Password protecting the entry. This is most likely in crypted form.
	 */
	public $passwd;
	/**
	 * @var bool whether this section is marked as default (booted after timeout)
	 */
	public $isDefault = false;
	/**
	 * @var bool Menu entry is not visible (can only be triggered by timeout)
	 */
	public $isHidden = false;
	/**
	 * @var bool Disable this entry, making it unselectable
	 */
	public $isDisabled = false;
	/**
	 * @var int Value of the LOCALBOOT field
	 */
	public $localBoot;
	/**
	 * @var string hotkey to trigger item. Only valid after calling mangle()
	 */
	public $hotkey;

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

	public function mangle()
	{
		if (($i = strpos($this->title, '^')) !== false) {
			$this->hotkey = strtoupper($this->title{$i+1});
			$this->title = substr($this->title, 0, $i) . substr($this->title, $i + 1);
		}
		if (strpos($this->append, 'initrd=') !== false) {
			$parts = preg_split('/\s+/', $this->append);
			$this->append = '';
			for ($i = 0; $i < count($parts); ++$i) {
				if (preg_match('/^initrd=(.*)$/', $parts[$i], $out)) {
					if (!empty($this->initrd)) {
						$this->initrd .= ',';
					}
					$this->initrd .= $out[1];
				} else {
					$this->append .= ' ' . $parts[$i];
				}
			}
			$this->append = trim($this->append);
		}
		if (is_string($this->initrd)) {
			$this->initrd = explode(',', $this->initrd);
		} elseif (!is_array($this->initrd)) {
			$this->initrd = [];
		}
	}

}