r3616 Config: restore lost is_userdefined column
[racktables] / inc / functions.php
CommitLineData
b325120a 1<?php
e673ee24
DO
2/*
3*
4* This file is a library of computational functions for RackTables.
5*
6*/
7
8$loclist[0] = 'front';
9$loclist[1] = 'interior';
10$loclist[2] = 'rear';
11$loclist['front'] = 0;
12$loclist['interior'] = 1;
13$loclist['rear'] = 2;
14$template[0] = array (TRUE, TRUE, TRUE);
15$template[1] = array (TRUE, TRUE, FALSE);
16$template[2] = array (FALSE, TRUE, TRUE);
17$template[3] = array (TRUE, FALSE, FALSE);
18$template[4] = array (FALSE, TRUE, FALSE);
19$template[5] = array (FALSE, FALSE, TRUE);
20$templateWidth[0] = 3;
21$templateWidth[1] = 2;
22$templateWidth[2] = 2;
23$templateWidth[3] = 1;
24$templateWidth[4] = 1;
25$templateWidth[5] = 1;
26
482c7f35
DO
27// Entity type by page number mapping is 1:1 atm, but may change later.
28$etype_by_pageno = array
29(
30 'ipv4net' => 'ipv4net',
31 'ipv4rspool' => 'ipv4rspool',
32 'ipv4vs' => 'ipv4vs',
33 'object' => 'object',
34 'rack' => 'rack',
35 'user' => 'user',
36 'file' => 'file',
37);
38
ef4b16fb
DO
39// Rack thumbnail image width summands: "front", "interior" and "rear" elements w/o surrounding border.
40$rtwidth = array
41(
42 0 => 9,
43 1 => 21,
44 2 => 9
45);
46
c8824ff4
DO
47$netmaskbylen = array
48(
49 32 => '255.255.255.255',
50 31 => '255.255.255.254',
51 30 => '255.255.255.252',
52 29 => '255.255.255.248',
53 28 => '255.255.255.240',
54 27 => '255.255.255.224',
55 26 => '255.255.255.192',
56 25 => '255.255.255.128',
57 24 => '255.255.255.0',
58 23 => '255.255.254.0',
59 22 => '255.255.252.0',
60 21 => '255.255.248.0',
61 20 => '255.255.240.0',
62 19 => '255.255.224.0',
63 18 => '255.255.192.0',
64 17 => '255.255.128.0',
65 16 => '255.255.0.0',
66 15 => '255.254.0.0',
67 14 => '255.252.0.0',
68 13 => '255.248.0.0',
69 12 => '255.240.0.0',
70 11 => '255.224.0.0',
71 10 => '255.192.0.0',
72 9 => '255.128.0.0',
73 8 => '255.0.0.0',
74 7 => '254.0.0.0',
75 6 => '252.0.0.0',
76 5 => '248.0.0.0',
77 4 => '240.0.0.0',
78 3 => '224.0.0.0',
79 2 => '192.0.0.0',
80 1 => '128.0.0.0'
81);
82
83$wildcardbylen = array
84(
85 32 => '0.0.0.0',
86 31 => '0.0.0.1',
87 30 => '0.0.0.3',
88 29 => '0.0.0.7',
89 28 => '0.0.0.15',
90 27 => '0.0.0.31',
91 26 => '0.0.0.63',
92 25 => '0.0.0.127',
93 24 => '0.0.0.255',
94 23 => '0.0.1.255',
95 22 => '0.0.3.255',
96 21 => '0.0.7.255',
97 20 => '0.0.15.255',
98 19 => '0.0.31.255',
99 18 => '0.0.63.255',
100 17 => '0.0.127.255',
101 16 => '0.0.255.25',
102 15 => '0.1.255.255',
103 14 => '0.3.255.255',
104 13 => '0.7.255.255',
105 12 => '0.15.255.255',
106 11 => '0.31.255.255',
107 10 => '0.63.255.255',
108 9 => '0.127.255.255',
109 8 => '0.255.255.255',
110 7 => '1.255.255.255',
111 6 => '3.255.255.255',
112 5 => '7.255.255.255',
113 4 => '15.255.255.255',
114 3 => '31.255.255.255',
115 2 => '63.255.255.255',
116 1 => '127.255.255.255'
117);
118
f77323f1
DO
119// This function assures that specified argument was passed
120// and is a number greater than zero.
0cc24e9a 121function assertUIntArg ($argname, $allow_zero = FALSE)
f77323f1
DO
122{
123 if (!isset ($_REQUEST[$argname]))
5847d944 124 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is missing');
f77323f1 125 if (!is_numeric ($_REQUEST[$argname]))
5847d944 126 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a number');
f77323f1 127 if ($_REQUEST[$argname] < 0)
5847d944 128 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is less than zero');
ca92dc40 129 if (!$allow_zero and $_REQUEST[$argname] == 0)
5847d944 130 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is zero');
f77323f1
DO
131}
132
133// This function assures that specified argument was passed
134// and is a non-empty string.
0cc24e9a 135function assertStringArg ($argname, $ok_if_empty = FALSE)
f77323f1
DO
136{
137 if (!isset ($_REQUEST[$argname]))
5847d944 138 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is missing');
f77323f1 139 if (!is_string ($_REQUEST[$argname]))
5847d944 140 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
f77323f1 141 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
5847d944 142 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
f77323f1
DO
143}
144
0cc24e9a 145function assertBoolArg ($argname, $ok_if_empty = FALSE)
f77323f1
DO
146{
147 if (!isset ($_REQUEST[$argname]))
5847d944 148 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is missing');
f77323f1 149 if (!is_string ($_REQUEST[$argname]) or $_REQUEST[$argname] != 'on')
5847d944 150 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string');
f77323f1 151 if (!$ok_if_empty and !strlen ($_REQUEST[$argname]))
5847d944 152 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
f77323f1
DO
153}
154
0cc24e9a 155function assertIPv4Arg ($argname, $ok_if_empty = FALSE)
f77323f1 156{
0cc24e9a 157 assertStringArg ($argname, $ok_if_empty);
f77323f1 158 if (strlen ($_REQUEST[$argname]) and long2ip (ip2long ($_REQUEST[$argname])) !== $_REQUEST[$argname])
5847d944 159 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a valid ipv4 address');
f77323f1
DO
160}
161
e0d188ef
DO
162function assertPCREArg ($argname)
163{
164 assertStringArg ($argname, TRUE); // empty pattern is Ok
664ec705 165 if (FALSE === preg_match ($_REQUEST[$argname], 'test'))
e0d188ef
DO
166 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'PCRE validation failed');
167}
168
e673ee24
DO
169// Objects of some types should be explicitly shown as
170// anonymous (labelless). This function is a single place where the
171// decision about displayed name is made.
cfa8f3cf 172function setDisplayedName (&$cell)
e673ee24 173{
cfa8f3cf
DO
174 if ($cell['name'] != '')
175 $cell['dname'] = $cell['name'];
e673ee24 176 else
212c9d8a 177 {
cfa8f3cf
DO
178 $cell['atags'][] = array ('tag' => '$nameless');
179 if (considerConfiguredConstraint ($cell, 'NAMEWARN_LISTSRC'))
7fa7047a 180 $cell['dname'] = 'ANONYMOUS ' . decodeObjectType ($cell['objtype_id'], 'o');
212c9d8a 181 else
7fa7047a 182 $cell['dname'] = '[' . decodeObjectType ($cell['objtype_id'], 'o') . ']';
212c9d8a 183 }
e673ee24
DO
184}
185
186// This function finds height of solid rectangle of atoms, which are all
187// assigned to the same object. Rectangle base is defined by specified
188// template.
189function rectHeight ($rackData, $startRow, $template_idx)
190{
191 $height = 0;
192 // The first met object_id is used to match all the folowing IDs.
193 $object_id = 0;
194 global $template;
195 do
196 {
197 for ($locidx = 0; $locidx < 3; $locidx++)
198 {
199 // At least one value in template is TRUE, but the following block
200 // can meet 'skipped' atoms. Let's ensure we have something after processing
201 // the first row.
202 if ($template[$template_idx][$locidx])
203 {
204 if (isset ($rackData[$startRow - $height][$locidx]['skipped']))
205 break 2;
93e02204
DO
206 if (isset ($rackData[$startRow - $height][$locidx]['rowspan']))
207 break 2;
208 if (isset ($rackData[$startRow - $height][$locidx]['colspan']))
209 break 2;
e673ee24
DO
210 if ($rackData[$startRow - $height][$locidx]['state'] != 'T')
211 break 2;
212 if ($object_id == 0)
213 $object_id = $rackData[$startRow - $height][$locidx]['object_id'];
214 if ($object_id != $rackData[$startRow - $height][$locidx]['object_id'])
215 break 2;
216 }
217 }
218 // If the first row can't offer anything, bail out.
219 if ($height == 0 and $object_id == 0)
220 break;
221 $height++;
222 }
223 while ($startRow - $height > 0);
93e02204
DO
224# echo "for startRow==${startRow} and template==(" . ($template[$template_idx][0] ? 'T' : 'F');
225# echo ', ' . ($template[$template_idx][1] ? 'T' : 'F') . ', ' . ($template[$template_idx][2] ? 'T' : 'F');
226# echo ") height==${height}<br>\n";
e673ee24
DO
227 return $height;
228}
229
230// This function marks atoms to be avoided by rectHeight() and assigns rowspan/colspan
231// attributes.
232function markSpan (&$rackData, $startRow, $maxheight, $template_idx)
233{
234 global $template, $templateWidth;
235 $colspan = 0;
236 for ($height = 0; $height < $maxheight; $height++)
237 {
238 for ($locidx = 0; $locidx < 3; $locidx++)
239 {
240 if ($template[$template_idx][$locidx])
241 {
242 // Add colspan/rowspan to the first row met and mark the following ones to skip.
93e02204
DO
243 // Explicitly show even single-cell spanned atoms, because rectHeight()
244 // is expeciting this data for correct calculation.
e673ee24
DO
245 if ($colspan != 0)
246 $rackData[$startRow - $height][$locidx]['skipped'] = TRUE;
247 else
248 {
249 $colspan = $templateWidth[$template_idx];
93e02204 250 if ($colspan >= 1)
e673ee24 251 $rackData[$startRow - $height][$locidx]['colspan'] = $colspan;
93e02204 252 if ($maxheight >= 1)
e673ee24
DO
253 $rackData[$startRow - $height][$locidx]['rowspan'] = $maxheight;
254 }
255 }
256 }
257 }
258 return;
259}
260
93e02204
DO
261// This function sets rowspan/solspan/skipped atom attributes for renderRack()
262// What we actually have to do is to find _all_ possible rectangles for each unit
263// and then select the widest of those with the maximal square.
e673ee24
DO
264function markAllSpans (&$rackData = NULL)
265{
266 if ($rackData == NULL)
267 {
0cc24e9a 268 showWarning ('Invalid rackData', __FUNCTION__);
e673ee24
DO
269 return;
270 }
271 for ($i = $rackData['height']; $i > 0; $i--)
93e02204
DO
272 while (markBestSpan ($rackData, $i));
273}
274
275// Calculate height of 6 possible span templates (array is presorted by width
276// descending) and mark the best (if any).
277function markBestSpan (&$rackData, $i)
278{
279 global $template, $templateWidth;
280 for ($j = 0; $j < 6; $j++)
e673ee24 281 {
93e02204
DO
282 $height[$j] = rectHeight ($rackData, $i, $j);
283 $square[$j] = $height[$j] * $templateWidth[$j];
284 }
285 // find the widest rectangle of those with maximal height
286 $maxsquare = max ($square);
287 if (!$maxsquare)
288 return FALSE;
289 $best_template_index = 0;
290 for ($j = 0; $j < 6; $j++)
291 if ($square[$j] == $maxsquare)
e673ee24 292 {
93e02204
DO
293 $best_template_index = $j;
294 $bestheight = $height[$j];
295 break;
e673ee24 296 }
93e02204
DO
297 // distribute span marks
298 markSpan ($rackData, $i, $bestheight, $best_template_index);
299 return TRUE;
e673ee24
DO
300}
301
e673ee24
DO
302// We can mount 'F' atoms and unmount our own 'T' atoms.
303function applyObjectMountMask (&$rackData, $object_id)
304{
305 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
306 for ($locidx = 0; $locidx < 3; $locidx++)
307 switch ($rackData[$unit_no][$locidx]['state'])
308 {
309 case 'F':
310 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
311 break;
312 case 'T':
313 $rackData[$unit_no][$locidx]['enabled'] = ($rackData[$unit_no][$locidx]['object_id'] == $object_id);
314 break;
315 default:
316 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
317 }
318}
319
320// Design change means transition between 'F' and 'A' and back.
321function applyRackDesignMask (&$rackData)
322{
323 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
324 for ($locidx = 0; $locidx < 3; $locidx++)
325 switch ($rackData[$unit_no][$locidx]['state'])
326 {
327 case 'F':
328 case 'A':
329 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
330 break;
331 default:
332 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
333 }
334}
335
336// The same for 'F' and 'U'.
337function applyRackProblemMask (&$rackData)
338{
339 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
340 for ($locidx = 0; $locidx < 3; $locidx++)
341 switch ($rackData[$unit_no][$locidx]['state'])
342 {
343 case 'F':
344 case 'U':
345 $rackData[$unit_no][$locidx]['enabled'] = TRUE;
346 break;
347 default:
348 $rackData[$unit_no][$locidx]['enabled'] = FALSE;
349 }
350}
351
e673ee24
DO
352// This function highlights specified object (and removes previous highlight).
353function highlightObject (&$rackData, $object_id)
354{
355 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
356 for ($locidx = 0; $locidx < 3; $locidx++)
357 if
358 (
359 $rackData[$unit_no][$locidx]['state'] == 'T' and
360 $rackData[$unit_no][$locidx]['object_id'] == $object_id
361 )
362 $rackData[$unit_no][$locidx]['hl'] = 'h';
363 else
364 unset ($rackData[$unit_no][$locidx]['hl']);
365}
366
367// This function marks atoms to selected or not depending on their current state.
368function markupAtomGrid (&$data, $checked_state)
369{
370 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
371 for ($locidx = 0; $locidx < 3; $locidx++)
372 {
373 if (!($data[$unit_no][$locidx]['enabled'] === TRUE))
374 continue;
375 if ($data[$unit_no][$locidx]['state'] == $checked_state)
376 $data[$unit_no][$locidx]['checked'] = ' checked';
377 else
378 $data[$unit_no][$locidx]['checked'] = '';
379 }
380}
381
382// This function is almost a clone of processGridForm(), but doesn't save anything to database
383// Return value is the changed rack data.
384// Here we assume that correct filter has already been applied, so we just
385// set or unset checkbox inputs w/o changing atom state.
386function mergeGridFormToRack (&$rackData)
387{
388 $rack_id = $rackData['id'];
389 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--)
390 for ($locidx = 0; $locidx < 3; $locidx++)
391 {
392 if ($rackData[$unit_no][$locidx]['enabled'] != TRUE)
393 continue;
394 $inputname = "atom_${rack_id}_${unit_no}_${locidx}";
395 if (isset ($_REQUEST[$inputname]) and $_REQUEST[$inputname] == 'on')
396 $rackData[$unit_no][$locidx]['checked'] = ' checked';
397 else
398 $rackData[$unit_no][$locidx]['checked'] = '';
399 }
400}
401
bb0a44e9 402// netmask conversion from length to number
e673ee24
DO
403function binMaskFromDec ($maskL)
404{
bb0a44e9
DO
405 $map_straight = array (
406 0 => 0x00000000,
407 1 => 0x80000000,
408 2 => 0xc0000000,
409 3 => 0xe0000000,
410 4 => 0xf0000000,
411 5 => 0xf8000000,
412 6 => 0xfc000000,
413 7 => 0xfe000000,
414 8 => 0xff000000,
415 9 => 0xff800000,
416 10 => 0xffc00000,
417 11 => 0xffe00000,
418 12 => 0xfff00000,
419 13 => 0xfff80000,
420 14 => 0xfffc0000,
421 15 => 0xfffe0000,
422 16 => 0xffff0000,
423 17 => 0xffff8000,
424 18 => 0xffffc000,
425 19 => 0xffffe000,
426 20 => 0xfffff000,
427 21 => 0xfffff800,
428 22 => 0xfffffc00,
429 23 => 0xfffffe00,
430 24 => 0xffffff00,
431 25 => 0xffffff80,
432 26 => 0xffffffc0,
433 27 => 0xffffffe0,
434 28 => 0xfffffff0,
435 29 => 0xfffffff8,
436 30 => 0xfffffffc,
437 31 => 0xfffffffe,
438 32 => 0xffffffff,
439 );
440 return $map_straight[$maskL];
e673ee24
DO
441}
442
bb0a44e9 443// complementary value
e673ee24
DO
444function binInvMaskFromDec ($maskL)
445{
bb0a44e9
DO
446 $map_compl = array (
447 0 => 0xffffffff,
448 1 => 0x7fffffff,
449 2 => 0x3fffffff,
450 3 => 0x1fffffff,
451 4 => 0x0fffffff,
452 5 => 0x07ffffff,
453 6 => 0x03ffffff,
454 7 => 0x01ffffff,
455 8 => 0x00ffffff,
456 9 => 0x007fffff,
457 10 => 0x003fffff,
458 11 => 0x001fffff,
459 12 => 0x000fffff,
460 13 => 0x0007ffff,
461 14 => 0x0003ffff,
462 15 => 0x0001ffff,
463 16 => 0x0000ffff,
464 17 => 0x00007fff,
465 18 => 0x00003fff,
466 19 => 0x00001fff,
467 20 => 0x00000fff,
468 21 => 0x000007ff,
469 22 => 0x000003ff,
470 23 => 0x000001ff,
471 24 => 0x000000ff,
472 25 => 0x0000007f,
473 26 => 0x0000003f,
474 27 => 0x0000001f,
475 28 => 0x0000000f,
476 29 => 0x00000007,
477 30 => 0x00000003,
478 31 => 0x00000001,
479 32 => 0x00000000,
480 );
481 return $map_compl[$maskL];
e673ee24
DO
482}
483
e673ee24
DO
484// This function looks up 'has_problems' flag for 'T' atoms
485// and modifies 'hl' key. May be, this should be better done
61a1d996 486// in amplifyCell(). We don't honour 'skipped' key, because
e673ee24
DO
487// the function is also used for thumb creation.
488function markupObjectProblems (&$rackData)
489{
490 for ($i = $rackData['height']; $i > 0; $i--)
491 for ($locidx = 0; $locidx < 3; $locidx++)
492 if ($rackData[$i][$locidx]['state'] == 'T')
493 {
6297d584 494 $object = spotEntity ('object', $rackData[$i][$locidx]['object_id']);
e673ee24
DO
495 if ($object['has_problems'] == 'yes')
496 {
497 // Object can be already highlighted.
498 if (isset ($rackData[$i][$locidx]['hl']))
499 $rackData[$i][$locidx]['hl'] = $rackData[$i][$locidx]['hl'] . 'w';
500 else
501 $rackData[$i][$locidx]['hl'] = 'w';
502 }
503 }
504}
505
d516d719
DO
506// Return a uniformly (010203040506 or 0102030405060708) formatted address, if it is present
507// in the provided string, an empty string for an empty string or NULL for error.
e673ee24
DO
508function l2addressForDatabase ($string)
509{
e673ee24 510 $string = strtoupper ($string);
05771508
DO
511 switch (TRUE)
512 {
d516d719 513 case ($string == '' or preg_match (RE_L2_SOLID, $string) or preg_match (RE_L2_WWN_SOLID, $string)):
05771508 514 return $string;
d516d719
DO
515 case (preg_match (RE_L2_IFCFG, $string) or preg_match (RE_L2_WWN_COLON, $string)):
516 // reformat output of SunOS ifconfig
517 $ret = '';
518 foreach (explode (':', $string) as $byte)
519 $ret .= (strlen ($byte) == 1 ? '0' : '') . $byte;
520 return $ret;
05771508 521 case (preg_match (RE_L2_CISCO, $string)):
d516d719
DO
522 return str_replace ('.', '', $string);
523 case (preg_match (RE_L2_IPCFG, $string) or preg_match (RE_L2_WWN_HYPHEN, $string)):
524 return str_replace ('-', '', $string);
05771508
DO
525 default:
526 return NULL;
527 }
e673ee24
DO
528}
529
530function l2addressFromDatabase ($string)
531{
532 switch (strlen ($string))
533 {
534 case 12: // Ethernet
d516d719 535 case 16: // FireWire/Fibre Channel
e673ee24
DO
536 $ret = implode (':', str_split ($string, 2));
537 break;
538 default:
539 $ret = $string;
540 break;
541 }
542 return $ret;
543}
544
545// The following 2 functions return previous and next rack IDs for
546// a given rack ID. The order of racks is the same as in renderRackspace()
547// or renderRow().
548function getPrevIDforRack ($row_id = 0, $rack_id = 0)
549{
550 if ($row_id <= 0 or $rack_id <= 0)
551 {
0cc24e9a 552 showWarning ('Invalid arguments passed', __FUNCTION__);
e673ee24
DO
553 return NULL;
554 }
a8efc03e 555 $rackList = listCells ('rack', $row_id);
e673ee24
DO
556 doubleLink ($rackList);
557 if (isset ($rackList[$rack_id]['prev_key']))
558 return $rackList[$rack_id]['prev_key'];
559 return NULL;
560}
561
562function getNextIDforRack ($row_id = 0, $rack_id = 0)
563{
564 if ($row_id <= 0 or $rack_id <= 0)
565 {
0cc24e9a 566 showWarning ('Invalid arguments passed', __FUNCTION__);
e673ee24
DO
567 return NULL;
568 }
a8efc03e 569 $rackList = listCells ('rack', $row_id);
e673ee24
DO
570 doubleLink ($rackList);
571 if (isset ($rackList[$rack_id]['next_key']))
572 return $rackList[$rack_id]['next_key'];
573 return NULL;
574}
575
576// This function finds previous and next array keys for each array key and
577// modifies its argument accordingly.
578function doubleLink (&$array)
579{
580 $prev_key = NULL;
581 foreach (array_keys ($array) as $key)
582 {
583 if ($prev_key)
584 {
585 $array[$key]['prev_key'] = $prev_key;
586 $array[$prev_key]['next_key'] = $key;
587 }
588 $prev_key = $key;
589 }
590}
591
e673ee24
DO
592function sortTokenize ($a, $b)
593{
594 $aold='';
595 while ($a != $aold)
596 {
597 $aold=$a;
84986395
DO
598 $a = preg_replace('/[^a-zA-Z0-9]/',' ',$a);
599 $a = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$a);
600 $a = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$a);
e673ee24
DO
601 }
602
603 $bold='';
604 while ($b != $bold)
605 {
606 $bold=$b;
84986395
DO
607 $b = preg_replace('/[^a-zA-Z0-9]/',' ',$b);
608 $b = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$b);
609 $b = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$b);
e673ee24
DO
610 }
611
612
613
614 $ar = explode(' ', $a);
615 $br = explode(' ', $b);
616 for ($i=0; $i<count($ar) && $i<count($br); $i++)
617 {
618 $ret = 0;
619 if (is_numeric($ar[$i]) and is_numeric($br[$i]))
620 $ret = ($ar[$i]==$br[$i])?0:($ar[$i]<$br[$i]?-1:1);
621 else
622 $ret = strcasecmp($ar[$i], $br[$i]);
623 if ($ret != 0)
624 return $ret;
625 }
626 if ($i<count($ar))
627 return 1;
628 if ($i<count($br))
629 return -1;
630 return 0;
631}
632
633function sortByName ($a, $b)
634{
bbeb64fd
DO
635 $result = sortTokenize ($a['name'], $b['name']);
636 if ($result != 0)
637 return $result;
638 if ($a['iif_id'] != $b['iif_id'])
639 return $a['iif_id'] - $b['iif_id'];
640 $result = strcmp ($a['label'], $b['label']);
641 if ($result != 0)
642 return $result;
643 $result = strcmp ($a['l2address'], $b['l2address']);
644 if ($result != 0)
645 return $result;
646 return $a['id'] - $b['id'];
e673ee24
DO
647}
648
e673ee24
DO
649function sortObjectAddressesAndNames ($a, $b)
650{
651 $objname_cmp = sortTokenize($a['object_name'], $b['object_name']);
652 if ($objname_cmp == 0)
653 {
79f22c3d
DO
654 $name_a = (isset ($a['port_name'])) ? $a['port_name'] : '';
655 $name_b = (isset ($b['port_name'])) ? $b['port_name'] : '';
656 $objname_cmp = sortTokenize($name_a, $name_b);
e673ee24 657 if ($objname_cmp == 0)
e673ee24 658 sortTokenize($a['ip'], $b['ip']);
e673ee24
DO
659 return $objname_cmp;
660 }
661 return $objname_cmp;
662}
663
c31cd72c 664// This function returns an array of single element of object's FQDN attribute,
f321b50a
DO
665// if FQDN is set. The next choice is object's common name, if it looks like a
666// hostname. Otherwise an array of all 'regular' IP addresses of the
c31cd72c 667// object is returned (which may appear 0 and more elements long).
f321b50a 668function findAllEndpoints ($object_id, $fallback = '')
c31cd72c 669{
7fa7047a
DO
670 foreach (getAttrValues ($object_id) as $record)
671 if ($record['id'] == 3 && strlen ($record['value'])) // FQDN
c31cd72c 672 return array ($record['value']);
c31cd72c 673 $regular = array();
85970da2
DO
674 foreach (getObjectIPv4Allocations ($object_id) as $dottedquad => $alloc)
675 if ($alloc['type'] == 'regular')
676 $regular[] = $dottedquad;
59a83bd8 677 if (!count ($regular) && strlen ($fallback))
f321b50a 678 return array ($fallback);
c31cd72c
DO
679 return $regular;
680}
681
83ba6670
DO
682// Some records in the dictionary may be written as plain text or as Wiki
683// link in the following syntax:
684// 1. word
685// 2. [[word URL]] // FIXME: this isn't working
686// 3. [[word word word | URL]]
687// This function parses the line and returns text suitable for either A
688// (rendering <A HREF>) or O (for <OPTION>).
7fa7047a 689function parseWikiLink ($line, $which)
83ba6670 690{
010231c2 691 if (preg_match ('/^\[\[.+\]\]$/', $line) == 0)
24cbe8af 692 {
7fa7047a
DO
693 // always strip the marker for A-data, but let cookOptgroup()
694 // do this later (otherwise it can't sort groups out)
695 if ($which == 'a')
84986395 696 return preg_replace ('/^.+%GSKIP%/', '', preg_replace ('/^(.+)%GPASS%/', '\\1 ', $line));
24cbe8af
DO
697 else
698 return $line;
699 }
010231c2
DO
700 $line = preg_replace ('/^\[\[(.+)\]\]$/', '$1', $line);
701 $s = explode ('|', $line);
83ba6670 702 $o_value = trim ($s[0]);
83ba6670
DO
703 if ($which == 'o')
704 return $o_value;
84986395 705 $o_value = preg_replace ('/^.+%GSKIP%/', '', preg_replace ('/^(.+)%GPASS%/', '\\1 ', $o_value));
7fa7047a
DO
706 $a_value = trim ($s[1]);
707 return "<a href='${a_value}'>${o_value}</a>";
83ba6670
DO
708}
709
c9edf725
DO
710// FIXME: should this be saved as "P-data"?
711function execGMarker ($line)
712{
84986395 713 return preg_replace ('/^.+%GSKIP%/', '', preg_replace ('/^(.+)%GPASS%/', '\\1 ', $line));
c9edf725
DO
714}
715
177b1e9b
DO
716// rackspace usage for a single rack
717// (T + W + U) / (height * 3 - A)
11df133a 718function getRSUforRack ($data = NULL)
177b1e9b 719{
11df133a 720 if ($data == NULL)
177b1e9b 721 {
0cc24e9a 722 showWarning ('Invalid argument', __FUNCTION__);
177b1e9b
DO
723 return NULL;
724 }
6ffba290 725 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
9e60f7df
DO
726 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
727 for ($locidx = 0; $locidx < 3; $locidx++)
728 $counter[$data[$unit_no][$locidx]['state']]++;
dfa3c075 729 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
177b1e9b
DO
730}
731
11df133a
DO
732// Same for row.
733function getRSUforRackRow ($rowData = NULL)
734{
bb26a59e 735 if ($rowData === NULL)
11df133a 736 {
0cc24e9a 737 showWarning ('Invalid argument', __FUNCTION__);
11df133a
DO
738 return NULL;
739 }
bb26a59e
DO
740 if (!count ($rowData))
741 return 0;
11df133a 742 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0);
f81a2012 743 $total_height = 0;
dfa3c075
DO
744 foreach (array_keys ($rowData) as $rack_id)
745 {
61a1d996
DO
746 $data = spotEntity ('rack', $rack_id);
747 amplifyCell ($data);
dfa3c075 748 $total_height += $data['height'];
11df133a
DO
749 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--)
750 for ($locidx = 0; $locidx < 3; $locidx++)
751 $counter[$data[$unit_no][$locidx]['state']]++;
dfa3c075
DO
752 }
753 return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
11df133a
DO
754}
755
9af110b4
DO
756// Make sure the string is always wrapped with LF characters
757function lf_wrap ($str)
758{
759 $ret = trim ($str, "\r\n");
59a83bd8 760 if (strlen ($ret))
9af110b4
DO
761 $ret .= "\n";
762 return $ret;
763}
764
e6e7d8b3
DO
765// Adopted from Mantis BTS code.
766function string_insert_hrefs ($s)
767{
768 if (getConfigVar ('DETECT_URLS') != 'yes')
769 return $s;
770 # Find any URL in a string and replace it by a clickable link
771 $s = preg_replace( '/(([[:alpha:]][-+.[:alnum:]]*):\/\/(%[[:digit:]A-Fa-f]{2}|[-_.!~*\';\/?%^\\\\:@&={\|}+$#\(\),\[\][:alnum:]])+)/se',
772 "'<a href=\"'.rtrim('\\1','.').'\">\\1</a> [<a href=\"'.rtrim('\\1','.').'\" target=\"_blank\">^</a>]'",
773 $s);
774 $s = preg_replace( '/\b' . email_regex_simple() . '\b/i',
775 '<a href="mailto:\0">\0</a>',
776 $s);
777 return $s;
778}
779
780// Idem.
781function email_regex_simple ()
782{
783 return "(([a-z0-9!#*+\/=?^_{|}~-]+(?:\.[a-z0-9!#*+\/=?^_{|}~-]+)*)" . # recipient
784 "\@((?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?))"; # @domain
785}
786
118e4c38
DO
787// Parse AUTOPORTS_CONFIG and return a list of generated pairs (port_type, port_name)
788// for the requested object_type_id.
789function getAutoPorts ($type_id)
790{
791 $ret = array();
792 $typemap = explode (';', str_replace (' ', '', getConfigVar ('AUTOPORTS_CONFIG')));
793 foreach ($typemap as $equation)
794 {
795 $tmp = explode ('=', $equation);
796 if (count ($tmp) != 2)
797 continue;
798 $objtype_id = $tmp[0];
799 if ($objtype_id != $type_id)
800 continue;
801 $portlist = $tmp[1];
802 foreach (explode ('+', $portlist) as $product)
803 {
804 $tmp = explode ('*', $product);
805 if (count ($tmp) != 3)
806 continue;
807 $nports = $tmp[0];
808 $port_type = $tmp[1];
809 $format = $tmp[2];
810 for ($i = 0; $i < $nports; $i++)
811 $ret[] = array ('type' => $port_type, 'name' => @sprintf ($format, $i));
812 }
813 }
814 return $ret;
815}
816
86256b96
DO
817// Use pre-served trace to traverse the tree, then place given node where it belongs.
818function pokeNode (&$tree, $trace, $key, $value, $threshold = 0)
819{
820 // This function needs the trace to be followed FIFO-way. The fastest
821 // way to do so is to use array_push() for putting values into the
822 // list and array_shift() for getting them out. This exposed up to 11%
823 // performance gain compared to other patterns of array_push/array_unshift/
824 // array_reverse/array_pop/array_shift conjunction.
825 $myid = array_shift ($trace);
826 if (!count ($trace)) // reached the target
9dfd4cc9 827 {
86256b96
DO
828 if (!$threshold or ($threshold and $tree[$myid]['kidc'] + 1 < $threshold))
829 $tree[$myid]['kids'][$key] = $value;
830 // Reset accumulated records once, when the limit is reached, not each time
831 // after that.
832 if (++$tree[$myid]['kidc'] == $threshold)
833 $tree[$myid]['kids'] = array();
834 }
835 else // not yet
836 {
837 $self = __FUNCTION__;
838 $self ($tree[$myid]['kids'], $trace, $key, $value, $threshold);
9dfd4cc9 839 }
9dfd4cc9
DO
840}
841
0ba76ca2
DO
842// Likewise traverse the tree with the trace and return the final node.
843function peekNode ($tree, $trace, $target_id)
844{
845 $self = __FUNCTION__;
846 if (NULL === ($next = array_shift ($trace))) // warm
847 {
848 foreach ($tree as $node)
f1e27fe5 849 if (array_key_exists ('id', $node) and $node['id'] == $target_id) // hot
0ba76ca2
DO
850 return $node;
851 }
852 else // cold
853 {
854 foreach ($tree as $node)
3e576410 855 if (array_key_exists ('id', $node) and $node['id'] == $next) // warmer
0ba76ca2
DO
856 return $self ($node['kids'], $trace, $target_id);
857 }
858 // HCF
859 return NULL;
860}
861
d65353ad
DO
862// Build a tree from the item list and return it. Input and output data is
863// indexed by item id (nested items in output are recursively stored in 'kids'
51b6651a
DO
864// key, which is in turn indexed by id. Functions, which are ready to handle
865// tree collapsion/expansion themselves, may request non-zero threshold value
866// for smaller resulting tree.
3fb336f6 867function treeFromList (&$orig_nodelist, $threshold = 0, $return_main_payload = TRUE)
d65353ad 868{
86256b96 869 $tree = array();
3fb336f6 870 $nodelist = $orig_nodelist;
86256b96
DO
871 // Array equivalent of traceEntity() function.
872 $trace = array();
873 // set kidc and kids only once
874 foreach (array_keys ($nodelist) as $nodeid)
875 {
876 $nodelist[$nodeid]['kidc'] = 0;
877 $nodelist[$nodeid]['kids'] = array();
878 }
879 do
9dfd4cc9 880 {
86256b96
DO
881 $nextpass = FALSE;
882 foreach (array_keys ($nodelist) as $nodeid)
9dfd4cc9 883 {
86256b96
DO
884 // When adding a node to the working tree, book another
885 // iteration, because the new item could make a way for
886 // others onto the tree. Also remove any item added from
887 // the input list, so iteration base shrinks.
888 // First check if we can assign directly.
889 if ($nodelist[$nodeid]['parent_id'] == NULL)
9dfd4cc9 890 {
86256b96
DO
891 $tree[$nodeid] = $nodelist[$nodeid];
892 $trace[$nodeid] = array(); // Trace to root node is empty
893 unset ($nodelist[$nodeid]);
894 $nextpass = TRUE;
9dfd4cc9 895 }
86256b96
DO
896 // Now look if it fits somewhere on already built tree.
897 elseif (isset ($trace[$nodelist[$nodeid]['parent_id']]))
9dfd4cc9 898 {
86256b96
DO
899 // Trace to a node is a trace to its parent plus parent id.
900 $trace[$nodeid] = $trace[$nodelist[$nodeid]['parent_id']];
901 $trace[$nodeid][] = $nodelist[$nodeid]['parent_id'];
902 pokeNode ($tree, $trace[$nodeid], $nodeid, $nodelist[$nodeid], $threshold);
903 // path to any other node is made of all parent nodes plus the added node itself
904 unset ($nodelist[$nodeid]);
905 $nextpass = TRUE;
9dfd4cc9
DO
906 }
907 }
9dfd4cc9 908 }
86256b96 909 while ($nextpass);
3fb336f6 910 if (!$return_main_payload)
86256b96 911 return $nodelist;
3fb336f6
DO
912 // update each input node with its backtrace route
913 foreach ($trace as $nodeid => $route)
914 $orig_nodelist[$nodeid]['trace'] = $route;
915 return $tree;
9dfd4cc9
DO
916}
917
49fb1027 918// Build a tree from the tag list and return everything _except_ the tree.
573214e0
DO
919// IOW, return taginfo items, which have parent_id set and pointing outside
920// of the "normal" tree, which originates from the root.
49fb1027
DO
921function getOrphanedTags ()
922{
923 global $taglist;
86256b96 924 return treeFromList ($taglist, 0, FALSE);
49fb1027
DO
925}
926
6e49bd1f 927function serializeTags ($chain, $baseurl = '')
ba93bd98
DO
928{
929 $comma = '';
930 $ret = '';
6e49bd1f 931 foreach ($chain as $taginfo)
ba93bd98 932 {
e2ac59cf 933 $ret .= $comma .
a8efc03e 934 ($baseurl == '' ? '' : "<a href='${baseurl}cft[]=${taginfo['id']}'>") .
e2ac59cf
DO
935 $taginfo['tag'] .
936 ($baseurl == '' ? '' : '</a>');
ba93bd98
DO
937 $comma = ', ';
938 }
939 return $ret;
940}
941
ba93bd98
DO
942// Return the list of missing implicit tags.
943function getImplicitTags ($oldtags)
944{
3fb336f6
DO
945 global $taglist;
946 $tmp = array();
947 foreach ($oldtags as $taginfo)
948 $tmp = array_merge ($tmp, $taglist[$taginfo['id']]['trace']);
949 // don't call array_unique here, it is in the function we will call now
950 return buildTagChainFromIds ($tmp);
ba93bd98
DO
951}
952
6e49bd1f 953// Minimize the chain: exclude all implicit tags and return the result.
5fd2a004
DO
954// This function makes use of an external cache with a miss/hit ratio
955// about 3/7 (ticket:255).
3fb336f6 956function getExplicitTagsOnly ($chain)
ab379543 957{
5fd2a004 958 global $taglist, $tagRelCache;
ab379543 959 $ret = array();
5fd2a004
DO
960 foreach (array_keys ($chain) as $keyA) // check each A
961 {
962 $tagidA = $chain[$keyA]['id'];
963 // do not include A in result, if A is seen on the trace of any B!=A
964 foreach (array_keys ($chain) as $keyB)
965 {
966 $tagidB = $chain[$keyB]['id'];
967 if ($tagidA == $tagidB)
968 continue;
969 if (!isset ($tagRelCache[$tagidA][$tagidB]))
0745db4d 970 $tagRelCache[$tagidA][$tagidB] = in_array ($tagidA, $taglist[$tagidB]['trace']);
5fd2a004
DO
971 if ($tagRelCache[$tagidA][$tagidB] === TRUE) // A is ancestor of B
972 continue 2; // skip this A
973 }
974 $ret[] = $chain[$keyA];
975 }
74ccacff
DO
976 return $ret;
977}
978
2c21a10c 979// Universal autotags generator, a complementing function for loadEntityTags().
2c21a10c 980// Bypass key isn't strictly typed, but interpreted depending on the realm.
d16af52f 981function generateEntityAutoTags ($cell)
cce6b057 982{
cce6b057 983 $ret = array();
d16af52f 984 switch ($cell['realm'])
2c21a10c
DO
985 {
986 case 'rack':
d16af52f 987 $ret[] = array ('tag' => '$rackid_' . $cell['id']);
2c21a10c
DO
988 $ret[] = array ('tag' => '$any_rack');
989 break;
4c9d35ec 990 case 'object':
d16af52f
DO
991 $ret[] = array ('tag' => '$id_' . $cell['id']);
992 $ret[] = array ('tag' => '$typeid_' . $cell['objtype_id']);
2c21a10c 993 $ret[] = array ('tag' => '$any_object');
588b2b79 994 if (validTagName ('$cn_' . $cell['name'], TRUE))
d16af52f
DO
995 $ret[] = array ('tag' => '$cn_' . $cell['name']);
996 if (!strlen ($cell['rack_id']))
914b439b 997 $ret[] = array ('tag' => '$unmounted');
6d472f26
DO
998 if (!$cell['nports'])
999 $ret[] = array ('tag' => '$portless');
5d6de575
DO
1000 if ($cell['asset_no'] == '')
1001 $ret[] = array ('tag' => '$no_asset_tag');
b48d8d61
DO
1002 if ($cell['runs8021Q'])
1003 $ret[] = array ('tag' => '$runs_8021Q');
2c21a10c 1004 break;
4c9d35ec 1005 case 'ipv4net':
d16af52f
DO
1006 $ret[] = array ('tag' => '$ip4netid_' . $cell['id']);
1007 $ret[] = array ('tag' => '$ip4net-' . str_replace ('.', '-', $cell['ip']) . '-' . $cell['mask']);
b9b915d5
DO
1008 for ($i = 8; $i < 32; $i++)
1009 {
1010 // these conditions hit 1 to 3 times per each i
1011 if ($cell['mask'] >= $i)
1012 $ret[] = array ('tag' => '$masklen_ge_' . $i);
1013 if ($cell['mask'] <= $i)
1014 $ret[] = array ('tag' => '$masklen_le_' . $i);
1015 if ($cell['mask'] == $i)
1016 $ret[] = array ('tag' => '$masklen_eq_' . $i);
1017 }
2c21a10c
DO
1018 $ret[] = array ('tag' => '$any_ip4net');
1019 $ret[] = array ('tag' => '$any_net');
1020 break;
1021 case 'ipv4vs':
d16af52f 1022 $ret[] = array ('tag' => '$ipv4vsid_' . $cell['id']);
2c21a10c
DO
1023 $ret[] = array ('tag' => '$any_ipv4vs');
1024 $ret[] = array ('tag' => '$any_vs');
1025 break;
1026 case 'ipv4rspool':
d16af52f 1027 $ret[] = array ('tag' => '$ipv4rspid_' . $cell['id']);
2c21a10c
DO
1028 $ret[] = array ('tag' => '$any_ipv4rsp');
1029 $ret[] = array ('tag' => '$any_rsp');
1030 break;
1031 case 'user':
b82cce3f
DO
1032 // {$username_XXX} autotag is generated always, but {$userid_XXX}
1033 // appears only for accounts, which exist in local database.
d16af52f
DO
1034 $ret[] = array ('tag' => '$username_' . $cell['user_name']);
1035 if (isset ($cell['user_id']))
1036 $ret[] = array ('tag' => '$userid_' . $cell['user_id']);
2c21a10c
DO
1037 break;
1038 case 'file':
d16af52f 1039 $ret[] = array ('tag' => '$fileid_' . $cell['id']);
2c21a10c
DO
1040 $ret[] = array ('tag' => '$any_file');
1041 break;
d16af52f 1042 default: // HCF!
2c21a10c
DO
1043 break;
1044 }
4c9d35ec
DO
1045 // {$tagless} doesn't apply to users
1046 switch ($cell['realm'])
1047 {
1048 case 'rack':
1049 case 'object':
1050 case 'ipv4net':
1051 case 'ipv4vs':
1052 case 'ipv4rspool':
1053 case 'file':
1054 if (!count ($cell['etags']))
1055 $ret[] = array ('tag' => '$untagged');
1056 break;
1057 default:
1058 break;
1059 }
f9bc186f
DO
1060 return $ret;
1061}
1062
abef7149
DO
1063// Check, if the given tag is present on the chain (will only work
1064// for regular tags with tag ID set.
1065function tagOnChain ($taginfo, $tagchain)
1066{
1067 if (!isset ($taginfo['id']))
1068 return FALSE;
1069 foreach ($tagchain as $test)
1070 if ($test['id'] == $taginfo['id'])
1071 return TRUE;
1072 return FALSE;
1073}
1074
f8821b96
DO
1075function tagNameOnChain ($tagname, $tagchain)
1076{
1077 foreach ($tagchain as $test)
1078 if ($test['tag'] == $tagname)
1079 return TRUE;
1080 return FALSE;
1081}
1082
abef7149
DO
1083// Return TRUE, if two tags chains differ (order of tags doesn't matter).
1084// Assume, that neither of the lists contains duplicates.
1085// FIXME: a faster, than O(x^2) method is possible for this calculation.
1086function tagChainCmp ($chain1, $chain2)
1087{
1088 if (count ($chain1) != count ($chain2))
1089 return TRUE;
1090 foreach ($chain1 as $taginfo1)
1091 if (!tagOnChain ($taginfo1, $chain2))
1092 return TRUE;
1093 return FALSE;
1094}
1095
0df8c52b 1096function redirectIfNecessary ()
da958e52 1097{
750d26d2
DO
1098 global
1099 $trigger,
1100 $pageno,
1101 $tabno;
53bae67b
DO
1102 $pmap = array
1103 (
1104 'accounts' => 'userlist',
1105 'rspools' => 'ipv4rsplist',
1106 'rspool' => 'ipv4rsp',
1107 'vservices' => 'ipv4vslist',
1108 'vservice' => 'ipv4vs',
c78a40ec
DO
1109 'objects' => 'depot',
1110 'objgroup' => 'depot',
53bae67b
DO
1111 );
1112 $tmap = array();
1113 $tmap['objects']['newmulti'] = 'addmore';
1114 $tmap['objects']['newobj'] = 'addmore';
1115 $tmap['object']['switchvlans'] = 'livevlans';
1116 $tmap['object']['slb'] = 'editrspvs';
72d8ced3
DO
1117 $tmap['object']['portfwrd'] = 'nat4';
1118 $tmap['object']['network'] = 'ipv4';
53bae67b
DO
1119 if (isset ($pmap[$pageno]))
1120 redirectUser ($pmap[$pageno], $tabno);
1121 if (isset ($tmap[$pageno][$tabno]))
1122 redirectUser ($pageno, $tmap[$pageno][$tabno]);
750d26d2 1123 // check if we accidentaly got on a dynamic tab that shouldn't be shown for this object
3a7bdcc6
DO
1124 if
1125 (
1126 isset ($trigger[$pageno][$tabno]) and
1127 !strlen (call_user_func ($trigger[$pageno][$tabno]))
1128 )
750d26d2 1129 redirectUser ($pageno, 'default');
0df8c52b
DO
1130}
1131
329ec966
DY
1132function prepareNavigation() {
1133 global
1134 $pageno,
1135 $tabno;
1136
1137 $pageno = (isset ($_REQUEST['page'])) ? $_REQUEST['page'] : 'index';
1138
1139// Special handling of tab number to substitute the "last" index where applicable.
1140// Always show explicitly requested tab, substitute the last used name in case
1141// it is awailable, fall back to the default one.
1142
1143 if (isset ($_REQUEST['tab'])) {
1144 $tabno = $_REQUEST['tab'];
329ec966 1145 } elseif (basename($_SERVER['PHP_SELF']) == 'index.php' and getConfigVar ('SHOW_LAST_TAB') == 'yes' and isset ($_SESSION['RTLT'][$pageno])) {
750d26d2 1146 redirectUser ($pageno, $_SESSION['RTLT'][$pageno]);
329ec966
DY
1147 } else {
1148 $tabno = 'default';
1149 }
1150}
1151
0df8c52b
DO
1152function fixContext ($target = NULL)
1153{
1154 global
1155 $pageno,
1156 $auto_tags,
1157 $expl_tags,
1158 $impl_tags,
1159 $target_given_tags,
1160 $user_given_tags,
1161 $etype_by_pageno,
1162 $page;
53bae67b 1163
0df8c52b
DO
1164 if ($target !== NULL)
1165 {
1166 $target_given_tags = $target['etags'];
1167 // Don't reset autochain, because auth procedures could push stuff there in.
1168 // Another important point is to ignore 'user' realm, so we don't infuse effective
1169 // context with autotags of the displayed account.
1170 if ($target['realm'] != 'user')
1171 $auto_tags = array_merge ($auto_tags, $target['atags']);
1172 }
1173 elseif (array_key_exists ($pageno, $etype_by_pageno))
53da7dca
DO
1174 {
1175 // Each page listed in the map above requires one uint argument.
0a50efb4 1176 $target_realm = $etype_by_pageno[$pageno];
0cc24e9a 1177 assertUIntArg ($page[$pageno]['bypass']);
0a50efb4 1178 $target_id = $_REQUEST[$page[$pageno]['bypass']];
b135a49d 1179 $target = spotEntity ($target_realm, $target_id);
53da7dca 1180 $target_given_tags = $target['etags'];
0df8c52b 1181 if ($target['realm'] != 'user')
53da7dca
DO
1182 $auto_tags = array_merge ($auto_tags, $target['atags']);
1183 }
4c9b513a
DO
1184 // Explicit and implicit chains should be normally empty at this point, so
1185 // overwrite the contents anyway.
1186 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1187 $impl_tags = getImplicitTags ($expl_tags);
da958e52
DO
1188}
1189
abef7149
DO
1190// Take a list of user-supplied tag IDs to build a list of valid taginfo
1191// records indexed by tag IDs (tag chain).
6e49bd1f 1192function buildTagChainFromIds ($tagidlist)
ab379543 1193{
20c901a7 1194 global $taglist;
ab379543 1195 $ret = array();
abef7149 1196 foreach (array_unique ($tagidlist) as $tag_id)
ab379543
DO
1197 if (isset ($taglist[$tag_id]))
1198 $ret[] = $taglist[$tag_id];
1199 return $ret;
1200}
1201
7cc02fc1
DO
1202// Process a given tag tree and return only meaningful branches. The resulting
1203// (sub)tree will have refcnt leaves on every last branch.
a26a6ccc 1204function getObjectiveTagTree ($tree, $realm, $preselect)
7cc02fc1 1205{
51b6651a 1206 $self = __FUNCTION__;
7cc02fc1
DO
1207 $ret = array();
1208 foreach ($tree as $taginfo)
1209 {
a26a6ccc
DO
1210 $subsearch = $self ($taginfo['kids'], $realm, $preselect);
1211 // If the current node addresses something, add it to the result
1212 // regardless of how many sub-nodes it features.
1213 if
7cc02fc1 1214 (
a26a6ccc
DO
1215 isset ($taginfo['refcnt'][$realm]) or
1216 count ($subsearch) > 1 or
3fb336f6 1217 in_array ($taginfo['id'], $preselect)
a26a6ccc
DO
1218 )
1219 $ret[] = array
1220 (
1221 'id' => $taginfo['id'],
1222 'tag' => $taginfo['tag'],
1223 'parent_id' => $taginfo['parent_id'],
1224 'refcnt' => $taginfo['refcnt'],
1225 'kids' => $subsearch
1226 );
1227 else
1228 $ret = array_merge ($ret, $subsearch);
7cc02fc1
DO
1229 }
1230 return $ret;
1231}
1232
7ddb2c05
DO
1233// Get taginfo record by tag name, return NULL, if record doesn't exist.
1234function getTagByName ($target_name)
1235{
1236 global $taglist;
1237 foreach ($taglist as $taginfo)
1238 if ($taginfo['tag'] == $target_name)
1239 return $taginfo;
1240 return NULL;
1241}
1242
fc73c734
DO
1243// Merge two chains, filtering dupes out. Return the resulting superset.
1244function mergeTagChains ($chainA, $chainB)
1245{
1246 // $ret = $chainA;
1247 // Reindex by tag id in any case.
1248 $ret = array();
1249 foreach ($chainA as $tag)
1250 $ret[$tag['id']] = $tag;
1251 foreach ($chainB as $tag)
1252 if (!isset ($ret[$tag['id']]))
1253 $ret[$tag['id']] = $tag;
1254 return $ret;
1255}
1256
31c941ec
DO
1257function getCellFilter ()
1258{
7da7450c 1259 global $sic;
eca0114c 1260 global $pageno;
01b9a31a 1261 $staticFilter = getConfigVar ('STATIC_FILTER');
3d670bba 1262 if (isset ($_REQUEST['tagfilter']) and is_array ($_REQUEST['tagfilter']))
23cdc7e9
DO
1263 {
1264 $_REQUEST['cft'] = $_REQUEST['tagfilter'];
1265 unset ($_REQUEST['tagfilter']);
1266 }
01b9a31a 1267 if (isset ($_SESSION[$pageno]['tagfilter']) and is_array ($_SESSION[$pageno]['tagfilter']) and !(isset($_REQUEST['cft'])) and $staticFilter == 'yes')
b8970c7e 1268 {
eca0114c 1269 $_REQUEST['cft'] = $_SESSION[$pageno]['tagfilter'];
b8970c7e 1270 }
01b9a31a 1271 if (isset ($_SESSION[$pageno]['cfe']) and !(isset($sic['cfe'])) and $staticFilter == 'yes')
b8970c7e 1272 {
eca0114c 1273 $sic['cfe'] = $_SESSION[$pageno]['cfe'];
b8970c7e 1274 }
01b9a31a 1275 if (isset ($_SESSION[$pageno]['andor']) and !(isset($_REQUEST['andor'])) and $staticFilter == 'yes')
b8970c7e 1276 {
eca0114c 1277 $_REQUEST['andor'] = $_SESSION[$pageno]['andor'];
b8970c7e
RF
1278 }
1279
eca0114c 1280
3d670bba
DO
1281 $ret = array
1282 (
23cdc7e9
DO
1283 'tagidlist' => array(),
1284 'tnamelist' => array(),
1285 'pnamelist' => array(),
1286 'andor' => '',
3d670bba 1287 'text' => '',
23cdc7e9
DO
1288 'extratext' => '',
1289 'expression' => array(),
a8efc03e 1290 'urlextra' => '', // Just put text here and let makeHref call urlencode().
3d670bba 1291 );
23cdc7e9 1292 switch (TRUE)
31c941ec 1293 {
23cdc7e9
DO
1294 case (!isset ($_REQUEST['andor'])):
1295 $andor2 = getConfigVar ('FILTER_DEFAULT_ANDOR');
3d670bba 1296 break;
23cdc7e9
DO
1297 case ($_REQUEST['andor'] == 'and'):
1298 case ($_REQUEST['andor'] == 'or'):
eca0114c 1299 $_SESSION[$pageno]['andor'] = $_REQUEST['andor'];
23cdc7e9 1300 $ret['andor'] = $andor2 = $_REQUEST['andor'];
a8efc03e 1301 $ret['urlextra'] .= '&andor=' . $ret['andor'];
3d670bba
DO
1302 break;
1303 default:
0cc24e9a 1304 showWarning ('Invalid and/or switch value in submitted form', __FUNCTION__);
3d670bba 1305 return NULL;
31c941ec 1306 }
23cdc7e9
DO
1307 $andor1 = '';
1308 // Both tags and predicates, which don't exist, should be
1309 // handled somehow. Discard them silently for now.
1310 if (isset ($_REQUEST['cft']) and is_array ($_REQUEST['cft']))
31c941ec 1311 {
eca0114c 1312 $_SESSION[$pageno]['tagfilter'] = $_REQUEST['cft'];
23cdc7e9
DO
1313 global $taglist;
1314 foreach ($_REQUEST['cft'] as $req_id)
1315 if (isset ($taglist[$req_id]))
1316 {
1317 $ret['tagidlist'][] = $req_id;
1318 $ret['tnamelist'][] = $taglist[$req_id]['tag'];
1319 $ret['text'] .= $andor1 . '{' . $taglist[$req_id]['tag'] . '}';
1320 $andor1 = ' ' . $andor2 . ' ';
a8efc03e 1321 $ret['urlextra'] .= '&cft[]=' . $req_id;
23cdc7e9
DO
1322 }
1323 }
1324 if (isset ($_REQUEST['cfp']) and is_array ($_REQUEST['cfp']))
1325 {
1326 global $pTable;
1327 foreach ($_REQUEST['cfp'] as $req_name)
1328 if (isset ($pTable[$req_name]))
1329 {
1330 $ret['pnamelist'][] = $req_name;
1331 $ret['text'] .= $andor1 . '[' . $req_name . ']';
1332 $andor1 = ' ' . $andor2 . ' ';
a8efc03e 1333 $ret['urlextra'] .= '&cfp[]=' . $req_name;
23cdc7e9
DO
1334 }
1335 }
7da7450c
DO
1336 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function.
1337 if (isset ($sic['cfe']))
a8efc03e 1338 {
eca0114c 1339 $_SESSION[$pageno]['cfe'] = $sic['cfe'];
7da7450c
DO
1340 // Only consider extra text, when it is a correct RackCode expression.
1341 $parse = spotPayload ($sic['cfe'], 'SYNT_EXPR');
1342 if ($parse['result'] == 'ACK')
1343 {
1344 $ret['extratext'] = trim ($sic['cfe']);
1345 $ret['urlextra'] .= '&cfe=' . $ret['extratext'];
1346 }
a8efc03e 1347 }
23cdc7e9
DO
1348 $finaltext = array();
1349 if (strlen ($ret['text']))
1350 $finaltext[] = '(' . $ret['text'] . ')';
1351 if (strlen ($ret['extratext']))
1352 $finaltext[] = '(' . $ret['extratext'] . ')';
1353 $finaltext = implode (' ' . $andor2 . ' ', $finaltext);
1354 if (strlen ($finaltext))
1355 {
1356 $parse = spotPayload ($finaltext, 'SYNT_EXPR');
1357 $ret['expression'] = $parse['result'] == 'ACK' ? $parse['load'] : NULL;
8c699525
DO
1358 // It's not quite fair enough to put the blame of the whole text onto
1359 // non-empty "extra" portion of it, but it's the only user-generated portion
1360 // of it, thus the most probable cause of parse error.
1361 if (strlen ($ret['extratext']))
1362 $ret['extraclass'] = $parse['result'] == 'ACK' ? 'validation-success' : 'validation-error';
31c941ec 1363 }
3d670bba 1364 return $ret;
31c941ec
DO
1365}
1366
db55cf54
DO
1367// Return an empty message log.
1368function emptyLog ()
1369{
1370 return array
1371 (
1372 'v' => 2,
1373 'm' => array()
1374 );
1375}
1376
2987fc1f
DO
1377// Return a message log consisting of only one message.
1378function oneLiner ($code, $args = array())
1379{
db55cf54 1380 $ret = emptyLog();
2987fc1f
DO
1381 $ret['m'][] = count ($args) ? array ('c' => $code, 'a' => $args) : array ('c' => $code);
1382 return $ret;
46f92ff7
DO
1383}
1384
e53b1246
DO
1385// Merge message payload from two message logs given and return the result.
1386function mergeLogs ($log1, $log2)
1387{
1388 $ret = emptyLog();
1389 $ret['m'] = array_merge ($log1['m'], $log2['m']);
1390 return $ret;
1391}
1392
9f54e6e9 1393function validTagName ($s, $allow_autotag = FALSE)
2eeeca80 1394{
84986395 1395 if (1 == preg_match (TAGNAME_REGEXP, $s))
9f54e6e9 1396 return TRUE;
84986395 1397 if ($allow_autotag and 1 == preg_match (AUTOTAGNAME_REGEXP, $s))
9f54e6e9
DO
1398 return TRUE;
1399 return FALSE;
2eeeca80
DO
1400}
1401
53bae67b
DO
1402function redirectUser ($p, $t)
1403{
790a60e8
DO
1404 global $page;
1405 $l = "index.php?page=${p}&tab=${t}";
53bae67b
DO
1406 if (isset ($page[$p]['bypass']) and isset ($_REQUEST[$page[$p]['bypass']]))
1407 $l .= '&' . $page[$p]['bypass'] . '=' . $_REQUEST[$page[$p]['bypass']];
1408 header ("Location: " . $l);
1409 die;
1410}
1411
9f3e5caa
DO
1412function getRackCodeStats ()
1413{
1414 global $rackCode;
914b439b 1415 $defc = $grantc = $modc = 0;
9f3e5caa
DO
1416 foreach ($rackCode as $s)
1417 switch ($s['type'])
1418 {
1419 case 'SYNT_DEFINITION':
1420 $defc++;
1421 break;
1422 case 'SYNT_GRANT':
1423 $grantc++;
1424 break;
914b439b
DO
1425 case 'SYNT_CTXMOD':
1426 $modc++;
1427 break;
9f3e5caa
DO
1428 default:
1429 break;
1430 }
914b439b
DO
1431 $ret = array
1432 (
1433 'Definition sentences' => $defc,
1434 'Grant sentences' => $grantc,
1435 'Context mod sentences' => $modc
1436 );
9f3e5caa
DO
1437 return $ret;
1438}
1439
d5157018
DO
1440function getRackImageWidth ()
1441{
529eac25
DO
1442 global $rtwidth;
1443 return 3 + $rtwidth[0] + $rtwidth[1] + $rtwidth[2] + 3;
d5157018
DO
1444}
1445
1446function getRackImageHeight ($units)
1447{
1448 return 3 + 3 + $units * 2;
1449}
1450
2987fc1f
DO
1451// Perform substitutions and return resulting string
1452// used solely by buildLVSConfig()
1453function apply_macros ($macros, $subject)
1454{
1455 $ret = $subject;
1456 foreach ($macros as $search => $replace)
1457 $ret = str_replace ($search, $replace, $ret);
1458 return $ret;
1459}
1460
1461function buildLVSConfig ($object_id = 0)
1462{
1463 if ($object_id <= 0)
1464 {
0cc24e9a 1465 showWarning ('Invalid argument', __FUNCTION__);
2987fc1f
DO
1466 return;
1467 }
6297d584 1468 $oInfo = spotEntity ('object', $object_id);
2987fc1f
DO
1469 $lbconfig = getSLBConfig ($object_id);
1470 if ($lbconfig === NULL)
1471 {
0cc24e9a 1472 showWarning ('getSLBConfig() failed', __FUNCTION__);
2987fc1f
DO
1473 return;
1474 }
1475 $newconfig = "#\n#\n# This configuration has been generated automatically by RackTables\n";
1476 $newconfig .= "# for object_id == ${object_id}\n# object name: ${oInfo['name']}\n#\n#\n\n\n";
1477 foreach ($lbconfig as $vs_id => $vsinfo)
1478 {
1479 $newconfig .= "########################################################\n" .
59a83bd8
DO
1480 "# VS (id == ${vs_id}): " . (!strlen ($vsinfo['vs_name']) ? 'NO NAME' : $vsinfo['vs_name']) . "\n" .
1481 "# RS pool (id == ${vsinfo['pool_id']}): " . (!strlen ($vsinfo['pool_name']) ? 'ANONYMOUS' : $vsinfo['pool_name']) . "\n" .
2987fc1f
DO
1482 "########################################################\n";
1483 # The order of inheritance is: VS -> LB -> pool [ -> RS ]
1484 $macros = array
1485 (
1486 '%VIP%' => $vsinfo['vip'],
1487 '%VPORT%' => $vsinfo['vport'],
1488 '%PROTO%' => $vsinfo['proto'],
1489 '%VNAME%' => $vsinfo['vs_name'],
1490 '%RSPOOLNAME%' => $vsinfo['pool_name']
1491 );
1492 $newconfig .= "virtual_server ${vsinfo['vip']} ${vsinfo['vport']} {\n";
1493 $newconfig .= "\tprotocol ${vsinfo['proto']}\n";
1494 $newconfig .= apply_macros
1495 (
1496 $macros,
1497 lf_wrap ($vsinfo['vs_vsconfig']) .
1498 lf_wrap ($vsinfo['lb_vsconfig']) .
1499 lf_wrap ($vsinfo['pool_vsconfig'])
1500 );
1501 foreach ($vsinfo['rslist'] as $rs)
1502 {
59a83bd8 1503 if (!strlen ($rs['rsport']))
79a9edb4 1504 $rs['rsport'] = $vsinfo['vport'];
2987fc1f
DO
1505 $macros['%RSIP%'] = $rs['rsip'];
1506 $macros['%RSPORT%'] = $rs['rsport'];
1507 $newconfig .= "\treal_server ${rs['rsip']} ${rs['rsport']} {\n";
1508 $newconfig .= apply_macros
1509 (
1510 $macros,
1511 lf_wrap ($vsinfo['vs_rsconfig']) .
1512 lf_wrap ($vsinfo['lb_rsconfig']) .
1513 lf_wrap ($vsinfo['pool_rsconfig']) .
1514 lf_wrap ($rs['rs_rsconfig'])
1515 );
1516 $newconfig .= "\t}\n";
1517 }
1518 $newconfig .= "}\n\n\n";
1519 }
3dd72e34
DO
1520 // FIXME: deal somehow with Mac-styled text, the below replacement will screw it up
1521 return str_replace ("\r", '', $newconfig);
2987fc1f
DO
1522}
1523
2d318652
DO
1524// Indicate occupation state of each IP address: none, ordinary or problematic.
1525function markupIPv4AddrList (&$addrlist)
1526{
1527 foreach (array_keys ($addrlist) as $ip_bin)
1528 {
d983f70a
DO
1529 $refc = array
1530 (
00c5d8cb
DO
1531 'shared' => 0, // virtual
1532 'virtual' => 0, // loopback
1533 'regular' => 0, // connected host
1534 'router' => 0 // connected gateway
d983f70a
DO
1535 );
1536 foreach ($addrlist[$ip_bin]['allocs'] as $a)
1537 $refc[$a['type']]++;
00c5d8cb 1538 $nvirtloopback = ($refc['shared'] + $refc['virtual'] > 0) ? 1 : 0; // modulus of virtual + shared
d983f70a
DO
1539 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever
1540 $nrealms = $nreserved + $nvirtloopback + $refc['regular'] + $refc['router']; // latter two are connected and router allocations
2d318652
DO
1541
1542 if ($nrealms == 1)
1543 $addrlist[$ip_bin]['class'] = 'trbusy';
1544 elseif ($nrealms > 1)
1545 $addrlist[$ip_bin]['class'] = 'trerror';
1546 else
1547 $addrlist[$ip_bin]['class'] = '';
1548 }
1549}
1550
04d619d0
DO
1551// Scan the given address list (returned by scanIPv4Space) and return a list of all routers found.
1552function findRouters ($addrlist)
1553{
1554 $ret = array();
1555 foreach ($addrlist as $addr)
1556 foreach ($addr['allocs'] as $alloc)
1557 if ($alloc['type'] == 'router')
1558 $ret[] = array
1559 (
1560 'id' => $alloc['object_id'],
1561 'iface' => $alloc['name'],
1562 'dname' => $alloc['object_name'],
1563 'addr' => $addr['ip']
1564 );
1565 return $ret;
1566}
1567
fb7a4967
DO
1568// Assist in tag chain sorting.
1569function taginfoCmp ($tagA, $tagB)
1570{
1571 return $tagA['ci'] - $tagB['ci'];
1572}
1573
1327d9dd
DO
1574// Compare networks. When sorting a tree, the records on the list will have
1575// distinct base IP addresses.
3444ecf2
DO
1576// "The comparison function must return an integer less than, equal to, or greater
1577// than zero if the first argument is considered to be respectively less than,
1578// equal to, or greater than the second." (c) PHP manual
1327d9dd
DO
1579function IPv4NetworkCmp ($netA, $netB)
1580{
2d75c30b
DO
1581 // There's a problem just substracting one u32 integer from another,
1582 // because the result may happen big enough to become a negative i32
1583 // integer itself (PHP tries to cast everything it sees to signed int)
3444ecf2
DO
1584 // The comparison below must treat positive and negative values of both
1585 // arguments.
1586 // Equal values give instant decision regardless of their [equal] sign.
1587 if ($netA['ip_bin'] == $netB['ip_bin'])
2d75c30b 1588 return 0;
3444ecf2
DO
1589 // Same-signed values compete arithmetically within one of i32 contiguous ranges:
1590 // 0x00000001~0x7fffffff 1~2147483647
1591 // 0 doesn't have any sign, and network 0.0.0.0 isn't allowed
1592 // 0x80000000~0xffffffff -2147483648~-1
1593 $signA = $netA['ip_bin'] / abs ($netA['ip_bin']);
1594 $signB = $netB['ip_bin'] / abs ($netB['ip_bin']);
1595 if ($signA == $signB)
1596 {
1597 if ($netA['ip_bin'] > $netB['ip_bin'])
1598 return 1;
1599 else
1600 return -1;
1601 }
1602 else // With only one of two values being negative, it... wins!
1603 {
1604 if ($netA['ip_bin'] < $netB['ip_bin'])
1605 return 1;
1606 else
1607 return -1;
1608 }
1327d9dd
DO
1609}
1610
fb7a4967 1611// Modify the given tag tree so, that each level's items are sorted alphabetically.
1327d9dd 1612function sortTree (&$tree, $sortfunc = '')
fb7a4967 1613{
59a83bd8 1614 if (!strlen ($sortfunc))
1327d9dd 1615 return;
51b6651a 1616 $self = __FUNCTION__;
1327d9dd 1617 usort ($tree, $sortfunc);
fb7a4967
DO
1618 // Don't make a mistake of directly iterating over the items of current level, because this way
1619 // the sorting will be performed on a _copy_ if each item, not the item itself.
1620 foreach (array_keys ($tree) as $tagid)
51b6651a 1621 $self ($tree[$tagid]['kids'], $sortfunc);
fb7a4967
DO
1622}
1623
0137d53c
DO
1624function iptree_fill (&$netdata)
1625{
59a83bd8 1626 if (!isset ($netdata['kids']) or !count ($netdata['kids']))
0137d53c 1627 return;
a987ff52 1628 // If we really have nested prefixes, they must fit into the tree.
0137d53c
DO
1629 $worktree = array
1630 (
1631 'ip_bin' => $netdata['ip_bin'],
1632 'mask' => $netdata['mask']
1633 );
1634 foreach ($netdata['kids'] as $pfx)
1635 iptree_embed ($worktree, $pfx);
1636 $netdata['kids'] = iptree_construct ($worktree);
1637 $netdata['kidc'] = count ($netdata['kids']);
1638}
1639
1640function iptree_construct ($node)
1641{
1642 $self = __FUNCTION__;
1643
1644 if (!isset ($node['right']))
1645 {
1646 if (!isset ($node['ip']))
1647 {
1648 $node['ip'] = long2ip ($node['ip_bin']);
1649 $node['kids'] = array();
fec0c8da 1650 $node['kidc'] = 0;
0137d53c
DO
1651 $node['name'] = '';
1652 }
1653 return array ($node);
1654 }
1655 else
1656 return array_merge ($self ($node['left']), $self ($node['right']));
1657}
1658
1659function iptree_embed (&$node, $pfx)
1660{
1661 $self = __FUNCTION__;
1662
1663 // hit?
1664 if ($node['ip_bin'] == $pfx['ip_bin'] and $node['mask'] == $pfx['mask'])
1665 {
1666 $node = $pfx;
1667 return;
1668 }
1669 if ($node['mask'] == $pfx['mask'])
1670 {
0cc24e9a 1671 throw new RuntimeException('Internal error, the recurring loop lost control');
0137d53c
DO
1672 }
1673
1674 // split?
1675 if (!isset ($node['right']))
1676 {
a987ff52
DO
1677 // Fill in db_first/db_last to make it possible to run scanIPv4Space() on the node.
1678 $node['left']['mask'] = $node['mask'] + 1;
0137d53c 1679 $node['left']['ip_bin'] = $node['ip_bin'];
a987ff52
DO
1680 $node['left']['db_first'] = sprintf ('%u', $node['left']['ip_bin']);
1681 $node['left']['db_last'] = sprintf ('%u', $node['left']['ip_bin'] | binInvMaskFromDec ($node['left']['mask']));
1682
1683 $node['right']['mask'] = $node['mask'] + 1;
0137d53c 1684 $node['right']['ip_bin'] = $node['ip_bin'] + binInvMaskFromDec ($node['mask'] + 1) + 1;
a987ff52
DO
1685 $node['right']['db_first'] = sprintf ('%u', $node['right']['ip_bin']);
1686 $node['right']['db_last'] = sprintf ('%u', $node['right']['ip_bin'] | binInvMaskFromDec ($node['right']['mask']));
0137d53c
DO
1687 }
1688
1689 // repeat!
1690 if (($node['left']['ip_bin'] & binMaskFromDec ($node['left']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
1691 $self ($node['left'], $pfx);
1692 elseif (($node['right']['ip_bin'] & binMaskFromDec ($node['right']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
1693 $self ($node['right'], $pfx);
1694 else
1695 {
0cc24e9a 1696 throw new RuntimeException ('Internal error, cannot decide between left and right');
0137d53c
DO
1697 }
1698}
1699
3b81cb98 1700function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
0137d53c 1701{
59a83bd8 1702 if (!strlen ($func))
0137d53c
DO
1703 return;
1704 $self = __FUNCTION__;
1705 foreach (array_keys ($tree) as $key)
1706 {
1707 $func ($tree[$key]);
59a83bd8 1708 if (strlen ($stopfunc) and $stopfunc ($tree[$key]))
c3315231 1709 continue;
0137d53c
DO
1710 $self ($tree[$key]['kids'], $func);
1711 }
1712}
1713
a987ff52
DO
1714function loadIPv4AddrList (&$netinfo)
1715{
1716 loadOwnIPv4Addresses ($netinfo);
1717 markupIPv4AddrList ($netinfo['addrlist']);
1718}
b18d26dc 1719
b6b87070 1720function countOwnIPv4Addresses (&$node)
b18d26dc 1721{
b6b87070 1722 $toscan = array();
737a3f72 1723 $node['addrt'] = 0;
4d0dbd87
DO
1724 $node['mask_bin'] = binMaskFromDec ($node['mask']);
1725 $node['mask_bin_inv'] = binInvMaskFromDec ($node['mask']);
1726 $node['db_first'] = sprintf ('%u', 0x00000000 + $node['ip_bin'] & $node['mask_bin']);
1727 $node['db_last'] = sprintf ('%u', 0x00000000 + $node['ip_bin'] | ($node['mask_bin_inv']));
59a83bd8 1728 if (!count ($node['kids']))
737a3f72 1729 {
f7414fa5 1730 $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
737a3f72
DO
1731 $node['addrt'] = binInvMaskFromDec ($node['mask']) + 1;
1732 }
b18d26dc 1733 else
b6b87070 1734 foreach ($node['kids'] as $nested)
b18d26dc 1735 if (!isset ($nested['id'])) // spare
737a3f72 1736 {
f7414fa5 1737 $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
737a3f72
DO
1738 $node['addrt'] += binInvMaskFromDec ($nested['mask']) + 1;
1739 }
4d0dbd87
DO
1740 // Don't do anything more, because the displaying function will load the addresses anyway.
1741 return;
f7414fa5 1742 $node['addrc'] = count (scanIPv4Space ($toscan));
b6b87070
DO
1743}
1744
c3315231
DO
1745function nodeIsCollapsed ($node)
1746{
1747 return $node['symbol'] == 'node-collapsed';
1748}
1749
b6b87070
DO
1750function loadOwnIPv4Addresses (&$node)
1751{
1752 $toscan = array();
43eb71f1 1753 if (!isset ($node['kids']) or !count ($node['kids']))
f7414fa5 1754 $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
b6b87070
DO
1755 else
1756 foreach ($node['kids'] as $nested)
1757 if (!isset ($nested['id'])) // spare
f7414fa5
DO
1758 $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
1759 $node['addrlist'] = scanIPv4Space ($toscan);
b6b87070 1760 $node['addrc'] = count ($node['addrlist']);
b18d26dc
DO
1761}
1762
fec0c8da
DO
1763function prepareIPv4Tree ($netlist, $expanded_id = 0)
1764{
573214e0
DO
1765 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
1766 // so perform necessary pre-processing to make orphans belong to root. This trick
1767 // was earlier performed by getIPv4NetworkList().
1768 $netids = array_keys ($netlist);
1769 foreach ($netids as $cid)
1770 if (!in_array ($netlist[$cid]['parent_id'], $netids))
1771 $netlist[$cid]['parent_id'] = NULL;
fec0c8da
DO
1772 $tree = treeFromList ($netlist); // medium call
1773 sortTree ($tree, 'IPv4NetworkCmp');
fec0c8da 1774 // complement the tree before markup to make the spare networks have "symbol" set
c3315231
DO
1775 treeApplyFunc ($tree, 'iptree_fill');
1776 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
1777 // count addresses after the markup to skip computation for hidden tree nodes
1778 treeApplyFunc ($tree, 'countOwnIPv4Addresses', 'nodeIsCollapsed');
fec0c8da
DO
1779 return $tree;
1780}
1781
1782// Check all items of the tree recursively, until the requested target id is
1783// found. Mark all items leading to this item as "expanded", collapsing all
1784// the rest, which exceed the given threshold (if the threshold is given).
1785function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0)
1786{
1787 $self = __FUNCTION__;
1788 $ret = FALSE;
1789 foreach (array_keys ($tree) as $key)
1790 {
5388794d 1791 $here = ($target === 'ALL' or ($target > 0 and isset ($tree[$key]['id']) and $tree[$key]['id'] == $target));
fec0c8da
DO
1792 $below = $self ($tree[$key]['kids'], $threshold, $target);
1793 if (!$tree[$key]['kidc']) // terminal node
1794 $tree[$key]['symbol'] = 'spacer';
1795 elseif ($tree[$key]['kidc'] < $threshold)
1796 $tree[$key]['symbol'] = 'node-expanded-static';
1797 elseif ($here or $below)
1798 $tree[$key]['symbol'] = 'node-expanded';
1799 else
1800 $tree[$key]['symbol'] = 'node-collapsed';
1801 $ret = ($ret or $here or $below); // parentheses are necessary for this to be computed correctly
1802 }
1803 return $ret;
1804}
1805
e1ae3fb4
AD
1806// Convert entity name to human-readable value
1807function formatEntityName ($name) {
1808 switch ($name)
1809 {
1810 case 'ipv4net':
1811 return 'IPv4 Network';
1812 case 'ipv4rspool':
1813 return 'IPv4 RS Pool';
1814 case 'ipv4vs':
1815 return 'IPv4 Virtual Service';
1816 case 'object':
1817 return 'Object';
1818 case 'rack':
1819 return 'Rack';
1820 case 'user':
1821 return 'User';
1822 }
1823 return 'invalid';
1824}
1825
1826// Take a MySQL or other generic timestamp and make it prettier
1827function formatTimestamp ($timestamp) {
1828 return date('n/j/y g:iA', strtotime($timestamp));
1829}
1830
8bc5d1e4
DO
1831// Display hrefs for all of a file's parents. If scissors are requested,
1832// prepend cutting button to each of them.
1833function serializeFileLinks ($links, $scissors = FALSE)
e1ae3fb4 1834{
e1ae3fb4
AD
1835 $comma = '';
1836 $ret = '';
1837 foreach ($links as $link_id => $li)
1838 {
1839 switch ($li['entity_type'])
1840 {
1841 case 'ipv4net':
1842 $params = "page=ipv4net&id=";
1843 break;
1844 case 'ipv4rspool':
1845 $params = "page=ipv4rspool&pool_id=";
1846 break;
1847 case 'ipv4vs':
1848 $params = "page=ipv4vs&vs_id=";
1849 break;
1850 case 'object':
1851 $params = "page=object&object_id=";
1852 break;
1853 case 'rack':
1854 $params = "page=rack&rack_id=";
1855 break;
1856 case 'user':
1857 $params = "page=user&user_id=";
1858 break;
1859 }
8bc5d1e4
DO
1860 $ret .= $comma;
1861 if ($scissors)
1862 {
1863 $ret .= "<a href='" . makeHrefProcess(array('op'=>'unlinkFile', 'link_id'=>$link_id)) . "'";
1864 $ret .= getImageHREF ('cut') . '</a> ';
1865 }
790a60e8 1866 $ret .= sprintf("<a href='index.php?%s%s'>%s</a>", $params, $li['entity_id'], $li['name']);
8bc5d1e4 1867 $comma = '<br>';
e1ae3fb4 1868 }
e1ae3fb4
AD
1869 return $ret;
1870}
1871
1872// Convert filesize to appropriate unit and make it human-readable
1873function formatFileSize ($bytes) {
1874 // bytes
1875 if($bytes < 1024) // bytes
1876 return "${bytes} bytes";
1877
1878 // kilobytes
1879 if ($bytes < 1024000)
1880 return sprintf ("%.1fk", round (($bytes / 1024), 1));
1881
1882 // megabytes
1883 return sprintf ("%.1f MB", round (($bytes / 1024000), 1));
1884}
1885
1886// Reverse of formatFileSize, it converts human-readable value to bytes
1887function convertToBytes ($value) {
1888 $value = trim($value);
1889 $last = strtolower($value[strlen($value)-1]);
1890 switch ($last)
1891 {
1892 case 'g':
1893 $value *= 1024;
1894 case 'm':
1895 $value *= 1024;
1896 case 'k':
1897 $value *= 1024;
1898 }
1899
1900 return $value;
1901}
1902
4fbb5a00
DY
1903function ip_quad2long ($ip)
1904{
1905 return sprintf("%u", ip2long($ip));
1906}
1907
1908function ip_long2quad ($quad)
1909{
1910 return long2ip($quad);
1911}
1912
1913function makeHref($params = array())
1914{
790a60e8 1915 $ret = 'index.php?';
4fbb5a00 1916 $first = true;
4fbb5a00
DY
1917 foreach($params as $key=>$value)
1918 {
1919 if (!$first)
1920 $ret.='&';
1921 $ret .= urlencode($key).'='.urlencode($value);
1922 $first = false;
1923 }
1924 return $ret;
1925}
1926
1927function makeHrefProcess($params = array())
1928{
790a60e8
DO
1929 global $pageno, $tabno;
1930 $ret = 'process.php?';
4fbb5a00 1931 $first = true;
9f14a7ef
DY
1932 if (!isset($params['page']))
1933 $params['page'] = $pageno;
1934 if (!isset($params['tab']))
1935 $params['tab'] = $tabno;
4fbb5a00
DY
1936 foreach($params as $key=>$value)
1937 {
1938 if (!$first)
1939 $ret.='&';
1940 $ret .= urlencode($key).'='.urlencode($value);
1941 $first = false;
1942 }
1943 return $ret;
1944}
1945
39106006 1946function makeHrefForHelper ($helper_name, $params = array())
4fbb5a00 1947{
790a60e8 1948 $ret = 'popup.php?helper=' . $helper_name;
4fbb5a00 1949 foreach($params as $key=>$value)
39106006 1950 $ret .= '&'.urlencode($key).'='.urlencode($value);
4fbb5a00
DY
1951 return $ret;
1952}
1953
f3d274bf
DO
1954// Process the given list of records to build data suitable for printNiftySelect()
1955// (like it was formerly executed by printSelect()). Screen out vendors according
1956// to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already
1957// selected OPTION is protected from being screened.
1958function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0)
1959{
1960 $ret = array();
1961 // Always keep "other" OPTGROUP at the SELECT bottom.
1962 $therest = array();
1963 foreach ($recordList as $dict_key => $dict_value)
1964 if (strpos ($dict_value, '%GSKIP%') !== FALSE)
1965 {
1966 $tmp = explode ('%GSKIP%', $dict_value, 2);
1967 $ret[$tmp[0]][$dict_key] = $tmp[1];
1968 }
1969 elseif (strpos ($dict_value, '%GPASS%') !== FALSE)
1970 {
1971 $tmp = explode ('%GPASS%', $dict_value, 2);
1972 $ret[$tmp[0]][$dict_key] = $tmp[1];
1973 }
1974 else
1975 $therest[$dict_key] = $dict_value;
1976 if ($object_type_id != 0)
1977 {
1978 $screenlist = array();
1979 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve)
eea3ca5e 1980 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs)){
f3d274bf 1981 $screenlist[] = $regs[1];
eea3ca5e 1982 }
f3d274bf
DO
1983 foreach (array_keys ($ret) as $vendor)
1984 if (in_array ($vendor, $screenlist))
1985 {
1986 $ok_to_screen = TRUE;
1987 if ($existing_value)
1988 foreach (array_keys ($ret[$vendor]) as $recordkey)
1989 if ($recordkey == $existing_value)
1990 {
1991 $ok_to_screen = FALSE;
1992 break;
1993 }
1994 if ($ok_to_screen)
1995 unset ($ret[$vendor]);
1996 }
1997 }
1998 $ret['other'] = $therest;
1999 return $ret;
2000}
2001
f8874cdb
DO
2002function dos2unix ($text)
2003{
2004 return str_replace ("\r\n", "\n", $text);
2005}
2006
9dd73255
DO
2007function unix2dos ($text)
2008{
2009 return str_replace ("\n", "\r\n", $text);
2010}
2011
a0527aef 2012function buildPredicateTable ($parsetree)
2c21a10c 2013{
a0527aef
DO
2014 $ret = array();
2015 foreach ($parsetree as $sentence)
72e8baf6 2016 if ($sentence['type'] == 'SYNT_DEFINITION')
a0527aef 2017 $ret[$sentence['term']] = $sentence['definition'];
72e8baf6
DO
2018 // Now we have predicate table filled in with the latest definitions of each
2019 // particular predicate met. This isn't as chik, as on-the-fly predicate
2020 // overloading during allow/deny scan, but quite sufficient for this task.
a0527aef
DO
2021 return $ret;
2022}
2023
2024// Take a list of records and filter against given RackCode expression. Return
2025// the original list intact, if there was no filter requested, but return an
2026// empty list, if there was an error.
573214e0 2027function filterCellList ($list_in, $expression = array())
d08d766d
DO
2028{
2029 if ($expression === NULL)
2030 return array();
2031 if (!count ($expression))
2032 return $list_in;
d08d766d
DO
2033 $list_out = array();
2034 foreach ($list_in as $item_key => $item_value)
573214e0 2035 if (TRUE === judgeCell ($item_value, $expression))
d08d766d
DO
2036 $list_out[$item_key] = $item_value;
2037 return $list_out;
2038}
2039
212c9d8a 2040// Tell, if the given expression is true for the given entity. Take complete record on input.
573214e0 2041function judgeCell ($cell, $expression)
d08d766d
DO
2042{
2043 global $pTable;
2044 return eval_expression
2045 (
2046 $expression,
2047 array_merge
2048 (
573214e0
DO
2049 $cell['etags'],
2050 $cell['itags'],
2051 $cell['atags']
d08d766d
DO
2052 ),
2053 $pTable,
2054 TRUE
2055 );
2056}
2057
7ddbcf59 2058// Tell, if a constraint from config option permits given record.
212c9d8a 2059function considerConfiguredConstraint ($cell, $varname)
c6bc0ac5 2060{
7ddbcf59 2061 if (!strlen (getConfigVar ($varname)))
c6bc0ac5 2062 return TRUE; // no restriction
7ddbcf59
DO
2063 global $parseCache;
2064 if (!isset ($parseCache[$varname]))
2065 // getConfigVar() doesn't re-read the value from DB because of its
2066 // own cache, so there is no race condition here between two calls.
2067 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2068 if ($parseCache[$varname]['result'] != 'ACK')
2069 return FALSE; // constraint set, but cannot be used due to compilation error
212c9d8a 2070 return judgeCell ($cell, $parseCache[$varname]['load']);
c6bc0ac5
DO
2071}
2072
17112e81
DO
2073// Return list of records in the given realm, which conform to
2074// the given RackCode expression. If the realm is unknown or text
2075// doesn't validate as a RackCode expression, return NULL.
2076// Otherwise (successful scan) return a list of all matched
2077// records, even if the list is empty (array() !== NULL). If the
2078// text is an empty string, return all found records in the given
2079// realm.
2080function scanRealmByText ($realm = NULL, $ftext = '')
2081{
2082 switch ($realm)
2083 {
2084 case 'object':
2085 case 'user':
2086 case 'ipv4net':
2087 case 'file':
39776127 2088 case 'ipv4vs':
41780975 2089 case 'ipv4rspool':
17112e81
DO
2090 if (!strlen ($ftext = trim ($ftext)))
2091 $fexpr = array();
2092 else
2093 {
2094 $fparse = spotPayload ($ftext, 'SYNT_EXPR');
2095 if ($fparse['result'] != 'ACK')
2096 return NULL;
2097 $fexpr = $fparse['load'];
2098 }
2099 return filterCellList (listCells ($realm), $fexpr);
2100 default:
c5f84f48 2101 throw new InvalidArgException ('$realm', $realm);
17112e81
DO
2102 }
2103
2104}
39776127
DO
2105
2106function getIPv4VSOptions ()
2107{
2108 $ret = array();
2109 foreach (listCells ('ipv4vs') as $vsid => $vsinfo)
59a83bd8 2110 $ret[$vsid] = $vsinfo['dname'] . (!strlen ($vsinfo['name']) ? '' : " (${vsinfo['name']})");
39776127
DO
2111 return $ret;
2112}
2113
41780975
DO
2114function getIPv4RSPoolOptions ()
2115{
2116 $ret = array();
2117 foreach (listCells ('ipv4rspool') as $pool_id => $poolInfo)
2118 $ret[$pool_id] = $poolInfo['name'];
2119 return $ret;
2120}
2121
d16af52f
DO
2122// Derive a complete cell structure from the given username regardless
2123// if it is a local account or not.
2124function constructUserCell ($username)
2125{
2126 if (NULL !== ($userid = getUserIDByUsername ($username)))
2127 return spotEntity ('user', $userid);
2128 $ret = array
2129 (
2130 'realm' => 'user',
2131 'user_name' => $username,
2132 'user_realname' => '',
2133 'etags' => array(),
2134 'itags' => array(),
d16af52f 2135 );
5689cefd 2136 $ret['atags'] = generateEntityAutoTags ($ret);
d16af52f
DO
2137 return $ret;
2138}
2139
56a797ef
DO
2140// Let's have this debug helper here to enable debugging of process.php w/o interface.php.
2141function dump ($var)
2142{
2143 echo '<div align=left><pre>';
2144 print_r ($var);
2145 echo '</pre></div>';
2146}
2147
9e51318b
DO
2148function getTagChart ($limit = 0, $realm = 'total', $special_tags = array())
2149{
2150 global $taglist;
2151 // first build top-N chart...
2152 $toplist = array();
2153 foreach ($taglist as $taginfo)
2154 if (isset ($taginfo['refcnt'][$realm]))
2155 $toplist[$taginfo['id']] = $taginfo['refcnt'][$realm];
2156 arsort ($toplist, SORT_NUMERIC);
2157 $ret = array();
2158 $done = 0;
2159 foreach (array_keys ($toplist) as $tag_id)
2160 {
2161 $ret[$tag_id] = $taglist[$tag_id];
2162 if (++$done == $limit)
2163 break;
2164 }
2165 // ...then make sure, that every item of the special list is shown
2166 // (using the same sort order)
2167 $extra = array();
2168 foreach ($special_tags as $taginfo)
2169 if (!array_key_exists ($taginfo['id'], $ret))
2170 $extra[$taginfo['id']] = $taglist[$taginfo['id']]['refcnt'][$realm];
2171 arsort ($extra, SORT_NUMERIC);
2172 foreach (array_keys ($extra) as $tag_id)
2173 $ret[] = $taglist[$tag_id];
2174 return $ret;
2175}
2176
7fa7047a
DO
2177function decodeObjectType ($objtype_id, $style = 'r')
2178{
2179 static $types = array();
2180 if (!count ($types))
2181 $types = array
2182 (
2183 'r' => readChapter (CHAP_OBJTYPE),
2184 'a' => readChapter (CHAP_OBJTYPE, 'a'),
2185 'o' => readChapter (CHAP_OBJTYPE, 'o')
2186 );
2187 return $types[$style][$objtype_id];
2188}
2189
0df8c52b
DO
2190function isolatedPermission ($p, $t, $cell)
2191{
2192 // This function is called from both "file" page and a number of other pages,
2193 // which have already fixed security context and authorized the user for it.
2194 // OTOH, it is necessary here to authorize against the current file, which
2195 // means saving the current context and building a new one.
2196 global
2197 $expl_tags,
2198 $impl_tags,
2199 $target_given_tags,
2200 $auto_tags;
2201 // push current context
2202 $orig_expl_tags = $expl_tags;
2203 $orig_impl_tags = $impl_tags;
2204 $orig_target_given_tags = $target_given_tags;
2205 $orig_auto_tags = $auto_tags;
2206 // retarget
2207 fixContext ($cell);
2208 // remember decision
2209 $ret = permitted ($p, $t);
2210 // pop context
2211 $expl_tags = $orig_expl_tags;
2212 $impl_tags = $orig_impl_tags;
2213 $target_given_tags = $orig_target_given_tags;
2214 $auto_tags = $orig_auto_tags;
2215 return $ret;
2216}
2217
3153a326
DO
2218function getPortListPrefs()
2219{
2220 $ret = array();
2221 if (0 >= ($ret['iif_pick'] = getConfigVar ('DEFAULT_PORT_IIF_ID')))
2222 $ret['iif_pick'] = 1;
2223 $ret['oif_picks'] = array();
2224 foreach (explode (';', getConfigVar ('DEFAULT_PORT_OIF_IDS')) as $tmp)
2225 {
2226 $tmp = explode ('=', trim ($tmp));
2227 if (count ($tmp) == 2 and $tmp[0] > 0 and $tmp[1] > 0)
2228 $ret['oif_picks'][$tmp[0]] = $tmp[1];
2229 }
2230 // enforce default value
2231 if (!array_key_exists (1, $ret['oif_picks']))
2232 $ret['oif_picks'][1] = 24;
2233 $ret['selected'] = $ret['iif_pick'] . '-' . $ret['oif_picks'][$ret['iif_pick']];
2234 return $ret;
2235}
2236
2dfa1b73
DO
2237// Return data for printNiftySelect() with port type options. All OIF options
2238// for the default IIF will be shown, but only the default OIFs will be present
2239// for each other IIFs. IIFs, for which there is no default OIF, will not
2240// be listed.
2241// This SELECT will be used for the "add new port" form.
2242function getNewPortTypeOptions()
2243{
2244 $ret = array();
2245 $prefs = getPortListPrefs();
2246 foreach (getPortInterfaceCompat() as $row)
2247 {
2248 if ($row['iif_id'] == $prefs['iif_pick'])
2249 $optgroup = $row['iif_name'];
2250 elseif (array_key_exists ($row['iif_id'], $prefs['oif_picks']) and $prefs['oif_picks'][$row['iif_id']] == $row['oif_id'])
2251 $optgroup = 'other';
2252 else
2253 continue;
2254 if (!array_key_exists ($optgroup, $ret))
2255 $ret[$optgroup] = array();
2256 $ret[$optgroup][$row['iif_id'] . '-' . $row['oif_id']] = $row['oif_name'];
2257 }
2258 return $ret;
2259}
2260
8198f2c6
DO
2261function getVLANDomain ($vdid)
2262{
2263 $ret = getVLANDomainInfo ($vdid);
2264 $ret['vlanlist'] = array();
8198f2c6
DO
2265 foreach (getDomainVLANs ($vdid) as $vlan_id => $vlan_descr)
2266 $ret['vlanlist'][$vlan_id] = $vlan_descr;
2267 $ret['switchlist'] = getVLANDomainSwitches ($vdid);
2268 return $ret;
2269}
2270
2271// Return a serialized version of VLAN configuration for a port.
2272// If a native VLAN is defined, print it first. All other VLANs
d0dadd80
DO
2273// are tagged and are listed after a plus sign. When no configuration
2274// is set for a port, return "default" string.
4f860864 2275function serializeVLANPack ($vlanport)
8198f2c6 2276{
4f860864
DO
2277 if (!array_key_exists ('mode', $vlanport))
2278 return 'error';
2279 switch ($vlanport['mode'])
2280 {
2281 case 'none':
2282 return 'none';
2283 case 'access':
2284 $ret = 'A';
2285 break;
2286 case 'trunk':
2287 $ret = 'T';
2288 break;
36a70b71
DO
2289 case 'uplink':
2290 $ret = 'U';
2291 break;
2292 case 'downlink':
2293 $ret = 'D';
2294 break;
4f860864
DO
2295 default:
2296 return 'error';
2297 }
8198f2c6 2298 $tagged = array();
4f860864
DO
2299 foreach ($vlanport['allowed'] as $vlan_id)
2300 if ($vlan_id != $vlanport['native'])
8198f2c6
DO
2301 $tagged[] = $vlan_id;
2302 sort ($tagged);
4f860864 2303 $ret .= $vlanport['native'] ? $vlanport['native'] : '';
e9be55de
DO
2304 $tagged_bits = array();
2305 $id_from = $id_to = 0;
2306 foreach ($tagged as $next_id)
2307 {
2308 if ($id_to)
2309 {
2310 if ($next_id == $id_to + 1) // merge
2311 {
2312 $id_to = $next_id;
2313 continue;
2314 }
2315 // flush
2316 $tagged_bits[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}";
2317 }
2318 $id_from = $id_to = $next_id; // start next pair
2319 }
2320 // pull last pair
2321 if ($id_to)
2322 $tagged_bits[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}";
8198f2c6 2323 if (count ($tagged))
a9953bf1 2324 $ret .= '+' . implode (', ', $tagged_bits);
d0dadd80 2325 return strlen ($ret) ? $ret : 'default';
8198f2c6
DO
2326}
2327
8846b060
DO
2328// Decode VLAN compound key (which is a string formatted DOMAINID-VLANID) and
2329// return the numbers as an array of two.
2330function decodeVLANCK ($string)
2331{
2332 $matches = array();
2333 if (1 != preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $string, $matches))
2334 throw new InvalidArgException ('VLAN compound key', $string);
2335 return array ($matches[1], $matches[2]);
2336}
2337
ce85f5c8
DO
2338// Return VLAN name formatted for HTML output (note, that input
2339// argument comes from database unescaped).
a72aa89f
DO
2340function formatVLANName ($vlaninfo, $context = 'markup long')
2341{
2342 switch ($context)
2343 {
2344 case 'option':
2345 $ret = $vlaninfo['vlan_id'];
2346 if ($vlaninfo['vlan_descr'] != '')
2347 $ret .= ' ' . niftyString ($vlaninfo['vlan_descr']);
2348 return $ret;
2349 case 'label':
2350 $ret = $vlaninfo['vlan_id'];
2351 if ($vlaninfo['vlan_descr'] != '')
2352 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2353 return $ret;
0812b506
DO
2354 case 'plain long':
2355 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2356 if ($vlaninfo['vlan_descr'] != '')
2357 $ret .= ' (' . niftyString ($vlaninfo['vlan_descr']) . ')';
2358 return $ret;
a72aa89f
DO
2359 case 'markup long':
2360 default:
2361 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2362 $ret .= ' @' . niftyString ($vlaninfo['domain_descr']);
2363 if ($vlaninfo['vlan_descr'] != '')
2364 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2365 return $ret;
2366 }
ce85f5c8
DO
2367}
2368
ef6653b1 2369function ios12ReadVLANConfig ($input)
f10dd5cc 2370{
ef6653b1
DO
2371 $ret = array
2372 (
2373 'vlanlist' => array(),
2374 'portdata' => array(),
2375 );
2376 $procfunc = 'ios12ScanTopLevel';
f10dd5cc
DO
2377 foreach (explode ("\n", $input) as $line)
2378 $procfunc = $procfunc ($ret, $line);
2379 return $ret;
2380}
2381
ef6653b1 2382function ios12ScanTopLevel (&$work, $line)
f10dd5cc
DO
2383{
2384 $matches = array();
ef6653b1
DO
2385 switch (TRUE)
2386 {
d0642bae 2387 case (preg_match ('@^interface ((Ethernet|FastEthernet|GigabitEthernet|TenGigabitEthernet|Port-channel)[[:digit:]]+(/[[:digit:]]+)*)$@', $line, $matches)):
ef6653b1
DO
2388 // map interface name
2389 $matches[1] = preg_replace ('@^Ethernet(.+)$@', 'et\\1', $matches[1]);
2390 $matches[1] = preg_replace ('@^FastEthernet(.+)$@', 'fa\\1', $matches[1]);
2391 $matches[1] = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $matches[1]);
2392 $matches[1] = preg_replace ('@^TenGigabitEthernet(.+)$@', 'te\\1', $matches[1]);
d0642bae 2393 $matches[1] = preg_replace ('@^Port-channel(.+)$@', 'po\\1', $matches[1]);
ef6653b1
DO
2394 $work['current'] = array ('port_name' => $matches[1]);
2395 return 'ios12PickSwitchportCommand'; // switch to interface block reading
2396 case (preg_match ('/^VLAN Name Status Ports$/', $line, $matches)):
2397 return 'ios12PickVLANCommand';
2398 default:
f10dd5cc 2399 return __FUNCTION__; // continue scan
ef6653b1 2400 }
f10dd5cc
DO
2401}
2402
ef6653b1 2403function ios12PickSwitchportCommand (&$work, $line)
f10dd5cc
DO
2404{
2405 if ($line[0] != ' ') // end of interface section
2406 {
f10dd5cc 2407 // save work, if it makes sense
0721a106 2408 switch (TRUE)
f10dd5cc 2409 {
a13d4b2d
DO
2410 case $work['current']['ignore']:
2411 $work['portdata'][$work['current']['port_name']] = array
2412 (
2413 'mode' => 'none',
2414 'allowed' => array(),
2415 'native' => 0,
2416 );
2417 break;
0721a106
DO
2418 case 'access' == $work['current']['mode']:
2419 if (!array_key_exists ('access vlan', $work['current']))
2420 $work['current']['access vlan'] = 1;
ac210c9e 2421 $work['portdata'][$work['current']['port_name']] = array
f10dd5cc 2422 (
4f860864 2423 'mode' => 'access',
f10dd5cc
DO
2424 'allowed' => array ($work['current']['access vlan']),
2425 'native' => $work['current']['access vlan'],
2426 );
2427 break;
0721a106
DO
2428 case 'trunk' == $work['current']['mode']:
2429 if (!array_key_exists ('trunk native vlan', $work['current']))
2430 $work['current']['trunk native vlan'] = 1;
2431 if (!array_key_exists ('trunk allowed vlan', $work['current']))
2432 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID, VLAN_MAX_ID);
48c286aa
DO
2433 // Having configured VLAN as "native" doesn't mean anything
2434 // as long as it's not listed on the "allowed" line.
2435 $effective_native = in_array
2436 (
2437 $work['current']['trunk native vlan'],
2438 $work['current']['trunk allowed vlan']
2439 ) ? $work['current']['trunk native vlan'] : 0;
ac210c9e 2440 $work['portdata'][$work['current']['port_name']] = array
f10dd5cc 2441 (
4f860864 2442 'mode' => 'trunk',
e9be55de 2443 'allowed' => $work['current']['trunk allowed vlan'],
48c286aa 2444 'native' => $effective_native,
f10dd5cc
DO
2445 );
2446 break;
2447 default:
0721a106
DO
2448 // dot1q-tunnel, dynamic, private-vlan or even none --
2449 // show in returned config and let user decide, if they
2450 // want to fix device config or work around these ports
2451 // by means of VST.
2452 $work['portdata'][$work['current']['port_name']] = array
2453 (
2454 'mode' => 'none',
2455 'allowed' => array(),
2456 'native' => 0,
2457 );
2458 break;
f10dd5cc
DO
2459 }
2460 unset ($work['current']);
ef6653b1 2461 return 'ios12ScanTopLevel';
f10dd5cc
DO
2462 }
2463 // not yet
2464 $matches = array();
2465 switch (TRUE)
2466 {
2467 case (preg_match ('@^ switchport mode (.+)$@', $line, $matches)):
2468 $work['current']['mode'] = $matches[1];
2469 break;
2470 case (preg_match ('@^ switchport access vlan (.+)$@', $line, $matches)):
2471 $work['current']['access vlan'] = $matches[1];
2472 break;
2473 case (preg_match ('@^ switchport trunk native vlan (.+)$@', $line, $matches)):
2474 $work['current']['trunk native vlan'] = $matches[1];
2475 break;
2476 case (preg_match ('@^ switchport trunk allowed vlan add (.+)$@', $line, $matches)):
2477 $work['current']['trunk allowed vlan'] = array_merge
2478 (
2479 $work['current']['trunk allowed vlan'],
2480 iosParseVLANString ($matches[1])
2481 );
2482 break;
2483 case (preg_match ('@^ switchport trunk allowed vlan (.+)$@', $line, $matches)):
2484 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
2485 break;
9cf66518
DO
2486 case preg_match ('@^ channel-group @', $line):
2487 // port-channel subinterface config follows that of the master interface
2488 case preg_match ('@^ ip address @', $line):
2489 // L3 interface does no switchport functions
a13d4b2d
DO
2490 $work['current']['ignore'] = TRUE;
2491 break;
f10dd5cc
DO
2492 default: // suppress warning on irrelevant config clause
2493 }
2494 return __FUNCTION__;
2495}
2496
ef6653b1
DO
2497function ios12PickVLANCommand (&$work, $line)
2498{
2499 $matches = array();
2500 switch (TRUE)
2501 {
2502 case ($line == '---- -------------------------------- --------- -------------------------------'):
2503 // ignore the rest of VLAN table header;
2504 break;
2505 case (preg_match ('@! END OF VLAN LIST$@', $line)):
2506 return 'ios12ScanTopLevel';
2507 case (preg_match ('@^([[:digit:]]+) {1,4}.{32} active @', $line, $matches)):
2508 if (!array_key_exists ($matches[1], $work['vlanlist']))
2509 $work['vlanlist'][] = $matches[1];
2510 break;
2511 default:
2512 }
2513 return __FUNCTION__;
2514}
2515
f10dd5cc
DO
2516function iosParseVLANString ($string)
2517{
2518 $ret = array();
2519 foreach (explode (',', $string) as $item)
2520 {
2521 $matches = array();
735e323f 2522 $item = trim ($item, ' ');
f10dd5cc
DO
2523 if (preg_match ('/^([[:digit:]]+)$/', $item, $matches))
2524 $ret[] = $matches[1];
2525 elseif (preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $item, $matches))
2526 $ret = array_merge ($ret, range ($matches[1], $matches[2]));
2527 }
2528 return $ret;
2529}
2530
dbc00990
DO
2531// Another finite automata to read a dialect of Foundry configuration.
2532function fdry5ReadVLANConfig ($input)
2533{
ef6653b1
DO
2534 $ret = array
2535 (
2536 'vlanlist' => array(),
2537 'portdata' => array(),
2538 );
dbc00990
DO
2539 $procfunc = 'fdry5ScanTopLevel';
2540 foreach (explode ("\n", $input) as $line)
2541 $procfunc = $procfunc ($ret, $line);
2542 return $ret;
2543}
2544
2545function fdry5ScanTopLevel (&$work, $line)
2546{
2547 $matches = array();
2548 switch (TRUE)
2549 {
2550 case (preg_match ('@^vlan ([[:digit:]]+)( name .+)? (by port)$@', $line, $matches)):
ef6653b1
DO
2551 if (!array_key_exists ($matches[1], $work['vlanlist']))
2552 $work['vlanlist'][] = $matches[1];
dbc00990
DO
2553 $work['current'] = array ('vlan_id' => $matches[1]);
2554 return 'fdry5PickVLANSubcommand';
2555 case (preg_match ('@^interface ethernet ([[:digit:]]+/[[:digit:]]+/[[:digit:]]+)$@', $line, $matches)):
2556 $work['current'] = array ('port_name' => 'e' . $matches[1]);
2557 return 'fdry5PickInterfaceSubcommand';
2558 default:
2559 return __FUNCTION__;
2560 }
2561}
2562
2563function fdry5PickVLANSubcommand (&$work, $line)
2564{
2565 if ($line[0] != ' ') // end of VLAN section
2566 {
2567 unset ($work['current']);
2568 return 'fdry5ScanTopLevel';
2569 }
2570 // not yet
2571 $matches = array();
2572 switch (TRUE)
2573 {
2574 case (preg_match ('@^ tagged (.+)$@', $line, $matches)):
2575 // add current VLAN to 'allowed' list of each mentioned port
2576 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
ac210c9e
DO
2577 if (array_key_exists ($port_name, $work['portdata']))
2578 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
dbc00990 2579 else
ac210c9e 2580 $work['portdata'][$port_name] = array
dbc00990 2581 (
3dcfd931 2582 'mode' => 'trunk',
dbc00990
DO
2583 'allowed' => array ($work['current']['vlan_id']),
2584 'native' => 0, // can be updated later
2585 );
7b7c07da 2586 $work['portdata'][$port_name]['mode'] = 'trunk';
dbc00990
DO
2587 break;
2588 case (preg_match ('@^ untagged (.+)$@', $line, $matches)):
2589 // replace 'native' column of each mentioned port with current VLAN ID
2590 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
7b7c07da 2591 {
ac210c9e 2592 if (array_key_exists ($port_name, $work['portdata']))
dbc00990 2593 {
ac210c9e
DO
2594 $work['portdata'][$port_name]['native'] = $work['current']['vlan_id'];
2595 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
dbc00990
DO
2596 }
2597 else
ac210c9e 2598 $work['portdata'][$port_name] = array
dbc00990 2599 (
3dcfd931 2600 'mode' => 'access',
dbc00990
DO
2601 'allowed' => array ($work['current']['vlan_id']),
2602 'native' => $work['current']['vlan_id'],
2603 );
7b7c07da
DO
2604 // Untagged ports are initially assumed to be access ports, and
2605 // when this assumption is right, this is the final port mode state.
2606 // When the port is dual-mode one, this is detected and justified
2607 // later in "interface" section of config text.
2608 $work['portdata'][$port_name]['mode'] = 'access';
2609 }
dbc00990
DO
2610 break;
2611 default: // nom-nom
2612 }
2613 return __FUNCTION__;
2614}
2615
2616function fdry5PickInterfaceSubcommand (&$work, $line)
2617{
2618 if ($line[0] != ' ') // end of interface section
2619 {
2620 if (array_key_exists ('dual-mode', $work['current']))
2621 {
ac210c9e 2622 if (array_key_exists ($work['current']['port_name'], $work['portdata']))
dbc00990 2623 // update existing record
ac210c9e 2624 $work['portdata'][$work['current']['port_name']]['native'] = $work['current']['dual-mode'];
dbc00990
DO
2625 else
2626 // add new
ac210c9e 2627 $work['portdata'][$work['current']['port_name']] = array
dbc00990 2628 (
dbc00990
DO
2629 'allowed' => array ($work['current']['dual-mode']),
2630 'native' => $work['current']['dual-mode'],
2631 );
7b7c07da
DO
2632 // a dual-mode port is always considered a trunk port
2633 // (but not in the IronWare's meaning of "trunk") regardless of
2634 // number of assigned tagged VLANs
2635 $work['portdata'][$work['current']['port_name']]['mode'] = 'trunk';
dbc00990
DO
2636 }
2637 unset ($work['current']);
2638 return 'fdry5ScanTopLevel';
2639 }
2640 $matches = array();
2641 switch (TRUE)
2642 {
2643 case (preg_match ('@^ dual-mode( +[[:digit:]]+ *)?$@', $line, $matches)):
2644 // default VLAN ID for dual-mode command is 1
2645 $work['current']['dual-mode'] = strlen (trim ($matches[1])) ? trim ($matches[1]) : 1;
2646 break;
9cf66518 2647 // FIXME: trunk/link-aggregate/ip address pulls port from 802.1Q field
dbc00990
DO
2648 default: // nom-nom
2649 }
2650 return __FUNCTION__;
2651}
2652
2653function fdry5ParsePortString ($string)
2654{
2655 $ret = array();
2656 $tokens = explode (' ', trim ($string));
2657 while (count ($tokens))
2658 {
2659 $letters = array_shift ($tokens); // "ethe", "to"
2660 $numbers = array_shift ($tokens); // "x", "x/x", "x/x/x"
2661 switch ($letters)
2662 {
2663 case 'ethe':
2664 if ($prev_numbers != NULL)
2665 $ret[] = 'e' . $prev_numbers;
2666 $prev_numbers = $numbers;
2667 break;
2668 case 'to':
2669 $ret = array_merge ($ret, fdry5GenPortRange ($prev_numbers, $numbers));
2670 $prev_numbers = NULL; // no action on next token
2671 break;
2672 default: // ???
2673 return array();
2674 }
2675 }
2676 // flush delayed item
2677 if ($prev_numbers != NULL)
2678 $ret[] = 'e' . $prev_numbers;
2679 return $ret;
2680}
2681
2682// Take two indices in form "x", "x/x" or "x/x/x" and return the range of
2683// ports spanning from the first to the last. The switch software makes it
2684// easier to perform, because "ethe x/x/x to y/y/y" ranges never cross
2685// unit/slot boundary (every index except the last remains constant).
2686function fdry5GenPortRange ($from, $to)
2687{
2688 $matches = array();
2689 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $from, $matches))
2690 return array();
2691 $prefix = 'e' . $matches[1] . $matches[2];
2692 $from_idx = $matches[3];
2693 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $to, $matches))
2694 return array();
2695 $to_idx = $matches[3];
2696 for ($i = $from_idx; $i <= $to_idx; $i++)
2697 $ret[] = $prefix . $i;
2698 return $ret;
2699}
2700
eafdbb42
DO
2701// an implementation for Huawei syntax
2702function vrp53ReadVLANConfig ($input)
2703{
2704 $ret = array
2705 (
2706 'vlanlist' => array(),
2707 'portdata' => array(),
2708 );
2709 $procfunc = 'vrp53ScanTopLevel';
2710 foreach (explode ("\n", $input) as $line)
2711 $procfunc = $procfunc ($ret, $line);
2712 return $ret;
2713}
2714
2715function vrp53ScanTopLevel (&$work, $line)
2716{
2717 $matches = array();
2718 switch (TRUE)
2719 {
2720 case (preg_match ('@^ vlan batch (.+)$@', $line, $matches)):
2721 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
2722 $work['vlanlist'][] = $vlan_id;
2723 return __FUNCTION__;
66a28cce 2724 case (preg_match ('@^interface ((GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
eafdbb42
DO
2725 $matches[1] = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $matches[1]);
2726 $matches[1] = preg_replace ('@^XGigabitEthernet(.+)$@', 'xg\\1', $matches[1]);
66a28cce 2727 $matches[1] = preg_replace ('@^Eth-Trunk(.+)$@', 'et\\1', $matches[1]);
eafdbb42
DO
2728 $work['current'] = array ('port_name' => $matches[1]);
2729 return 'vrp53PickInterfaceSubcommand';
2730 default:
2731 return __FUNCTION__;
2732 }
2733}
2734
2735function vrp53ParseVLANString ($string)
2736{
2737 $string = preg_replace ('/ to /', '-', $string);
2738 $string = preg_replace ('/ /', ',', $string);
2739 return iosParseVLANString ($string);
2740}
2741
2742function vrp53PickInterfaceSubcommand (&$work, $line)
2743{
2744 if ($line[0] == '#') // end of interface section
2745 {
2746 // Configuration Guide - Ethernet 3.3.4:
2747 // "By default, the interface type is hybrid."
2748 if (!array_key_exists ('link-type', $work['current']))
2749 $work['current']['link-type'] = 'hybrid';
2750 if (!array_key_exists ('allowed', $work['current']))
2751 $work['current']['allowed'] = array();
2752 if (!array_key_exists ('native', $work['current']))
2753 $work['current']['native'] = 0;
2754 switch ($work['current']['link-type'])
2755 {
2756 case 'access':
7107574b
DO
2757 // VRP does not assign access ports to VLAN1 by default,
2758 // leaving them blocked.
2759 $work['portdata'][$work['current']['port_name']] =
2760 $work['current']['native'] ? array
2761 (
2762 'allowed' => $work['current']['allowed'],
2763 'native' => $work['current']['native'],
2764 'mode' => 'access',
2765 ) : array
2766 (
2767 'mode' => 'none',
2768 'allowed' => array(),
2769 'native' => 0,
2770 );
eafdbb42
DO
2771 break;
2772 case 'trunk':
2773 $work['portdata'][$work['current']['port_name']] = array
2774 (
2775 'allowed' => $work['current']['allowed'],
2776 'native' => 0,
7b7c07da 2777 'mode' => 'trunk',
eafdbb42
DO
2778 );
2779 break;
2780 case 'hybrid':
2781 $work['portdata'][$work['current']['port_name']] = array
2782 (
2783 'allowed' => $work['current']['allowed'],
2784 'native' => $work['current']['native'],
7b7c07da 2785 'mode' => 'trunk',
eafdbb42
DO
2786 );
2787 break;
2788 default: // dot1q-tunnel ?
2789 }
2790 unset ($work['current']);
2791 return 'vrp53ScanTopLevel';
2792 }
2793 $matches = array();
2794 switch (TRUE)
2795 {
2796 case (preg_match ('@^ port default vlan ([[:digit:]]+)$@', $line, $matches)):
2797 $work['current']['native'] = $matches[1];
2798 if (!array_key_exists ('allowed', $work['current']))
2799 $work['current']['allowed'] = array();
2800 if (!in_array ($matches[1], $work['current']['allowed']))
2801 $work['current']['allowed'][] = $matches[1];
2802 break;
2803 case (preg_match ('@^ port link-type (.+)$@', $line, $matches)):
2804 $work['current']['link-type'] = $matches[1];
2805 break;
2806 case (preg_match ('@^ port trunk allow-pass vlan (.+)$@', $line, $matches)):
2807 if (!array_key_exists ('allowed', $work['current']))
2808 $work['current']['allowed'] = array();
2809 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
2810 if (!in_array ($vlan_id, $work['current']['allowed']))
2811 $work['current']['allowed'][] = $vlan_id;
2812 break;
66a28cce 2813 // TODO: make sure, that a port with "eth-trunk" clause always ends up in "none" mode
eafdbb42
DO
2814 default: // nom-nom
2815 }
2816 return __FUNCTION__;
2817}
2818
18216a3b
DO
2819function nxos4Read8021QConfig ($input)
2820{
48400875
DO
2821 $ret = array
2822 (
2823 'vlanlist' => array(),
2824 'portdata' => array(),
2825 );
2826 $procfunc = 'nxos4ScanTopLevel';
2827 foreach (explode ("\n", $input) as $line)
2828 $procfunc = $procfunc ($ret, $line);
2829 return $ret;
2830}
2831
2832function nxos4ScanTopLevel (&$work, $line)
2833{
2834 $matches = array();
2835 switch (TRUE)
2836 {
2837 case (preg_match ('@^interface ((Ethernet)[[:digit:]]+(/[[:digit:]]+)*)$@', $line, $matches)):
2838 $matches[1] = preg_replace ('@^Ethernet(.+)$@', 'e\\1', $matches[1]);
2839 $work['current'] = array ('port_name' => $matches[1]);
2840 return 'nxos4PickSwitchportCommand';
2841 case (preg_match ('@^vlan ([[:digit:]]+)$@', $line, $matches)):
2842 $work['vlanlist'][] = $matches[1];
2843 return 'nxos4PickVLANs';
2844 default:
2845 return __FUNCTION__; // continue scan
2846 }
2847}
2848
2849function nxos4PickVLANs (&$work, $line)
2850{
2851 switch (TRUE)
2852 {
2853 case ($line == ''): // end of VLAN list
2854 return 'nxos4ScanTopLevel';
2855 case (preg_match ('@^vlan ([[:digit:]]+)$@', $line, $matches)):
2856 $work['vlanlist'][] = $matches[1];
2857 default: // VLAN name or any other text
2858 return __FUNCTION__;
2859 }
2860}
2861
2862function nxos4PickSwitchportCommand (&$work, $line)
2863{
2864 if ($line == '') // end of interface section
2865 {
2866 // fill in defaults
2867 // below assumes "system default switchport" mode set on the device
2868 if (!array_key_exists ('mode', $work['current']))
2869 $work['current']['mode'] = 'access';
2870 // save work, if it makes sense
2871 switch ($work['current']['mode'])
2872 {
2873 case 'access':
2874 if (!array_key_exists ('access vlan', $work['current']))
2875 $work['current']['access vlan'] = 1;
2876 $work['portdata'][$work['current']['port_name']] = array
2877 (
2878 'mode' => 'access',
2879 'allowed' => array ($work['current']['access vlan']),
2880 'native' => $work['current']['access vlan'],
2881 );
2882 break;
2883 case 'trunk':
2884 if (!array_key_exists ('trunk native vlan', $work['current']))
2885 $work['current']['trunk native vlan'] = 1;
2886 if (!array_key_exists ('trunk allowed vlan', $work['current']))
2887 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID, VLAN_MAX_ID);
2888 // Having configured VLAN as "native" doesn't mean anything
2889 // as long as it's not listed on the "allowed" line.
2890 $effective_native = in_array
2891 (
2892 $work['current']['trunk native vlan'],
2893 $work['current']['trunk allowed vlan']
2894 ) ? $work['current']['trunk native vlan'] : 0;
2895 $work['portdata'][$work['current']['port_name']] = array
2896 (
2897 'mode' => 'trunk',
2898 'allowed' => $work['current']['trunk allowed vlan'],
2899 'native' => $effective_native,
2900 );
2901 break;
2902 default:
2903 // dot1q-tunnel, dynamic, private-vlan --- skip these
2904 }
2905 unset ($work['current']);
2906 return 'nxos4ScanTopLevel';
2907 }
2908 // not yet
2909 $matches = array();
2910 switch (TRUE)
2911 {
2912 case (preg_match ('@^ switchport mode (.+)$@', $line, $matches)):
2913 $work['current']['mode'] = $matches[1];
2914 break;
2915 case (preg_match ('@^ switchport access vlan (.+)$@', $line, $matches)):
2916 $work['current']['access vlan'] = $matches[1];
2917 break;
2918 case (preg_match ('@^ switchport trunk native vlan (.+)$@', $line, $matches)):
2919 $work['current']['trunk native vlan'] = $matches[1];
2920 break;
2921 case (preg_match ('@^ switchport trunk allowed vlan add (.+)$@', $line, $matches)):
2922 $work['current']['trunk allowed vlan'] = array_merge
2923 (
2924 $work['current']['trunk allowed vlan'],
2925 iosParseVLANString ($matches[1])
2926 );
2927 break;
2928 case (preg_match ('@^ switchport trunk allowed vlan (.+)$@', $line, $matches)):
2929 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
2930 break;
2931 default: // suppress warning on irrelevant config clause
2932 }
2933 return __FUNCTION__;
18216a3b
DO
2934}
2935
bb35bb93
DO
2936// Scan given array and return the key, which addresses the first item
2937// with requested column set to given value (or NULL if there is none such).
19350222
DO
2938// Note that 0 and NULL mean completely different things and thus
2939// require strict checking (=== and !===).
dbc00990
DO
2940function scanArrayForItem ($table, $scan_column, $scan_value)
2941{
2942 foreach ($table as $key => $row)
2943 if ($row[$scan_column] == $scan_value)
2944 return $key;
2945 return NULL;
2946}
2947
f56f0f20
DO
2948// Get a list of VLAN management pseudo-commands and return a text
2949// of real vendor-specific commands, which implement the work.
3f61f175
DO
2950// This work is done in two rounds:
2951// 1. For "add allowed" and "rem allowed" commands detect continuous
2952// sequences of VLAN IDs and replace them with ranges of form "A-B",
2953// where B>A.
2954// 2. Iterate over the resulting list and produce real CLI commands.
f56f0f20
DO
2955function ios12TranslatePushQueue ($queue)
2956{
3dcfd931 2957 $ret = "configure terminal\n";
cc6a6c4e 2958 foreach ($queue as $cmd)
f56f0f20
DO
2959 switch ($cmd['opcode'])
2960 {
2961 case 'create VLAN':
2962 $ret .= "vlan ${cmd['arg1']}\nexit\n";
2963 break;
2964 case 'destroy VLAN':
2965 $ret .= "no vlan ${cmd['arg1']}\n";
2966 break;
2967 case 'add allowed':
f56f0f20 2968 case 'rem allowed':
cc6a6c4e
DO
2969 $clause = $cmd['opcode'] == 'add allowed' ? 'add' : 'remove';
2970 $ret .= "interface ${cmd['port']}\n";
2971 foreach (listToRanges ($cmd['vlans']) as $range)
2972 $ret .= "switchport trunk allowed vlan ${clause} " .
2973 ($range['from'] == $range['to'] ? $range['to'] : "${range['from']}-${range['to']}") .
2974 "\n";
2975 $ret .= "exit\n";
f56f0f20
DO
2976 break;
2977 case 'set native':
2978 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan ${cmd['arg2']}\nexit\n";
2979 break;
2980 case 'unset native':
2981 $ret .= "interface ${cmd['arg1']}\nno switchport trunk native vlan ${cmd['arg2']}\nexit\n";
2982 break;
3dcfd931
DO
2983 case 'set access':
2984 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
2985 break;
2986 case 'unset access':
2987 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
2988 break;
2989 case 'set mode':
0e353a73
DO
2990 $ret .= "interface ${cmd['arg1']}\nswitchport mode ${cmd['arg2']}\n";
2991 if ($cmd['arg2'] == 'trunk')
2992 $ret .= "no switchport trunk native vlan\nswitchport trunk allowed vlan none\n";
2993 $ret .= "exit\n";
3dcfd931 2994 break;
f56f0f20
DO
2995 }
2996 $ret .= "end\n";
2997 return $ret;
2998}
2999
3000function fdry5TranslatePushQueue ($queue)
3001{
3002 $ret = "conf t\n";
3003 foreach ($queue as $cmd)
3004 switch ($cmd['opcode'])
3005 {
3006 case 'create VLAN':
3007 $ret .= "vlan ${cmd['arg1']}\nexit\n";
3008 break;
3009 case 'destroy VLAN':
3010 $ret .= "no vlan ${cmd['arg1']}\n";
3011 break;
3012 case 'add allowed':
cc6a6c4e
DO
3013 foreach ($cmd['vlans'] as $vlan_id)
3014 $ret .= "vlan ${vlan_id}\ntagged ${cmd['port']}\nexit\n";
f56f0f20
DO
3015 break;
3016 case 'rem allowed':
cc6a6c4e
DO
3017 foreach ($cmd['vlans'] as $vlan_id)
3018 $ret .= "vlan ${vlan_id}\nno tagged ${cmd['port']}\nexit\n";
f56f0f20
DO
3019 break;
3020 case 'set native':
3021 $ret .= "interface ${cmd['arg1']}\ndual-mode ${cmd['arg2']}\nexit\n";
3022 break;
3023 case 'unset native':
3024 $ret .= "interface ${cmd['arg1']}\nno dual-mode ${cmd['arg2']}\nexit\n";
3025 break;
3dcfd931
DO
3026 case 'set access':
3027 $ret .= "vlan ${cmd['arg2']}\nuntagged ${cmd['arg1']}\nexit\n";
3028 break;
3029 case 'unset access':
3030 $ret .= "vlan ${cmd['arg2']}\nno untagged ${cmd['arg1']}\nexit\n";
3031 break;
3032 case 'set mode': // NOP
3033 break;
f56f0f20
DO
3034 }
3035 $ret .= "end\n";
a18c2042 3036 return $ret;
5456f90f
DO
3037}
3038
d74013ee
DO
3039function vrp53TranslatePushQueue ($queue)
3040{
3041 $ret = "system-view\n";
3042 foreach ($queue as $cmd)
3043 switch ($cmd['opcode'])
3044 {
3045 case 'create VLAN':
3046 $ret .= "vlan ${cmd['arg1']}\nquit\n";
3047 break;
3048 case 'destroy VLAN':
3049 $ret .= "undo vlan ${cmd['arg1']}\n";
3050 break;
3051 case 'add allowed':
d74013ee 3052 case 'rem allowed':
cc6a6c4e
DO
3053 $clause = $cmd['opcode'] == 'add allowed' ? '' : 'undo ';
3054 $ret .= "interface ${cmd['port']}\n";
3055 foreach (listToRanges ($cmd['vlans']) as $range)
3056 $ret .= "${clause}port trunk allow-pass vlan " .
3057 ($range['from'] == $range['to'] ? $range['to'] : "${range['from']} to ${range['to']}") .
3058 "\n";
3059 $ret .= "quit\n";
d74013ee
DO
3060 break;
3061 case 'set native':
3dcfd931 3062 case 'set access':
d74013ee
DO
3063 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
3064 break;
3065 case 'unset native':
3dcfd931 3066 case 'unset access':
d74013ee
DO
3067 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
3068 break;
3dcfd931
DO
3069 case 'set mode':
3070 $modemap = array ('access' => 'access', 'trunk' => 'hybrid');
0e353a73
DO
3071 $ret .= "interface ${cmd['arg1']}\nport link-type " . $modemap[$cmd['arg2']] . "\n";
3072 if ($cmd['arg2'] == 'hybrid')
3073 $ret .= "undo port default vlan\nundo port trunk allow-pass vlan all\n";
3074 $ret .= "quit\n";
3dcfd931 3075 break;
d74013ee
DO
3076 }
3077 $ret .= "return\n";
3078 return $ret;
3079}
3080
66658512
DO
3081// Return TRUE, if every value of A1 is present in A2 and vice versa,
3082// regardless of each array's sort order and indexing.
3083function array_values_same ($a1, $a2)
3084{
3085 return !count (array_diff ($a1, $a2)) and !count (array_diff ($a2, $a1));
3086}
3087
1ce258f7
DO
3088// Use the VLAN switch template to set VST role for each port of
3089// the provided list. Return resulting list.
bc254f49 3090function apply8021QOrder ($vst_id, $portlist)
25930440 3091{
bc254f49 3092 $vst = getVLANSwitchTemplate ($vst_id);
1ce258f7
DO
3093 foreach (array_keys ($portlist) as $port_name)
3094 {
3095 foreach ($vst['rules'] as $rule)
3096 if (preg_match ($rule['port_pcre'], $port_name))
25930440
DO
3097 {
3098 $portlist[$port_name]['vst_role'] = $rule['port_role'];
091768aa 3099 $portlist[$port_name]['wrt_vlans'] = buildVLANFilter ($rule['port_role'], $rule['wrt_vlans']);
1ce258f7 3100 continue 2;
25930440 3101 }
1ce258f7
DO
3102 $portlist[$port_name]['vst_role'] = 'none';
3103 }
25930440
DO
3104 return $portlist;
3105}
3106
091768aa
DO
3107// return a sequence of ranges for given string form and port role
3108function buildVLANFilter ($role, $string)
be28b696 3109{
091768aa 3110 // set base
1ce258f7 3111 switch ($role)
be28b696 3112 {
1ce258f7
DO
3113 case 'access': // 1-4094
3114 $min = VLAN_MIN_ID;
3115 $max = VLAN_MAX_ID;
3116 break;
3117 case 'trunk': // 2-4094
3118 case 'uplink':
65da0c15 3119 case 'downlink':
1ce258f7
DO
3120 $min = VLAN_MIN_ID + 1;
3121 $max = VLAN_MAX_ID;
3122 break;
65da0c15 3123 default: // none
1ce258f7 3124 return array();
be28b696 3125 }
091768aa
DO
3126 if ($string == '') // fast track
3127 return array (array ('from' => $min, 'to' => $max));
3128 // transform
3129 $vlanidlist = array();
1ce258f7
DO
3130 foreach (iosParseVLANString ($string) as $vlan_id)
3131 if ($min <= $vlan_id and $vlan_id <= $max)
091768aa 3132 $vlanidlist[] = $vlan_id;
cc6a6c4e
DO
3133 return listToRanges ($vlanidlist);
3134}
3135
3136// pack set of integers into list of integer ranges
3137// e.g. (1, 2, 3, 5, 6, 7, 9, 11) => ((1, 3), (5, 7), (9, 9), (11, 11))
3138function listToRanges ($vlanidlist)
3139{
091768aa
DO
3140 sort ($vlanidlist);
3141 $ret = array();
3142 $from = $to = NULL;
3143 foreach ($vlanidlist as $vlan_id)
3144 if ($from == NULL)
3145 $from = $to = $vlan_id;
3146 elseif ($to + 1 == $vlan_id)
3147 $to = $vlan_id;
3148 else
3149 {
3150 $ret[] = array ('from' => $from, 'to' => $to);
3151 $from = $to = $vlan_id;
3152 }
3153 if ($from != NULL)
3154 $ret[] = array ('from' => $from, 'to' => $to);
be28b696
DO
3155 return $ret;
3156}
3157
091768aa
DO
3158// return TRUE, if given VLAN ID belongs to one of filter's ranges
3159function matchVLANFilter ($vlan_id, $vfilter)
3160{
3161 foreach ($vfilter as $range)
3162 if ($range['from'] <= $vlan_id and $vlan_id <= $range['to'])
3163 return TRUE;
3164 return FALSE;
3165}
3166
bcd14540
DO
3167function exportSwitch8021QConfig
3168(
3169 $vswitch,
3170 $device_vlanlist,
3171 $before,
3172 $changes
3173)
3174{
3175 // only ignore VLANs, which exist and are explicitly shown as "alien"
3176 $old_managed_vlans = array();
3177 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3178 foreach ($device_vlanlist as $vlan_id)
3179 if
3180 (
3181 !array_key_exists ($vlan_id, $domain_vlanlist) or
3182 $domain_vlanlist[$vlan_id]['vlan_type'] != 'alien'
3183 )
3184 $old_managed_vlans[] = $vlan_id;
3185 $ports_to_do = array();
3186 $after = $before;
3187 foreach ($changes as $port_name => $port)
3188 {
3189 $ports_to_do[$port_name] = array
3190 (
3191 'old_mode' => $before[$port_name]['mode'],
3192 'old_allowed' => $before[$port_name]['allowed'],
3193 'old_native' => $before[$port_name]['native'],
3194 'new_mode' => $port['mode'],
3195 'new_allowed' => $port['allowed'],
3196 'new_native' => $port['native'],
3197 );
3198 $after[$port_name] = $port;
3199 }
3200 // New VLAN table is a union of:
3201 // 1. all compulsory VLANs
3202 // 2. all "current" non-alien allowed VLANs of those ports, which are left
3203 // intact (regardless if a VLAN exists in VLAN domain, but looking,
3204 // if it is present in device's own VLAN table)
3205 // 3. all "new" allowed VLANs of those ports, which we do "push" now
3206 // Like for old_managed_vlans, a VLANs is never listed, only if it
3207 // exists and belongs to "alien" type.
3208 $new_managed_vlans = array();
3209 // 1
3210 foreach ($domain_vlanlist as $vlan_id => $vlan)
3211 if ($vlan['vlan_type'] == 'compulsory')
3212 $new_managed_vlans[] = $vlan_id;
3213 // 2
3214 foreach ($before as $port_name => $port)
3215 if (!array_key_exists ($port_name, $changes))
3216 foreach ($port['allowed'] as $vlan_id)
3217 {
3218 if (in_array ($vlan_id, $new_managed_vlans))
3219 continue;
3220 if
3221 (
3222 array_key_exists ($vlan_id, $domain_vlanlist) and
3223 $domain_vlanlist[$vlan_id]['vlan_type'] == 'alien'
3224 )
3225 continue;
3226 if (in_array ($vlan_id, $device_vlanlist))
3227 $new_managed_vlans[] = $vlan_id;
3228 }
3229 // 3
3230 foreach ($changes as $port)
10b6b476 3231 foreach ($port['allowed'] as $vlan_id)
bcd14540
DO
3232 if
3233 (
3234 $domain_vlanlist[$vlan_id]['vlan_type'] == 'ondemand' and
3235 !in_array ($vlan_id, $new_managed_vlans)
3236 )
3237 $new_managed_vlans[] = $vlan_id;
3238 $crq = array();
3239 // Before removing each old VLAN as such it is necessary to unassign
3240 // ports from it (to remove VLAN from each ports' list of "allowed"
3241 // VLANs). This change in turn requires, that a port's "native"
3242 // VLAN isn't set to the one being removed from its "allowed" list.
3243 foreach ($ports_to_do as $port_name => $port)
3244 switch ($port['old_mode'] . '->' . $port['new_mode'])
3245 {
3246 case 'trunk->trunk':
3247 // "old" native is set and differs from the "new" native
3248 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
3249 $crq[] = array
3250 (
3251 'opcode' => 'unset native',
3252 'arg1' => $port_name,
3253 'arg2' => $port['old_native'],
3254 );
cc6a6c4e 3255 if (count ($tmp = array_diff ($port['old_allowed'], $port['new_allowed'])))
bcd14540
DO
3256 $crq[] = array
3257 (
3258 'opcode' => 'rem allowed',
cc6a6c4e
DO
3259 'port' => $port_name,
3260 'vlans' => $tmp,
bcd14540
DO
3261 );
3262 break;
3263 case 'access->access':
3264 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
3265 $crq[] = array
3266 (
3267 'opcode' => 'unset access',
3268 'arg1' => $port_name,
3269 'arg2' => $port['old_native'],
3270 );
3271 break;
3272 case 'access->trunk':
3273 $crq[] = array
3274 (
3275 'opcode' => 'unset access',
3276 'arg1' => $port_name,
3277 'arg2' => $port['old_native'],
3278 );
3279 break;
3280 case 'trunk->access':
3281 $crq[] = array
3282 (
3283 'opcode' => 'unset native',
3284 'arg1' => $port_name,
3285 'arg2' => $port['old_native'],
3286 );
cc6a6c4e 3287 if (count ($port['old_allowed']))
bcd14540
DO
3288 $crq[] = array
3289 (
3290 'opcode' => 'rem allowed',
cc6a6c4e
DO
3291 'port' => $port_name,
3292 'vlans' => $port['old_allowed'],
bcd14540
DO
3293 );
3294 break;
3295 default:
3296 throw new RuntimeException ('error in ports_to_do structure');
3297 }
3298 // Now it is safe to unconfigure VLANs, which still exist on device,
3299 // but are not present on the "new" list.
3300 // FIXME: put all IDs into one pseudo-command to make it easier
3301 // for translators to create/destroy VLANs in batches, where
3302 // target platform allows them to do.
3303 foreach (array_diff ($old_managed_vlans, $new_managed_vlans) as $vlan_id)
3304 $crq[] = array
3305 (
3306 'opcode' => 'destroy VLAN',
3307 'arg1' => $vlan_id,
3308 );
3309 // Configure VLANs, which must be present on the device, but are not yet.
3310 foreach (array_diff ($new_managed_vlans, $old_managed_vlans) as $vlan_id)
3311 $crq[] = array
3312 (
3313 'opcode' => 'create VLAN',
3314 'arg1' => $vlan_id,
3315 );
3316 // Now, when all new VLANs are created (queued), it is safe to assign (queue)
3317 // ports to the new VLANs.
3318 foreach ($ports_to_do as $port_name => $port)
3319 switch ($port['old_mode'] . '->' . $port['new_mode'])
3320 {
3321 case 'trunk->trunk':
3322 // For each allowed VLAN, which is present on the "new" list and missing from
3323 // the "old" one, queue a command to assign current port to that VLAN.
cc6a6c4e 3324 if (count ($tmp = array_diff ($port['new_allowed'], $port['old_allowed'])))
bcd14540
DO
3325 $crq[] = array
3326 (
3327 'opcode' => 'add allowed',
cc6a6c4e
DO
3328 'port' => $port_name,
3329 'vlans' => $tmp,
bcd14540
DO
3330 );
3331 // One of the "allowed" VLANs for this port may probably be "native".
3332 // "new native" is set and differs from "old native"
3333 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
3334 $crq[] = array
3335 (
3336 'opcode' => 'set native',
3337 'arg1' => $port_name,
3338 'arg2' => $port['new_native'],
3339 );
3340 break;
3341 case 'access->access':
3342 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
3343 $crq[] = array
3344 (
3345 'opcode' => 'set access',
3346 'arg1' => $port_name,
3347 'arg2' => $port['new_native'],
3348 );
3349 break;
3350 case 'access->trunk':
3351 $crq[] = array
3352 (
3353 'opcode' => 'set mode',
3354 'arg1' => $port_name,
3355 'arg2' => $port['new_mode'],
3356 );
cc6a6c4e 3357 if (count ($port['new_allowed']))
bcd14540
DO
3358 $crq[] = array
3359 (
3360 'opcode' => 'add allowed',
cc6a6c4e
DO
3361 'port' => $port_name,
3362 'vlans' => $port['new_allowed'],
bcd14540
DO
3363 );
3364 $crq[] = array
3365 (
3366 'opcode' => 'set native',
3367 'arg1' => $port_name,
3368 'arg2' => $port['new_native'],
3369 );
3370 break;
3371 case 'trunk->access':
3372 $crq[] = array
3373 (
3374 'opcode' => 'set mode',
3375 'arg1' => $port_name,
3376 'arg2' => $port['new_mode'],
3377 );
3378 $crq[] = array
3379 (
3380 'opcode' => 'set access',
3381 'arg1' => $port_name,
3382 'arg2' => $port['new_native'],
3383 );
3384 break;
3385 default:
3386 throw new RuntimeException ('error in ports_to_do structure');
3387 }
3388 setDevice8021QConfig ($vswitch['object_id'], $crq);
3389 return count ($crq);
3390}
3391
af204724 3392// filter list of changed ports to cancel changes forbidden by VST and domain
af204724
DO
3393function filter8021QChangeRequests
3394(
3395 $domain_vlanlist,
3396 $before, // current saved configuration of all ports
3397 $changes // changed ports with VST markup
3398)
3399{
3400 $domain_immune_vlans = array();
3401 foreach ($domain_vlanlist as $vlan_id => $vlan)
3402 if ($vlan['vlan_type'] == 'alien')
3403 $domain_immune_vlans[] = $vlan_id;
3404 $ret = array();
3405 foreach ($changes as $port_name => $port)
3406 {
af204724 3407 // find and cancel any changes regarding immune VLANs
034a61c9
DO
3408 switch ($port['vst_role'])
3409 {
3410 case 'access':
3411 if ($port['mode'] != 'access') // VST violation
3412 continue 2; // ignore change request
3413 foreach ($domain_immune_vlans as $immune)
af204724
DO
3414 // Reverting an attempt to set an access port from
3415 // "normal" VLAN to immune one (or vice versa) requires
3416 // special handling, becase the calling function has
3417 // discarded the old contents of 'allowed' for current port.
af204724
DO
3418 if
3419 (
3420 $before[$port_name]['native'] == $immune or
3421 $port['native'] == $immune
3422 )
3423 {
3424 $port['native'] = $before[$port_name]['native'];
3425 $port['allowed'] = array ($port['native']);
034a61c9
DO
3426 // Such reversal happens either once or never for an
3427 // access port.
3428 break;
af204724 3429 }
034a61c9
DO
3430 break;
3431 case 'trunk':
3432 case 'uplink':
3433 case 'downlink':
3434 if ($port['mode'] != 'trunk')
3435 continue 2;
3436 foreach ($domain_immune_vlans as $immune)
af204724
DO
3437 if (in_array ($immune, $before[$port_name]['allowed'])) // was allowed before
3438 {
3439 if (!in_array ($immune, $port['allowed']))
3440 $port['allowed'][] = $immune; // restore
3441 if ($before[$port_name]['native'] == $immune) // and was native
3442 $port['native'] = $immune; // also restore
3443 }
3444 else // wasn't
3445 {
3446 if (in_array ($immune, $port['allowed']))
3447 unset ($port['allowed'][array_search ($immune, $port['allowed'])]); // cancel
3448 if ($port['native'] == $immune)
3449 $port['native'] = $before[$port_name]['native'];
3450 }
034a61c9
DO
3451 break;
3452 default:
3453 continue 2;
3454 }
3455 // save work
af204724
DO
3456 $ret[$port_name] = $port;
3457 }
3458 return $ret;
3459}
3460
4741e9c3 3461// take port list with order applied and return uplink ports in the same format
ca7f0af4 3462function produceUplinkPorts ($domain_vlanlist, $portlist)
4741e9c3
DO
3463{
3464 $ret = array();
ca7f0af4
DO
3465 $employed = array();
3466 foreach ($domain_vlanlist as $vlan_id => $vlan)
3467 if ($vlan['vlan_type'] == 'compulsory')
3468 $employed[] = $vlan_id;
3469 foreach ($portlist as $port_name => $port)
3470 if ($port['vst_role'] != 'uplink')
3471 foreach ($port['allowed'] as $vlan_id)
3472 if (!in_array ($vlan_id, $employed))
3473 $employed[] = $vlan_id;
3474 foreach ($portlist as $port_name => $port)
3475 if ($port['vst_role'] == 'uplink')
3476 {
1ce258f7
DO
3477 $employed_here = array();
3478 foreach ($employed as $vlan_id)
091768aa 3479 if (matchVLANFilter ($vlan_id, $port['wrt_vlans']))
1ce258f7 3480 $employed_here[] = $vlan_id;
ca7f0af4
DO
3481 $ret[$port_name] = array
3482 (
034a61c9 3483 'vst_role' => 'uplink',
ca7f0af4
DO
3484 'mode' => 'trunk',
3485 'allowed' => $employed_here,
3486 'native' => 0,
3487 );
3488 }
4741e9c3
DO
3489 return $ret;
3490}
3491
9c45ea37
DO
3492function same8021QConfigs ($a, $b)
3493{
3494 return $a['mode'] == $b['mode'] &&
3495 array_values_same ($a['allowed'], $b['allowed']) &&
3496 $a['native'] == $b['native'];
3497}
3498
26dec09b
DO
3499/*
3500
3501Relation between desired (D), cached (C) and running (R)
3502copies of switch ports (P) list.
3503
3504 D C R
3505+---+ +---+ +---+
3506| P |-----| P |-? +--| P |
3507+---+ +---+ / +---+
3508| P |-----| P |--+ ?-| P |
3509+---+ +---+ +---+
3510| P |-----| P |-------| P |
3511+---+ +---+ +---+
3512| P |-----| P |--+ ?-| P |
3513+---+ +---+ \ +---+
3514| P |-----| P |--+ +--| P |
3515+---+ +---+ \ +---+
3516 +--| P |
3517 +---+
3518 ?-| P |
3519 +---+
3520
3521A modified local version of a port in "conflict" state ignores remote
3522changes until remote change maintains its difference. Once both edits
3523match, the local copy "locks" on the remote and starts tracking it.
3524
3525v
3526a "o" -- remOte version
3527l "l" -- Local version
3528u "b" -- Both versions
3529e
3530
3531^
3532| o b
3533| o
3534| l l l l l l b b
3535| o o b
3536| o b
3537|
3538| o
3539|
3540|
35410----------------------------------------------> time
3542
3543*/
be28b696
DO
3544function get8021QSyncOptions
3545(
3546 $vswitch,
3547 $D, // desired config
3548 $C, // cached config
3549 $R // running-config
3550)
3551{
3552 $default_port = array
3553 (
3554 'mode' => 'access',
3555 'allowed' => array (VLAN_DFL_ID),
3556 'native' => VLAN_DFL_ID,
3557 );
ab25b0d0 3558 $ret = array();
24832534 3559 $allports = array();
ab25b0d0 3560 foreach (array_unique (array_merge (array_keys ($C), array_keys ($R))) as $pn)
d3082829
DO
3561 $allports[$pn] = array();
3562 foreach (apply8021QOrder ($vswitch['template_id'], $allports) as $pn => $port)
be28b696 3563 {
7a375475
DO
3564 // catch anomalies early
3565 if ($port['vst_role'] == 'none')
3566 {
3567 if ((!array_key_exists ($pn, $R) or $R[$pn]['mode'] == 'none') and !array_key_exists ($pn, $C))
3568 $ret[$pn] = array ('status' => 'none');
3569 else
3570 $ret[$pn] = array
3571 (
3572 'status' => 'martian_conflict',
3573 'left' => array_key_exists ($pn, $C) ? $C[$pn] : array ('mode' => 'none'),
3574 'right' => array_key_exists ($pn, $R) ? $R[$pn] : array ('mode' => 'none'),
3575 );
3576 continue;
3577 }
3578 elseif ((!array_key_exists ($pn, $R) or $R[$pn]['mode'] == 'none') and array_key_exists ($pn, $C))
3579 {
3580 $ret[$pn] = array
3581 (
3582 'status' => 'martian_conflict',
3583 'left' => array_key_exists ($pn, $C) ? $C[$pn] : array ('mode' => 'none'),
3584 'right' => array_key_exists ($pn, $R) ? $R[$pn] : array ('mode' => 'none'),
3585 );
3586 continue;
3587 }
26dec09b
DO
3588 // (DC_): port missing from device
3589 if (!array_key_exists ($pn, $R))
be28b696 3590 {
ef016293
DO
3591 $ret[$pn] = array ('left' => $D[$pn]);
3592 if (same8021QConfigs ($D[$pn], $default_port))
3593 $ret[$pn]['status'] = 'ok_to_delete';
3594 else
3595 {
3596 $ret[$pn]['status'] = 'delete_conflict';
3597 $ret[$pn]['lastseen'] = $C[$pn];
3598 }
be28b696
DO
3599 continue;
3600 }
26dec09b
DO
3601 // (__R): port missing from DB
3602 if (!array_key_exists ($pn, $C))
be28b696 3603 {
7a375475
DO
3604 // Allow importing any configuration, which passes basic
3605 // validation. If port mode doesn't match its VST role,
3606 // this will be handled later WRT each port.
ab25b0d0
DO
3607 $ret[$pn] = array
3608 (
7a375475 3609 'status' => acceptable8021QConfig ($R[$pn]) ? 'ok_to_add' : 'add_conflict',
ab25b0d0
DO
3610 'right' => $R[$pn],
3611 );
be28b696
DO
3612 continue;
3613 }
3614 $D_eq_C = same8021QConfigs ($D[$pn], $C[$pn]);
3615 $C_eq_R = same8021QConfigs ($C[$pn], $R[$pn]);
26dec09b 3616 // (DCR), D = C = R: data in sync
be28b696 3617 if ($D_eq_C and $C_eq_R) // implies D == R
ab25b0d0
DO
3618 {
3619 $ret[$pn] = array
3620 (
3621 'status' => 'in_sync',
3622 'both' => $R[$pn],
3623 );
be28b696 3624 continue;
ab25b0d0 3625 }
26dec09b 3626 // (DCR), D = C: no local edit in the way
be28b696 3627 if ($D_eq_C)
ab25b0d0
DO
3628 $ret[$pn] = array
3629 (
3630 'status' => 'ok_to_pull',
3631 'left' => $D[$pn],
3632 'right' => $R[$pn],
3633 );
26dec09b 3634 // (DCR), C = R: no remote edit in the way
be28b696 3635 elseif ($C_eq_R)
ab25b0d0
DO
3636 $ret[$pn] = array
3637 (
3638 'status' => 'ok_to_push',
3639 'left' => $D[$pn],
3640 'right' => $R[$pn],
3641 );
26dec09b 3642 // (DCR), D = R: end of version conflict, restore tracking
be28b696 3643 elseif (same8021QConfigs ($D[$pn], $R[$pn]))
ab25b0d0
DO
3644 $ret[$pn] = array
3645 (
d973196a 3646 'status' => 'ok_to_merge',
ab25b0d0
DO
3647 'both' => $R[$pn],
3648 );
26dec09b 3649 else // D != C, C != R, D != R: version conflict
ab25b0d0
DO
3650 $ret[$pn] = array
3651 (
d3082829
DO
3652 'status' => ($port['vst_role'] == 'access' or $port['vst_role'] == 'trunk') ?
3653 // In case the port is normally updated by user, let him
3654 // resolve the conflict. If the system manages this port,
3655 // arrange the data to let remote version go down.
3656 'merge_conflict' : 'ok_to_push_with_merge',
ab25b0d0
DO
3657 'left' => $D[$pn],
3658 'right' => $R[$pn],
3659 );
be28b696 3660 }
ab25b0d0 3661 return $ret;
be28b696
DO
3662}
3663
ca5d4cbc 3664// return number of records updated successfully of FALSE, if a conflict was in the way
d973196a 3665function exec8021QDeploy ($object_id, $do_push)
ca5d4cbc
DO
3666{
3667 global $dbxlink;
b3a27170 3668 $nsaved = $npushed = $nsaved_uplinks = 0;
ca5d4cbc
DO
3669 $dbxlink->beginTransaction();
3670 if (NULL === $vswitch = getVLANSwitchInfo ($object_id, 'FOR UPDATE'))
3671 throw new InvalidArgException ('object_id', $object_id, 'VLAN domain is not set for this object');
3672 $D = getStored8021QConfig ($vswitch['object_id'], 'desired');
3673 $C = getStored8021QConfig ($vswitch['object_id'], 'cached');
d973196a
DO
3674 try
3675 {
3676 $R = getRunning8021QConfig ($vswitch['object_id']);
3677 }
3678 catch (Exception $e)
3679 {
3680 $prepared = $dbxlink->prepare ("UPDATE VLANSwitch SET last_errno = ?, last_error_ts = NOW() WHERE object_id = ?");
3681 $prepared->execute (array (E_8021Q_PULL_REMOTE_ERROR, $vswitch['object_id']));
3682 $dbxlink->commit();
3683 return 0;
3684 }
ca5d4cbc 3685 $conflict = FALSE;
ca5d4cbc 3686 $ok_to_push = array();
d973196a 3687 foreach (get8021QSyncOptions ($vswitch, $D, $C, $R['portdata']) as $pn => $port)
ca5d4cbc
DO
3688 {
3689 // always update cache with new data from switch
3690 switch ($port['status'])
3691 {
d973196a
DO
3692 case 'ok_to_merge':
3693 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['both']);
ca5d4cbc
DO
3694 break;
3695 case 'ok_to_delete':
d973196a 3696 $nsaved += del8021QPort ($vswitch['object_id'], $pn);
ca5d4cbc
DO
3697 break;
3698 case 'ok_to_add':
d973196a 3699 $nsaved += add8021QPort ($vswitch['object_id'], $pn, $port['right']);
ca5d4cbc
DO
3700 break;
3701 case 'delete_conflict':
3702 case 'merge_conflict':
7a375475
DO
3703 case 'add_conflict':
3704 case 'martian_conflict':
ca5d4cbc
DO
3705 $conflict = TRUE;
3706 break;
3707 case 'ok_to_pull':
d973196a
DO
3708 upd8021QPort ('desired', $vswitch['object_id'], $pn, $port['right']);
3709 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right']);
3710 $nsaved++;
ca5d4cbc 3711 break;
d3082829
DO
3712 case 'ok_to_push_with_merge':
3713 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right']);
3714 // fall through
ca5d4cbc
DO
3715 case 'ok_to_push':
3716 $ok_to_push[$pn] = $port['left'];
3717 break;
3718 }
3719 }
32c7fad1
DO
3720 // redo uplinks unconditionally
3721 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3722 $Dnew = apply8021QOrder ($vswitch['template_id'], getStored8021QConfig ($vswitch['object_id'], 'desired'));
034a61c9
DO
3723 // Take new "desired" configuration and derive uplink port configuration
3724 // from it. Then cancel changes to immune VLANs and save resulting
3725 // changes (if any left).
3726 $new_uplinks = filter8021QChangeRequests ($domain_vlanlist, $Dnew, produceUplinkPorts ($domain_vlanlist, $Dnew));
b3a27170
DO
3727 $nsaved_uplinks += replace8021QPorts ('desired', $vswitch['object_id'], $Dnew, $new_uplinks);
3728 if ($nsaved + $nsaved_uplinks)
94080e1c 3729 {
32c7fad1
DO
3730 // saved configuration has changed (either "user" ports have changed,
3731 // or uplinks, or both), so bump revision number up)
d973196a 3732 $prepared = $dbxlink->prepare ('UPDATE VLANSwitch SET mutex_rev = mutex_rev + 1, last_change = NOW(), out_of_sync = "yes" WHERE object_id = ?');
94080e1c 3733 $prepared->execute (array ($vswitch['object_id']));
ca5d4cbc 3734 }
d973196a 3735 if ($conflict)
ca5d4cbc 3736 {
e6579841 3737 $prepared = $dbxlink->prepare ('UPDATE VLANSwitch SET out_of_sync = "yes", last_errno = ?, last_error_ts = NOW() WHERE object_id = ?');
d973196a
DO
3738 $prepared->execute (array (E_8021Q_VERSION_CONFLICT, $vswitch['object_id']));
3739 }
52ce8bd7 3740 else
d973196a 3741 {
52ce8bd7
DO
3742 $prepared = $dbxlink->prepare ("UPDATE VLANSwitch SET last_errno = ?, last_error_ts = NOW() WHERE object_id = ?");
3743 $prepared->execute (array (E_8021Q_NOERROR, $vswitch['object_id']));
4799a8df
DO
3744 // Modified uplinks are very likely to differ from those in R-copy,
3745 // so don't mark device as clean, if this happened. This can cost
3746 // us an additional, empty round of sync, but at least out_of_sync
3747 // won't be mistakenly set to 'no'.
3748 // FIXME: A cleaner way of coupling pull and push operations would
3749 // be to split this function into two.
3750 if (!count ($ok_to_push) and !$nsaved_uplinks)
ca5d4cbc 3751 {
52ce8bd7 3752 $prepared = $dbxlink->prepare ("UPDATE VLANSwitch SET out_of_sync = 'no' WHERE object_id = ?");
d973196a
DO
3753 $prepared->execute (array ($vswitch['object_id']));
3754 }
52ce8bd7 3755 elseif ($do_push)
d973196a 3756 {
52ce8bd7
DO
3757 $prepared = $dbxlink->prepare ("UPDATE VLANSwitch SET last_push_started = NOW() WHERE object_id = ?");
3758 $prepared->execute (array ($vswitch['object_id']));
3759 try
3760 {
3761 $npushed += exportSwitch8021QConfig ($vswitch, $R['vlanlist'], $R['portdata'], $ok_to_push);
3762 // update cache for ports deployed
3763 replace8021QPorts ('cached', $vswitch['object_id'], $R['portdata'], $ok_to_push);
3764 $prepared = $dbxlink->prepare ('UPDATE VLANSwitch SET last_push_finished = NOW(), out_of_sync = "no", last_errno = ? WHERE object_id = ?');
3765 $prepared->execute (array (E_8021Q_NOERROR, $vswitch['object_id']));
3766 }
3767 catch (RuntimeException $r)
3768 {
e6579841 3769 $prepared = $dbxlink->prepare ('UPDATE VLANSwitch SET out_of_sync = "yes", last_error_ts = NOW(), last_errno = ? WHERE object_id = ?');
52ce8bd7
DO
3770