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