r3611 renderObject8021QPorts(): provide correct argument to array_diff()
[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');
2c21a10c 1002 break;
4c9d35ec 1003 case 'ipv4net':
d16af52f
DO
1004 $ret[] = array ('tag' => '$ip4netid_' . $cell['id']);
1005 $ret[] = array ('tag' => '$ip4net-' . str_replace ('.', '-', $cell['ip']) . '-' . $cell['mask']);
b9b915d5
DO
1006 for ($i = 8; $i < 32; $i++)
1007 {
1008 // these conditions hit 1 to 3 times per each i
1009 if ($cell['mask'] >= $i)
1010 $ret[] = array ('tag' => '$masklen_ge_' . $i);
1011 if ($cell['mask'] <= $i)
1012 $ret[] = array ('tag' => '$masklen_le_' . $i);
1013 if ($cell['mask'] == $i)
1014 $ret[] = array ('tag' => '$masklen_eq_' . $i);
1015 }
2c21a10c
DO
1016 $ret[] = array ('tag' => '$any_ip4net');
1017 $ret[] = array ('tag' => '$any_net');
1018 break;
1019 case 'ipv4vs':
d16af52f 1020 $ret[] = array ('tag' => '$ipv4vsid_' . $cell['id']);
2c21a10c
DO
1021 $ret[] = array ('tag' => '$any_ipv4vs');
1022 $ret[] = array ('tag' => '$any_vs');
1023 break;
1024 case 'ipv4rspool':
d16af52f 1025 $ret[] = array ('tag' => '$ipv4rspid_' . $cell['id']);
2c21a10c
DO
1026 $ret[] = array ('tag' => '$any_ipv4rsp');
1027 $ret[] = array ('tag' => '$any_rsp');
1028 break;
1029 case 'user':
b82cce3f
DO
1030 // {$username_XXX} autotag is generated always, but {$userid_XXX}
1031 // appears only for accounts, which exist in local database.
d16af52f
DO
1032 $ret[] = array ('tag' => '$username_' . $cell['user_name']);
1033 if (isset ($cell['user_id']))
1034 $ret[] = array ('tag' => '$userid_' . $cell['user_id']);
2c21a10c
DO
1035 break;
1036 case 'file':
d16af52f 1037 $ret[] = array ('tag' => '$fileid_' . $cell['id']);
2c21a10c
DO
1038 $ret[] = array ('tag' => '$any_file');
1039 break;
d16af52f 1040 default: // HCF!
2c21a10c
DO
1041 break;
1042 }
4c9d35ec
DO
1043 // {$tagless} doesn't apply to users
1044 switch ($cell['realm'])
1045 {
1046 case 'rack':
1047 case 'object':
1048 case 'ipv4net':
1049 case 'ipv4vs':
1050 case 'ipv4rspool':
1051 case 'file':
1052 if (!count ($cell['etags']))
1053 $ret[] = array ('tag' => '$untagged');
1054 break;
1055 default:
1056 break;
1057 }
f9bc186f
DO
1058 return $ret;
1059}
1060
abef7149
DO
1061// Check, if the given tag is present on the chain (will only work
1062// for regular tags with tag ID set.
1063function tagOnChain ($taginfo, $tagchain)
1064{
1065 if (!isset ($taginfo['id']))
1066 return FALSE;
1067 foreach ($tagchain as $test)
1068 if ($test['id'] == $taginfo['id'])
1069 return TRUE;
1070 return FALSE;
1071}
1072
f8821b96
DO
1073function tagNameOnChain ($tagname, $tagchain)
1074{
1075 foreach ($tagchain as $test)
1076 if ($test['tag'] == $tagname)
1077 return TRUE;
1078 return FALSE;
1079}
1080
abef7149
DO
1081// Return TRUE, if two tags chains differ (order of tags doesn't matter).
1082// Assume, that neither of the lists contains duplicates.
1083// FIXME: a faster, than O(x^2) method is possible for this calculation.
1084function tagChainCmp ($chain1, $chain2)
1085{
1086 if (count ($chain1) != count ($chain2))
1087 return TRUE;
1088 foreach ($chain1 as $taginfo1)
1089 if (!tagOnChain ($taginfo1, $chain2))
1090 return TRUE;
1091 return FALSE;
1092}
1093
0df8c52b 1094function redirectIfNecessary ()
da958e52 1095{
750d26d2
DO
1096 global
1097 $trigger,
1098 $pageno,
1099 $tabno;
53bae67b
DO
1100 $pmap = array
1101 (
1102 'accounts' => 'userlist',
1103 'rspools' => 'ipv4rsplist',
1104 'rspool' => 'ipv4rsp',
1105 'vservices' => 'ipv4vslist',
1106 'vservice' => 'ipv4vs',
c78a40ec
DO
1107 'objects' => 'depot',
1108 'objgroup' => 'depot',
53bae67b
DO
1109 );
1110 $tmap = array();
1111 $tmap['objects']['newmulti'] = 'addmore';
1112 $tmap['objects']['newobj'] = 'addmore';
1113 $tmap['object']['switchvlans'] = 'livevlans';
1114 $tmap['object']['slb'] = 'editrspvs';
72d8ced3
DO
1115 $tmap['object']['portfwrd'] = 'nat4';
1116 $tmap['object']['network'] = 'ipv4';
53bae67b
DO
1117 if (isset ($pmap[$pageno]))
1118 redirectUser ($pmap[$pageno], $tabno);
1119 if (isset ($tmap[$pageno][$tabno]))
1120 redirectUser ($pageno, $tmap[$pageno][$tabno]);
750d26d2 1121 // check if we accidentaly got on a dynamic tab that shouldn't be shown for this object
3a7bdcc6
DO
1122 if
1123 (
1124 isset ($trigger[$pageno][$tabno]) and
1125 !strlen (call_user_func ($trigger[$pageno][$tabno]))
1126 )
750d26d2 1127 redirectUser ($pageno, 'default');
0df8c52b
DO
1128}
1129
329ec966
DY
1130function prepareNavigation() {
1131 global
1132 $pageno,
1133 $tabno;
1134
1135 $pageno = (isset ($_REQUEST['page'])) ? $_REQUEST['page'] : 'index';
1136
1137// Special handling of tab number to substitute the "last" index where applicable.
1138// Always show explicitly requested tab, substitute the last used name in case
1139// it is awailable, fall back to the default one.
1140
1141 if (isset ($_REQUEST['tab'])) {
1142 $tabno = $_REQUEST['tab'];
329ec966 1143 } elseif (basename($_SERVER['PHP_SELF']) == 'index.php' and getConfigVar ('SHOW_LAST_TAB') == 'yes' and isset ($_SESSION['RTLT'][$pageno])) {
750d26d2 1144 redirectUser ($pageno, $_SESSION['RTLT'][$pageno]);
329ec966
DY
1145 } else {
1146 $tabno = 'default';
1147 }
1148}
1149
0df8c52b
DO
1150function fixContext ($target = NULL)
1151{
1152 global
1153 $pageno,
1154 $auto_tags,
1155 $expl_tags,
1156 $impl_tags,
1157 $target_given_tags,
1158 $user_given_tags,
1159 $etype_by_pageno,
1160 $page;
53bae67b 1161
0df8c52b
DO
1162 if ($target !== NULL)
1163 {
1164 $target_given_tags = $target['etags'];
1165 // Don't reset autochain, because auth procedures could push stuff there in.
1166 // Another important point is to ignore 'user' realm, so we don't infuse effective
1167 // context with autotags of the displayed account.
1168 if ($target['realm'] != 'user')
1169 $auto_tags = array_merge ($auto_tags, $target['atags']);
1170 }
1171 elseif (array_key_exists ($pageno, $etype_by_pageno))
53da7dca
DO
1172 {
1173 // Each page listed in the map above requires one uint argument.
0a50efb4 1174 $target_realm = $etype_by_pageno[$pageno];
0cc24e9a 1175 assertUIntArg ($page[$pageno]['bypass']);
0a50efb4 1176 $target_id = $_REQUEST[$page[$pageno]['bypass']];
b135a49d 1177 $target = spotEntity ($target_realm, $target_id);
53da7dca 1178 $target_given_tags = $target['etags'];
0df8c52b 1179 if ($target['realm'] != 'user')
53da7dca
DO
1180 $auto_tags = array_merge ($auto_tags, $target['atags']);
1181 }
4c9b513a
DO
1182 // Explicit and implicit chains should be normally empty at this point, so
1183 // overwrite the contents anyway.
1184 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags);
1185 $impl_tags = getImplicitTags ($expl_tags);
da958e52
DO
1186}
1187
abef7149
DO
1188// Take a list of user-supplied tag IDs to build a list of valid taginfo
1189// records indexed by tag IDs (tag chain).
6e49bd1f 1190function buildTagChainFromIds ($tagidlist)
ab379543 1191{
20c901a7 1192 global $taglist;
ab379543 1193 $ret = array();
abef7149 1194 foreach (array_unique ($tagidlist) as $tag_id)
ab379543
DO
1195 if (isset ($taglist[$tag_id]))
1196 $ret[] = $taglist[$tag_id];
1197 return $ret;
1198}
1199
7cc02fc1
DO
1200// Process a given tag tree and return only meaningful branches. The resulting
1201// (sub)tree will have refcnt leaves on every last branch.
a26a6ccc 1202function getObjectiveTagTree ($tree, $realm, $preselect)
7cc02fc1 1203{
51b6651a 1204 $self = __FUNCTION__;
7cc02fc1
DO
1205 $ret = array();
1206 foreach ($tree as $taginfo)
1207 {
a26a6ccc
DO
1208 $subsearch = $self ($taginfo['kids'], $realm, $preselect);
1209 // If the current node addresses something, add it to the result
1210 // regardless of how many sub-nodes it features.
1211 if
7cc02fc1 1212 (
a26a6ccc
DO
1213 isset ($taginfo['refcnt'][$realm]) or
1214 count ($subsearch) > 1 or
3fb336f6 1215 in_array ($taginfo['id'], $preselect)
a26a6ccc
DO
1216 )
1217 $ret[] = array
1218 (
1219 'id' => $taginfo['id'],
1220 'tag' => $taginfo['tag'],
1221 'parent_id' => $taginfo['parent_id'],
1222 'refcnt' => $taginfo['refcnt'],
1223 'kids' => $subsearch
1224 );
1225 else
1226 $ret = array_merge ($ret, $subsearch);
7cc02fc1
DO
1227 }
1228 return $ret;
1229}
1230
7ddb2c05
DO
1231// Get taginfo record by tag name, return NULL, if record doesn't exist.
1232function getTagByName ($target_name)
1233{
1234 global $taglist;
1235 foreach ($taglist as $taginfo)
1236 if ($taginfo['tag'] == $target_name)
1237 return $taginfo;
1238 return NULL;
1239}
1240
fc73c734
DO
1241// Merge two chains, filtering dupes out. Return the resulting superset.
1242function mergeTagChains ($chainA, $chainB)
1243{
1244 // $ret = $chainA;
1245 // Reindex by tag id in any case.
1246 $ret = array();
1247 foreach ($chainA as $tag)
1248 $ret[$tag['id']] = $tag;
1249 foreach ($chainB as $tag)
1250 if (!isset ($ret[$tag['id']]))
1251 $ret[$tag['id']] = $tag;
1252 return $ret;
1253}
1254
31c941ec
DO
1255function getCellFilter ()
1256{
7da7450c 1257 global $sic;
eca0114c 1258 global $pageno;
01b9a31a 1259 $staticFilter = getConfigVar ('STATIC_FILTER');
3d670bba 1260 if (isset ($_REQUEST['tagfilter']) and is_array ($_REQUEST['tagfilter']))
23cdc7e9
DO
1261 {
1262 $_REQUEST['cft'] = $_REQUEST['tagfilter'];
1263 unset ($_REQUEST['tagfilter']);
1264 }
01b9a31a 1265 if (isset ($_SESSION[$pageno]['tagfilter']) and is_array ($_SESSION[$pageno]['tagfilter']) and !(isset($_REQUEST['cft'])) and $staticFilter == 'yes')
b8970c7e 1266 {
eca0114c 1267 $_REQUEST['cft'] = $_SESSION[$pageno]['tagfilter'];
b8970c7e 1268 }
01b9a31a 1269 if (isset ($_SESSION[$pageno]['cfe']) and !(isset($sic['cfe'])) and $staticFilter == 'yes')
b8970c7e 1270 {
eca0114c 1271 $sic['cfe'] = $_SESSION[$pageno]['cfe'];
b8970c7e 1272 }
01b9a31a 1273 if (isset ($_SESSION[$pageno]['andor']) and !(isset($_REQUEST['andor'])) and $staticFilter == 'yes')
b8970c7e 1274 {
eca0114c 1275 $_REQUEST['andor'] = $_SESSION[$pageno]['andor'];
b8970c7e
RF
1276 }
1277
eca0114c 1278
3d670bba
DO
1279 $ret = array
1280 (
23cdc7e9
DO
1281 'tagidlist' => array(),
1282 'tnamelist' => array(),
1283 'pnamelist' => array(),
1284 'andor' => '',
3d670bba 1285 'text' => '',
23cdc7e9
DO
1286 'extratext' => '',
1287 'expression' => array(),
a8efc03e 1288 'urlextra' => '', // Just put text here and let makeHref call urlencode().
3d670bba 1289 );
23cdc7e9 1290 switch (TRUE)
31c941ec 1291 {
23cdc7e9
DO
1292 case (!isset ($_REQUEST['andor'])):
1293 $andor2 = getConfigVar ('FILTER_DEFAULT_ANDOR');
3d670bba 1294 break;
23cdc7e9
DO
1295 case ($_REQUEST['andor'] == 'and'):
1296 case ($_REQUEST['andor'] == 'or'):
eca0114c 1297 $_SESSION[$pageno]['andor'] = $_REQUEST['andor'];
23cdc7e9 1298 $ret['andor'] = $andor2 = $_REQUEST['andor'];
a8efc03e 1299 $ret['urlextra'] .= '&andor=' . $ret['andor'];
3d670bba
DO
1300 break;
1301 default:
0cc24e9a 1302 showWarning ('Invalid and/or switch value in submitted form', __FUNCTION__);
3d670bba 1303 return NULL;
31c941ec 1304 }
23cdc7e9
DO
1305 $andor1 = '';
1306 // Both tags and predicates, which don't exist, should be
1307 // handled somehow. Discard them silently for now.
1308 if (isset ($_REQUEST['cft']) and is_array ($_REQUEST['cft']))
31c941ec 1309 {
eca0114c 1310 $_SESSION[$pageno]['tagfilter'] = $_REQUEST['cft'];
23cdc7e9
DO
1311 global $taglist;
1312 foreach ($_REQUEST['cft'] as $req_id)
1313 if (isset ($taglist[$req_id]))
1314 {
1315 $ret['tagidlist'][] = $req_id;
1316 $ret['tnamelist'][] = $taglist[$req_id]['tag'];
1317 $ret['text'] .= $andor1 . '{' . $taglist[$req_id]['tag'] . '}';
1318 $andor1 = ' ' . $andor2 . ' ';
a8efc03e 1319 $ret['urlextra'] .= '&cft[]=' . $req_id;
23cdc7e9
DO
1320 }
1321 }
1322 if (isset ($_REQUEST['cfp']) and is_array ($_REQUEST['cfp']))
1323 {
1324 global $pTable;
1325 foreach ($_REQUEST['cfp'] as $req_name)
1326 if (isset ($pTable[$req_name]))
1327 {
1328 $ret['pnamelist'][] = $req_name;
1329 $ret['text'] .= $andor1 . '[' . $req_name . ']';
1330 $andor1 = ' ' . $andor2 . ' ';
a8efc03e 1331 $ret['urlextra'] .= '&cfp[]=' . $req_name;
23cdc7e9
DO
1332 }
1333 }
7da7450c
DO
1334 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function.
1335 if (isset ($sic['cfe']))
a8efc03e 1336 {
eca0114c 1337 $_SESSION[$pageno]['cfe'] = $sic['cfe'];
7da7450c
DO
1338 // Only consider extra text, when it is a correct RackCode expression.
1339 $parse = spotPayload ($sic['cfe'], 'SYNT_EXPR');
1340 if ($parse['result'] == 'ACK')
1341 {
1342 $ret['extratext'] = trim ($sic['cfe']);
1343 $ret['urlextra'] .= '&cfe=' . $ret['extratext'];
1344 }
a8efc03e 1345 }
23cdc7e9
DO
1346 $finaltext = array();
1347 if (strlen ($ret['text']))
1348 $finaltext[] = '(' . $ret['text'] . ')';
1349 if (strlen ($ret['extratext']))
1350 $finaltext[] = '(' . $ret['extratext'] . ')';
1351 $finaltext = implode (' ' . $andor2 . ' ', $finaltext);
1352 if (strlen ($finaltext))
1353 {
1354 $parse = spotPayload ($finaltext, 'SYNT_EXPR');
1355 $ret['expression'] = $parse['result'] == 'ACK' ? $parse['load'] : NULL;
8c699525
DO
1356 // It's not quite fair enough to put the blame of the whole text onto
1357 // non-empty "extra" portion of it, but it's the only user-generated portion
1358 // of it, thus the most probable cause of parse error.
1359 if (strlen ($ret['extratext']))
1360 $ret['extraclass'] = $parse['result'] == 'ACK' ? 'validation-success' : 'validation-error';
31c941ec 1361 }
3d670bba 1362 return $ret;
31c941ec
DO
1363}
1364
db55cf54
DO
1365// Return an empty message log.
1366function emptyLog ()
1367{
1368 return array
1369 (
1370 'v' => 2,
1371 'm' => array()
1372 );
1373}
1374
2987fc1f
DO
1375// Return a message log consisting of only one message.
1376function oneLiner ($code, $args = array())
1377{
db55cf54 1378 $ret = emptyLog();
2987fc1f
DO
1379 $ret['m'][] = count ($args) ? array ('c' => $code, 'a' => $args) : array ('c' => $code);
1380 return $ret;
46f92ff7
DO
1381}
1382
e53b1246
DO
1383// Merge message payload from two message logs given and return the result.
1384function mergeLogs ($log1, $log2)
1385{
1386 $ret = emptyLog();
1387 $ret['m'] = array_merge ($log1['m'], $log2['m']);
1388 return $ret;
1389}
1390
9f54e6e9 1391function validTagName ($s, $allow_autotag = FALSE)
2eeeca80 1392{
84986395 1393 if (1 == preg_match (TAGNAME_REGEXP, $s))
9f54e6e9 1394 return TRUE;
84986395 1395 if ($allow_autotag and 1 == preg_match (AUTOTAGNAME_REGEXP, $s))
9f54e6e9
DO
1396 return TRUE;
1397 return FALSE;
2eeeca80
DO
1398}
1399
53bae67b
DO
1400function redirectUser ($p, $t)
1401{
790a60e8
DO
1402 global $page;
1403 $l = "index.php?page=${p}&tab=${t}";
53bae67b
DO
1404 if (isset ($page[$p]['bypass']) and isset ($_REQUEST[$page[$p]['bypass']]))
1405 $l .= '&' . $page[$p]['bypass'] . '=' . $_REQUEST[$page[$p]['bypass']];
1406 header ("Location: " . $l);
1407 die;
1408}
1409
9f3e5caa
DO
1410function getRackCodeStats ()
1411{
1412 global $rackCode;
914b439b 1413 $defc = $grantc = $modc = 0;
9f3e5caa
DO
1414 foreach ($rackCode as $s)
1415 switch ($s['type'])
1416 {
1417 case 'SYNT_DEFINITION':
1418 $defc++;
1419 break;
1420 case 'SYNT_GRANT':
1421 $grantc++;
1422 break;
914b439b
DO
1423 case 'SYNT_CTXMOD':
1424 $modc++;
1425 break;
9f3e5caa
DO
1426 default:
1427 break;
1428 }
914b439b
DO
1429 $ret = array
1430 (
1431 'Definition sentences' => $defc,
1432 'Grant sentences' => $grantc,
1433 'Context mod sentences' => $modc
1434 );
9f3e5caa
DO
1435 return $ret;
1436}
1437
d5157018
DO
1438function getRackImageWidth ()
1439{
529eac25
DO
1440 global $rtwidth;
1441 return 3 + $rtwidth[0] + $rtwidth[1] + $rtwidth[2] + 3;
d5157018
DO
1442}
1443
1444function getRackImageHeight ($units)
1445{
1446 return 3 + 3 + $units * 2;
1447}
1448
2987fc1f
DO
1449// Perform substitutions and return resulting string
1450// used solely by buildLVSConfig()
1451function apply_macros ($macros, $subject)
1452{
1453 $ret = $subject;
1454 foreach ($macros as $search => $replace)
1455 $ret = str_replace ($search, $replace, $ret);
1456 return $ret;
1457}
1458
1459function buildLVSConfig ($object_id = 0)
1460{
1461 if ($object_id <= 0)
1462 {
0cc24e9a 1463 showWarning ('Invalid argument', __FUNCTION__);
2987fc1f
DO
1464 return;
1465 }
6297d584 1466 $oInfo = spotEntity ('object', $object_id);
2987fc1f
DO
1467 $lbconfig = getSLBConfig ($object_id);
1468 if ($lbconfig === NULL)
1469 {
0cc24e9a 1470 showWarning ('getSLBConfig() failed', __FUNCTION__);
2987fc1f
DO
1471 return;
1472 }
1473 $newconfig = "#\n#\n# This configuration has been generated automatically by RackTables\n";
1474 $newconfig .= "# for object_id == ${object_id}\n# object name: ${oInfo['name']}\n#\n#\n\n\n";
1475 foreach ($lbconfig as $vs_id => $vsinfo)
1476 {
1477 $newconfig .= "########################################################\n" .
59a83bd8
DO
1478 "# VS (id == ${vs_id}): " . (!strlen ($vsinfo['vs_name']) ? 'NO NAME' : $vsinfo['vs_name']) . "\n" .
1479 "# RS pool (id == ${vsinfo['pool_id']}): " . (!strlen ($vsinfo['pool_name']) ? 'ANONYMOUS' : $vsinfo['pool_name']) . "\n" .
2987fc1f
DO
1480 "########################################################\n";
1481 # The order of inheritance is: VS -> LB -> pool [ -> RS ]
1482 $macros = array
1483 (
1484 '%VIP%' => $vsinfo['vip'],
1485 '%VPORT%' => $vsinfo['vport'],
1486 '%PROTO%' => $vsinfo['proto'],
1487 '%VNAME%' => $vsinfo['vs_name'],
1488 '%RSPOOLNAME%' => $vsinfo['pool_name']
1489 );
1490 $newconfig .= "virtual_server ${vsinfo['vip']} ${vsinfo['vport']} {\n";
1491 $newconfig .= "\tprotocol ${vsinfo['proto']}\n";
1492 $newconfig .= apply_macros
1493 (
1494 $macros,
1495 lf_wrap ($vsinfo['vs_vsconfig']) .
1496 lf_wrap ($vsinfo['lb_vsconfig']) .
1497 lf_wrap ($vsinfo['pool_vsconfig'])
1498 );
1499 foreach ($vsinfo['rslist'] as $rs)
1500 {
59a83bd8 1501 if (!strlen ($rs['rsport']))
79a9edb4 1502 $rs['rsport'] = $vsinfo['vport'];
2987fc1f
DO
1503 $macros['%RSIP%'] = $rs['rsip'];
1504 $macros['%RSPORT%'] = $rs['rsport'];
1505 $newconfig .= "\treal_server ${rs['rsip']} ${rs['rsport']} {\n";
1506 $newconfig .= apply_macros
1507 (
1508 $macros,
1509 lf_wrap ($vsinfo['vs_rsconfig']) .
1510 lf_wrap ($vsinfo['lb_rsconfig']) .
1511 lf_wrap ($vsinfo['pool_rsconfig']) .
1512 lf_wrap ($rs['rs_rsconfig'])
1513 );
1514 $newconfig .= "\t}\n";
1515 }
1516 $newconfig .= "}\n\n\n";
1517 }
3dd72e34
DO
1518 // FIXME: deal somehow with Mac-styled text, the below replacement will screw it up
1519 return str_replace ("\r", '', $newconfig);
2987fc1f
DO
1520}
1521
2d318652
DO
1522// Indicate occupation state of each IP address: none, ordinary or problematic.
1523function markupIPv4AddrList (&$addrlist)
1524{
1525 foreach (array_keys ($addrlist) as $ip_bin)
1526 {
d983f70a
DO
1527 $refc = array
1528 (
00c5d8cb
DO
1529 'shared' => 0, // virtual
1530 'virtual' => 0, // loopback
1531 'regular' => 0, // connected host
1532 'router' => 0 // connected gateway
d983f70a
DO
1533 );
1534 foreach ($addrlist[$ip_bin]['allocs'] as $a)
1535 $refc[$a['type']]++;
00c5d8cb 1536 $nvirtloopback = ($refc['shared'] + $refc['virtual'] > 0) ? 1 : 0; // modulus of virtual + shared
d983f70a
DO
1537 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever
1538 $nrealms = $nreserved + $nvirtloopback + $refc['regular'] + $refc['router']; // latter two are connected and router allocations
2d318652
DO
1539
1540 if ($nrealms == 1)
1541 $addrlist[$ip_bin]['class'] = 'trbusy';
1542 elseif ($nrealms > 1)
1543 $addrlist[$ip_bin]['class'] = 'trerror';
1544 else
1545 $addrlist[$ip_bin]['class'] = '';
1546 }
1547}
1548
04d619d0
DO
1549// Scan the given address list (returned by scanIPv4Space) and return a list of all routers found.
1550function findRouters ($addrlist)
1551{
1552 $ret = array();
1553 foreach ($addrlist as $addr)
1554 foreach ($addr['allocs'] as $alloc)
1555 if ($alloc['type'] == 'router')
1556 $ret[] = array
1557 (
1558 'id' => $alloc['object_id'],
1559 'iface' => $alloc['name'],
1560 'dname' => $alloc['object_name'],
1561 'addr' => $addr['ip']
1562 );
1563 return $ret;
1564}
1565
fb7a4967
DO
1566// Assist in tag chain sorting.
1567function taginfoCmp ($tagA, $tagB)
1568{
1569 return $tagA['ci'] - $tagB['ci'];
1570}
1571
1327d9dd
DO
1572// Compare networks. When sorting a tree, the records on the list will have
1573// distinct base IP addresses.
3444ecf2
DO
1574// "The comparison function must return an integer less than, equal to, or greater
1575// than zero if the first argument is considered to be respectively less than,
1576// equal to, or greater than the second." (c) PHP manual
1327d9dd
DO
1577function IPv4NetworkCmp ($netA, $netB)
1578{
2d75c30b
DO
1579 // There's a problem just substracting one u32 integer from another,
1580 // because the result may happen big enough to become a negative i32
1581 // integer itself (PHP tries to cast everything it sees to signed int)
3444ecf2
DO
1582 // The comparison below must treat positive and negative values of both
1583 // arguments.
1584 // Equal values give instant decision regardless of their [equal] sign.
1585 if ($netA['ip_bin'] == $netB['ip_bin'])
2d75c30b 1586 return 0;
3444ecf2
DO
1587 // Same-signed values compete arithmetically within one of i32 contiguous ranges:
1588 // 0x00000001~0x7fffffff 1~2147483647
1589 // 0 doesn't have any sign, and network 0.0.0.0 isn't allowed
1590 // 0x80000000~0xffffffff -2147483648~-1
1591 $signA = $netA['ip_bin'] / abs ($netA['ip_bin']);
1592 $signB = $netB['ip_bin'] / abs ($netB['ip_bin']);
1593 if ($signA == $signB)
1594 {
1595 if ($netA['ip_bin'] > $netB['ip_bin'])
1596 return 1;
1597 else
1598 return -1;
1599 }
1600 else // With only one of two values being negative, it... wins!
1601 {
1602 if ($netA['ip_bin'] < $netB['ip_bin'])
1603 return 1;
1604 else
1605 return -1;
1606 }
1327d9dd
DO
1607}
1608
fb7a4967 1609// Modify the given tag tree so, that each level's items are sorted alphabetically.
1327d9dd 1610function sortTree (&$tree, $sortfunc = '')
fb7a4967 1611{
59a83bd8 1612 if (!strlen ($sortfunc))
1327d9dd 1613 return;
51b6651a 1614 $self = __FUNCTION__;
1327d9dd 1615 usort ($tree, $sortfunc);
fb7a4967
DO
1616 // Don't make a mistake of directly iterating over the items of current level, because this way
1617 // the sorting will be performed on a _copy_ if each item, not the item itself.
1618 foreach (array_keys ($tree) as $tagid)
51b6651a 1619 $self ($tree[$tagid]['kids'], $sortfunc);
fb7a4967
DO
1620}
1621
0137d53c
DO
1622function iptree_fill (&$netdata)
1623{
59a83bd8 1624 if (!isset ($netdata['kids']) or !count ($netdata['kids']))
0137d53c 1625 return;
a987ff52 1626 // If we really have nested prefixes, they must fit into the tree.
0137d53c
DO
1627 $worktree = array
1628 (
1629 'ip_bin' => $netdata['ip_bin'],
1630 'mask' => $netdata['mask']
1631 );
1632 foreach ($netdata['kids'] as $pfx)
1633 iptree_embed ($worktree, $pfx);
1634 $netdata['kids'] = iptree_construct ($worktree);
1635 $netdata['kidc'] = count ($netdata['kids']);
1636}
1637
1638function iptree_construct ($node)
1639{
1640 $self = __FUNCTION__;
1641
1642 if (!isset ($node['right']))
1643 {
1644 if (!isset ($node['ip']))
1645 {
1646 $node['ip'] = long2ip ($node['ip_bin']);
1647 $node['kids'] = array();
fec0c8da 1648 $node['kidc'] = 0;
0137d53c
DO
1649 $node['name'] = '';
1650 }
1651 return array ($node);
1652 }
1653 else
1654 return array_merge ($self ($node['left']), $self ($node['right']));
1655}
1656
1657function iptree_embed (&$node, $pfx)
1658{
1659 $self = __FUNCTION__;
1660
1661 // hit?
1662 if ($node['ip_bin'] == $pfx['ip_bin'] and $node['mask'] == $pfx['mask'])
1663 {
1664 $node = $pfx;
1665 return;
1666 }
1667 if ($node['mask'] == $pfx['mask'])
1668 {
0cc24e9a 1669 throw new RuntimeException('Internal error, the recurring loop lost control');
0137d53c
DO
1670 }
1671
1672 // split?
1673 if (!isset ($node['right']))
1674 {
a987ff52
DO
1675 // Fill in db_first/db_last to make it possible to run scanIPv4Space() on the node.
1676 $node['left']['mask'] = $node['mask'] + 1;
0137d53c 1677 $node['left']['ip_bin'] = $node['ip_bin'];
a987ff52
DO
1678 $node['left']['db_first'] = sprintf ('%u', $node['left']['ip_bin']);
1679 $node['left']['db_last'] = sprintf ('%u', $node['left']['ip_bin'] | binInvMaskFromDec ($node['left']['mask']));
1680
1681 $node['right']['mask'] = $node['mask'] + 1;
0137d53c 1682 $node['right']['ip_bin'] = $node['ip_bin'] + binInvMaskFromDec ($node['mask'] + 1) + 1;
a987ff52
DO
1683 $node['right']['db_first'] = sprintf ('%u', $node['right']['ip_bin']);
1684 $node['right']['db_last'] = sprintf ('%u', $node['right']['ip_bin'] | binInvMaskFromDec ($node['right']['mask']));
0137d53c
DO
1685 }
1686
1687 // repeat!
1688 if (($node['left']['ip_bin'] & binMaskFromDec ($node['left']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
1689 $self ($node['left'], $pfx);
1690 elseif (($node['right']['ip_bin'] & binMaskFromDec ($node['right']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
1691 $self ($node['right'], $pfx);
1692 else
1693 {
0cc24e9a 1694 throw new RuntimeException ('Internal error, cannot decide between left and right');
0137d53c
DO
1695 }
1696}
1697
3b81cb98 1698function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
0137d53c 1699{
59a83bd8 1700 if (!strlen ($func))
0137d53c
DO
1701 return;
1702 $self = __FUNCTION__;
1703 foreach (array_keys ($tree) as $key)
1704 {
1705 $func ($tree[$key]);
59a83bd8 1706 if (strlen ($stopfunc) and $stopfunc ($tree[$key]))
c3315231 1707 continue;
0137d53c
DO
1708 $self ($tree[$key]['kids'], $func);
1709 }
1710}
1711
a987ff52
DO
1712function loadIPv4AddrList (&$netinfo)
1713{
1714 loadOwnIPv4Addresses ($netinfo);
1715 markupIPv4AddrList ($netinfo['addrlist']);
1716}
b18d26dc 1717
b6b87070 1718function countOwnIPv4Addresses (&$node)
b18d26dc 1719{
b6b87070 1720 $toscan = array();
737a3f72 1721 $node['addrt'] = 0;
4d0dbd87
DO
1722 $node['mask_bin'] = binMaskFromDec ($node['mask']);
1723 $node['mask_bin_inv'] = binInvMaskFromDec ($node['mask']);
1724 $node['db_first'] = sprintf ('%u', 0x00000000 + $node['ip_bin'] & $node['mask_bin']);
1725 $node['db_last'] = sprintf ('%u', 0x00000000 + $node['ip_bin'] | ($node['mask_bin_inv']));
59a83bd8 1726 if (!count ($node['kids']))
737a3f72 1727 {
f7414fa5 1728 $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
737a3f72
DO
1729 $node['addrt'] = binInvMaskFromDec ($node['mask']) + 1;
1730 }
b18d26dc 1731 else
b6b87070 1732 foreach ($node['kids'] as $nested)
b18d26dc 1733 if (!isset ($nested['id'])) // spare
737a3f72 1734 {
f7414fa5 1735 $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
737a3f72
DO
1736 $node['addrt'] += binInvMaskFromDec ($nested['mask']) + 1;
1737 }
4d0dbd87
DO
1738 // Don't do anything more, because the displaying function will load the addresses anyway.
1739 return;
f7414fa5 1740 $node['addrc'] = count (scanIPv4Space ($toscan));
b6b87070
DO
1741}
1742
c3315231
DO
1743function nodeIsCollapsed ($node)
1744{
1745 return $node['symbol'] == 'node-collapsed';
1746}
1747
b6b87070
DO
1748function loadOwnIPv4Addresses (&$node)
1749{
1750 $toscan = array();
43eb71f1 1751 if (!isset ($node['kids']) or !count ($node['kids']))
f7414fa5 1752 $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
b6b87070
DO
1753 else
1754 foreach ($node['kids'] as $nested)
1755 if (!isset ($nested['id'])) // spare
f7414fa5
DO
1756 $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
1757 $node['addrlist'] = scanIPv4Space ($toscan);
b6b87070 1758 $node['addrc'] = count ($node['addrlist']);
b18d26dc
DO
1759}
1760
fec0c8da
DO
1761function prepareIPv4Tree ($netlist, $expanded_id = 0)
1762{
573214e0
DO
1763 // treeFromList() requires parent_id to be correct for an item to get onto the tree,
1764 // so perform necessary pre-processing to make orphans belong to root. This trick
1765 // was earlier performed by getIPv4NetworkList().
1766 $netids = array_keys ($netlist);
1767 foreach ($netids as $cid)
1768 if (!in_array ($netlist[$cid]['parent_id'], $netids))
1769 $netlist[$cid]['parent_id'] = NULL;
fec0c8da
DO
1770 $tree = treeFromList ($netlist); // medium call
1771 sortTree ($tree, 'IPv4NetworkCmp');
fec0c8da 1772 // complement the tree before markup to make the spare networks have "symbol" set
c3315231
DO
1773 treeApplyFunc ($tree, 'iptree_fill');
1774 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
1775 // count addresses after the markup to skip computation for hidden tree nodes
1776 treeApplyFunc ($tree, 'countOwnIPv4Addresses', 'nodeIsCollapsed');
fec0c8da
DO
1777 return $tree;
1778}
1779
1780// Check all items of the tree recursively, until the requested target id is
1781// found. Mark all items leading to this item as "expanded", collapsing all
1782// the rest, which exceed the given threshold (if the threshold is given).
1783function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0)
1784{
1785 $self = __FUNCTION__;
1786 $ret = FALSE;
1787 foreach (array_keys ($tree) as $key)
1788 {
5388794d 1789 $here = ($target === 'ALL' or ($target > 0 and isset ($tree[$key]['id']) and $tree[$key]['id'] == $target));
fec0c8da
DO
1790 $below = $self ($tree[$key]['kids'], $threshold, $target);
1791 if (!$tree[$key]['kidc']) // terminal node
1792 $tree[$key]['symbol'] = 'spacer';
1793 elseif ($tree[$key]['kidc'] < $threshold)
1794 $tree[$key]['symbol'] = 'node-expanded-static';
1795 elseif ($here or $below)
1796 $tree[$key]['symbol'] = 'node-expanded';
1797 else
1798 $tree[$key]['symbol'] = 'node-collapsed';
1799 $ret = ($ret or $here or $below); // parentheses are necessary for this to be computed correctly
1800 }
1801 return $ret;
1802}
1803
e1ae3fb4
AD
1804// Convert entity name to human-readable value
1805function formatEntityName ($name) {
1806 switch ($name)
1807 {
1808 case 'ipv4net':
1809 return 'IPv4 Network';
1810 case 'ipv4rspool':
1811 return 'IPv4 RS Pool';
1812 case 'ipv4vs':
1813 return 'IPv4 Virtual Service';
1814 case 'object':
1815 return 'Object';
1816 case 'rack':
1817 return 'Rack';
1818 case 'user':
1819 return 'User';
1820 }
1821 return 'invalid';
1822}
1823
1824// Take a MySQL or other generic timestamp and make it prettier
1825function formatTimestamp ($timestamp) {
1826 return date('n/j/y g:iA', strtotime($timestamp));
1827}
1828
8bc5d1e4
DO
1829// Display hrefs for all of a file's parents. If scissors are requested,
1830// prepend cutting button to each of them.
1831function serializeFileLinks ($links, $scissors = FALSE)
e1ae3fb4 1832{
e1ae3fb4
AD
1833 $comma = '';
1834 $ret = '';
1835 foreach ($links as $link_id => $li)
1836 {
1837 switch ($li['entity_type'])
1838 {
1839 case 'ipv4net':
1840 $params = "page=ipv4net&id=";
1841 break;
1842 case 'ipv4rspool':
1843 $params = "page=ipv4rspool&pool_id=";
1844 break;
1845 case 'ipv4vs':
1846 $params = "page=ipv4vs&vs_id=";
1847 break;
1848 case 'object':
1849 $params = "page=object&object_id=";
1850 break;
1851 case 'rack':
1852 $params = "page=rack&rack_id=";
1853 break;
1854 case 'user':
1855 $params = "page=user&user_id=";
1856 break;
1857 }
8bc5d1e4
DO
1858 $ret .= $comma;
1859 if ($scissors)
1860 {
1861 $ret .= "<a href='" . makeHrefProcess(array('op'=>'unlinkFile', 'link_id'=>$link_id)) . "'";
1862 $ret .= getImageHREF ('cut') . '</a> ';
1863 }
790a60e8 1864 $ret .= sprintf("<a href='index.php?%s%s'>%s</a>", $params, $li['entity_id'], $li['name']);
8bc5d1e4 1865 $comma = '<br>';
e1ae3fb4 1866 }
e1ae3fb4
AD
1867 return $ret;
1868}
1869
1870// Convert filesize to appropriate unit and make it human-readable
1871function formatFileSize ($bytes) {
1872 // bytes
1873 if($bytes < 1024) // bytes
1874 return "${bytes} bytes";
1875
1876 // kilobytes
1877 if ($bytes < 1024000)
1878 return sprintf ("%.1fk", round (($bytes / 1024), 1));
1879
1880 // megabytes
1881 return sprintf ("%.1f MB", round (($bytes / 1024000), 1));
1882}
1883
1884// Reverse of formatFileSize, it converts human-readable value to bytes
1885function convertToBytes ($value) {
1886 $value = trim($value);
1887 $last = strtolower($value[strlen($value)-1]);
1888 switch ($last)
1889 {
1890 case 'g':
1891 $value *= 1024;
1892 case 'm':
1893 $value *= 1024;
1894 case 'k':
1895 $value *= 1024;
1896 }
1897
1898 return $value;
1899}
1900
4fbb5a00
DY
1901function ip_quad2long ($ip)
1902{
1903 return sprintf("%u", ip2long($ip));
1904}
1905
1906function ip_long2quad ($quad)
1907{
1908 return long2ip($quad);
1909}
1910
1911function makeHref($params = array())
1912{
790a60e8 1913 $ret = 'index.php?';
4fbb5a00 1914 $first = true;
4fbb5a00
DY
1915 foreach($params as $key=>$value)
1916 {
1917 if (!$first)
1918 $ret.='&';
1919 $ret .= urlencode($key).'='.urlencode($value);
1920 $first = false;
1921 }
1922 return $ret;
1923}
1924
1925function makeHrefProcess($params = array())
1926{
790a60e8
DO
1927 global $pageno, $tabno;
1928 $ret = 'process.php?';
4fbb5a00 1929 $first = true;
9f14a7ef
DY
1930 if (!isset($params['page']))
1931 $params['page'] = $pageno;
1932 if (!isset($params['tab']))
1933 $params['tab'] = $tabno;
4fbb5a00
DY
1934 foreach($params as $key=>$value)
1935 {
1936 if (!$first)
1937 $ret.='&';
1938 $ret .= urlencode($key).'='.urlencode($value);
1939 $first = false;
1940 }
1941 return $ret;
1942}
1943
39106006 1944function makeHrefForHelper ($helper_name, $params = array())
4fbb5a00 1945{
790a60e8 1946 $ret = 'popup.php?helper=' . $helper_name;
4fbb5a00 1947 foreach($params as $key=>$value)
39106006 1948 $ret .= '&'.urlencode($key).'='.urlencode($value);
4fbb5a00
DY
1949 return $ret;
1950}
1951
f3d274bf
DO
1952// Process the given list of records to build data suitable for printNiftySelect()
1953// (like it was formerly executed by printSelect()). Screen out vendors according
1954// to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already
1955// selected OPTION is protected from being screened.
1956function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0)
1957{
1958 $ret = array();
1959 // Always keep "other" OPTGROUP at the SELECT bottom.
1960 $therest = array();
1961 foreach ($recordList as $dict_key => $dict_value)
1962 if (strpos ($dict_value, '%GSKIP%') !== FALSE)
1963 {
1964 $tmp = explode ('%GSKIP%', $dict_value, 2);
1965 $ret[$tmp[0]][$dict_key] = $tmp[1];
1966 }
1967 elseif (strpos ($dict_value, '%GPASS%') !== FALSE)
1968 {
1969 $tmp = explode ('%GPASS%', $dict_value, 2);
1970 $ret[$tmp[0]][$dict_key] = $tmp[1];
1971 }
1972 else
1973 $therest[$dict_key] = $dict_value;
1974 if ($object_type_id != 0)
1975 {
1976 $screenlist = array();
1977 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve)
eea3ca5e 1978 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs)){
f3d274bf 1979 $screenlist[] = $regs[1];
eea3ca5e 1980 }
f3d274bf
DO
1981 foreach (array_keys ($ret) as $vendor)
1982 if (in_array ($vendor, $screenlist))
1983 {
1984 $ok_to_screen = TRUE;
1985 if ($existing_value)
1986 foreach (array_keys ($ret[$vendor]) as $recordkey)
1987 if ($recordkey == $existing_value)
1988 {
1989 $ok_to_screen = FALSE;
1990 break;
1991 }
1992 if ($ok_to_screen)
1993 unset ($ret[$vendor]);
1994 }
1995 }
1996 $ret['other'] = $therest;
1997 return $ret;
1998}
1999
f8874cdb
DO
2000function dos2unix ($text)
2001{
2002 return str_replace ("\r\n", "\n", $text);
2003}
2004
9dd73255
DO
2005function unix2dos ($text)
2006{
2007 return str_replace ("\n", "\r\n", $text);
2008}
2009
a0527aef 2010function buildPredicateTable ($parsetree)
2c21a10c 2011{
a0527aef
DO
2012 $ret = array();
2013 foreach ($parsetree as $sentence)
72e8baf6 2014 if ($sentence['type'] == 'SYNT_DEFINITION')
a0527aef 2015 $ret[$sentence['term']] = $sentence['definition'];
72e8baf6
DO
2016 // Now we have predicate table filled in with the latest definitions of each
2017 // particular predicate met. This isn't as chik, as on-the-fly predicate
2018 // overloading during allow/deny scan, but quite sufficient for this task.
a0527aef
DO
2019 return $ret;
2020}
2021
2022// Take a list of records and filter against given RackCode expression. Return
2023// the original list intact, if there was no filter requested, but return an
2024// empty list, if there was an error.
573214e0 2025function filterCellList ($list_in, $expression = array())
d08d766d
DO
2026{
2027 if ($expression === NULL)
2028 return array();
2029 if (!count ($expression))
2030 return $list_in;
d08d766d
DO
2031 $list_out = array();
2032 foreach ($list_in as $item_key => $item_value)
573214e0 2033 if (TRUE === judgeCell ($item_value, $expression))
d08d766d
DO
2034 $list_out[$item_key] = $item_value;
2035 return $list_out;
2036}
2037
212c9d8a 2038// Tell, if the given expression is true for the given entity. Take complete record on input.
573214e0 2039function judgeCell ($cell, $expression)
d08d766d
DO
2040{
2041 global $pTable;
2042 return eval_expression
2043 (
2044 $expression,
2045 array_merge
2046 (
573214e0
DO
2047 $cell['etags'],
2048 $cell['itags'],
2049 $cell['atags']
d08d766d
DO
2050 ),
2051 $pTable,
2052 TRUE
2053 );
2054}
2055
7ddbcf59 2056// Tell, if a constraint from config option permits given record.
212c9d8a 2057function considerConfiguredConstraint ($cell, $varname)
c6bc0ac5 2058{
7ddbcf59 2059 if (!strlen (getConfigVar ($varname)))
c6bc0ac5 2060 return TRUE; // no restriction
7ddbcf59
DO
2061 global $parseCache;
2062 if (!isset ($parseCache[$varname]))
2063 // getConfigVar() doesn't re-read the value from DB because of its
2064 // own cache, so there is no race condition here between two calls.
2065 $parseCache[$varname] = spotPayload (getConfigVar ($varname), 'SYNT_EXPR');
2066 if ($parseCache[$varname]['result'] != 'ACK')
2067 return FALSE; // constraint set, but cannot be used due to compilation error
212c9d8a 2068 return judgeCell ($cell, $parseCache[$varname]['load']);
c6bc0ac5
DO
2069}
2070
17112e81
DO
2071// Return list of records in the given realm, which conform to
2072// the given RackCode expression. If the realm is unknown or text
2073// doesn't validate as a RackCode expression, return NULL.
2074// Otherwise (successful scan) return a list of all matched
2075// records, even if the list is empty (array() !== NULL). If the
2076// text is an empty string, return all found records in the given
2077// realm.
2078function scanRealmByText ($realm = NULL, $ftext = '')
2079{
2080 switch ($realm)
2081 {
2082 case 'object':
2083 case 'user':
2084 case 'ipv4net':
2085 case 'file':
39776127 2086 case 'ipv4vs':
41780975 2087 case 'ipv4rspool':
17112e81
DO
2088 if (!strlen ($ftext = trim ($ftext)))
2089 $fexpr = array();
2090 else
2091 {
2092 $fparse = spotPayload ($ftext, 'SYNT_EXPR');
2093 if ($fparse['result'] != 'ACK')
2094 return NULL;
2095 $fexpr = $fparse['load'];
2096 }
2097 return filterCellList (listCells ($realm), $fexpr);
2098 default:
c5f84f48 2099 throw new InvalidArgException ('$realm', $realm);
17112e81
DO
2100 }
2101
2102}
39776127
DO
2103
2104function getIPv4VSOptions ()
2105{
2106 $ret = array();
2107 foreach (listCells ('ipv4vs') as $vsid => $vsinfo)
59a83bd8 2108 $ret[$vsid] = $vsinfo['dname'] . (!strlen ($vsinfo['name']) ? '' : " (${vsinfo['name']})");
39776127
DO
2109 return $ret;
2110}
2111
41780975
DO
2112function getIPv4RSPoolOptions ()
2113{
2114 $ret = array();
2115 foreach (listCells ('ipv4rspool') as $pool_id => $poolInfo)
2116 $ret[$pool_id] = $poolInfo['name'];
2117 return $ret;
2118}
2119
d16af52f
DO
2120// Derive a complete cell structure from the given username regardless
2121// if it is a local account or not.
2122function constructUserCell ($username)
2123{
2124 if (NULL !== ($userid = getUserIDByUsername ($username)))
2125 return spotEntity ('user', $userid);
2126 $ret = array
2127 (
2128 'realm' => 'user',
2129 'user_name' => $username,
2130 'user_realname' => '',
2131 'etags' => array(),
2132 'itags' => array(),
d16af52f 2133 );
5689cefd 2134 $ret['atags'] = generateEntityAutoTags ($ret);
d16af52f
DO
2135 return $ret;
2136}
2137
56a797ef
DO
2138// Let's have this debug helper here to enable debugging of process.php w/o interface.php.
2139function dump ($var)
2140{
2141 echo '<div align=left><pre>';
2142 print_r ($var);
2143 echo '</pre></div>';
2144}
2145
9e51318b
DO
2146function getTagChart ($limit = 0, $realm = 'total', $special_tags = array())
2147{
2148 global $taglist;
2149 // first build top-N chart...
2150 $toplist = array();
2151 foreach ($taglist as $taginfo)
2152 if (isset ($taginfo['refcnt'][$realm]))
2153 $toplist[$taginfo['id']] = $taginfo['refcnt'][$realm];
2154 arsort ($toplist, SORT_NUMERIC);
2155 $ret = array();
2156 $done = 0;
2157 foreach (array_keys ($toplist) as $tag_id)
2158 {
2159 $ret[$tag_id] = $taglist[$tag_id];
2160 if (++$done == $limit)
2161 break;
2162 }
2163 // ...then make sure, that every item of the special list is shown
2164 // (using the same sort order)
2165 $extra = array();
2166 foreach ($special_tags as $taginfo)
2167 if (!array_key_exists ($taginfo['id'], $ret))
2168 $extra[$taginfo['id']] = $taglist[$taginfo['id']]['refcnt'][$realm];
2169 arsort ($extra, SORT_NUMERIC);
2170 foreach (array_keys ($extra) as $tag_id)
2171 $ret[] = $taglist[$tag_id];
2172 return $ret;
2173}
2174
7fa7047a
DO
2175function decodeObjectType ($objtype_id, $style = 'r')
2176{
2177 static $types = array();
2178 if (!count ($types))
2179 $types = array
2180 (
2181 'r' => readChapter (CHAP_OBJTYPE),
2182 'a' => readChapter (CHAP_OBJTYPE, 'a'),
2183 'o' => readChapter (CHAP_OBJTYPE, 'o')
2184 );
2185 return $types[$style][$objtype_id];
2186}
2187
0df8c52b
DO
2188function isolatedPermission ($p, $t, $cell)
2189{
2190 // This function is called from both "file" page and a number of other pages,
2191 // which have already fixed security context and authorized the user for it.
2192 // OTOH, it is necessary here to authorize against the current file, which
2193 // means saving the current context and building a new one.
2194 global
2195 $expl_tags,
2196 $impl_tags,
2197 $target_given_tags,
2198 $auto_tags;
2199 // push current context
2200 $orig_expl_tags = $expl_tags;
2201 $orig_impl_tags = $impl_tags;
2202 $orig_target_given_tags = $target_given_tags;
2203 $orig_auto_tags = $auto_tags;
2204 // retarget
2205 fixContext ($cell);
2206 // remember decision
2207 $ret = permitted ($p, $t);
2208 // pop context
2209 $expl_tags = $orig_expl_tags;
2210 $impl_tags = $orig_impl_tags;
2211 $target_given_tags = $orig_target_given_tags;
2212 $auto_tags = $orig_auto_tags;
2213 return $ret;
2214}
2215
3153a326
DO
2216function getPortListPrefs()
2217{
2218 $ret = array();
2219 if (0 >= ($ret['iif_pick'] = getConfigVar ('DEFAULT_PORT_IIF_ID')))
2220 $ret['iif_pick'] = 1;
2221 $ret['oif_picks'] = array();
2222 foreach (explode (';', getConfigVar ('DEFAULT_PORT_OIF_IDS')) as $tmp)
2223 {
2224 $tmp = explode ('=', trim ($tmp));
2225 if (count ($tmp) == 2 and $tmp[0] > 0 and $tmp[1] > 0)
2226 $ret['oif_picks'][$tmp[0]] = $tmp[1];
2227 }
2228 // enforce default value
2229 if (!array_key_exists (1, $ret['oif_picks']))
2230 $ret['oif_picks'][1] = 24;
2231 $ret['selected'] = $ret['iif_pick'] . '-' . $ret['oif_picks'][$ret['iif_pick']];
2232 return $ret;
2233}
2234
2dfa1b73
DO
2235// Return data for printNiftySelect() with port type options. All OIF options
2236// for the default IIF will be shown, but only the default OIFs will be present
2237// for each other IIFs. IIFs, for which there is no default OIF, will not
2238// be listed.
2239// This SELECT will be used for the "add new port" form.
2240function getNewPortTypeOptions()
2241{
2242 $ret = array();
2243 $prefs = getPortListPrefs();
2244 foreach (getPortInterfaceCompat() as $row)
2245 {
2246 if ($row['iif_id'] == $prefs['iif_pick'])
2247 $optgroup = $row['iif_name'];
2248 elseif (array_key_exists ($row['iif_id'], $prefs['oif_picks']) and $prefs['oif_picks'][$row['iif_id']] == $row['oif_id'])
2249 $optgroup = 'other';
2250 else
2251 continue;
2252 if (!array_key_exists ($optgroup, $ret))
2253 $ret[$optgroup] = array();
2254 $ret[$optgroup][$row['iif_id'] . '-' . $row['oif_id']] = $row['oif_name'];
2255 }
2256 return $ret;
2257}
2258
8198f2c6
DO
2259function getVLANDomain ($vdid)
2260{
2261 $ret = getVLANDomainInfo ($vdid);
2262 $ret['vlanlist'] = array();
8198f2c6
DO
2263 foreach (getDomainVLANs ($vdid) as $vlan_id => $vlan_descr)
2264 $ret['vlanlist'][$vlan_id] = $vlan_descr;
2265 $ret['switchlist'] = getVLANDomainSwitches ($vdid);
2266 return $ret;
2267}
2268
2269// Return a serialized version of VLAN configuration for a port.
2270// If a native VLAN is defined, print it first. All other VLANs
d0dadd80
DO
2271// are tagged and are listed after a plus sign. When no configuration
2272// is set for a port, return "default" string.
4f860864 2273function serializeVLANPack ($vlanport)
8198f2c6 2274{
4f860864
DO
2275 if (!array_key_exists ('mode', $vlanport))
2276 return 'error';
2277 switch ($vlanport['mode'])
2278 {
2279 case 'none':
2280 return 'none';
2281 case 'access':
2282 $ret = 'A';
2283 break;
2284 case 'trunk':
2285 $ret = 'T';
2286 break;
36a70b71
DO
2287 case 'uplink':
2288 $ret = 'U';
2289 break;
2290 case 'downlink':
2291 $ret = 'D';
2292 break;
4f860864
DO
2293 default:
2294 return 'error';
2295 }
8198f2c6 2296 $tagged = array();
4f860864
DO
2297 foreach ($vlanport['allowed'] as $vlan_id)
2298 if ($vlan_id != $vlanport['native'])
8198f2c6
DO
2299 $tagged[] = $vlan_id;
2300 sort ($tagged);
4f860864 2301 $ret .= $vlanport['native'] ? $vlanport['native'] : '';
e9be55de
DO
2302 $tagged_bits = array();
2303 $id_from = $id_to = 0;
2304 foreach ($tagged as $next_id)
2305 {
2306 if ($id_to)
2307 {
2308 if ($next_id == $id_to + 1) // merge
2309 {
2310 $id_to = $next_id;
2311 continue;
2312 }
2313 // flush
2314 $tagged_bits[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}";
2315 }
2316 $id_from = $id_to = $next_id; // start next pair
2317 }
2318 // pull last pair
2319 if ($id_to)
2320 $tagged_bits[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}";
8198f2c6 2321 if (count ($tagged))
a9953bf1 2322 $ret .= '+' . implode (', ', $tagged_bits);
d0dadd80 2323 return strlen ($ret) ? $ret : 'default';
8198f2c6
DO
2324}
2325
8846b060
DO
2326// Decode VLAN compound key (which is a string formatted DOMAINID-VLANID) and
2327// return the numbers as an array of two.
2328function decodeVLANCK ($string)
2329{
2330 $matches = array();
2331 if (1 != preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $string, $matches))
2332 throw new InvalidArgException ('VLAN compound key', $string);
2333 return array ($matches[1], $matches[2]);
2334}
2335
ce85f5c8
DO
2336// Return VLAN name formatted for HTML output (note, that input
2337// argument comes from database unescaped).
a72aa89f
DO
2338function formatVLANName ($vlaninfo, $context = 'markup long')
2339{
2340 switch ($context)
2341 {
2342 case 'option':
2343 $ret = $vlaninfo['vlan_id'];
2344 if ($vlaninfo['vlan_descr'] != '')
2345 $ret .= ' ' . niftyString ($vlaninfo['vlan_descr']);
2346 return $ret;
2347 case 'label':
2348 $ret = $vlaninfo['vlan_id'];
2349 if ($vlaninfo['vlan_descr'] != '')
2350 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2351 return $ret;
0812b506
DO
2352 case 'plain long':
2353 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2354 if ($vlaninfo['vlan_descr'] != '')
2355 $ret .= ' (' . niftyString ($vlaninfo['vlan_descr']) . ')';
2356 return $ret;
a72aa89f
DO
2357 case 'markup long':
2358 default:
2359 $ret = 'VLAN' . $vlaninfo['vlan_id'];
2360 $ret .= ' @' . niftyString ($vlaninfo['domain_descr']);
2361 if ($vlaninfo['vlan_descr'] != '')
2362 $ret .= ' <i>(' . niftyString ($vlaninfo['vlan_descr']) . ')</i>';
2363 return $ret;
2364 }
ce85f5c8
DO
2365}
2366
ef6653b1 2367function ios12ReadVLANConfig ($input)
f10dd5cc 2368{
ef6653b1
DO
2369 $ret = array
2370 (
2371 'vlanlist' => array(),
2372 'portdata' => array(),
2373 );
2374 $procfunc = 'ios12ScanTopLevel';
f10dd5cc
DO
2375 foreach (explode ("\n", $input) as $line)
2376 $procfunc = $procfunc ($ret, $line);
2377 return $ret;
2378}
2379
ef6653b1 2380function ios12ScanTopLevel (&$work, $line)
f10dd5cc
DO
2381{
2382 $matches = array();
ef6653b1
DO
2383 switch (TRUE)
2384 {
d0642bae 2385 case (preg_match ('@^interface ((Ethernet|FastEthernet|GigabitEthernet|TenGigabitEthernet|Port-channel)[[:digit:]]+(/[[:digit:]]+)*)$@', $line, $matches)):
ef6653b1
DO
2386 // map interface name
2387 $matches[1] = preg_replace ('@^Ethernet(.+)$@', 'et\\1', $matches[1]);
2388 $matches[1] = preg_replace ('@^FastEthernet(.+)$@', 'fa\\1', $matches[1]);
2389 $matches[1] = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $matches[1]);
2390 $matches[1] = preg_replace ('@^TenGigabitEthernet(.+)$@', 'te\\1', $matches[1]);
d0642bae 2391 $matches[1] = preg_replace ('@^Port-channel(.+)$@', 'po\\1', $matches[1]);
ef6653b1
DO
2392 $work['current'] = array ('port_name' => $matches[1]);
2393 return 'ios12PickSwitchportCommand'; // switch to interface block reading
2394 case (preg_match ('/^VLAN Name Status Ports$/', $line, $matches)):
2395 return 'ios12PickVLANCommand';
2396 default:
f10dd5cc 2397 return __FUNCTION__; // continue scan
ef6653b1 2398 }
f10dd5cc
DO
2399}
2400
ef6653b1 2401function ios12PickSwitchportCommand (&$work, $line)
f10dd5cc
DO
2402{
2403 if ($line[0] != ' ') // end of interface section
2404 {
f10dd5cc 2405 // save work, if it makes sense
0721a106 2406 switch (TRUE)
f10dd5cc 2407 {
a13d4b2d
DO
2408 case $work['current']['ignore']:
2409 $work['portdata'][$work['current']['port_name']] = array
2410 (
2411 'mode' => 'none',
2412 'allowed' => array(),
2413 'native' => 0,
2414 );
2415 break;
0721a106
DO
2416 case 'access' == $work['current']['mode']:
2417 if (!array_key_exists ('access vlan', $work['current']))
2418 $work['current']['access vlan'] = 1;
ac210c9e 2419 $work['portdata'][$work['current']['port_name']] = array
f10dd5cc 2420 (
4f860864 2421 'mode' => 'access',
f10dd5cc
DO
2422 'allowed' => array ($work['current']['access vlan']),
2423 'native' => $work['current']['access vlan'],
2424 );
2425 break;
0721a106
DO
2426 case 'trunk' == $work['current']['mode']:
2427 if (!array_key_exists ('trunk native vlan', $work['current']))
2428 $work['current']['trunk native vlan'] = 1;
2429 if (!array_key_exists ('trunk allowed vlan', $work['current']))
2430 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID, VLAN_MAX_ID);
48c286aa
DO
2431 // Having configured VLAN as "native" doesn't mean anything
2432 // as long as it's not listed on the "allowed" line.
2433 $effective_native = in_array
2434 (
2435 $work['current']['trunk native vlan'],
2436 $work['current']['trunk allowed vlan']
2437 ) ? $work['current']['trunk native vlan'] : 0;
ac210c9e 2438 $work['portdata'][$work['current']['port_name']] = array
f10dd5cc 2439 (
4f860864 2440 'mode' => 'trunk',
e9be55de 2441 'allowed' => $work['current']['trunk allowed vlan'],
48c286aa 2442 'native' => $effective_native,
f10dd5cc
DO
2443 );
2444 break;
2445 default:
0721a106
DO
2446 // dot1q-tunnel, dynamic, private-vlan or even none --
2447 // show in returned config and let user decide, if they
2448 // want to fix device config or work around these ports
2449 // by means of VST.
2450 $work['portdata'][$work['current']['port_name']] = array
2451 (
2452 'mode' => 'none',
2453 'allowed' => array(),
2454 'native' => 0,
2455 );
2456 break;
f10dd5cc
DO
2457 }
2458 unset ($work['current']);
ef6653b1 2459 return 'ios12ScanTopLevel';
f10dd5cc
DO
2460 }
2461 // not yet
2462 $matches = array();
2463 switch (TRUE)
2464 {
2465 case (preg_match ('@^ switchport mode (.+)$@', $line, $matches)):
2466 $work['current']['mode'] = $matches[1];
2467 break;
2468 case (preg_match ('@^ switchport access vlan (.+)$@', $line, $matches)):
2469 $work['current']['access vlan'] = $matches[1];
2470 break;
2471 case (preg_match ('@^ switchport trunk native vlan (.+)$@', $line, $matches)):
2472 $work['current']['trunk native vlan'] = $matches[1];
2473 break;
2474 case (preg_match ('@^ switchport trunk allowed vlan add (.+)$@', $line, $matches)):
2475 $work['current']['trunk allowed vlan'] = array_merge
2476 (
2477 $work['current']['trunk allowed vlan'],
2478 iosParseVLANString ($matches[1])
2479 );
2480 break;
2481 case (preg_match ('@^ switchport trunk allowed vlan (.+)$@', $line, $matches)):
2482 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
2483 break;
9cf66518
DO
2484 case preg_match ('@^ channel-group @', $line):
2485 // port-channel subinterface config follows that of the master interface
2486 case preg_match ('@^ ip address @', $line):
2487 // L3 interface does no switchport functions
a13d4b2d
DO
2488 $work['current']['ignore'] = TRUE;
2489 break;
f10dd5cc
DO
2490 default: // suppress warning on irrelevant config clause
2491 }
2492 return __FUNCTION__;
2493}
2494
ef6653b1
DO
2495function ios12PickVLANCommand (&$work, $line)
2496{
2497 $matches = array();
2498 switch (TRUE)
2499 {
2500 case ($line == '---- -------------------------------- --------- -------------------------------'):
2501 // ignore the rest of VLAN table header;
2502 break;
2503 case (preg_match ('@! END OF VLAN LIST$@', $line)):
2504 return 'ios12ScanTopLevel';
2505 case (preg_match ('@^([[:digit:]]+) {1,4}.{32} active @', $line, $matches)):
2506 if (!array_key_exists ($matches[1], $work['vlanlist']))
2507 $work['vlanlist'][] = $matches[1];
2508 break;
2509 default:
2510 }
2511 return __FUNCTION__;
2512}
2513
f10dd5cc
DO
2514function iosParseVLANString ($string)
2515{
2516 $ret = array();
2517 foreach (explode (',', $string) as $item)
2518 {
2519 $matches = array();
735e323f 2520 $item = trim ($item, ' ');
f10dd5cc
DO
2521 if (preg_match ('/^([[:digit:]]+)$/', $item, $matches))
2522 $ret[] = $matches[1];
2523 elseif (preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $item, $matches))
2524 $ret = array_merge ($ret, range ($matches[1], $matches[2]));
2525 }
2526 return $ret;
2527}
2528
dbc00990
DO
2529// Another finite automata to read a dialect of Foundry configuration.
2530function fdry5ReadVLANConfig ($input)
2531{
ef6653b1
DO
2532 $ret = array
2533 (
2534 'vlanlist' => array(),
2535 'portdata' => array(),
2536 );
dbc00990
DO
2537 $procfunc = 'fdry5ScanTopLevel';
2538 foreach (explode ("\n", $input) as $line)
2539 $procfunc = $procfunc ($ret, $line);
2540 return $ret;
2541}
2542
2543function fdry5ScanTopLevel (&$work, $line)
2544{
2545 $matches = array();
2546 switch (TRUE)
2547 {
2548 case (preg_match ('@^vlan ([[:digit:]]+)( name .+)? (by port)$@', $line, $matches)):
ef6653b1
DO
2549 if (!array_key_exists ($matches[1], $work['vlanlist']))
2550 $work['vlanlist'][] = $matches[1];
dbc00990
DO
2551 $work['current'] = array ('vlan_id' => $matches[1]);
2552 return 'fdry5PickVLANSubcommand';
2553 case (preg_match ('@^interface ethernet ([[:digit:]]+/[[:digit:]]+/[[:digit:]]+)$@', $line, $matches)):
2554 $work['current'] = array ('port_name' => 'e' . $matches[1]);
2555 return 'fdry5PickInterfaceSubcommand';
2556 default:
2557 return __FUNCTION__;
2558 }
2559}
2560
2561function fdry5PickVLANSubcommand (&$work, $line)
2562{
2563 if ($line[0] != ' ') // end of VLAN section
2564 {
2565 unset ($work['current']);
2566 return 'fdry5ScanTopLevel';
2567 }
2568 // not yet
2569 $matches = array();
2570 switch (TRUE)
2571 {
2572 case (preg_match ('@^ tagged (.+)$@', $line, $matches)):
2573 // add current VLAN to 'allowed' list of each mentioned port
2574 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
ac210c9e
DO
2575 if (array_key_exists ($port_name, $work['portdata']))
2576 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
dbc00990 2577 else
ac210c9e 2578 $work['portdata'][$port_name] = array
dbc00990 2579 (
3dcfd931 2580 'mode' => 'trunk',
dbc00990
DO
2581 'allowed' => array ($work['current']['vlan_id']),
2582 'native' => 0, // can be updated later
2583 );
7b7c07da 2584 $work['portdata'][$port_name]['mode'] = 'trunk';
dbc00990
DO
2585 break;
2586 case (preg_match ('@^ untagged (.+)$@', $line, $matches)):
2587 // replace 'native' column of each mentioned port with current VLAN ID
2588 foreach (fdry5ParsePortString ($matches[1]) as $port_name)
7b7c07da 2589 {
ac210c9e 2590 if (array_key_exists ($port_name, $work['portdata']))
dbc00990 2591 {
ac210c9e
DO
2592 $work['portdata'][$port_name]['native'] = $work['current']['vlan_id'];
2593 $work['portdata'][$port_name]['allowed'][] = $work['current']['vlan_id'];
dbc00990
DO
2594 }
2595 else
ac210c9e 2596 $work['portdata'][$port_name] = array
dbc00990 2597 (
3dcfd931 2598 'mode' => 'access',
dbc00990
DO
2599 'allowed' => array ($work['current']['vlan_id']),
2600 'native' => $work['current']['vlan_id'],
2601 );
7b7c07da
DO
2602 // Untagged ports are initially assumed to be access ports, and
2603 // when this assumption is right, this is the final port mode state.
2604 // When the port is dual-mode one, this is detected and justified
2605 // later in "interface" section of config text.
2606 $work['portdata'][$port_name]['mode'] = 'access';
2607 }
dbc00990
DO
2608 break;
2609 default: // nom-nom
2610 }
2611 return __FUNCTION__;
2612}
2613
2614function fdry5PickInterfaceSubcommand (&$work, $line)
2615{
2616 if ($line[0] != ' ') // end of interface section
2617 {
2618 if (array_key_exists ('dual-mode', $work['current']))
2619 {
ac210c9e 2620 if (array_key_exists ($work['current']['port_name'], $work['portdata']))
dbc00990 2621 // update existing record
ac210c9e 2622 $work['portdata'][$work['current']['port_name']]['native'] = $work['current']['dual-mode'];
dbc00990
DO
2623 else
2624 // add new
ac210c9e 2625 $work['portdata'][$work['current']['port_name']] = array
dbc00990 2626 (
dbc00990
DO
2627 'allowed' => array ($work['current']['dual-mode']),
2628 'native' => $work['current']['dual-mode'],
2629 );
7b7c07da
DO
2630 // a dual-mode port is always considered a trunk port
2631 // (but not in the IronWare's meaning of "trunk") regardless of
2632 // number of assigned tagged VLANs
2633 $work['portdata'][$work['current']['port_name']]['mode'] = 'trunk';
dbc00990
DO
2634 }
2635 unset ($work['current']);
2636 return 'fdry5ScanTopLevel';
2637 }
2638 $matches = array();
2639 switch (TRUE)
2640 {
2641 case (preg_match ('@^ dual-mode( +[[:digit:]]+ *)?$@', $line, $matches)):
2642 // default VLAN ID for dual-mode command is 1
2643 $work['current']['dual-mode'] = strlen (trim ($matches[1])) ? trim ($matches[1]) : 1;
2644 break;
9cf66518 2645 // FIXME: trunk/link-aggregate/ip address pulls port from 802.1Q field
dbc00990
DO
2646 default: // nom-nom
2647 }
2648 return __FUNCTION__;
2649}
2650
2651function fdry5ParsePortString ($string)
2652{
2653 $ret = array();
2654 $tokens = explode (' ', trim ($string));
2655 while (count ($tokens))
2656 {
2657 $letters = array_shift ($tokens); // "ethe", "to"
2658 $numbers = array_shift ($tokens); // "x", "x/x", "x/x/x"
2659 switch ($letters)
2660 {
2661 case 'ethe':
2662 if ($prev_numbers != NULL)
2663 $ret[] = 'e' . $prev_numbers;
2664 $prev_numbers = $numbers;
2665 break;
2666 case 'to':
2667 $ret = array_merge ($ret, fdry5GenPortRange ($prev_numbers, $numbers));
2668 $prev_numbers = NULL; // no action on next token
2669 break;
2670 default: // ???
2671 return array();
2672 }
2673 }
2674 // flush delayed item
2675 if ($prev_numbers != NULL)
2676 $ret[] = 'e' . $prev_numbers;
2677 return $ret;
2678}
2679
2680// Take two indices in form "x", "x/x" or "x/x/x" and return the range of
2681// ports spanning from the first to the last. The switch software makes it
2682// easier to perform, because "ethe x/x/x to y/y/y" ranges never cross
2683// unit/slot boundary (every index except the last remains constant).
2684function fdry5GenPortRange ($from, $to)
2685{
2686 $matches = array();
2687 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $from, $matches))
2688 return array();
2689 $prefix = 'e' . $matches[1] . $matches[2];
2690 $from_idx = $matches[3];
2691 if (1 !== preg_match ('@^([[:digit:]]+/)?([[:digit:]]+/)?([[:digit:]]+)$@', $to, $matches))
2692 return array();
2693 $to_idx = $matches[3];
2694 for ($i = $from_idx; $i <= $to_idx; $i++)
2695 $ret[] = $prefix . $i;
2696 return $ret;
2697}
2698
eafdbb42
DO
2699// an implementation for Huawei syntax
2700function vrp53ReadVLANConfig ($input)
2701{
2702 $ret = array
2703 (
2704 'vlanlist' => array(),
2705 'portdata' => array(),
2706 );
2707 $procfunc = 'vrp53ScanTopLevel';
2708 foreach (explode ("\n", $input) as $line)
2709 $procfunc = $procfunc ($ret, $line);
2710 return $ret;
2711}
2712
2713function vrp53ScanTopLevel (&$work, $line)
2714{
2715 $matches = array();
2716 switch (TRUE)
2717 {
2718 case (preg_match ('@^ vlan batch (.+)$@', $line, $matches)):
2719 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
2720 $work['vlanlist'][] = $vlan_id;
2721 return __FUNCTION__;
66a28cce 2722 case (preg_match ('@^interface ((GigabitEthernet|XGigabitEthernet|Eth-Trunk)([[:digit:]]+(/[[:digit:]]+)*))$@', $line, $matches)):
eafdbb42
DO
2723 $matches[1] = preg_replace ('@^GigabitEthernet(.+)$@', 'gi\\1', $matches[1]);
2724 $matches[1] = preg_replace ('@^XGigabitEthernet(.+)$@', 'xg\\1', $matches[1]);
66a28cce 2725 $matches[1] = preg_replace ('@^Eth-Trunk(.+)$@', 'et\\1', $matches[1]);
eafdbb42
DO
2726 $work['current'] = array ('port_name' => $matches[1]);
2727 return 'vrp53PickInterfaceSubcommand';
2728 default:
2729 return __FUNCTION__;
2730 }
2731}
2732
2733function vrp53ParseVLANString ($string)
2734{
2735 $string = preg_replace ('/ to /', '-', $string);
2736 $string = preg_replace ('/ /', ',', $string);
2737 return iosParseVLANString ($string);
2738}
2739
2740function vrp53PickInterfaceSubcommand (&$work, $line)
2741{
2742 if ($line[0] == '#') // end of interface section
2743 {
2744 // Configuration Guide - Ethernet 3.3.4:
2745 // "By default, the interface type is hybrid."
2746 if (!array_key_exists ('link-type', $work['current']))
2747 $work['current']['link-type'] = 'hybrid';
2748 if (!array_key_exists ('allowed', $work['current']))
2749 $work['current']['allowed'] = array();
2750 if (!array_key_exists ('native', $work['current']))
2751 $work['current']['native'] = 0;
2752 switch ($work['current']['link-type'])
2753 {
2754 case 'access':
7107574b
DO
2755 // VRP does not assign access ports to VLAN1 by default,
2756 // leaving them blocked.
2757 $work['portdata'][$work['current']['port_name']] =
2758 $work['current']['native'] ? array
2759 (
2760 'allowed' => $work['current']['allowed'],
2761 'native' => $work['current']['native'],
2762 'mode' => 'access',
2763 ) : array
2764 (
2765 'mode' => 'none',
2766 'allowed' => array(),
2767 'native' => 0,
2768 );
eafdbb42
DO
2769 break;
2770 case 'trunk':
2771 $work['portdata'][$work['current']['port_name']] = array
2772 (
2773 'allowed' => $work['current']['allowed'],
2774 'native' => 0,
7b7c07da 2775 'mode' => 'trunk',
eafdbb42
DO
2776 );
2777 break;
2778 case 'hybrid':
2779 $work['portdata'][$work['current']['port_name']] = array
2780 (
2781 'allowed' => $work['current']['allowed'],
2782 'native' => $work['current']['native'],
7b7c07da 2783 'mode' => 'trunk',
eafdbb42
DO
2784 );
2785 break;
2786 default: // dot1q-tunnel ?
2787 }
2788 unset ($work['current']);
2789 return 'vrp53ScanTopLevel';
2790 }
2791 $matches = array();
2792 switch (TRUE)
2793 {
2794 case (preg_match ('@^ port default vlan ([[:digit:]]+)$@', $line, $matches)):
2795 $work['current']['native'] = $matches[1];
2796 if (!array_key_exists ('allowed', $work['current']))
2797 $work['current']['allowed'] = array();
2798 if (!in_array ($matches[1], $work['current']['allowed']))
2799 $work['current']['allowed'][] = $matches[1];
2800 break;
2801 case (preg_match ('@^ port link-type (.+)$@', $line, $matches)):
2802 $work['current']['link-type'] = $matches[1];
2803 break;
2804 case (preg_match ('@^ port trunk allow-pass vlan (.+)$@', $line, $matches)):
2805 if (!array_key_exists ('allowed', $work['current']))
2806 $work['current']['allowed'] = array();
2807 foreach (vrp53ParseVLANString ($matches[1]) as $vlan_id)
2808 if (!in_array ($vlan_id, $work['current']['allowed']))
2809 $work['current']['allowed'][] = $vlan_id;
2810 break;
66a28cce 2811 // TODO: make sure, that a port with "eth-trunk" clause always ends up in "none" mode
eafdbb42
DO
2812 default: // nom-nom
2813 }
2814 return __FUNCTION__;
2815}
2816
18216a3b
DO
2817function nxos4Read8021QConfig ($input)
2818{
48400875
DO
2819 $ret = array
2820 (
2821 'vlanlist' => array(),
2822 'portdata' => array(),
2823 );
2824 $procfunc = 'nxos4ScanTopLevel';
2825 foreach (explode ("\n", $input) as $line)
2826 $procfunc = $procfunc ($ret, $line);
2827 return $ret;
2828}
2829
2830function nxos4ScanTopLevel (&$work, $line)
2831{
2832 $matches = array();
2833 switch (TRUE)
2834 {
2835 case (preg_match ('@^interface ((Ethernet)[[:digit:]]+(/[[:digit:]]+)*)$@', $line, $matches)):
2836 $matches[1] = preg_replace ('@^Ethernet(.+)$@', 'e\\1', $matches[1]);
2837 $work['current'] = array ('port_name' => $matches[1]);
2838 return 'nxos4PickSwitchportCommand';
2839 case (preg_match ('@^vlan ([[:digit:]]+)$@', $line, $matches)):
2840 $work['vlanlist'][] = $matches[1];
2841 return 'nxos4PickVLANs';
2842 default:
2843 return __FUNCTION__; // continue scan
2844 }
2845}
2846
2847function nxos4PickVLANs (&$work, $line)
2848{
2849 switch (TRUE)
2850 {
2851 case ($line == ''): // end of VLAN list
2852 return 'nxos4ScanTopLevel';
2853 case (preg_match ('@^vlan ([[:digit:]]+)$@', $line, $matches)):
2854 $work['vlanlist'][] = $matches[1];
2855 default: // VLAN name or any other text
2856 return __FUNCTION__;
2857 }
2858}
2859
2860function nxos4PickSwitchportCommand (&$work, $line)
2861{
2862 if ($line == '') // end of interface section
2863 {
2864 // fill in defaults
2865 // below assumes "system default switchport" mode set on the device
2866 if (!array_key_exists ('mode', $work['current']))
2867 $work['current']['mode'] = 'access';
2868 // save work, if it makes sense
2869 switch ($work['current']['mode'])
2870 {
2871 case 'access':
2872 if (!array_key_exists ('access vlan', $work['current']))
2873 $work['current']['access vlan'] = 1;
2874 $work['portdata'][$work['current']['port_name']] = array
2875 (
2876 'mode' => 'access',
2877 'allowed' => array ($work['current']['access vlan']),
2878 'native' => $work['current']['access vlan'],
2879 );
2880 break;
2881 case 'trunk':
2882 if (!array_key_exists ('trunk native vlan', $work['current']))
2883 $work['current']['trunk native vlan'] = 1;
2884 if (!array_key_exists ('trunk allowed vlan', $work['current']))
2885 $work['current']['trunk allowed vlan'] = range (VLAN_MIN_ID, VLAN_MAX_ID);
2886 // Having configured VLAN as "native" doesn't mean anything
2887 // as long as it's not listed on the "allowed" line.
2888 $effective_native = in_array
2889 (
2890 $work['current']['trunk native vlan'],
2891 $work['current']['trunk allowed vlan']
2892 ) ? $work['current']['trunk native vlan'] : 0;
2893 $work['portdata'][$work['current']['port_name']] = array
2894 (
2895 'mode' => 'trunk',
2896 'allowed' => $work['current']['trunk allowed vlan'],
2897 'native' => $effective_native,
2898 );
2899 break;
2900 default:
2901 // dot1q-tunnel, dynamic, private-vlan --- skip these
2902 }
2903 unset ($work['current']);
2904 return 'nxos4ScanTopLevel';
2905 }
2906 // not yet
2907 $matches = array();
2908 switch (TRUE)
2909 {
2910 case (preg_match ('@^ switchport mode (.+)$@', $line, $matches)):
2911 $work['current']['mode'] = $matches[1];
2912 break;
2913 case (preg_match ('@^ switchport access vlan (.+)$@', $line, $matches)):
2914 $work['current']['access vlan'] = $matches[1];
2915 break;
2916 case (preg_match ('@^ switchport trunk native vlan (.+)$@', $line, $matches)):
2917 $work['current']['trunk native vlan'] = $matches[1];
2918 break;
2919 case (preg_match ('@^ switchport trunk allowed vlan add (.+)$@', $line, $matches)):
2920 $work['current']['trunk allowed vlan'] = array_merge
2921 (
2922 $work['current']['trunk allowed vlan'],
2923 iosParseVLANString ($matches[1])
2924 );
2925 break;
2926 case (preg_match ('@^ switchport trunk allowed vlan (.+)$@', $line, $matches)):
2927 $work['current']['trunk allowed vlan'] = iosParseVLANString ($matches[1]);
2928 break;
2929 default: // suppress warning on irrelevant config clause
2930 }
2931 return __FUNCTION__;
18216a3b
DO
2932}
2933
bb35bb93
DO
2934// Scan given array and return the key, which addresses the first item
2935// with requested column set to given value (or NULL if there is none such).
19350222
DO
2936// Note that 0 and NULL mean completely different things and thus
2937// require strict checking (=== and !===).
dbc00990
DO
2938function scanArrayForItem ($table, $scan_column, $scan_value)
2939{
2940 foreach ($table as $key => $row)
2941 if ($row[$scan_column] == $scan_value)
2942 return $key;
2943 return NULL;
2944}
2945
f56f0f20
DO
2946// Get a list of VLAN management pseudo-commands and return a text
2947// of real vendor-specific commands, which implement the work.
3f61f175
DO
2948// This work is done in two rounds:
2949// 1. For "add allowed" and "rem allowed" commands detect continuous
2950// sequences of VLAN IDs and replace them with ranges of form "A-B",
2951// where B>A.
2952// 2. Iterate over the resulting list and produce real CLI commands.
f56f0f20
DO
2953function ios12TranslatePushQueue ($queue)
2954{
3dcfd931 2955 $ret = "configure terminal\n";
cc6a6c4e 2956 foreach ($queue as $cmd)
f56f0f20
DO
2957 switch ($cmd['opcode'])
2958 {
2959 case 'create VLAN':
2960 $ret .= "vlan ${cmd['arg1']}\nexit\n";
2961 break;
2962 case 'destroy VLAN':
2963 $ret .= "no vlan ${cmd['arg1']}\n";
2964 break;
2965 case 'add allowed':
f56f0f20 2966 case 'rem allowed':
cc6a6c4e
DO
2967 $clause = $cmd['opcode'] == 'add allowed' ? 'add' : 'remove';
2968 $ret .= "interface ${cmd['port']}\n";
2969 foreach (listToRanges ($cmd['vlans']) as $range)
2970 $ret .= "switchport trunk allowed vlan ${clause} " .
2971 ($range['from'] == $range['to'] ? $range['to'] : "${range['from']}-${range['to']}") .
2972 "\n";
2973 $ret .= "exit\n";
f56f0f20
DO
2974 break;
2975 case 'set native':
2976 $ret .= "interface ${cmd['arg1']}\nswitchport trunk native vlan ${cmd['arg2']}\nexit\n";
2977 break;
2978 case 'unset native':
2979 $ret .= "interface ${cmd['arg1']}\nno switchport trunk native vlan ${cmd['arg2']}\nexit\n";
2980 break;
3dcfd931
DO
2981 case 'set access':
2982 $ret .= "interface ${cmd['arg1']}\nswitchport access vlan ${cmd['arg2']}\nexit\n";
2983 break;
2984 case 'unset access':
2985 $ret .= "interface ${cmd['arg1']}\nno switchport access vlan\nexit\n";
2986 break;
2987 case 'set mode':
0e353a73
DO
2988 $ret .= "interface ${cmd['arg1']}\nswitchport mode ${cmd['arg2']}\n";
2989 if ($cmd['arg2'] == 'trunk')
2990 $ret .= "no switchport trunk native vlan\nswitchport trunk allowed vlan none\n";
2991 $ret .= "exit\n";
3dcfd931 2992 break;
f56f0f20
DO
2993 }
2994 $ret .= "end\n";
2995 return $ret;
2996}
2997
2998function fdry5TranslatePushQueue ($queue)
2999{
3000 $ret = "conf t\n";
3001 foreach ($queue as $cmd)
3002 switch ($cmd['opcode'])
3003 {
3004 case 'create VLAN':
3005 $ret .= "vlan ${cmd['arg1']}\nexit\n";
3006 break;
3007 case 'destroy VLAN':
3008 $ret .= "no vlan ${cmd['arg1']}\n";
3009 break;
3010 case 'add allowed':
cc6a6c4e
DO
3011 foreach ($cmd['vlans'] as $vlan_id)
3012 $ret .= "vlan ${vlan_id}\ntagged ${cmd['port']}\nexit\n";
f56f0f20
DO
3013 break;
3014 case 'rem allowed':
cc6a6c4e
DO
3015 foreach ($cmd['vlans'] as $vlan_id)
3016 $ret .= "vlan ${vlan_id}\nno tagged ${cmd['port']}\nexit\n";
f56f0f20
DO
3017 break;
3018 case 'set native':
3019 $ret .= "interface ${cmd['arg1']}\ndual-mode ${cmd['arg2']}\nexit\n";
3020 break;
3021 case 'unset native':
3022 $ret .= "interface ${cmd['arg1']}\nno dual-mode ${cmd['arg2']}\nexit\n";
3023 break;
3dcfd931
DO
3024 case 'set access':
3025 $ret .= "vlan ${cmd['arg2']}\nuntagged ${cmd['arg1']}\nexit\n";
3026 break;
3027 case 'unset access':
3028 $ret .= "vlan ${cmd['arg2']}\nno untagged ${cmd['arg1']}\nexit\n";
3029 break;
3030 case 'set mode': // NOP
3031 break;
f56f0f20
DO
3032 }
3033 $ret .= "end\n";
a18c2042 3034 return $ret;
5456f90f
DO
3035}
3036
d74013ee
DO
3037function vrp53TranslatePushQueue ($queue)
3038{
3039 $ret = "system-view\n";
3040 foreach ($queue as $cmd)
3041 switch ($cmd['opcode'])
3042 {
3043 case 'create VLAN':
3044 $ret .= "vlan ${cmd['arg1']}\nquit\n";
3045 break;
3046 case 'destroy VLAN':
3047 $ret .= "undo vlan ${cmd['arg1']}\n";
3048 break;
3049 case 'add allowed':
d74013ee 3050 case 'rem allowed':
cc6a6c4e
DO
3051 $clause = $cmd['opcode'] == 'add allowed' ? '' : 'undo ';
3052 $ret .= "interface ${cmd['port']}\n";
3053 foreach (listToRanges ($cmd['vlans']) as $range)
3054 $ret .= "${clause}port trunk allow-pass vlan " .
3055 ($range['from'] == $range['to'] ? $range['to'] : "${range['from']} to ${range['to']}") .
3056 "\n";
3057 $ret .= "quit\n";
d74013ee
DO
3058 break;
3059 case 'set native':
3dcfd931 3060 case 'set access':
d74013ee
DO
3061 $ret .= "interface ${cmd['arg1']}\nport default vlan ${cmd['arg2']}\nquit\n";
3062 break;
3063 case 'unset native':
3dcfd931 3064 case 'unset access':
d74013ee
DO
3065 $ret .= "interface ${cmd['arg1']}\nundo port default vlan\nquit\n";
3066 break;
3dcfd931
DO
3067 case 'set mode':
3068 $modemap = array ('access' => 'access', 'trunk' => 'hybrid');
0e353a73
DO
3069 $ret .= "interface ${cmd['arg1']}\nport link-type " . $modemap[$cmd['arg2']] . "\n";
3070 if ($cmd['arg2'] == 'hybrid')
3071 $ret .= "undo port default vlan\nundo port trunk allow-pass vlan all\n";
3072 $ret .= "quit\n";
3dcfd931 3073 break;
d74013ee
DO
3074 }
3075 $ret .= "return\n";
3076 return $ret;
3077}
3078
66658512
DO
3079// Return TRUE, if every value of A1 is present in A2 and vice versa,
3080// regardless of each array's sort order and indexing.
3081function array_values_same ($a1, $a2)
3082{
3083 return !count (array_diff ($a1, $a2)) and !count (array_diff ($a2, $a1));
3084}
3085
1ce258f7
DO
3086// Use the VLAN switch template to set VST role for each port of
3087// the provided list. Return resulting list.
bc254f49 3088function apply8021QOrder ($vst_id, $portlist)
25930440 3089{
bc254f49 3090 $vst = getVLANSwitchTemplate ($vst_id);
1ce258f7
DO
3091 foreach (array_keys ($portlist) as $port_name)
3092 {
3093 foreach ($vst['rules'] as $rule)
3094 if (preg_match ($rule['port_pcre'], $port_name))
25930440
DO
3095 {
3096 $portlist[$port_name]['vst_role'] = $rule['port_role'];
091768aa 3097 $portlist[$port_name]['wrt_vlans'] = buildVLANFilter ($rule['port_role'], $rule['wrt_vlans']);
1ce258f7 3098 continue 2;
25930440 3099 }
1ce258f7
DO
3100 $portlist[$port_name]['vst_role'] = 'none';
3101 }
25930440
DO
3102 return $portlist;
3103}
3104
091768aa
DO
3105// return a sequence of ranges for given string form and port role
3106function buildVLANFilter ($role, $string)
be28b696 3107{
091768aa 3108 // set base
1ce258f7 3109 switch ($role)
be28b696 3110 {
1ce258f7
DO
3111 case 'access': // 1-4094
3112 $min = VLAN_MIN_ID;
3113 $max = VLAN_MAX_ID;
3114 break;
3115 case 'trunk': // 2-4094
3116 case 'uplink':
65da0c15 3117 case 'downlink':
1ce258f7
DO
3118 $min = VLAN_MIN_ID + 1;
3119 $max = VLAN_MAX_ID;
3120 break;
65da0c15 3121 default: // none
1ce258f7 3122 return array();
be28b696 3123 }
091768aa
DO
3124 if ($string == '') // fast track
3125 return array (array ('from' => $min, 'to' => $max));
3126 // transform
3127 $vlanidlist = array();
1ce258f7
DO
3128 foreach (iosParseVLANString ($string) as $vlan_id)
3129 if ($min <= $vlan_id and $vlan_id <= $max)
091768aa 3130 $vlanidlist[] = $vlan_id;
cc6a6c4e
DO
3131 return listToRanges ($vlanidlist);
3132}
3133
3134// pack set of integers into list of integer ranges
3135// e.g. (1, 2, 3, 5, 6, 7, 9, 11) => ((1, 3), (5, 7), (9, 9), (11, 11))
3136function listToRanges ($vlanidlist)
3137{
091768aa
DO
3138 sort ($vlanidlist);
3139 $ret = array();
3140 $from = $to = NULL;
3141 foreach ($vlanidlist as $vlan_id)
3142 if ($from == NULL)
3143 $from = $to = $vlan_id;
3144 elseif ($to + 1 == $vlan_id)
3145 $to = $vlan_id;
3146 else
3147 {
3148 $ret[] = array ('from' => $from, 'to' => $to);
3149 $from = $to = $vlan_id;
3150 }
3151 if ($from != NULL)
3152 $ret[] = array ('from' => $from, 'to' => $to);
be28b696
DO
3153 return $ret;
3154}
3155
091768aa
DO
3156// return TRUE, if given VLAN ID belongs to one of filter's ranges
3157function matchVLANFilter ($vlan_id, $vfilter)
3158{
3159 foreach ($vfilter as $range)
3160 if ($range['from'] <= $vlan_id and $vlan_id <= $range['to'])
3161 return TRUE;
3162 return FALSE;
3163}
3164
bcd14540
DO
3165function exportSwitch8021QConfig
3166(
3167 $vswitch,
3168 $device_vlanlist,
3169 $before,
3170 $changes
3171)
3172{
3173 // only ignore VLANs, which exist and are explicitly shown as "alien"
3174 $old_managed_vlans = array();
3175 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3176 foreach ($device_vlanlist as $vlan_id)
3177 if
3178 (
3179 !array_key_exists ($vlan_id, $domain_vlanlist) or
3180 $domain_vlanlist[$vlan_id]['vlan_type'] != 'alien'
3181 )
3182 $old_managed_vlans[] = $vlan_id;
3183 $ports_to_do = array();
3184 $after = $before;
3185 foreach ($changes as $port_name => $port)
3186 {
3187 $ports_to_do[$port_name] = array
3188 (
3189 'old_mode' => $before[$port_name]['mode'],
3190 'old_allowed' => $before[$port_name]['allowed'],
3191 'old_native' => $before[$port_name]['native'],
3192 'new_mode' => $port['mode'],
3193 'new_allowed' => $port['allowed'],
3194 'new_native' => $port['native'],
3195 );
3196 $after[$port_name] = $port;
3197 }
3198 // New VLAN table is a union of:
3199 // 1. all compulsory VLANs
3200 // 2. all "current" non-alien allowed VLANs of those ports, which are left
3201 // intact (regardless if a VLAN exists in VLAN domain, but looking,
3202 // if it is present in device's own VLAN table)
3203 // 3. all "new" allowed VLANs of those ports, which we do "push" now
3204 // Like for old_managed_vlans, a VLANs is never listed, only if it
3205 // exists and belongs to "alien" type.
3206 $new_managed_vlans = array();
3207 // 1
3208 foreach ($domain_vlanlist as $vlan_id => $vlan)
3209 if ($vlan['vlan_type'] == 'compulsory')
3210 $new_managed_vlans[] = $vlan_id;
3211 // 2
3212 foreach ($before as $port_name => $port)
3213 if (!array_key_exists ($port_name, $changes))
3214 foreach ($port['allowed'] as $vlan_id)
3215 {
3216 if (in_array ($vlan_id, $new_managed_vlans))
3217 continue;
3218 if
3219 (
3220 array_key_exists ($vlan_id, $domain_vlanlist) and
3221 $domain_vlanlist[$vlan_id]['vlan_type'] == 'alien'
3222 )
3223 continue;
3224 if (in_array ($vlan_id, $device_vlanlist))
3225 $new_managed_vlans[] = $vlan_id;
3226 }
3227 // 3
3228 foreach ($changes as $port)
10b6b476 3229 foreach ($port['allowed'] as $vlan_id)
bcd14540
DO
3230 if
3231 (
3232 $domain_vlanlist[$vlan_id]['vlan_type'] == 'ondemand' and
3233 !in_array ($vlan_id, $new_managed_vlans)
3234 )
3235 $new_managed_vlans[] = $vlan_id;
3236 $crq = array();
3237 // Before removing each old VLAN as such it is necessary to unassign
3238 // ports from it (to remove VLAN from each ports' list of "allowed"
3239 // VLANs). This change in turn requires, that a port's "native"
3240 // VLAN isn't set to the one being removed from its "allowed" list.
3241 foreach ($ports_to_do as $port_name => $port)
3242 switch ($port['old_mode'] . '->' . $port['new_mode'])
3243 {
3244 case 'trunk->trunk':
3245 // "old" native is set and differs from the "new" native
3246 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
3247 $crq[] = array
3248 (
3249 'opcode' => 'unset native',
3250 'arg1' => $port_name,
3251 'arg2' => $port['old_native'],
3252 );
cc6a6c4e 3253 if (count ($tmp = array_diff ($port['old_allowed'], $port['new_allowed'])))
bcd14540
DO
3254 $crq[] = array
3255 (
3256 'opcode' => 'rem allowed',
cc6a6c4e
DO
3257 'port' => $port_name,
3258 'vlans' => $tmp,
bcd14540
DO
3259 );
3260 break;
3261 case 'access->access':
3262 if ($port['old_native'] and $port['old_native'] != $port['new_native'])
3263 $crq[] = array
3264 (
3265 'opcode' => 'unset access',
3266 'arg1' => $port_name,
3267 'arg2' => $port['old_native'],
3268 );
3269 break;
3270 case 'access->trunk':
3271 $crq[] = array
3272 (
3273 'opcode' => 'unset access',
3274 'arg1' => $port_name,
3275 'arg2' => $port['old_native'],
3276 );
3277 break;
3278 case 'trunk->access':
3279 $crq[] = array
3280 (
3281 'opcode' => 'unset native',
3282 'arg1' => $port_name,
3283 'arg2' => $port['old_native'],
3284 );
cc6a6c4e 3285 if (count ($port['old_allowed']))
bcd14540
DO
3286 $crq[] = array
3287 (
3288 'opcode' => 'rem allowed',
cc6a6c4e
DO
3289 'port' => $port_name,
3290 'vlans' => $port['old_allowed'],
bcd14540
DO
3291 );
3292 break;
3293 default:
3294 throw new RuntimeException ('error in ports_to_do structure');
3295 }
3296 // Now it is safe to unconfigure VLANs, which still exist on device,
3297 // but are not present on the "new" list.
3298 // FIXME: put all IDs into one pseudo-command to make it easier
3299 // for translators to create/destroy VLANs in batches, where
3300 // target platform allows them to do.
3301 foreach (array_diff ($old_managed_vlans, $new_managed_vlans) as $vlan_id)
3302 $crq[] = array
3303 (
3304 'opcode' => 'destroy VLAN',
3305 'arg1' => $vlan_id,
3306 );
3307 // Configure VLANs, which must be present on the device, but are not yet.
3308 foreach (array_diff ($new_managed_vlans, $old_managed_vlans) as $vlan_id)
3309 $crq[] = array
3310 (
3311 'opcode' => 'create VLAN',
3312 'arg1' => $vlan_id,
3313 );
3314 // Now, when all new VLANs are created (queued), it is safe to assign (queue)
3315 // ports to the new VLANs.
3316 foreach ($ports_to_do as $port_name => $port)
3317 switch ($port['old_mode'] . '->' . $port['new_mode'])
3318 {
3319 case 'trunk->trunk':
3320 // For each allowed VLAN, which is present on the "new" list and missing from
3321 // the "old" one, queue a command to assign current port to that VLAN.
cc6a6c4e 3322 if (count ($tmp = array_diff ($port['new_allowed'], $port['old_allowed'])))
bcd14540
DO
3323 $crq[] = array
3324 (
3325 'opcode' => 'add allowed',
cc6a6c4e
DO
3326 'port' => $port_name,
3327 'vlans' => $tmp,
bcd14540
DO
3328 );
3329 // One of the "allowed" VLANs for this port may probably be "native".
3330 // "new native" is set and differs from "old native"
3331 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
3332 $crq[] = array
3333 (
3334 'opcode' => 'set native',
3335 'arg1' => $port_name,
3336 'arg2' => $port['new_native'],
3337 );
3338 break;
3339 case 'access->access':
3340 if ($port['new_native'] and $port['new_native'] != $port['old_native'])
3341 $crq[] = array
3342 (
3343 'opcode' => 'set access',
3344 'arg1' => $port_name,
3345 'arg2' => $port['new_native'],
3346 );
3347 break;
3348 case 'access->trunk':
3349 $crq[] = array
3350 (
3351 'opcode' => 'set mode',
3352 'arg1' => $port_name,
3353 'arg2' => $port['new_mode'],
3354 );
cc6a6c4e 3355 if (count ($port['new_allowed']))
bcd14540
DO
3356 $crq[] = array
3357 (
3358 'opcode' => 'add allowed',
cc6a6c4e
DO
3359 'port' => $port_name,
3360 'vlans' => $port['new_allowed'],
bcd14540
DO
3361 );
3362 $crq[] = array
3363 (
3364 'opcode' => 'set native',
3365 'arg1' => $port_name,
3366 'arg2' => $port['new_native'],
3367 );
3368 break;
3369 case 'trunk->access':
3370 $crq[] = array
3371 (
3372 'opcode' => 'set mode',
3373 'arg1' => $port_name,
3374 'arg2' => $port['new_mode'],
3375 );
3376 $crq[] = array
3377 (
3378 'opcode' => 'set access',
3379 'arg1' => $port_name,
3380 'arg2' => $port['new_native'],
3381 );
3382 break;
3383 default:
3384 throw new RuntimeException ('error in ports_to_do structure');
3385 }
3386 setDevice8021QConfig ($vswitch['object_id'], $crq);
3387 return count ($crq);
3388}
3389
af204724 3390// filter list of changed ports to cancel changes forbidden by VST and domain
af204724
DO
3391function filter8021QChangeRequests
3392(
3393 $domain_vlanlist,
3394 $before, // current saved configuration of all ports
3395 $changes // changed ports with VST markup
3396)
3397{
3398 $domain_immune_vlans = array();
3399 foreach ($domain_vlanlist as $vlan_id => $vlan)
3400 if ($vlan['vlan_type'] == 'alien')
3401 $domain_immune_vlans[] = $vlan_id;
3402 $ret = array();
3403 foreach ($changes as $port_name => $port)
3404 {
af204724 3405 // find and cancel any changes regarding immune VLANs
034a61c9
DO
3406 switch ($port['vst_role'])
3407 {
3408 case 'access':
3409 if ($port['mode'] != 'access') // VST violation
3410 continue 2; // ignore change request
3411 foreach ($domain_immune_vlans as $immune)
af204724
DO
3412 // Reverting an attempt to set an access port from
3413 // "normal" VLAN to immune one (or vice versa) requires
3414 // special handling, becase the calling function has
3415 // discarded the old contents of 'allowed' for current port.
af204724
DO
3416 if
3417 (
3418 $before[$port_name]['native'] == $immune or
3419 $port['native'] == $immune
3420 )
3421 {
3422 $port['native'] = $before[$port_name]['native'];
3423 $port['allowed'] = array ($port['native']);
034a61c9
DO
3424 // Such reversal happens either once or never for an
3425 // access port.
3426 break;
af204724 3427 }
034a61c9
DO
3428 break;
3429 case 'trunk':
3430 case 'uplink':
3431 case 'downlink':
3432 if ($port['mode'] != 'trunk')
3433 continue 2;
3434 foreach ($domain_immune_vlans as $immune)
af204724
DO
3435 if (in_array ($immune, $before[$port_name]['allowed'])) // was allowed before
3436 {
3437 if (!in_array ($immune, $port['allowed']))
3438 $port['allowed'][] = $immune; // restore
3439 if ($before[$port_name]['native'] == $immune) // and was native
3440 $port['native'] = $immune; // also restore
3441 }
3442 else // wasn't
3443 {
3444 if (in_array ($immune, $port['allowed']))
3445 unset ($port['allowed'][array_search ($immune, $port['allowed'])]); // cancel
3446 if ($port['native'] == $immune)
3447 $port['native'] = $before[$port_name]['native'];
3448 }
034a61c9
DO
3449 break;
3450 default:
3451 continue 2;
3452 }
3453 // save work
af204724
DO
3454 $ret[$port_name] = $port;
3455 }
3456 return $ret;
3457}
3458
4741e9c3 3459// take port list with order applied and return uplink ports in the same format
ca7f0af4 3460function produceUplinkPorts ($domain_vlanlist, $portlist)
4741e9c3
DO
3461{
3462 $ret = array();
ca7f0af4
DO
3463 $employed = array();
3464 foreach ($domain_vlanlist as $vlan_id => $vlan)
3465 if ($vlan['vlan_type'] == 'compulsory')
3466 $employed[] = $vlan_id;
3467 foreach ($portlist as $port_name => $port)
3468 if ($port['vst_role'] != 'uplink')
3469 foreach ($port['allowed'] as $vlan_id)
3470 if (!in_array ($vlan_id, $employed))
3471 $employed[] = $vlan_id;
3472 foreach ($portlist as $port_name => $port)
3473 if ($port['vst_role'] == 'uplink')
3474 {
1ce258f7
DO
3475 $employed_here = array();
3476 foreach ($employed as $vlan_id)
091768aa 3477 if (matchVLANFilter ($vlan_id, $port['wrt_vlans']))
1ce258f7 3478 $employed_here[] = $vlan_id;
ca7f0af4
DO
3479 $ret[$port_name] = array
3480 (
034a61c9 3481 'vst_role' => 'uplink',
ca7f0af4
DO
3482 'mode' => 'trunk',
3483 'allowed' => $employed_here,
3484 'native' => 0,
3485 );
3486 }
4741e9c3
DO
3487 return $ret;
3488}
3489
9c45ea37
DO
3490function same8021QConfigs ($a, $b)
3491{
3492 return $a['mode'] == $b['mode'] &&
3493 array_values_same ($a['allowed'], $b['allowed']) &&
3494 $a['native'] == $b['native'];
3495}
3496
26dec09b
DO
3497/*
3498
3499Relation between desired (D), cached (C) and running (R)
3500copies of switch ports (P) list.
3501
3502 D C R
3503+---+ +---+ +---+
3504| P |-----| P |-? +--| P |
3505+---+ +---+ / +---+
3506| P |-----| P |--+ ?-| P |
3507+---+ +---+ +---+
3508| P |-----| P |-------| P |
3509+---+ +---+ +---+
3510| P |-----| P |--+ ?-| P |
3511+---+ +---+ \ +---+
3512| P |-----| P |--+ +--| P |
3513+---+ +---+ \ +---+
3514 +--| P |
3515 +---+
3516 ?-| P |
3517 +---+
3518
3519A modified local version of a port in "conflict" state ignores remote
3520changes until remote change maintains its difference. Once both edits
3521match, the local copy "locks" on the remote and starts tracking it.
3522
3523v
3524a "o" -- remOte version
3525l "l" -- Local version
3526u "b" -- Both versions
3527e
3528
3529^
3530| o b
3531| o
3532| l l l l l l b b
3533| o o b
3534| o b
3535|
3536| o
3537|
3538|
35390----------------------------------------------> time
3540
3541*/
be28b696
DO
3542function get8021QSyncOptions
3543(
3544 $vswitch,
3545 $D, // desired config
3546 $C, // cached config
3547 $R // running-config
3548)
3549{
3550 $default_port = array
3551 (
3552 'mode' => 'access',
3553 'allowed' => array (VLAN_DFL_ID),
3554 'native' => VLAN_DFL_ID,
3555 );
ab25b0d0 3556 $ret = array();
24832534 3557 $allports = array();
ab25b0d0 3558 foreach (array_unique (array_merge (array_keys ($C), array_keys ($R))) as $pn)
d3082829
DO
3559 $allports[$pn] = array();
3560 foreach (apply8021QOrder ($vswitch['template_id'], $allports) as $pn => $port)
be28b696 3561 {
7a375475
DO
3562 // catch anomalies early
3563 if ($port['vst_role'] == 'none')
3564 {
3565 if ((!array_key_exists ($pn, $R) or $R[$pn]['mode'] == 'none') and !array_key_exists ($pn, $C))
3566 $ret[$pn] = array ('status' => 'none');
3567 else
3568 $ret[$pn] = array
3569 (
3570 'status' => 'martian_conflict',
3571 'left' => array_key_exists ($pn, $C) ? $C[$pn] : array ('mode' => 'none'),
3572 'right' => array_key_exists ($pn, $R) ? $R[$pn] : array ('mode' => 'none'),
3573 );
3574 continue;
3575 }
3576 elseif ((!array_key_exists ($pn, $R) or $R[$pn]['mode'] == 'none') and array_key_exists ($pn, $C))
3577 {
3578 $ret[$pn] = array
3579 (
3580 'status' => 'martian_conflict',
3581 'left' => array_key_exists ($pn, $C) ? $C[$pn] : array ('mode' => 'none'),
3582 'right' => array_key_exists ($pn, $R) ? $R[$pn] : array ('mode' => 'none'),
3583 );
3584 continue;
3585 }
26dec09b
DO
3586 // (DC_): port missing from device
3587 if (!array_key_exists ($pn, $R))
be28b696 3588 {
ef016293
DO
3589 $ret[$pn] = array ('left' => $D[$pn]);
3590 if (same8021QConfigs ($D[$pn], $default_port))
3591 $ret[$pn]['status'] = 'ok_to_delete';
3592 else
3593 {
3594 $ret[$pn]['status'] = 'delete_conflict';
3595 $ret[$pn]['lastseen'] = $C[$pn];
3596 }
be28b696
DO
3597 continue;
3598 }
26dec09b
DO
3599 // (__R): port missing from DB
3600 if (!array_key_exists ($pn, $C))
be28b696 3601 {
7a375475
DO
3602 // Allow importing any configuration, which passes basic
3603 // validation. If port mode doesn't match its VST role,
3604 // this will be handled later WRT each port.
ab25b0d0
DO
3605 $ret[$pn] = array
3606 (
7a375475 3607 'status' => acceptable8021QConfig ($R[$pn]) ? 'ok_to_add' : 'add_conflict',
ab25b0d0
DO
3608 'right' => $R[$pn],
3609 );
be28b696
DO
3610 continue;
3611 }
3612 $D_eq_C = same8021QConfigs ($D[$pn], $C[$pn]);
3613 $C_eq_R = same8021QConfigs ($C[$pn], $R[$pn]);
26dec09b 3614 // (DCR), D = C = R: data in sync
be28b696 3615 if ($D_eq_C and $C_eq_R) // implies D == R
ab25b0d0
DO
3616 {
3617 $ret[$pn] = array
3618 (
3619 'status' => 'in_sync',
3620 'both' => $R[$pn],
3621 );
be28b696 3622 continue;
ab25b0d0 3623 }
26dec09b 3624 // (DCR), D = C: no local edit in the way
be28b696 3625 if ($D_eq_C)
ab25b0d0
DO
3626 $ret[$pn] = array
3627 (
3628 'status' => 'ok_to_pull',
3629 'left' => $D[$pn],
3630 'right' => $R[$pn],
3631 );
26dec09b 3632 // (DCR), C = R: no remote edit in the way
be28b696 3633 elseif ($C_eq_R)
ab25b0d0
DO
3634 $ret[$pn] = array
3635 (
3636 'status' => 'ok_to_push',
3637 'left' => $D[$pn],
3638 'right' => $R[$pn],
3639 );
26dec09b 3640 // (DCR), D = R: end of version conflict, restore tracking
be28b696 3641 elseif (same8021QConfigs ($D[$pn], $R[$pn]))
ab25b0d0
DO
3642 $ret[$pn] = array
3643 (
d973196a 3644 'status' => 'ok_to_merge',
ab25b0d0
DO
3645 'both' => $R[$pn],
3646 );
26dec09b 3647 else // D != C, C != R, D != R: version conflict
ab25b0d0
DO
3648 $ret[$pn] = array
3649 (
d3082829
DO
3650 'status' => ($port['vst_role'] == 'access' or $port['vst_role'] == 'trunk') ?
3651 // In case the port is normally updated by user, let him
3652 // resolve the conflict. If the system manages this port,
3653 // arrange the data to let remote version go down.
3654 'merge_conflict' : 'ok_to_push_with_merge',
ab25b0d0
DO
3655 'left' => $D[$pn],
3656 'right' => $R[$pn],
3657 );
be28b696 3658 }
ab25b0d0 3659 return $ret;
be28b696
DO
3660}
3661
ca5d4cbc 3662// return number of records updated successfully of FALSE, if a conflict was in the way
d973196a 3663function exec8021QDeploy ($object_id, $do_push)
ca5d4cbc
DO
3664{
3665 global $dbxlink;
b3a27170 3666 $nsaved = $npushed = $nsaved_uplinks = 0;
ca5d4cbc
DO
3667 $dbxlink->beginTransaction();
3668 if (NULL === $vswitch = getVLANSwitchInfo ($object_id, 'FOR UPDATE'))
3669 throw new InvalidArgException ('object_id', $object_id, 'VLAN domain is not set for this object');
3670 $D = getStored8021QConfig ($vswitch['object_id'], 'desired');
3671 $C = getStored8021QConfig ($vswitch['object_id'], 'cached');
d973196a
DO
3672 try
3673 {
3674 $R = getRunning8021QConfig ($vswitch['object_id']);
3675 }
3676 catch (Exception $e)
3677 {
3678 $prepared = $dbxlink->prepare ("UPDATE VLANSwitch SET last_errno = ?, last_error_ts = NOW() WHERE object_id = ?");
3679 $prepared->execute (array (E_8021Q_PULL_REMOTE_ERROR, $vswitch['object_id']));
3680 $dbxlink->commit();
3681 return 0;
3682 }
ca5d4cbc 3683 $conflict = FALSE;
ca5d4cbc 3684 $ok_to_push = array();
d973196a 3685 foreach (get8021QSyncOptions ($vswitch, $D, $C, $R['portdata']) as $pn => $port)
ca5d4cbc
DO
3686 {
3687 // always update cache with new data from switch
3688 switch ($port['status'])
3689 {
d973196a
DO
3690 case 'ok_to_merge':
3691 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['both']);
ca5d4cbc
DO
3692 break;
3693 case 'ok_to_delete':
d973196a 3694 $nsaved += del8021QPort ($vswitch['object_id'], $pn);
ca5d4cbc
DO
3695 break;
3696 case 'ok_to_add':
d973196a 3697 $nsaved += add8021QPort ($vswitch['object_id'], $pn, $port['right']);
ca5d4cbc
DO
3698 break;
3699 case 'delete_conflict':
3700 case 'merge_conflict':
7a375475
DO
3701 case 'add_conflict':
3702 case 'martian_conflict':
ca5d4cbc
DO
3703 $conflict = TRUE;
3704 break;
3705 case 'ok_to_pull':
d973196a
DO
3706 upd8021QPort ('desired', $vswitch['object_id'], $pn, $port['right']);
3707 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right']);
3708 $nsaved++;
ca5d4cbc 3709 break;
d3082829
DO
3710 case 'ok_to_push_with_merge':
3711 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right']);
3712 // fall through
ca5d4cbc
DO
3713 case 'ok_to_push':
3714 $ok_to_push[$pn] = $port['left'];
3715 break;
3716 }
3717 }
32c7fad1
DO
3718 // redo uplinks unconditionally
3719 $domain_vlanlist = getDomainVLANs ($vswitch['domain_id']);
3720 $Dnew = apply8021QOrder ($vswitch['template_id'], getStored8021QConfig ($vswitch['object_id'], 'desired'));
034a61c9
DO
3721 // Take new "desired" configuration and derive uplink port configuration
3722 // from it. Then cancel changes to immune VLANs and save resulting
3723 // changes (if any left).
3724 $new_uplinks = filter8021QChangeRequests ($domain_vlanlist, $Dnew, produceUplinkPorts ($domain_vlanlist, $Dnew));
b3a27170
DO
3725 $nsaved_uplinks += replace8021QPorts ('desired', $vswitch['object_id'], $Dnew, $new_uplinks);
3726 if ($nsaved + $nsaved_uplinks)
94080e1c 3727 {
32c7fad1
DO
3728 // saved configuration has changed (either "user" ports have changed,
3729 // or uplinks, or both), so bump revision number up)
d973196a 3730 $prepared = $dbxlink->prepare ('UPDATE VLANSwitch SET mutex_rev = mutex_rev + 1, last_change = NOW(), out_of_sync = "yes" WHERE object_id = ?');
94080e1c 3731 $prepared->execute (array ($vswitch['object_id']));
ca5d4cbc 3732 }
d973196a 3733 if ($conflict)
ca5d4cbc 3734 {
e6579841 3735 $prepared = $dbxlink->prepare ('UPDATE VLANSwitch SET out_of_sync = "yes", last_errno = ?, last_error_ts = NOW() WHERE object_id = ?');
d973196a
DO
3736 $prepared->execute (array (E_8021Q_VERSION_CONFLICT, $vswitch['object_id']));
3737 }
52ce8bd7 3738 else
d973196a 3739 {
52ce8bd7
DO
3740 $prepared = $dbxlink->prepare ("UPDATE VLANSwitch SET last_errno = ?, last_error_ts = NOW() WHERE object_id = ?");
3741 $prepared->execute (array (E_8021Q_NOERROR, $vswitch['object_id']));
4799a8df
DO
3742 // Modified uplinks are very likely to differ from those in R-copy,
3743 // so don't mark device as clean, if this happened. This can cost
3744 // us an additional, empty round of sync, but at least out_of_sync
3745 // won't be mistakenly set to 'no'.
3746 // FIXME: A cleaner way of coupling pull and push operations would
3747 // be to split this function into two.
3748 if (!count ($ok_to_push) and !$nsaved_uplinks)
ca5d4cbc 3749 {
52ce8bd7 3750 $prepared = $dbxlink->prepare ("UPDATE VLANSwitch SET out_of_sync = 'no' WHERE object_id = ?");
d973196a
DO
3751 $prepared->execute (array ($vswitch['object_id']));
3752 }
52ce8bd7 3753 elseif ($do_push)
d973196a 3754 {
52ce8bd7
DO
3755 $prepared = $dbxlink->prepare ("UPDATE VLANSwitch SET last_push_started = NOW() WHERE object_id = ?");
3756 $prepared->execute (array ($vswitch['object_id']));
3757 try
3758 {
3759 $npushed += exportSwitch8021QConfig ($vswitch, $R['vlanlist'], $R['portdata'], $ok_to_push);
3760 // update cache for ports deployed
3761 replace8021QPorts ('cached', $vswitch['object_id'], $R['portdata'], $ok_to_push);
3762 $prepared = $dbxlink->prepare ('UPDATE VLANSwitch SET last_push_finished = NOW(), out_of_sync = "no", last_errno = ? WHERE object_id = ?');
3763 $prepared->execute (array (E_8021Q_NOERROR, $vswitch['object_id']));
3764 }
3765 catch (RuntimeException $r)
3766 {
e6579841 3767 $prepared = $dbxlink->prepare ('UPDATE VLANSwitch SET out_of_sync = "yes", last_error_ts = NOW(), last_errno = ? WHERE object_id = ?');
52ce8bd7
DO
3768 $prepared->execute (E_8021Q_PUSH_REMOTE_ERROR, array ($vswitch['object_id']));
3769 }
ca5d4cbc
DO
3770 }