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