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