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