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