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