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