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