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