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