1<?php 2 3# This file is a part of RackTables, a datacenter and server room management 4# framework. See accompanying file "COPYING" for the full copyright and 5# licensing information. 6 7/* 8* 9* This file is a library of computational functions for RackTables. 10* 11*/ 12 13defineIfNotDefined ('TAGNAME_REGEXP', '/^[\p{L}0-9-]( ?([._~+%-] ?)?[\p{L}0-9:])*(%|\+)?$/u'); 14defineIfNotDefined ('AUTOTAGNAME_REGEXP', '/^\$[\p{L}0-9-]( ?([._~+%-] ?)?[\p{L}0-9:])*(%|\+)?$/u'); 15 16$loclist[0] = 'front'; 17$loclist[1] = 'interior'; 18$loclist[2] = 'rear'; 19$loclist['front'] = 0; 20$loclist['interior'] = 1; 21$loclist['rear'] = 2; 22$template[0] = array (TRUE, TRUE, TRUE); 23$template[1] = array (TRUE, TRUE, FALSE); 24$template[2] = array (FALSE, TRUE, TRUE); 25$template[3] = array (TRUE, FALSE, FALSE); 26$template[4] = array (FALSE, TRUE, FALSE); 27$template[5] = array (FALSE, FALSE, TRUE); 28$templateWidth[0] = 3; 29$templateWidth[1] = 2; 30$templateWidth[2] = 2; 31$templateWidth[3] = 1; 32$templateWidth[4] = 1; 33$templateWidth[5] = 1; 34 35$message_buffering = FALSE; 36 37define ('CHAP_OBJTYPE', 1); 38define ('RE_L2_IFCFG', '/^[0-9A-F]{2}(:[0-9A-F]{2}){5}$/'); // most ifconfigs 39define ('RE_L2_IFCFG_SUNOS', '/^[0-9A-F]{1,2}(:[0-9A-F]{1,2}){5}$/'); // SunOS ifconfig 40define ('RE_L2_CISCO', '/^[0-9A-F]{4}(\.[0-9A-F]{4}){2}$/'); 41define ('RE_L2_HP', '/^[0-9A-F]{6}-[0-9A-F]{6}$/'); 42define ('RE_L2_HUAWEI', '/^[0-9A-F]{4}(-[0-9A-F]{4}){2}$/'); 43define ('RE_L2_SOLID', '/^[0-9A-F]{12}$/'); 44define ('RE_L2_IPCFG', '/^[0-9A-F]{2}(-[0-9A-F]{2}){5}$/'); 45define ('RE_L2_WWN_COLON', '/^[0-9A-F]{2}(:[0-9A-F]{2}){7}$/'); 46define ('RE_L2_WWN_HYPHEN', '/^[0-9A-F]{2}(-[0-9A-F]{2}){7}$/'); 47define ('RE_L2_WWN_SOLID', '/^[0-9A-F]{16}$/'); 48define ('RE_L2_IPOIB_COLON', '/^[0-9A-F]{2}(:[0-9A-F]{2}){19}$/'); 49define ('RE_L2_IPOIB_HYPHEN', '/^[0-9A-F]{2}(-[0-9A-F]{2}){19}$/'); 50define ('RE_L2_IPOIB_SOLID', '/^[0-9A-F]{40}$/'); 51define ('RE_IP4_ADDR', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}$#'); 52define ('RE_IP4_NET', '#^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$#'); 53define ('RE_STATIC_URI', '#^(?:[[:alnum:]]+[[:alnum:]_.-]*/)+[[:alnum:]\._-]+\.([[:alpha:]]+)$#'); 54define ('E_8021Q_NOERROR', 0); 55define ('E_8021Q_VERSION_CONFLICT', 101); 56define ('E_8021Q_PULL_REMOTE_ERROR', 102); 57define ('E_8021Q_PUSH_REMOTE_ERROR', 103); 58define ('E_8021Q_SYNC_DISABLED', 104); 59define ('VLAN_MIN_ID', 1); 60define ('VLAN_MAX_ID', 4094); 61define ('VLAN_DFL_ID', 1); 62define ('TAB_REMEMBER_TIMEOUT', 300); 63 64// Entity type by page number mapping is 1:1 atm, but may change later. 65$etype_by_pageno = array 66( 67 'ipv4net' => 'ipv4net', 68 'ipv6net' => 'ipv6net', 69 'ipv4rspool' => 'ipv4rspool', 70 'ipv4vs' => 'ipv4vs', 71 'ipvs' => 'ipvs', 72 'object' => 'object', 73 'rack' => 'rack', 74 'row' => 'row', 75 'location' => 'location', 76 'user' => 'user', 77 'file' => 'file', 78 'vst' => 'vst', 79); 80$pageno_by_etype = array_flip ($etype_by_pageno); 81 82// Rack thumbnail image width summands: "front", "interior" and "rear" elements w/o surrounding border. 83$rtwidth = array 84( 85 0 => 9, 86 1 => 21, 87 2 => 9 88); 89 90$location_obj_types = array 91( 92 1560, 93 1561, 94 1562 95); 96 97// 802.1Q deploy queue titles 98$dqtitle = array 99( 100 'sync_aging' => 'Normal, aging', 101 'resync_aging' => 'Failed, aging', 102 'sync_ready' => 'Normal, ready for sync', 103 'resync_ready' => 'Failed, ready for retry', 104 'disabled' => 'Sync disabled', 105 'done' => 'Up to date', 106); 107 108$wdm_packs = array 109( 110 '1000cwdm80' => array 111 ( 112 'title' => '1000Base-CWDM80 (8 channels)', 113 'iif_ids' => array (3, 4), 114 'oif_ids' => array (1209, 1210, 1211, 1212, 1213, 1214, 1215, 1216), 115 ), 116 '1000dwdm80' => array // ITU channels 20~61 117 ( 118 'title' => '1000Base-DWDM80 (42 channels)', 119 'iif_ids' => array (3, 4), 120 'oif_ids' => array 121 ( 122 1217, 1218, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226, 123 1227, 1228, 1229, 1230, 1231, 1232, 1233, 1234, 1235, 1236, 124 1237, 1238, 1239, 1240, 1241, 1242, 1243, 1244, 1245, 1246, 125 1247, 1248, 1249, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 126 1257, 1258 127 ), 128 ), 129 '10000dwdm80' => array // same channels for 10GE 130 ( 131 'title' => '10GBase-ZR-DWDM80 (42 channels)', 132 'iif_ids' => array (9, 6, 5, 8, 7), 133 'oif_ids' => array 134 ( 135 1259, 1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268, 136 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, 1278, 137 1279, 1280, 1281, 1282, 1283, 1284, 1285, 1286, 1287, 1288, 138 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298, 139 1299, 1300 140 ), 141 ), 142 '10000dwdm40' => array 143 ( 144 'title' => '10GBase-ER-DWDM40 (42 channels)', 145 'iif_ids' => array (9, 6, 5, 8, 7), 146 'oif_ids' => array 147 ( 148 1425, 1426, 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1434, 149 1435, 1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, 1444, 150 1445, 1446, 1447, 1448, 1449, 1450, 1451, 1452, 1453, 1454, 151 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1462, 1463, 1464, 152 1465, 1466 153 ), 154 ), 155); 156 157// Default input for renderExpirations(), can be overridden in local plugins. 158$expirations = array(); 159$expirations[21] = array 160( 161 array ('from' => -365, 'to' => 0, 'class' => 'has_problems_', 'title' => 'has expired within last year'), 162 array ('from' => 0, 'to' => 30, 'class' => 'row_', 'title' => 'expires within 30 days'), 163 array ('from' => 30, 'to' => 60, 'class' => 'row_', 'title' => 'expires within 60 days'), 164 array ('from' => 60, 'to' => 90, 'class' => 'row_', 'title' => 'expires within 90 days'), 165); 166$expirations[22] = $expirations[21]; 167$expirations[24] = $expirations[21]; 168 169$natv4_proto = array ('TCP' => 'TCP', 'UDP' => 'UDP', 'ALL' => 'ALL'); 170 171$log_messages = array(); // messages waiting for displaying 172 173function defineIfNotDefined ($constant, $value, $case_insensitive = FALSE) 174{ 175 if (defined ($constant) === FALSE) 176 define ($constant, $value, $case_insensitive); 177} 178 179// For backward compatibility only, remove later. 180function assertUIntArg ($argname, $allow_zero = FALSE) 181{ 182 return $allow_zero ? assertUnsignedIntArg ($argname) : assertNaturalNumArg ($argname); 183} 184 185// Tell whether the argument is a decimal integer (or, alternatively, a numeric 186// string with a decimal integer). 187function isInteger ($arg) 188{ 189 // In PHP 7.0.0 and later is_numeric() rejects a string that contains 190 // a hexadecimal number, help PHP 5 achieve the same result here. 191 return is_numeric ($arg) && 192 (! is_string ($arg) || FALSE === mb_strstr ($arg, '0x')) && 193 is_int (0 + $arg); 194} 195 196function isUnsignedInteger ($arg) 197{ 198 return isInteger ($arg) && $arg >= 0; 199} 200 201function isNaturalNumber ($arg) 202{ 203 return isInteger ($arg) && $arg >= 1; 204} 205 206function isHTMLColor ($color) 207{ 208 return 1 == preg_match ('/^[0-9A-F]{6}$/i', $color); 209} 210 211function isValidVLANID ($x) 212{ 213 return isInteger ($x) && $x >= VLAN_MIN_ID && $x <= VLAN_MAX_ID; 214} 215 216function assertUnsignedIntArg ($argname) 217{ 218 if (! isset ($_REQUEST[$argname])) 219 throw new InvalidRequestArgException ($argname, '', 'parameter is missing'); 220 if (! isUnsignedInteger ($_REQUEST[$argname])) 221 throw new InvalidRequestArgException ($argname, $_REQUEST[$argname], 'parameter is not an unsigned integer (or 0)'); 222 return $_REQUEST[$argname]; 223} 224 225function assertNaturalNumArg ($argname) 226{ 227 if (! isset ($_REQUEST[$argname])) 228 throw new InvalidRequestArgException ($argname, '', 'parameter is missing'); 229 if (! isNaturalNumber ($_REQUEST[$argname])) 230 throw new InvalidRequestArgException ($argname, $_REQUEST[$argname], 'parameter is not a natural number'); 231 return $_REQUEST[$argname]; 232} 233 234# Make sure the arg is a parsable date, return its UNIX timestamp equivalent 235# (or empty string for empty input, when allowed). 236# 237# FIXME: This function should be removed for the reasons below: 238# 1. Its naming is wrong as it would accept any argument value that is valid 239# per DATETIME_FORMAT configuration variable, which by default is set to 240# date only but can include time if necessary. 241# 2. It converts the target value from a string to a UNIX timestamp, which is 242# not the semantics of similar functions. 243# 3. It is not used anywhere in the code anymore. 244function assertDateArg ($argname, $ok_if_empty = FALSE) 245{ 246 if ('' == $arg = assertStringArg ($argname, $ok_if_empty)) 247 return ''; 248 try 249 { 250 return timestampFromDatetimestr ($arg); 251 } 252 catch (InvalidArgException $e) 253 { 254 throw $e->newIRAE ($argname); 255 } 256} 257 258// This function assures that specified argument was passed 259// and is a non-empty string. 260function assertStringArg ($argname, $ok_if_empty = FALSE) 261{ 262 global $sic; 263 if (!isset ($_REQUEST[$argname])) 264 throw new InvalidRequestArgException($argname, '', 'parameter is missing'); 265 if (!is_string ($_REQUEST[$argname])) 266 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string'); 267 if (! $ok_if_empty && $_REQUEST[$argname] == '') 268 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string'); 269 return $sic[$argname]; 270} 271 272function assertBoolArg ($argname, $ok_if_empty = FALSE) 273{ 274 if (!isset ($_REQUEST[$argname])) 275 throw new InvalidRequestArgException($argname, '', 'parameter is missing'); 276 if (! is_string ($_REQUEST[$argname]) || $_REQUEST[$argname] != 'on') 277 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a string'); 278 if (! $ok_if_empty && $_REQUEST[$argname] == '') 279 throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string'); 280 return $_REQUEST[$argname] == TRUE; 281} 282 283// function returns binary IP address, or throws an exception 284function assertIPArg ($argname) 285{ 286 try 287 { 288 return ip_parse (assertStringArg ($argname)); 289 } 290 catch (InvalidRequestArgException $irae) 291 { 292 throw $irae; 293 } 294 catch (InvalidArgException $e) 295 { 296 throw $e->newIRAE ($argname); 297 } 298} 299 300// function returns binary IPv4 address, or throws an exception 301function assertIPv4Arg ($argname) 302{ 303 try 304 { 305 return ip4_parse (assertStringArg ($argname)); 306 } 307 catch (InvalidRequestArgException $irae) 308 { 309 throw $irae; 310 } 311 catch (InvalidArgException $e) 312 { 313 throw $e->newIRAE ($argname); 314 } 315} 316 317// function returns binary IPv6 address, or throws an exception 318function assertIPv6Arg ($argname) 319{ 320 try 321 { 322 return ip6_parse (assertStringArg ($argname)); 323 } 324 catch (InvalidRequestArgException $irae) 325 { 326 throw $irae; 327 } 328 catch (InvalidArgException $e) 329 { 330 throw $e->newIRAE ($argname); 331 } 332} 333 334function assertPCREArg ($argname) 335{ 336 $arg = assertStringArg ($argname, TRUE); // empty pattern is Ok 337 if (FALSE === @preg_match ($arg, 'test')) 338 throw new InvalidRequestArgException($argname, $arg, 'PCRE validation failed'); 339 return $arg; 340} 341 342function isPCRE ($arg) 343{ 344 return isset ($arg) && FALSE !== @preg_match ($arg, 'test'); 345} 346 347function genericAssertion ($argname, $argtype) 348{ 349 global $sic; 350 switch ($argtype) 351 { 352 case 'string': 353 return assertStringArg ($argname); 354 case 'string0': 355 return assertStringArg ($argname, TRUE); 356 case 'natural0': 357 if ('' == assertStringArg ($argname, TRUE)) 358 return ''; 359 // fall through 360 // old style, for backward compatibility 361 case 'uint': 362 case 'natural': 363 return assertNaturalNumArg ($argname); 364 case 'unsigned0': 365 if ('' == assertStringArg ($argname, TRUE)) 366 return ''; 367 // fall through 368 // old style, for backward compatibility 369 case 'uint0': 370 case 'unsigned': 371 return assertUnsignedIntArg ($argname); 372 case 'decimal0': 373 if ('' == assertStringArg ($argname, TRUE)) 374 return ''; 375 // fall through 376 case 'decimal': 377 if (! preg_match ('/^\d+(\.\d+)?$/', assertStringArg ($argname))) 378 throw new InvalidRequestArgException ($argname, $sic[$argname], 'format error'); 379 return $sic[$argname]; 380 case 'inet': 381 return assertIPArg ($argname); 382 case 'inet4': 383 return assertIPv4Arg ($argname); 384 case 'inet6': 385 return assertIPv6Arg ($argname); 386 case 'l2address0': 387 if ('' == assertStringArg ($argname, TRUE)) 388 return ''; 389 // fall through 390 case 'l2address': 391 assertStringArg ($argname); 392 try 393 { 394 l2addressForDatabase ($sic[$argname]); 395 } 396 catch (InvalidArgException $iae) 397 { 398 throw $iae->newIRAE ($argname); 399 } 400 return $sic[$argname]; 401 case 'tag': 402 if (!validTagName (assertStringArg ($argname))) 403 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Invalid tag name'); 404 return $sic[$argname]; 405 case 'pcre': 406 return assertPCREArg ($argname); 407 case 'json': 408 if (NULL === ($ret = json_decode (assertStringArg ($argname), TRUE))) 409 throw new InvalidRequestArgException ($argname, '(omitted)', 'Invalid JSON code received from client'); 410 return $ret; 411 case 'array': 412 if (! array_key_exists ($argname, $_REQUEST)) 413 throw new InvalidRequestArgException ($argname, '(missing argument)'); 414 if (! is_array ($_REQUEST[$argname])) 415 throw new InvalidRequestArgException ($argname, '(omitted)', 'argument is not an array'); 416 return $_REQUEST[$argname]; 417 case 'array0': 418 if (! array_key_exists ($argname, $_REQUEST)) 419 return array(); 420 if (! is_array ($_REQUEST[$argname])) 421 throw new InvalidRequestArgException ($argname, '(omitted)', 'argument is not an array'); 422 return $_REQUEST[$argname]; 423 case 'datetime0': 424 if ('' == assertStringArg ($argname, TRUE)) 425 return ''; 426 // fall through 427 case 'datetime': 428 $argvalue = assertStringArg ($argname); 429 try 430 { 431 timestampFromDatetimestr ($argvalue); // discard the result on success 432 } 433 catch (InvalidArgException $iae) 434 { 435 throw $iae->newIRAE ($argname); 436 } 437 return $argvalue; 438 case 'dateonly0': 439 if ('' == assertStringArg ($argname, TRUE)) 440 return ''; 441 // fall through 442 case 'dateonly': 443 $argvalue = assertStringArg ($argname); 444 try 445 { 446 SQLDateFromDateStr ($argvalue); // discard the result on success 447 } 448 catch (InvalidArgException $iae) 449 { 450 throw $iae->newIRAE ($argname); 451 } 452 return $argvalue; 453 case 'enum/attr_type': 454 assertStringArg ($argname); 455 if (!in_array ($sic[$argname], array ('uint', 'float', 'string', 'dict','date'))) 456 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value'); 457 return $sic[$argname]; 458 case 'enum/vlan_type': 459 assertStringArg ($argname); 460 // "Alien" type is not valid until the logic is fixed to implement it in full. 461 if (!in_array ($sic[$argname], array ('ondemand', 'compulsory'))) 462 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value'); 463 return $sic[$argname]; 464 case 'enum/wdmstd': 465 assertStringArg ($argname); 466 global $wdm_packs; 467 if (! array_key_exists ($sic[$argname], $wdm_packs)) 468 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value'); 469 return $sic[$argname]; 470 case 'enum/ipproto': 471 assertStringArg ($argname); 472 global $vs_proto; 473 if (!array_key_exists ($sic[$argname], $vs_proto)) 474 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value'); 475 return $sic[$argname]; 476 case 'enum/natv4proto': 477 assertStringArg ($argname); 478 global $natv4_proto; 479 if (! array_key_exists ($sic[$argname], $natv4_proto)) 480 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value'); 481 return $sic[$argname]; 482 case 'enum/alloc_type': 483 assertStringArg ($argname); 484 if (!in_array ($sic[$argname], array ('regular', 'shared', 'virtual', 'router', 'sharedrouter', 'point2point'))) 485 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value'); 486 return $sic[$argname]; 487 case 'enum/dqcode': 488 assertStringArg ($argname); 489 global $dqtitle; 490 if (! array_key_exists ($sic[$argname], $dqtitle)) 491 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value'); 492 return $sic[$argname]; 493 case 'enum/yesno': 494 if (! in_array ($sic[$argname], array ('yes', 'no'))) 495 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value'); 496 return $sic[$argname]; 497 case 'iif': 498 assertNaturalNumArg ($argname); 499 if (!array_key_exists ($sic[$argname], getPortIIFOptions())) 500 throw new InvalidRequestArgException ($argname, $sic[$argname], 'Unknown value'); 501 return $sic[$argname]; 502 // 'vlan' -- any valid VLAN ID except the default 503 // 'vlan1' -- any valid VLAN ID including the default 504 case 'vlan': 505 case 'vlan1': 506 assertNaturalNumArg ($argname); 507 if (! isValidVLANID ($sic[$argname])) 508 throw new InvalidRequestArgException ($argname, $sic[$argname], 'not a valid VLAN ID'); 509 if ($argtype == 'vlan' && $sic[$argname] == VLAN_DFL_ID) 510 throw new InvalidRequestArgException ($argname, $sic[$argname], 'default VLAN not allowed'); 511 return $sic[$argname]; 512 case 'uint-vlan': 513 case 'uint-vlan1': 514 $argvalue = assertStringArg ($argname); 515 try 516 { 517 list ($vdom_id, $vlan_id) = decodeVLANCK ($argvalue); 518 } 519 catch (InvalidArgException $iae) 520 { 521 throw $iae->newIRAE ($argname); 522 } 523 if ($argtype == 'uint-vlan' && $vlan_id == VLAN_DFL_ID) 524 throw new InvalidRequestArgException ($argname, $argvalue, 'default VLAN not allowed'); 525 return $argvalue; 526 case 'rackcode/expr': 527 if ('' == assertStringArg ($argname, TRUE)) 528 return array(); 529 if (! $expr = compileExpression ($sic[$argname])) 530 throw new InvalidRequestArgException ($argname, $sic[$argname], 'not a valid RackCode expression'); 531 return $expr; 532 case 'htmlcolor0': 533 if ('' == assertStringArg ($argname, TRUE)) 534 return ''; 535 // fall through 536 case 'htmlcolor': 537 $argvalue = assertStringArg ($argname); 538 if (! isHTMLColor ($argvalue)) 539 throw new InvalidRequestArgException ($argname, $argvalue, 'not an HTML color'); 540 return $argvalue; 541 default: 542 throw new InvalidArgException ('argtype', $argtype); // comes not from user's input 543 } 544} 545 546// return HTML form checkbox value (TRUE of FALSE) by name of its input control 547function isCheckSet ($input_name, $mode = 'bool') 548{ 549 $value = isset ($_REQUEST[$input_name]) && $_REQUEST[$input_name] == 'on'; 550 switch ($mode) 551 { 552 case 'bool' : return $value; 553 case 'yesno': return $value ? 'yes' : 'no'; 554 default: throw new InvalidArgException ('mode', $mode); 555 } 556} 557 558// Validate and return "bypass" value for the current context, if one is 559// defined for it, or NULL otherwise. 560// There is at least one bit of code that depends on the NULL return value 561// (although it does not explicitly check for it), it is the "interface" case 562// in index.php, which makes an unconditional call to here. Changing this 563// function to throw an exception instead will require changing at least 564// that code too. 565function getBypassValue() 566{ 567 global $page, $pageno; 568 if (!array_key_exists ('bypass', $page[$pageno])) 569 return NULL; 570 if (!array_key_exists ('bypass_type', $page[$pageno])) 571 throw new RackTablesError ("Internal structure error at node '${pageno}' (bypass_type is not set)", RackTablesError::INTERNAL); 572 return genericAssertion ($page[$pageno]['bypass'], $page[$pageno]['bypass_type']); 573} 574 575// fills $args array with the bypass values of specified $pageno that are provided in $_REQUEST 576function fillBypassValues ($pageno, &$args) 577{ 578 global $page, $sic; 579 if (isset ($page[$pageno]['bypass'])) 580 { 581 $param_name = $page[$pageno]['bypass']; 582 if (! array_key_exists ($param_name, $args) && isset ($sic[$param_name])) 583 $args[$param_name] = $sic[$param_name]; 584 } 585 if (isset ($page[$pageno]['bypass_tabs'])) 586 foreach ($page[$pageno]['bypass_tabs'] as $param_name) 587 if (! array_key_exists ($param_name, $args) && isset ($sic[$param_name])) 588 $args[$param_name] = $sic[$param_name]; 589} 590 591// Objects of some types should be explicitly shown as 592// anonymous (labelless). This function is a single place where the 593// decision about displayed name is made. 594function formatObjectDisplayedName ($name, $objtype_id) 595{ 596 return ($name != '') ? $name : sprintf ('[%s]', decodeObjectType ($objtype_id)); 597} 598 599// Set the dname attribute within a cell 600function setDisplayedName (&$cell) 601{ 602 if ($cell['realm'] == 'object') 603 { 604 $cell['dname'] = formatObjectDisplayedName ($cell['name'], $cell['objtype_id']); 605 // If the object has a container, set its dname as well 606 if ($cell['container_id']) 607 $cell['container_dname'] = formatObjectDisplayedName ($cell['container_name'], $cell['container_objtype_id']); 608 } 609 elseif ($cell['realm'] == 'ipv4vs') 610 if ($cell['proto'] == 'MARK') 611 $cell['dname'] = 'fwmark: ' . implode ('', unpack ('N', substr ($cell['vip_bin'], 0, 4))); 612 else 613 $cell['dname'] = $cell['vip'] . ':' . $cell['vport'] . '/' . $cell['proto']; 614} 615 616// This function finds height of solid rectangle of atoms that are all 617// assigned to the same object. Rectangle base is defined by specified 618// template. 619function rectHeight ($rackData, $startRow, $template_idx) 620{ 621 $height = 0; 622 // The first met object_id is used to match all the folowing IDs. 623 $object_id = 0; 624 global $template; 625 do 626 { 627 for ($locidx = 0; $locidx < 3; $locidx++) 628 { 629 // At least one value in template is TRUE, but the following block 630 // can meet 'skipped' atoms. Let's ensure there is at least some result 631 // after processing the first row. 632 if ($template[$template_idx][$locidx]) 633 { 634 if 635 ( 636 isset ($rackData[$startRow - $height][$locidx]['skipped']) || 637 isset ($rackData[$startRow - $height][$locidx]['rowspan']) || 638 isset ($rackData[$startRow - $height][$locidx]['colspan']) || 639 $rackData[$startRow - $height][$locidx]['state'] != 'T' 640 ) 641 break 2; 642 if ($object_id == 0) 643 $object_id = $rackData[$startRow - $height][$locidx]['object_id']; 644 if ($object_id != $rackData[$startRow - $height][$locidx]['object_id']) 645 break 2; 646 } 647 } 648 // If the first row can't offer anything, bail out. 649 if ($height == 0 && $object_id == 0) 650 break; 651 $height++; 652 } 653 while ($startRow - $height > 0); 654 return $height; 655} 656 657// This function marks atoms to be avoided by rectHeight() and assigns rowspan/colspan 658// attributes. 659function markSpan (&$rackData, $startRow, $maxheight, $template_idx) 660{ 661 global $template, $templateWidth; 662 $colspan = 0; 663 for ($height = 0; $height < $maxheight; $height++) 664 for ($locidx = 0; $locidx < 3; $locidx++) 665 if ($template[$template_idx][$locidx]) 666 { 667 // Add colspan/rowspan to the first row met and mark the following ones to skip. 668 // Explicitly show even single-cell spanned atoms, because rectHeight() 669 // is expeciting this data for correct calculation. 670 if ($colspan != 0) 671 $rackData[$startRow - $height][$locidx]['skipped'] = TRUE; 672 else 673 { 674 $colspan = $templateWidth[$template_idx]; 675 if ($colspan >= 1) 676 $rackData[$startRow - $height][$locidx]['colspan'] = $colspan; 677 if ($maxheight >= 1) 678 $rackData[$startRow - $height][$locidx]['rowspan'] = $maxheight; 679 } 680 } 681} 682 683// This function sets rowspan/solspan/skipped atom attributes for renderRack() 684// by finding _all_ possible rectangles for each rack unit and then selecting 685// the widest of those with the maximal square. 686function markAllSpans (&$rackData) 687{ 688 for ($i = $rackData['height']; $i > 0; $i--) 689 while (markBestSpan ($rackData, $i)); 690} 691 692// Calculate height of 6 possible span templates (array is presorted by width 693// descending) and mark the best (if any). 694function markBestSpan (&$rackData, $i) 695{ 696 global $templateWidth; 697 $height = array(); 698 $square = array(); 699 foreach ($templateWidth as $j => $width) 700 { 701 $height[$j] = rectHeight ($rackData, $i, $j); 702 $square[$j] = $height[$j] * $width; 703 } 704 // find the widest rectangle of those with maximal height 705 if (0 == $maxsquare = max ($square)) 706 return FALSE; 707 $best_template_index = array_search ($maxsquare, $square); 708 // distribute span marks 709 markSpan ($rackData, $i, $height[$best_template_index], $best_template_index); 710 return TRUE; 711} 712 713// OK to mount to 'F' atoms and unmount from the current object's 'T' atoms. 714function applyObjectMountMask (&$rackData, $object_id) 715{ 716 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--) 717 for ($locidx = 0; $locidx < 3; $locidx++) 718 switch ($rackData[$unit_no][$locidx]['state']) 719 { 720 case 'F': 721 $rackData[$unit_no][$locidx]['enabled'] = TRUE; 722 break; 723 case 'T': 724 $rackData[$unit_no][$locidx]['enabled'] = ($rackData[$unit_no][$locidx]['object_id'] == $object_id); 725 break; 726 default: 727 $rackData[$unit_no][$locidx]['enabled'] = FALSE; 728 } 729} 730 731// check permissions for rack modification 732function rackModificationPermitted ($rackData, $op, $with_context=TRUE) 733{ 734 if ($with_context && ! permitted (NULL, NULL, $op)) 735 return FALSE; 736 $rack_op_annex = array_merge ($rackData['etags'], $rackData['itags'], $rackData['atags']); 737 return permitted (NULL, NULL, $op, $rack_op_annex); 738} 739 740// Design change means transition between 'F' and 'A' and back. 741function applyRackDesignMask (&$rackData) 742{ 743 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--) 744 for ($locidx = 0; $locidx < 3; $locidx++) 745 switch ($rackData[$unit_no][$locidx]['state']) 746 { 747 case 'F': 748 case 'A': 749 $rackData[$unit_no][$locidx]['enabled'] = TRUE; 750 break; 751 default: 752 $rackData[$unit_no][$locidx]['enabled'] = FALSE; 753 } 754} 755 756// The same for 'F' and 'U'. 757function applyRackProblemMask (&$rackData) 758{ 759 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--) 760 for ($locidx = 0; $locidx < 3; $locidx++) 761 switch ($rackData[$unit_no][$locidx]['state']) 762 { 763 case 'F': 764 case 'U': 765 $rackData[$unit_no][$locidx]['enabled'] = TRUE; 766 break; 767 default: 768 $rackData[$unit_no][$locidx]['enabled'] = FALSE; 769 } 770} 771 772// This function highlights specified object by amending the 773// 'hl' suffix of the class name. 774function highlightObject (&$rackData, $object_id) 775{ 776 // Also highlight parent objects 777 $object = spotEntity ('object', $object_id); 778 $parents = reindexById (getParents ($object, 'object')); 779 780 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--) 781 for ($locidx = 0; $locidx < 3; $locidx++) 782 { 783 $atom = &$rackData[$unit_no][$locidx]; 784 if 785 ( 786 $atom['state'] == 'T' && 787 ($atom['object_id'] == $object_id || isset ($parents[$atom['object_id']])) 788 ) 789 $atom['hl'] = 'h' . $atom['hl']; 790 } 791} 792 793// This function marks atoms to selected or not depending on their current state. 794function markupAtomGrid (&$data, $checked_state) 795{ 796 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--) 797 for ($locidx = 0; $locidx < 3; $locidx++) 798 if ($data[$unit_no][$locidx]['enabled']) 799 $data[$unit_no][$locidx]['checked'] = 800 $data[$unit_no][$locidx]['state'] == $checked_state ? ' checked' : ''; 801} 802 803// This function is almost a clone of processGridForm(), except it does not modify 804// the database. This code assumes that a correct filter has already been applied, 805// hence it just sets or unsets checkbox inputs and leaves the atom state intact. 806function mergeGridFormToRack (&$rackData) 807{ 808 $rack_id = $rackData['id']; 809 for ($unit_no = $rackData['height']; $unit_no > 0; $unit_no--) 810 for ($locidx = 0; $locidx < 3; $locidx++) 811 if ($rackData[$unit_no][$locidx]['enabled']) 812 $rackData[$unit_no][$locidx]['checked'] = 813 isCheckSet ("atom_${rack_id}_${unit_no}_${locidx}") ? ' checked' : ''; 814} 815 816// wrapper around ip4_mask and ip6_mask 817// netmask conversion from length to binary string 818// v4/v6 mode is toggled by $is_ipv6 parameter 819// Throws exception if $prefix_len is invalid 820function ip_mask ($prefix_len, $is_ipv6) 821{ 822 return $is_ipv6 ? ip6_mask ($prefix_len) : ip4_mask ($prefix_len); 823} 824 825// netmask conversion from length to binary string 826// Throws exception if $prefix_len is invalid 827function ip4_mask ($prefix_len) 828{ 829 static $mask = array 830 ( 831 "\x00\x00\x00\x00", // 0 832 "\x80\x00\x00\x00", // 1 833 "\xC0\x00\x00\x00", // 2 834 "\xE0\x00\x00\x00", // 3 835 "\xF0\x00\x00\x00", // 4 836 "\xF8\x00\x00\x00", // 5 837 "\xFC\x00\x00\x00", // 6 838 "\xFE\x00\x00\x00", // 7 839 "\xFF\x00\x00\x00", // 8 840 "\xFF\x80\x00\x00", // 9 841 "\xFF\xC0\x00\x00", // 10 842 "\xFF\xE0\x00\x00", // 11 843 "\xFF\xF0\x00\x00", // 12 844 "\xFF\xF8\x00\x00", // 13 845 "\xFF\xFC\x00\x00", // 14 846 "\xFF\xFE\x00\x00", // 15 847 "\xFF\xFF\x00\x00", // 16 848 "\xFF\xFF\x80\x00", // 17 849 "\xFF\xFF\xC0\x00", // 18 850 "\xFF\xFF\xE0\x00", // 19 851 "\xFF\xFF\xF0\x00", // 20 852 "\xFF\xFF\xF8\x00", // 21 853 "\xFF\xFF\xFC\x00", // 22 854 "\xFF\xFF\xFE\x00", // 23 855 "\xFF\xFF\xFF\x00", // 24 856 "\xFF\xFF\xFF\x80", // 25 857 "\xFF\xFF\xFF\xC0", // 26 858 "\xFF\xFF\xFF\xE0", // 27 859 "\xFF\xFF\xFF\xF0", // 28 860 "\xFF\xFF\xFF\xF8", // 29 861 "\xFF\xFF\xFF\xFC", // 30 862 "\xFF\xFF\xFF\xFE", // 31 863 "\xFF\xFF\xFF\xFF", // 32 864 ); 865 866 if ($prefix_len >= 0 && $prefix_len <= 32) 867 return $mask[$prefix_len]; 868 throw new InvalidArgException ('prefix_len', $prefix_len); 869} 870 871// netmask conversion from length to binary string 872// Throws exception if $prefix_len is invalid 873function ip6_mask ($prefix_len) 874{ 875 static $mask = array 876 ( 877 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 0 878 "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 1 879 "\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 2 880 "\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 3 881 "\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 4 882 "\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 5 883 "\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 6 884 "\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 7 885 "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 8 886 "\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 9 887 "\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 10 888 "\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 11 889 "\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 12 890 "\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 13 891 "\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 14 892 "\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 15 893 "\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 16 894 "\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 17 895 "\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 18 896 "\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 19 897 "\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 20 898 "\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 21 899 "\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 22 900 "\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 23 901 "\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 24 902 "\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 25 903 "\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 26 904 "\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 27 905 "\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 28 906 "\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 29 907 "\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 30 908 "\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 31 909 "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 32 910 "\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 33 911 "\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 34 912 "\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 35 913 "\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 36 914 "\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 37 915 "\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 38 916 "\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 39 917 "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 40 918 "\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 41 919 "\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 42 920 "\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 43 921 "\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 44 922 "\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 45 923 "\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 46 924 "\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 47 925 "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 48 926 "\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 49 927 "\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 50 928 "\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 51 929 "\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 52 930 "\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 53 931 "\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 54 932 "\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 55 933 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00", // 56 934 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00", // 57 935 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00\x00", // 58 936 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00\x00", // 59 937 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00\x00", // 60 938 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00\x00", // 61 939 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00\x00", // 62 940 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00\x00", // 63 941 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00", // 64 942 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00", // 65 943 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00\x00", // 66 944 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00\x00", // 67 945 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00\x00", // 68 946 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00\x00", // 69 947 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00\x00", // 70 948 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00\x00", // 71 949 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00", // 72 950 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00", // 73 951 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00\x00", // 74 952 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00\x00", // 75 953 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00\x00", // 76 954 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00\x00", // 77 955 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00\x00", // 78 956 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00\x00", // 79 957 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00", // 80 958 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00", // 81 959 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00\x00", // 82 960 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00\x00", // 83 961 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00\x00", // 84 962 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00\x00", // 85 963 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00\x00", // 86 964 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00\x00", // 87 965 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00", // 88 966 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00", // 89 967 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00\x00", // 90 968 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00\x00", // 91 969 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00\x00", // 92 970 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00\x00", // 93 971 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00\x00", // 94 972 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00\x00", // 95 973 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00", // 96 974 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00", // 97 975 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00\x00", // 98 976 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00\x00", // 99 977 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00\x00", // 100 978 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00\x00", // 101 979 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00\x00", // 102 980 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00\x00", // 103 981 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00", // 104 982 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00", // 105 983 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00\x00", // 106 984 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00\x00", // 107 985 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00\x00", // 108 986 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00\x00", // 109 987 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00\x00", // 110 988 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00\x00", // 111 989 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00", // 112 990 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00", // 113 991 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0\x00", // 114 992 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\x00", // 115 993 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0\x00", // 116 994 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8\x00", // 117 995 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC\x00", // 118 996 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE\x00", // 119 997 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", // 120 998 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80", // 121 999 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC0", // 122 1000 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0", // 123 1001 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF0", // 124 1002 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xF8", // 125 1003 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFC", // 126 1004 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFE", // 127 1005 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", // 128 1006 ); 1007 1008 if ($prefix_len >= 0 && $prefix_len <= 128) 1009 return $mask[$prefix_len]; 1010 throw new InvalidArgException ('prefix_len', $prefix_len); 1011} 1012 1013// Return a uniformly (010203040506 or 0102030405060708 or likewise) formatted 1014// L2 address for a string in one of the recognized formats, an empty string for 1015// an empty string or raise an exception on invalid input. 1016function l2addressForDatabase ($string) 1017{ 1018 $string = strtoupper (trim ($string)); 1019 $ret = ''; 1020 switch (TRUE) 1021 { 1022 case $string == '': 1023 case preg_match (RE_L2_SOLID, $string): 1024 case preg_match (RE_L2_WWN_SOLID, $string): 1025 case preg_match (RE_L2_IPOIB_SOLID, $string): 1026 $ret = $string; 1027 break; 1028 case preg_match (RE_L2_IFCFG, $string): 1029 case preg_match (RE_L2_WWN_COLON, $string): 1030 case preg_match (RE_L2_IPOIB_COLON, $string): 1031 $ret = str_replace (':', '', $string); 1032 break; 1033 // SunOS notation regexp will also match other ifconfigs notations hence should 1034 // be matched and conditioned after the others, not before. 1035 case preg_match (RE_L2_IFCFG_SUNOS, $string): 1036 foreach (explode (':', $string) as $byte) 1037 $ret .= (strlen ($byte) == 1 ? '0' : '') . $byte; 1038 break; 1039 case preg_match (RE_L2_CISCO, $string): 1040 $ret = str_replace ('.', '', $string); 1041 break; 1042 case preg_match (RE_L2_HP, $string): 1043 case preg_match (RE_L2_HUAWEI, $string): 1044 $ret = str_replace ('-', '', $string); 1045 break; 1046 case preg_match (RE_L2_IPCFG, $string): 1047 case preg_match (RE_L2_WWN_HYPHEN, $string): 1048 case preg_match (RE_L2_IPOIB_HYPHEN, $string): 1049 $ret = str_replace ('-', '', $string); 1050 break; 1051 default: 1052 throw new InvalidArgException ('string', $string, 'malformed MAC/WWN/IPoIB address'); 1053 } 1054 // Some switches return this invalid address through SNMP. Disregard the malformed data 1055 // and return an empty string, which will translate to NULL when (if) the address goes 1056 // into the database. This suppresses unnecessary violations of the L2 address constraint. 1057 if ($ret === '000000000000') 1058 $ret = ''; 1059 return $ret; 1060} 1061 1062// The input to this function is valid iff it is an output of l2addressForDatabase() or a NULL. 1063function l2addressFromDatabase ($string) 1064{ 1065 // $string normally comes from the database in uppercase, test it with 1066 // the regexps, which are now uppercase. 1067 switch (TRUE) 1068 { 1069 case $string === NULL: 1070 case $string === '': 1071 return ''; 1072 case preg_match (RE_L2_SOLID, $string): 1073 case preg_match (RE_L2_WWN_SOLID, $string): 1074 case preg_match (RE_L2_IPOIB_SOLID, $string): 1075 return implode (':', str_split ($string, 2)); 1076 default: 1077 throw new InvalidArgException ('string', $string, 'invalid format'); 1078 } 1079} 1080 1081function HTMLColorForDatabase ($string) 1082{ 1083 $ret = 0; 1084 // The HTTP request coming through the opspec declaration will indicate an undefined 1085 // color value with an empty string, PHP code in addition to that is likely to use 1086 // NULL, which comes from the database. 1087 if ($string === NULL || $string === '') 1088 return NULL; 1089 if (! isHTMLColor ($string) || 1 != sscanf (mb_strtoupper ($string), '%06X', $ret)) 1090 throw new InvalidArgException ('string', $string, 'not an HTML color'); 1091 return $ret; 1092} 1093 1094// No code currently depends on this function, it is here for completeness. 1095function HTMLColorFromDatabase ($u) 1096{ 1097 if ($u === NULL) 1098 return NULL; 1099 if (! isUnsignedInteger ($u)) 1100 throw new InvalidArgException ('u', $u, 'not an unsigned integer'); 1101 if ($u > 0xFFFFFF) 1102 throw new InvalidArgException ('u', $u, 'value out of range'); 1103 return sprintf ('%06X', $u); 1104} 1105 1106// DEPRECATED, remove in 0.21.0 1107function getPrevIDforRack ($row_id, $rack_id) 1108{ 1109 $n = getRackNeighbors ($row_id, $rack_id); 1110 return $n['prev']; 1111} 1112 1113// DEPRECATED, remove in 0.21.0 1114function getNextIDforRack ($row_id, $rack_id) 1115{ 1116 $n = getRackNeighbors ($row_id, $rack_id); 1117 return $n['next']; 1118} 1119 1120function getRackNeighbors ($row_id, $rack_id) 1121{ 1122 $ret = array ('prev' => NULL, 'next' => NULL); 1123 $ids = selectRackOrder ($row_id); 1124 $index = array_search ($rack_id, $ids); 1125 if ($index !== FALSE && $index > 0) 1126 $ret['prev'] = $ids[$index - 1]; 1127 if ($index !== FALSE && $index + 1 < count ($ids)) 1128 $ret['next'] = $ids[$index + 1]; 1129 return $ret; 1130} 1131 1132// Return a list of rack IDs that are P or less positions 1133// far from the given rack in its row. 1134function getProximateRacks ($rack_id, $proximity = 0) 1135{ 1136 $ret = array ($rack_id); 1137 if ($proximity > 0) 1138 { 1139 $rack = spotEntity ('rack', $rack_id); 1140 $rackList = selectRackOrder ($rack['row_id']); 1141 $cur_item = array_search ($rack_id, $rackList); 1142 if (FALSE !== $cur_item) 1143 { 1144 if ($todo = min ($cur_item, $proximity)) 1145 $ret = array_merge ($ret, array_slice ($rackList, $cur_item - $todo, $todo)); 1146 if ($todo = min (count ($rackList) - 1 - $cur_item, $proximity)) 1147 $ret = array_merge ($ret, array_slice ($rackList, $cur_item + 1, $todo)); 1148 } 1149 } 1150 return $ret; 1151} 1152 1153function sortTokenize ($a, $b) 1154{ 1155 $aold=''; 1156 while ($a != $aold) 1157 { 1158 $aold=$a; 1159 $a = preg_replace('/[^a-zA-Z0-9]/',' ',$a); 1160 $a = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$a); 1161 $a = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$a); 1162 } 1163 1164 $bold=''; 1165 while ($b != $bold) 1166 { 1167 $bold=$b; 1168 $b = preg_replace('/[^a-zA-Z0-9]/',' ',$b); 1169 $b = preg_replace('/([0-9])([a-zA-Z])/','\\1 \\2',$b); 1170 $b = preg_replace('/([a-zA-Z])([0-9])/','\\1 \\2',$b); 1171 } 1172 1173 $ar = explode(' ', $a); 1174 $br = explode(' ', $b); 1175 $arc = count ($ar); 1176 $brc = count ($br); 1177 for ($i = 0; $i < $arc && $i < $brc; $i++) 1178 { 1179 if (isUnsignedInteger ($ar[$i]) && isUnsignedInteger ($br[$i])) 1180 $ret = numCompare ($ar[$i], $br[$i]); 1181 else 1182 $ret = strcasecmp($ar[$i], $br[$i]); 1183 if ($ret != 0) 1184 return $ret; 1185 } 1186 return numCompare ($arc, $brc); 1187} 1188 1189// This function returns an array of single element of object's FQDN attribute, 1190// if FQDN is set. The next choice is object's common name, if it looks like a 1191// hostname. Otherwise an array of all 'regular' IP addresses of the 1192// object is returned (which may appear 0 and more elements long). 1193function findAllEndpoints ($object_id, $fallback = '') 1194{ 1195 foreach (getAttrValues ($object_id) as $record) 1196 if ($record['id'] == 3 && $record['value'] != '') // FQDN 1197 return array ($record['value']); 1198 $regular = array(); 1199 foreach (getObjectIPv4AllocationList ($object_id) as $ip_bin => $alloc) 1200 if ($alloc['type'] == 'regular') 1201 $regular[] = ip4_format ($ip_bin); 1202 // FIXME: add IPv6 allocations to this list 1203 if (!count ($regular) && $fallback != '') 1204 return array ($fallback); 1205 return $regular; 1206} 1207 1208// Some records in the dictionary may be written as plain text or as Wiki 1209// link in the following syntax: 1210// 1. word 1211// 2. [[word URL]] // FIXME: this isn't working 1212// 3. [[word word word | URL]] 1213// This function parses the line in $record['value'] and modifies $record: 1214// $record['o_value'] is set to be the first part of link (word word word) 1215// $record['a_value'] is the same, but with %GPASS and %GSKIP macros applied 1216// $record['href'] is set to URL if it is specified in the input value 1217function parseWikiLink (&$record) 1218{ 1219 if (! preg_match ('/^\[\[(.+)\]\]$/', $record['value'], $matches)) 1220 $record['o_value'] = $record['value']; 1221 else 1222 { 1223 $s = explode ('|', $matches[1]); 1224 if (isset ($s[1])) 1225 $record['href'] = trim ($s[1]); 1226 $record['o_value'] = trim ($s[0]); 1227 } 1228 $record['a_value'] = execGMarker ($record['o_value']); 1229} 1230 1231// FIXME: should this be saved as "P-data"? 1232function execGMarker ($line) 1233{ 1234 return preg_replace ('/^.+%GSKIP%/', '', 1235 preg_replace ('/^(.+)%GPASS%/', '\\1 ', 1236 preg_replace ('/%L\d+,\d+(H|V|)%/', '', $line))); 1237} 1238 1239// extract the layout information from the %L...% marker in the dictionary info 1240// This is somewhat similar to the %GPASS %GSKIP 1241function extractLayout (&$record) 1242{ 1243 if (preg_match ('/%L(\d+),(\d+)(H|V|)%/', $record['value'], $matches)) 1244 { 1245 $record['rows'] = $matches[1]; 1246 $record['cols'] = $matches[2]; 1247 $record['layout'] = $matches[3]; 1248 if ($record['layout'] == '') 1249 $record['layout'] = ($record['cols'] >= 4) ? 'V' : 'H'; 1250 } 1251} 1252 1253// rackspace usage for a single rack 1254// (T + W + U) / (height * 3 - A) 1255function getRSUforRack ($data) 1256{ 1257 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0); 1258 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--) 1259 for ($locidx = 0; $locidx < 3; $locidx++) 1260 $counter[$data[$unit_no][$locidx]['state']]++; 1261 return ($counter['T'] + $counter['W'] + $counter['U']) / 1262 ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']); 1263} 1264 1265// Same for row. 1266function getRSUforRow ($rowData) 1267{ 1268 if (!count ($rowData)) 1269 return 0; 1270 $counter = array ('A' => 0, 'U' => 0, 'T' => 0, 'W' => 0, 'F' => 0); 1271 $total_height = 0; 1272 foreach (array_keys ($rowData) as $rack_id) 1273 { 1274 $data = spotEntity ('rack', $rack_id); 1275 amplifyCell ($data); 1276 $total_height += $data['height']; 1277 for ($unit_no = $data['height']; $unit_no > 0; $unit_no--) 1278 for ($locidx = 0; $locidx < 3; $locidx++) 1279 $counter[$data[$unit_no][$locidx]['state']]++; 1280 } 1281 return ($counter['T'] + $counter['W'] + $counter['U']) / 1282 ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']); 1283} 1284 1285function string_insert_hrefs_callback ($m) 1286{ 1287 $t_url_href = 'href="' . rtrim($m[1], '.') . '"'; 1288 $s_url_replace = "<a ${t_url_href}>$m[1]</a> [<a ${t_url_href} target=\"_blank\">^</a>]"; 1289 return $s_url_replace; 1290} 1291 1292# Detect URLs and email addresses in the string and replace them with href anchors 1293# (adopted from MantisBT, core/string_api.php:string_insert_hrefs). 1294function string_insert_hrefs ($p_string) 1295{ 1296 static $s_url_regex = NULL; 1297 static $s_url_replace = NULL; 1298 static $s_email_regex = NULL; 1299 static $s_anchor_regex = '/(<a[^>]*>.*?<\/a>)/is'; 1300 1301 if (getConfigVar ('DETECT_URLS') != 'yes') 1302 return $p_string; 1303 1304 # Initialize static variables 1305 if (is_null ($s_url_regex)) 1306 { 1307 # URL regex 1308 $t_url_protocol = '(?:[[:alpha:]][-+.[:alnum:]]*):\/\/'; 1309 1310 # %2A notation in url's 1311 $t_url_hex = '%[[:digit:]A-Fa-f]{2}'; 1312 1313 # valid set of characters that may occur in url scheme. Note: - should be first (A-F != -AF). 1314 $t_url_valid_chars = '-_.,!~*\';\/?%^\\\\:@&={\|}+$#[:alnum:]\pL'; 1315 $t_url_chars = "(?:${t_url_hex}|[${t_url_valid_chars}\(\)\[\]])"; 1316 $t_url_chars2 = "(?:${t_url_hex}|[${t_url_valid_chars}])"; 1317 $t_url_chars_in_brackets = "(?:${t_url_hex}|[${t_url_valid_chars}\(\)])"; 1318 $t_url_chars_in_parens = "(?:${t_url_hex}|[${t_url_valid_chars}\[\]])"; 1319 1320 $t_url_part1 = "${t_url_chars}"; 1321 $t_url_part2 = "(?:\(${t_url_chars_in_parens}*\)|\[${t_url_chars_in_brackets}*\]|${t_url_chars2})"; 1322 1323 $s_url_regex = "/(${t_url_protocol}(${t_url_part1}*?${t_url_part2}+))/su"; 1324 1325 # URL replacement 1326 $t_url_href = "href=\"'.rtrim('\\1','.').'\""; 1327 $s_url_replace = "'<a ${t_url_href}>\\1</a> [<a ${t_url_href} target=\"_blank\">^</a>]'"; 1328 1329 # e-mail regex 1330 $s_email_regex = substr_replace (email_regex_simple(), '(?:mailto:)?', 1, 0); 1331 } 1332 1333 # Find any URL in a string and replace it by a clickable link 1334 $p_string = preg_replace_callback 1335 ( 1336 $s_url_regex, 1337 'string_insert_hrefs_callback', 1338 $p_string 1339 ); 1340 1341 # Find any email addresses in the string and replace them with a clickable 1342 # mailto: link, making sure that we skip processing of any existing anchor 1343 # tags, to avoid parts of URLs such as https://user@example.com/ or 1344 # http://user:password@example.com/ to be not treated as an email. 1345 $t_pieces = preg_split ($s_anchor_regex, $p_string, NULL, PREG_SPLIT_DELIM_CAPTURE); 1346 $p_string = ''; 1347 foreach ($t_pieces as $piece) 1348 if (preg_match ($s_anchor_regex, $piece)) 1349 $p_string .= $piece; 1350 else 1351 $p_string .= preg_replace ($s_email_regex, '<a href="mailto:\0">\0</a>', $piece); 1352 1353 return $p_string; 1354} 1355 1356# Adopted from MantisBT, core/email_api.php:email_regex_simple. 1357function email_regex_simple() 1358{ 1359 static $s_email_regex = NULL; 1360 1361 if (is_null ($s_email_regex)) 1362 { 1363 $t_recipient = "([a-z0-9!#*+\/=?^_{|}~-]+(?:\.[a-z0-9!#*+\/=?^_{|}~-]+)*)"; 1364 1365 # a domain is one or more subdomains 1366 $t_subdomain = "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)"; 1367 $t_domain = "(${t_subdomain}(?:\.${t_subdomain})*)"; 1368 1369 $s_email_regex = "/${t_recipient}\@${t_domain}/i"; 1370 } 1371 return $s_email_regex; 1372} 1373 1374// Parse AUTOPORTS_CONFIG and return a list of generated pairs (port_type, port_name) 1375// for the requested object. 1376function getAutoPorts ($object) 1377{ 1378 return parseAutoPortsConfig (getAutoPortsConfig ($object)); 1379} 1380 1381// Extract automatic ports schema from the AUTOPORTS_CONFIG 1382// based on the given object's type. Can be overriden 1383function getAutoPortsConfig ($object) 1384{ 1385 $override = callHook ('getAutoPortsConfig_hook', $object); 1386 if (isset ($override)) 1387 return $override; 1388 1389 $typemap = explode (';', str_replace (' ', '', getConfigVar ('AUTOPORTS_CONFIG'))); 1390 foreach ($typemap as $equation) 1391 { 1392 $tmp = explode ('=', $equation); 1393 if (count ($tmp) != 2) 1394 continue; 1395 $objtype_id = $tmp[0]; 1396 if ($objtype_id == $object['objtype_id']) 1397 return $tmp[1]; 1398 } 1399} 1400 1401function parseAutoPortsConfig ($schema) 1402{ 1403 $ret = array(); 1404 1405 foreach (explode ('+', $schema) as $product) 1406 { 1407 $tmp = explode ('*', $product); 1408 if (count ($tmp) > 4 || count ($tmp) < 3) 1409 continue; 1410 # format: <number of ports>*<port_type_id>[*<sprintf_name>*<startnumber>] 1411 $nports = $tmp[0]; 1412 $port_type = $tmp[1]; 1413 $format = $tmp[2]; 1414 $startnum = isset ($tmp[3]) ? $tmp[3] : 0; 1415 for ($i = 0; $i < $nports; $i++) 1416 $ret[] = array ('type' => $port_type, 'name' => @sprintf ($format, $i + $startnum)); 1417 } 1418 return $ret; 1419} 1420 1421// Use pre-served trace to traverse the tree, then place given node where it belongs. 1422function pokeNode (&$tree, $trace, $key, $value, $threshold = 0) 1423{ 1424 $self = __FUNCTION__; 1425 // This function needs the trace to be followed FIFO-way. The fastest 1426 // way to do so is to use array_push() for putting values into the 1427 // list and array_shift() for getting them out. This exposed up to 11% 1428 // performance gain compared to other patterns of array_push/array_unshift/ 1429 // array_reverse/array_pop/array_shift conjunction. 1430 $myid = array_shift ($trace); 1431 if (count ($trace)) // not yet reached the target 1432 $self ($tree[$myid]['kids'], $trace, $key, $value, $threshold); 1433 else // just did 1434 { 1435 if (! $threshold || ($threshold && $tree[$myid]['kidc'] + 1 < $threshold)) 1436 $tree[$myid]['kids'][$key] = $value; 1437 // Reset accumulated records once, when the limit is reached, not each time 1438 // after that. 1439 if (++$tree[$myid]['kidc'] == $threshold) 1440 $tree[$myid]['kids'] = array(); 1441 } 1442} 1443 1444// Likewise traverse the tree with the trace and return the final node. 1445// This function is not currently used in RackTables main code but it works well. 1446function peekNode ($tree, $trace, $target_id) 1447{ 1448 $self = __FUNCTION__; 1449 if (NULL === ($next = array_shift ($trace))) // warm 1450 { 1451 foreach ($tree as $node) 1452 if (array_key_exists ('id', $node) && $node['id'] == $target_id) // hot 1453 return $node; 1454 } 1455 else // cold 1456 { 1457 foreach ($tree as $node) 1458 if (array_key_exists ('id', $node) && $node['id'] == $next) // warmer 1459 return $self ($node['kids'], $trace, $target_id); 1460 } 1461 throw new RackTablesError ('inconsistent tree data', RackTablesError::INTERNAL); 1462} 1463 1464// The structure used in RackTables to represent tags is called a forest of rooted 1465// trees, which is a set of directed graphs each having a node appointed as root 1466// and exactly one path possible from the root node to every other node of the 1467// graph. 1468// 1469// The TagTree database table contains a generic list of graph nodes with each node 1470// having an optional incoming directed edge from any existing node. This table 1471// generally can encode any set of any directed graphs that allow at most one 1472// incoming edge per node. This includes but is not limited to the forest of rooted 1473// trees. However, a number of RackTables functions specifically relies upon 1474// consistent relations between the tags (presented either as a complete forest 1475// structure or just as each node's path from the root), hence an early validation 1476// step is required in the PHP code to implement the constraints in full. 1477// 1478// The function below implements this step. For every node on the input list that 1479// belongs to a forest of rooted trees it sets the 'trace' key to the sequence of 1480// node IDs that leads from tree root up to (but not including) the node. As an 1481// edge case, for each root node it sets this sequence to an empty list. For any 1482// nodes not in the forest (i.e., those that form any graph cycle or descend from 1483// such a cycle) it leaves 'trace' unset. 1484function addTraceToNodes ($nodelist) 1485{ 1486 foreach ($nodelist as $nodeid => $node) 1487 { 1488 $trace = array(); 1489 $parentid = $node['parent_id']; 1490 while ($parentid != NULL) 1491 { 1492 if (! isset ($nodelist[$parentid])) 1493 { 1494 // bad parent_id 1495 $trace = NULL; 1496 break; 1497 } 1498 1499 // check for cycles every 10 steps 1500 if (0 == (count ($trace) % 10) && in_array ($parentid, $trace)) 1501 { 1502 // cycle detected 1503 $trace = NULL; 1504 break; 1505 } 1506 array_unshift ($trace, $parentid); 1507 $parentid = $nodelist[$parentid]['parent_id']; 1508 } 1509 if (isset ($trace)) 1510 $nodelist[$nodeid]['trace'] = $trace; 1511 } 1512 return $nodelist; 1513} 1514 1515function treeItemCmp ($a, $b) 1516{ 1517 return $a['__tree_index'] - $b['__tree_index']; 1518} 1519 1520function getTagTree() 1521{ 1522 return treeFromList (getTagUsage()); 1523} 1524 1525// Build a tree from the item list and return it. Input and output data is 1526// indexed by item id (nested items in output are recursively stored in 'kids' 1527// key, which is in turn indexed by id. Functions that are ready to handle 1528// tree collapsion/expansion themselves may request non-zero threshold value 1529// for smaller resulting tree. 1530// FIXME: The 2nd argument to this function seems not to be used any more. 1531// FIXME: The structure this function returns is a forest of rooted trees 1532// despite the terminology it used to use. 1533function treeFromList ($nodelist, $threshold = 0) 1534{ 1535 $tree = array(); 1536 1537 // Preserve original ordering in __tree_index. 1538 $ti = 0; 1539 foreach (array_keys ($nodelist) as $key) 1540 { 1541 $nodelist[$key]['__tree_index'] = $ti++; 1542 $nodelist[$key]['kidc'] = 0; 1543 $nodelist[$key]['kids'] = array(); 1544 } 1545 1546 $done_ids = array(); 1547 do 1548 { 1549 $nextpass = FALSE; 1550 foreach (array_keys ($nodelist) as $nodeid) 1551 { 1552 $node = $nodelist[$nodeid]; 1553 // Skip any irrelevant nodes early as they will fail the checks below anyway. 1554 if (! array_key_exists ('trace', $node)) 1555 continue; 1556 $parentid = $node['parent_id']; 1557 // Moving a node from the input list to the output tree potentially enables more 1558 // nodes to make it from the list to the same tree as well, hence in this case make 1559 // another full round after the current one. 1560 1561 if ($parentid == NULL) // A root node? 1562 { 1563 $tree[$nodeid] = $node; 1564 unset ($nodelist[$nodeid]); 1565 $done_ids[] = $nodeid; 1566 $nextpass = TRUE; 1567 } 1568 elseif (in_array ($parentid, $done_ids)) // Has a direct parent node already on the tree? 1569 { 1570 // Being here implies the current node's trace is at least one element long. 1571 pokeNode ($tree, $node['trace'], $nodeid, $node, $threshold); 1572 unset ($nodelist[$nodeid]); 1573 $done_ids[] = $nodeid; 1574 $nextpass = TRUE; 1575 } 1576 } 1577 } 1578 while ($nextpass); 1579 sortTree ($tree, 'treeItemCmp'); // sort the resulting tree by the order in original list 1580 return $tree; 1581} 1582 1583// Return those tags that belong to the full list of tags but don't belong 1584// to the forest of rooted trees as found by addTraceToNodes(). 1585function getInvalidNodes ($nodelist) 1586{ 1587 $ret = array(); 1588 foreach ($nodelist as $node_id => $node) 1589 if (! array_key_exists ('trace', $node)) 1590 $ret[$node_id] = $node; 1591 return $ret; 1592} 1593 1594// Throw an exception unless it is OK to assign the given parent ID 1595// to the node with the given ID. 1596function assertValidParentId ($nodelist, $node_id, $parent_id) 1597{ 1598 if ($parent_id == 0) 1599 return; 1600 if ($parent_id == $node_id) 1601 throw new InvalidArgException ('parent_id', $parent_id, 'must be different from the tag ID'); 1602 if (! array_key_exists ($parent_id, $nodelist)) 1603 throw new InvalidArgException ('parent_id', $parent_id, 'must refer to an existing tag'); 1604 if (! array_key_exists ('trace', $nodelist[$parent_id])) 1605 throw new InvalidArgException ('parent_id', $parent_id, 'would add to an existing graph cycle'); 1606 if (in_array ($node_id, $nodelist[$parent_id]['trace'])) 1607 throw new InvalidArgException ('parent_id', $parent_id, 'would create a new graph cycle'); 1608} 1609 1610// Given an existing node ID filter a list of traced nodes and silently skip 1611// the nodes that are not valid parent node options. Filtering criteria are 1612// effectively the same as in the function above but use a simpler expression. 1613function getParentNodeOptionsExisting ($nodelist, $textfield, $node_id) 1614{ 1615 $ret = array (0 => '-- NONE --'); 1616 foreach ($nodelist as $key => $each) 1617 if 1618 ( 1619 $key != $node_id && 1620 array_key_exists ('trace', $each) && 1621 ! in_array ($node_id, $each['trace']) 1622 ) 1623 $ret[$key] = $each[$textfield]; 1624 return $ret; 1625} 1626 1627// Idem, but for a new node, which doesn't yet exist, or a node that is based 1628// on a circular reference. The condition is even simpler in this case. 1629function getParentNodeOptionsNew ($nodelist, $textfield) 1630{ 1631 $ret = array (0 => '-- NONE --'); 1632 foreach ($nodelist as $key => $each) 1633 if (array_key_exists ('trace', $each)) 1634 $ret[$key] = $each[$textfield]; 1635 return $ret; 1636} 1637 1638// removes implicit tags from ['etags'] array and fills ['itags'] array 1639// Replaces call sequence "getExplicitTagsOnly, getImplicitTags" 1640function sortEntityTags (&$cell) 1641{ 1642 global $taglist; 1643 if (! is_array ($cell['etags'])) 1644 throw new InvalidArgException ('cell[etags]', $cell['etags']); 1645 $cell['itags'] = array(); 1646 foreach ($cell['etags'] as $tag_id => $taginfo) 1647 foreach ($taglist[$tag_id]['trace'] as $parent_id) 1648 { 1649 $cell['itags'][$parent_id] = $taglist[$parent_id]; 1650 unset ($cell['etags'][$parent_id]); 1651 } 1652} 1653 1654// Return the list of missing implicit tags. 1655function getImplicitTags ($oldtags) 1656{ 1657 global $taglist; 1658 $tmp = array(); 1659 foreach ($oldtags as $taginfo) 1660 $tmp = array_merge ($tmp, $taglist[$taginfo['id']]['trace']); 1661 // The call below already includes a call to array_unique(). 1662 return buildTagChainFromIds ($tmp); 1663} 1664 1665// Minimize the chain: exclude all implicit tags and return the result. 1666// This function makes use of an external cache with a miss/hit ratio 1667// about 3/7 (ticket:255). 1668function getExplicitTagsOnly ($chain) 1669{ 1670 global $taglist, $tagRelCache; 1671 $ret = array(); 1672 foreach (array_keys ($chain) as $keyA) // check each A 1673 { 1674 $tagidA = $chain[$keyA]['id']; 1675 // do not include A in result, if A is seen on the trace of any B!=A 1676 foreach (array_keys ($chain) as $keyB) 1677 { 1678 $tagidB = $chain[$keyB]['id']; 1679 if ($tagidA == $tagidB) 1680 continue; 1681 if (!isset ($tagRelCache[$tagidA][$tagidB])) 1682 $tagRelCache[$tagidA][$tagidB] = in_array ($tagidA, $taglist[$tagidB]['trace']); 1683 if ($tagRelCache[$tagidA][$tagidB] === TRUE) // A is ancestor of B 1684 continue 2; // skip this A 1685 } 1686 $ret[] = $chain[$keyA]; 1687 } 1688 return $ret; 1689} 1690 1691// Check, if the given tag is present on the chain (will only work 1692// for regular tags with tag ID set. 1693function tagOnChain ($taginfo, $tagchain) 1694{ 1695 if (!isset ($taginfo['id'])) 1696 return FALSE; 1697 return NULL !== scanArrayForItem ($tagchain, 'id', $taginfo['id']); 1698} 1699 1700function tagNameOnChain ($tagname, $tagchain) 1701{ 1702 return NULL !== scanArrayForItem ($tagchain, 'tag', $tagname); 1703} 1704 1705// Return TRUE, if two tags chains differ (order of tags doesn't matter). 1706// Assume that neither of the lists contains duplicates. 1707// FIXME: a faster than O(x^2) method is possible for this calculation. 1708function tagChainCmp ($chain1, $chain2) 1709{ 1710 if (count ($chain1) != count ($chain2)) 1711 return TRUE; 1712 foreach ($chain1 as $taginfo1) 1713 if (!tagOnChain ($taginfo1, $chain2)) 1714 return TRUE; 1715 return FALSE; 1716} 1717 1718// returns an array of tag ids that have $tagid as its parent (all levels) 1719function getTagDescendents ($tagid) 1720{ 1721 global $taglist; 1722 $ret = array(); 1723 foreach ($taglist as $id => $taginfo) 1724 if (array_key_exists ('trace', $taginfo) && in_array ($tagid, $taginfo['trace'])) 1725 $ret[] = $id; 1726 return $ret; 1727} 1728 1729function redirectIfNecessary () 1730{ 1731 global 1732 $trigger, 1733 $pageno, 1734 $tabno; 1735 startSession(); 1736 if 1737 ( 1738 ! isset ($_REQUEST['tab']) && 1739 isset ($_SESSION['RTLT'][$pageno]) && 1740 getConfigVar ('SHOW_LAST_TAB') == 'yes' && 1741 permitted ($pageno, $_SESSION['RTLT'][$pageno]['tabname']) && 1742 time() - $_SESSION['RTLT'][$pageno]['time'] <= TAB_REMEMBER_TIMEOUT 1743 ) 1744 redirectUser (buildRedirectURL ($pageno, $_SESSION['RTLT'][$pageno]['tabname'])); 1745 1746 // Fall back to default when a trigger was OK about a tab when generating the previous page 1747 // but isn't OK anymore when generating the current page and the tab is the requested tab. 1748 if 1749 ( 1750 isset ($trigger[$pageno][$tabno]) && 1751 '' == call_user_func ($trigger[$pageno][$tabno]) 1752 ) 1753 { 1754 $_SESSION['RTLT'][$pageno]['dont_remember'] = 1; 1755 redirectUser (buildRedirectURL ($pageno, 'default')); 1756 } 1757 if (is_array (@$_SESSION['RTLT'][$pageno]) && isset ($_SESSION['RTLT'][$pageno]['dont_remember'])) 1758 unset ($_SESSION['RTLT'][$pageno]['dont_remember']); 1759 // store the last visited tab name 1760 if (isset ($_REQUEST['tab'])) 1761 $_SESSION['RTLT'][$pageno] = array ('tabname' => $tabno, 'time' => time()); 1762 session_commit(); // There was no redirection, unlock the session data. 1763} 1764 1765function prepareNavigation() 1766{ 1767 global $pageno, $tabno; 1768 $pageno = array_fetch ($_REQUEST, 'page', 'index'); 1769 $tabno = array_fetch ($_REQUEST, 'tab', 'default'); 1770} 1771 1772function fixContext ($target = NULL) 1773{ 1774 global 1775 $pageno, 1776 $auto_tags, 1777 $expl_tags, 1778 $impl_tags, 1779 $target_given_tags, 1780 $user_given_tags, 1781 $etype_by_pageno; 1782 1783 if ($target !== NULL) 1784 { 1785 $target_given_tags = $target['etags']; 1786 // Do not reset the autochain as it may have items generated during the authentication step. 1787 // Ignore the "user" realm to keep the displayed user account autotags out of the 1788 // effective security context. 1789 if ($target['realm'] != 'user') 1790 $auto_tags = array_merge ($auto_tags, $target['atags']); 1791 } 1792 elseif (array_key_exists ($pageno, $etype_by_pageno)) 1793 { 1794 // Each page listed in the map above requires one natural argument. 1795 $target = spotEntity ($etype_by_pageno[$pageno], getBypassValue()); 1796 $target_given_tags = $target['etags']; 1797 if ($target['realm'] != 'user') 1798 $auto_tags = array_merge ($auto_tags, $target['atags']); 1799 } 1800 elseif ($pageno == 'ipaddress' && $net = spotNetworkByIP (getBypassValue())) 1801 { 1802 // IP addresses inherit context tags from their parent networks 1803 $target_given_tags = $net['etags']; 1804 $auto_tags = array_merge ($auto_tags, $net['atags']); 1805 } 1806 // Explicit and implicit chains should be normally empty at this point, so 1807 // overwrite the contents anyway. 1808 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags); 1809 $impl_tags = getImplicitTags ($expl_tags); 1810} 1811 1812# Merge e/i/a-tags of the given cell structures into current context, when 1813# these aren't there yet. 1814function spreadContext ($extracell) 1815{ 1816 global 1817 $auto_tags, 1818 $expl_tags, 1819 $impl_tags, 1820 $target_given_tags, 1821 $user_given_tags; 1822 foreach ($extracell['atags'] as $taginfo) 1823 if (! tagNameOnChain ($taginfo['tag'], $auto_tags)) 1824 $auto_tags[] = $taginfo; 1825 $target_given_tags = mergeTagChains ($target_given_tags, $extracell['etags']); 1826 $expl_tags = mergeTagChains ($user_given_tags, $target_given_tags); 1827 $impl_tags = getImplicitTags ($expl_tags); 1828} 1829 1830# return a structure suitable for feeding into restoreContext() 1831function getContext() 1832{ 1833 global 1834 $auto_tags, 1835 $expl_tags, 1836 $impl_tags, 1837 $target_given_tags; 1838 return array 1839 ( 1840 'auto_tags' => $auto_tags, 1841 'expl_tags' => $expl_tags, 1842 'impl_tags' => $impl_tags, 1843 'target_given_tags' => $target_given_tags, 1844 ); 1845} 1846 1847function restoreContext ($ctx) 1848{ 1849 global 1850 $auto_tags, 1851 $expl_tags, 1852 $impl_tags, 1853 $target_given_tags; 1854 $auto_tags = $ctx['auto_tags']; 1855 $expl_tags = $ctx['expl_tags']; 1856 $impl_tags = $ctx['impl_tags']; 1857 $target_given_tags = $ctx['target_given_tags']; 1858} 1859 1860// Take a list of user-supplied tag IDs to build a list of valid taginfo 1861// records indexed by tag IDs (tag chain). 1862function buildTagChainFromIds ($tagidlist) 1863{ 1864 global $taglist; 1865 $ret = array(); 1866 foreach (array_unique ($tagidlist) as $tag_id) 1867 if (isset ($taglist[$tag_id])) 1868 $ret[] = $taglist[$tag_id]; 1869 return $ret; 1870} 1871 1872function buildTagIdsFromChain ($tagchain) 1873{ 1874 $ret = array(); 1875 foreach ($tagchain as $taginfo) 1876 $ret[] = $taginfo['id']; 1877 return array_unique ($ret); 1878} 1879 1880// Process a given tag tree and return only meaningful branches. The resulting 1881// (sub)tree will have refcnt leaves on every last branch. 1882function getObjectiveTagTree ($tree, $realm, $preselect) 1883{ 1884 $self = __FUNCTION__; 1885 $ret = array(); 1886 foreach ($tree as $taginfo) 1887 { 1888 $subsearch = $self ($taginfo['kids'], $realm, $preselect); 1889 // If the current node addresses something, add it to the result 1890 // regardless of how many sub-nodes it features. 1891 if 1892 ( 1893 isset ($taginfo['refcnt'][$realm]) || 1894 count ($subsearch) > 1 || 1895 in_array ($taginfo['id'], $preselect) 1896 ) 1897 $ret[] = array 1898 ( 1899 'id' => $taginfo['id'], 1900 'tag' => $taginfo['tag'], 1901 'parent_id' => $taginfo['parent_id'], 1902 'refcnt' => $taginfo['refcnt'], 1903 'color' => $taginfo['color'], 1904 'description' => $taginfo['description'], 1905 'kids' => $subsearch 1906 ); 1907 else 1908 $ret = array_merge ($ret, $subsearch); 1909 } 1910 return $ret; 1911} 1912 1913// Preprocess tag tree to get only tags that can effectively reduce given filter result, 1914// then pass shrinked tag tree to getObjectiveTagTree and return its result. 1915// This makes sense only if andor mode is 'and', otherwise function does not modify tree. 1916// 'Given filter' is a pair of $entity_list(filter result) and $preselect(filter data). 1917// 'Effectively' means reduce to non-empty result. 1918function getShrinkedTagTree ($entity_list, $realm, $preselect) 1919{ 1920 $tagtree = getTagTree(); 1921 if ($preselect['andor'] != 'and' || empty($entity_list) && $preselect['is_empty']) 1922 return getObjectiveTagTree($tagtree, $realm, $preselect['tagidlist']); 1923 1924 $used_tags = array(); //associative, keys - tag ids, values - taginfos 1925 foreach ($entity_list as $entity) 1926 { 1927 foreach ($entity['etags'] as $etag) 1928 if (! array_key_exists($etag['id'], $used_tags)) 1929 $used_tags[$etag['id']] = 1; 1930 else 1931 $used_tags[$etag['id']]++; 1932 1933 foreach ($entity['itags'] as $itag) 1934 if (! array_key_exists($itag['id'], $used_tags)) 1935 $used_tags[$itag['id']] = 0; 1936 } 1937 1938 $shrinked_tree = shrinkSubtree($tagtree, $used_tags, $preselect, $realm); 1939 return getObjectiveTagTree($shrinked_tree, $realm, $preselect['tagidlist']); 1940} 1941 1942// deletes item from tag subtree unless it exists in $used_tags and not preselected 1943function shrinkSubtree ($tree, $used_tags, $preselect, $realm) 1944{ 1945 $self = __FUNCTION__; 1946 1947 foreach ($tree as $i => &$item) 1948 { 1949 $item['kids'] = $self($item['kids'], $used_tags, $preselect, $realm); 1950 $item['kidc'] = count($item['kids']); 1951 if 1952 ( 1953 ! array_key_exists($item['id'], $used_tags) && 1954 ! in_array($item['id'], $preselect['tagidlist']) && 1955 ! $item['kidc'] 1956 ) 1957 unset($tree[$i]); 1958 else 1959 { 1960 if (isset ($used_tags[$item['id']]) && $used_tags[$item['id']]) 1961 $item['refcnt'][$realm] = $used_tags[$item['id']]; 1962 else 1963 unset($item['refcnt'][$realm]); 1964 } 1965 } 1966 return $tree; 1967} 1968 1969// Get taginfo record by tag name, return NULL, if record doesn't exist. 1970function getTagByName ($tag_name) 1971{ 1972 global $taglist; 1973 static $cache = NULL; 1974 if (! isset ($cache)) 1975 { 1976 $cache = array(); 1977 foreach ($taglist as $key => $taginfo) 1978 $cache[$taginfo['tag']] = $taginfo; 1979 } 1980 return array_fetch($cache, $tag_name, NULL); 1981} 1982 1983// Merge two chains, filtering dupes out. Return the resulting superset. 1984function mergeTagChains ($chainA, $chainB) 1985{ 1986 // $ret = $chainA; 1987 // Reindex by tag id in any case. 1988 $ret = array(); 1989 foreach ($chainA as $tag) 1990 $ret[$tag['id']] = $tag; 1991 foreach ($chainB as $tag) 1992 if (!isset ($ret[$tag['id']])) 1993 $ret[$tag['id']] = $tag; 1994 return $ret; 1995} 1996 1997# Return a list consisting of tag ID of the given tree node and IDs of all 1998# nodes it contains. 1999# This is an earlier and more generic variety of getTagDescendents(). 2000function getTagIDListForNode ($treenode) 2001{ 2002 $self = __FUNCTION__; 2003 $ret = array ($treenode['id']); 2004 foreach ($treenode['kids'] as $item) 2005 $ret = array_merge ($ret, $self ($item)); 2006 return $ret; 2007} 2008 2009function applyCellFilter ($realm, $cellfilter, $parent_id = NULL) 2010{ 2011 if ($res = callHook ('applyCellFilter_hook', $realm, $cellfilter, $parent_id)) 2012 return $res; 2013 return filterCellList (listCells ($realm, $parent_id), $cellfilter['expression']); 2014} 2015 2016function getCellFilter () 2017{ 2018 global $sic; 2019 global $pageno; 2020 $andor_used = FALSE; 2021 startSession(); 2022 // On the form submit "andor" appears on the request and means the user is 2023 // trying to start a new filter or clear the existing one. 2024 if (isset($_REQUEST['andor'])) 2025 $andor_used = TRUE; 2026 if ($andor_used || array_key_exists ('clear-cf', $_REQUEST)) 2027 unset($_SESSION[$pageno]); // delete saved filter 2028 2029 // otherwise inject saved filter to the $_REQUEST and $sic vars 2030 elseif (isset ($_SESSION[$pageno]['filter']) && is_array ($_SESSION[$pageno]['filter']) && getConfigVar ('STATIC_FILTER') == 'yes') 2031 foreach (array('andor', 'cfe', 'cft[]', 'cfp[]', 'nft[]', 'nfp[]') as $param) 2032 { 2033 $param = str_replace ('[]', '', $param, $is_array); 2034 if (! isset ($_REQUEST[$param]) && isset ($_SESSION[$pageno]['filter'][$param]) && (! $is_array || is_array ($_SESSION[$pageno]['filter'][$param]))) 2035 { 2036 $_REQUEST[$param] = $_SESSION[$pageno]['filter'][$param]; 2037 if (! $is_array) 2038 $sic[$param] = $_REQUEST[$param]; 2039 } 2040 } 2041 2042 $ret = array 2043 ( 2044 'tagidlist' => array(), 2045 'tnamelist' => array(), 2046 'pnamelist' => array(), 2047 'negatedlist' => array(), 2048 'andor' => '', 2049 'text' => '', 2050 'extratext' => '', 2051 'expression' => array(), 2052 'urlextra' => '', // Just put text here and let makeHref call urlencode(). 2053 'is_empty' => TRUE, 2054 ); 2055 switch (TRUE) 2056 { 2057 case (!isset ($_REQUEST['andor'])): 2058 $andor = getConfigVar ('FILTER_DEFAULT_ANDOR'); 2059 break; 2060 case ($_REQUEST['andor'] == 'and'): 2061 case ($_REQUEST['andor'] == 'or'): 2062 $_SESSION[$pageno]['filter']['andor'] = $_REQUEST['andor']; 2063 $ret['andor'] = $andor = $_REQUEST['andor']; 2064 break; 2065 default: 2066 showWarning ('Invalid and/or switch value in submitted form'); 2067 session_commit(); 2068 return NULL; 2069 } 2070 // Both tags and predicates that don't exist, should be 2071 // handled somehow. Discard them silently for now. 2072 global $taglist, $pTable; 2073 foreach (array ('cft', 'cfp', 'nft', 'nfp') as $param) 2074 if (isset ($_REQUEST[$param]) && is_array ($_REQUEST[$param])) 2075 { 2076 $_SESSION[$pageno]['filter'][$param] = $_REQUEST[$param]; 2077 foreach ($_REQUEST[$param] as $req_key) 2078 { 2079 if (strpos ($param, 'ft') !== FALSE) 2080 { 2081 // param is a taglist 2082 if (! isset ($taglist[$req_key])) 2083 continue; 2084 $ret['tagidlist'][] = $req_key; 2085 $ret['tnamelist'][] = $taglist[$req_key]['tag']; 2086 $text = '{' . $taglist[$req_key]['tag'] . '}'; 2087 } 2088 else 2089 { 2090 // param is a predicate list 2091 if (! isset ($pTable[$req_key])) 2092 continue; 2093 $ret['pnamelist'][] = $req_key; 2094 $text = '[' . $req_key . ']'; 2095 } 2096 if (strpos ($param, 'nf') === 0) 2097 { 2098 $text = "not $text"; 2099 $ret['negatedlist'][] = $req_key; 2100 } 2101 if (! empty ($ret['text'])) 2102 { 2103 $andor_used = TRUE; 2104 $ret['text'] .= " $andor "; 2105 } 2106 $ret['text'] .= $text; 2107 $ret['urlextra'] .= '&' . $param . '[]=' . $req_key; 2108 } 2109 } 2110 // Extra text comes from TEXTAREA and is easily screwed by standard escaping function. 2111 if (isset ($sic['cfe'])) 2112 { 2113 $_SESSION[$pageno]['filter']['cfe'] = $sic['cfe']; 2114 $ret['extratext'] = trim ($sic['cfe']); 2115 $ret['urlextra'] .= '&cfe=' . $ret['extratext']; 2116 } 2117 $finaltext = array(); 2118 if ($ret['text'] != '') 2119 $finaltext[] = '(' . $ret['text'] . ')'; 2120 if ($ret['extratext'] != '') 2121 $finaltext[] = '(' . $ret['extratext'] . ')'; 2122 $andor_used = $andor_used || (count($finaltext) > 1); 2123 $finaltext = implode (' ' . $andor . ' ', $finaltext); 2124 if ($finaltext != '') 2125 { 2126 $ret['is_empty'] = FALSE; 2127 $ret['expression'] = compileExpression ($finaltext); 2128 // It's not quite fair enough to put the blame of the whole text onto 2129 // non-empty "extra" portion of it, but it's the only user-generated portion 2130 // of it, thus the most probable cause of parse error. 2131 if ($ret['extratext'] != '') 2132 $ret['extraclass'] = $ret['expression'] ? 'validation-success' : 'validation-error'; 2133 } 2134 if (! $andor_used) 2135 $ret['andor'] = getConfigVar ('FILTER_DEFAULT_ANDOR'); 2136 else 2137 $ret['urlextra'] .= '&andor=' . $ret['andor']; 2138 session_commit(); 2139 return $ret; 2140} 2141 2142function buildRedirectURL ($nextpage = NULL, $nexttab = NULL, $moreArgs = array()) 2143{ 2144 $params = array(); 2145 if ($nextpage !== NULL) 2146 $params['page'] = $nextpage; 2147 if ($nexttab !== NULL) 2148 $params['tab'] = $nexttab; 2149 $params = makePageParams ($params + $moreArgs); 2150 unset ($params['module']); // 'interface' module is the default 2151 return makeHref ($params); 2152} 2153 2154// store the accumulated message list into he $SESSION array to display them later 2155function backupLogMessages() 2156{ 2157 global $log_messages; 2158 if (! empty ($log_messages)) 2159 { 2160 startSession(); 2161 $_SESSION['log'] = $log_messages; 2162 session_commit(); 2163 } 2164} 2165 2166function redirectUser ($url) 2167{ 2168 backupLogMessages(); 2169 header ("Location: " . $url); 2170 die; 2171} 2172 2173function getRackImageWidth () 2174{ 2175 global $rtwidth; 2176 return 3 + $rtwidth[0] + $rtwidth[1] + $rtwidth[2] + 3; 2177} 2178 2179function getRackImageHeight ($units) 2180{ 2181 return 3 + 3 + $units * 2; 2182} 2183 2184// Indicate occupation state of each IP address: none, ordinary or problematic. 2185// Returns number of marked up (busy) addresses 2186function markupIPAddrList (&$addrlist) 2187{ 2188 $used = 0; 2189 foreach (array_keys ($addrlist) as $ip_bin) 2190 { 2191 $refc = array 2192 ( 2193 'shared' => 0, // virtual 2194 'virtual' => 0, // loopback 2195 'regular' => 0, // connected host 2196 'router' => 0, // connected gateway 2197 'sharedrouter' => 0, // VRRP gateway 2198 'point2point' => 0, 2199 ); 2200 $nallocs = 0; 2201 foreach ($addrlist[$ip_bin]['allocs'] as $a) 2202 { 2203 $refc[$a['type']]++; 2204 $nallocs++; 2205 } 2206 $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever 2207 if ($nallocs && $nreserved || // reserved cannot be allocated 2208 $nallocs > 1 && $nallocs != $refc['shared'] && $refc['sharedrouter'] == 0 || // multiple shared IPs allowed 2209 $nallocs > 1 && $nallocs != $refc['sharedrouter'] && $refc['shared'] ==0) // multiple shared routers allowed 2210 { 2211 $addrlist[$ip_bin]['class'] = 'trerror'; 2212 ++$used; 2213 } 2214 elseif (! isIPAddressEmpty ($addrlist[$ip_bin], array ('name', 'comment', 'inpf', 'outpf'))) // these fields don't trigger the 'busy' status 2215 { 2216 $addrlist[$ip_bin]['class'] = 'trbusy'; 2217 ++$used; 2218 } 2219 else 2220 $addrlist[$ip_bin]['class'] = ''; 2221 } 2222 return $used; 2223} 2224 2225function findNetRouters ($net) 2226{ 2227 if (isset ($net['own_addrlist'])) 2228 $own_addrlist = $net['own_addrlist']; 2229 else 2230 { 2231 // do not call loadIPAddrList, it is expensive. 2232 // instead, do our own DB scan only for router allocations 2233 $rtrlist = scanIPNet ($net, IPSCAN_DO_ALLOCS | IPSCAN_RTR_ONLY); 2234 $own_addrlist = filterOwnAddrList ($net, $rtrlist); 2235 } 2236 return findRouters ($own_addrlist); 2237} 2238 2239// Scan the given address list (returned by scanIPv4Space/scanIPv6Space) and return a list of all routers found. 2240function findRouters ($addrlist) 2241{ 2242 $ret = array(); 2243 foreach ($addrlist as $addr) 2244 foreach ($addr['allocs'] as $alloc) 2245 if ($alloc['type'] == 'router' || $alloc['type'] == 'sharedrouter') 2246 $ret[] = array 2247 ( 2248 'id' => $alloc['object_id'], 2249 'iface' => $alloc['name'], 2250 'dname' => $alloc['object_name'], 2251 'ip_bin' => $addr['ip_bin'], 2252 ); 2253 return $ret; 2254} 2255 2256function numSign ($x) 2257{ 2258 if ($x < 0) 2259 return -1; 2260 if ($x > 0) 2261 return 1; 2262 return 0; 2263} 2264 2265function numCompare ($a, $b) 2266{ 2267 return numSign ($a - $b); 2268} 2269 2270// compare binary IPs (IPv4 are less than IPv6) 2271// valid return values are: 1, 0, -1 2272function IPCmp ($ip_binA, $ip_binB) 2273{ 2274 if (strlen ($ip_binA) !== strlen ($ip_binB)) 2275 return numCompare (strlen ($ip_binA), strlen ($ip_binB)); 2276 return numSign (strcmp ($ip_binA, $ip_binB)); 2277} 2278 2279// Binary compare the first addresses of each pair 2280// If the first addresses of the compared pairs are equal, compare last addresses in reverse order 2281// valid return values are: 1, 0, -1 2282function IPSpaceCmp ($pairA, $pairB) 2283{ 2284 $first = IPCmp ($pairA['first'], $pairB['first']); 2285 return $first ? $first : IPCmp ($pairB['last'], $pairA['last']); 2286} 2287 2288// filter netsted ip pairs 2289function reduceIPPairList ($pairlist) 2290{ 2291 $ret = array(); 2292 $left = $pairlist; 2293 usort ($left, 'IPSpaceCmp'); 2294 while ($left) 2295 { 2296 $agg = array_shift ($left); 2297 $ret[] = $agg; 2298 foreach ($left as $id => $pair) 2299 if ($pair['first'] >= $agg['first'] && $pair['last'] <= $agg['last']) 2300 unset ($left[$id]); 2301 } 2302 return $ret; 2303} 2304 2305// Compare networks. When sorting a tree, the records on the list will have 2306// distinct base IP addresses. 2307// valid return values are: 2, 1, 0, -1, -2 2308// -2, 2 have special meaning: $netA includes $netB or vice versa, respecively 2309// "The comparison function must return an integer less than, equal to, or greater 2310// than zero if the first argument is considered to be respectively less than, 2311// equal to, or greater than the second." (c) PHP manual 2312function IPNetworkCmp ($netA, $netB) 2313{ 2314 $ret = IPCmp ($netA['ip_bin'], $netB['ip_bin']); 2315 if ($ret == 0) 2316 $ret = $netA['mask'] < $netB['mask'] ? -1 : ($netA['mask'] > $netB['mask'] ? 1 : 0); 2317 if ($ret == -1 && $netA['ip_bin'] === ($netB['ip_bin'] & $netA['mask_bin'])) 2318 $ret = -2; 2319 if ($ret == 1 && $netB['ip_bin'] === ($netA['ip_bin'] & $netB['mask_bin'])) 2320 $ret = 2; 2321 return $ret; 2322} 2323 2324function IPNetContainsOrEqual ($netA, $netB) 2325{ 2326 $res = IPNetworkCmp ($netA, $netB); 2327 return ($res == -2 || $res == 0); 2328} 2329 2330function IPNetContains ($netA, $netB) 2331{ 2332 return (-2 == IPNetworkCmp ($netA, $netB)); 2333} 2334 2335function IPNetsIntersect ($netA, $netB) 2336{ 2337 return ($netA['ip_bin'] & $netB['mask_bin']) === $netB['ip_bin'] || 2338 ($netB['ip_bin'] & $netA['mask_bin']) === $netA['ip_bin']; 2339} 2340 2341function ip_in_range ($ip_bin, $range) 2342{ 2343 return ($ip_bin & $range['mask_bin']) === $range['ip_bin']; 2344} 2345 2346// Sort each level of the tree independently using the given compare function. 2347function sortTree (&$tree, $cmpfunc = '') 2348{ 2349 $self = __FUNCTION__; 2350 if (! is_callable ($cmpfunc)) 2351 throw new InvalidArgException ('cmpfunc', $cmpfunc, 'is not a callable function'); 2352 usort ($tree, $cmpfunc); 2353 foreach (array_keys ($tree) as $tagid) 2354 $self ($tree[$tagid]['kids'], $cmpfunc); 2355} 2356 2357function iptree_fill (&$netdata) 2358{ 2359 if (! isset ($netdata['kids']) || ! count ($netdata['kids'])) 2360 return; 2361 2362 foreach ($netdata['spare_ranges'] as $mask => $list) 2363 { 2364 $spare_mask = $mask; 2365 // align spare IPv6 nets by nibble boundary 2366 if (strlen ($netdata['ip_bin']) == 16 && $mask % 4) 2367 $spare_mask = $mask + 4 - ($mask % 4); 2368 foreach ($list as $ip_bin) 2369 foreach (splitNetworkByMask (constructIPRange ($ip_bin, $mask), $spare_mask) as $spare) 2370 $netdata['kids'][] = $spare + array('kids' => array(), 'kidc' => 0, 'name' => ''); 2371 } 2372 2373 if (count ($netdata['kids']) != $netdata['kidc']) 2374 { 2375 $netdata['kidc'] = count ($netdata['kids']); 2376 usort ($netdata['kids'], 'IPNetworkCmp'); 2377 } 2378} 2379 2380// returns TRUE if inet_ntop and inet_pton functions exist 2381function is_inet_avail() 2382{ 2383 static $ret = NULL; 2384 if (! isset ($ret)) 2385 $ret = is_callable ('inet_pton') && is_callable ('inet_ntop'); 2386 return $ret; 2387} 2388 2389// returns TRUE if inet_ntop and inet_pton functions exist and support IPv6 2390function is_inet6_avail() 2391{ 2392 static $ret = NULL; 2393 if (! isset ($ret)) 2394 $ret = is_inet_avail() && defined ('AF_INET6'); 2395 return $ret; 2396} 2397 2398function ip_format ($ip_bin) 2399{ 2400 switch (strlen ($ip_bin)) 2401 { 2402 case 4: return ip4_format ($ip_bin); 2403 case 16: return ip6_format ($ip_bin); 2404 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2405 } 2406} 2407 2408function ip4_format ($ip_bin) 2409{ 2410 if (4 == strlen ($ip_bin)) 2411 { 2412 if (is_inet_avail()) 2413 { 2414 $ret = @inet_ntop ($ip_bin); 2415 if ($ret !== FALSE) 2416 return $ret; 2417 } 2418 else 2419 return implode ('.', unpack ('C*', $ip_bin)); 2420 } 2421 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2422} 2423 2424function ip6_format ($ip_bin) 2425{ 2426 do { 2427 if (16 != strlen ($ip_bin)) 2428 break; 2429 2430 if (is_inet6_avail()) 2431 { 2432 $ret = @inet_ntop ($ip_bin); 2433 if ($ret !== FALSE) 2434 return $ret; 2435 break; 2436 } 2437 2438 // maybe this is IPv6-to-IPv4 address? 2439 if (substr ($ip_bin, 0, 12) == "\0\0\0\0\0\0\0\0\0\0\xff\xff") 2440 return '::ffff:' . implode ('.', unpack ('C*', substr ($ip_bin, 12, 4))); 2441 2442 $result = array(); 2443 $hole_index = NULL; 2444 $max_hole_index = NULL; 2445 $hole_length = 0; 2446 $max_hole_length = 0; 2447 2448 for ($i = 0; $i < 8; $i++) 2449 { 2450 $unpacked = unpack ('n', substr ($ip_bin, $i * 2, 2)); 2451 $value = array_shift ($unpacked); 2452 $result[] = dechex ($value & 0xffff); 2453 if ($value != 0) 2454 { 2455 unset ($hole_index); 2456 $hole_length = 0; 2457 } 2458 else 2459 { 2460 if (! isset ($hole_index)) 2461 $hole_index = $i; 2462 if (++$hole_length >= $max_hole_length) 2463 { 2464 $max_hole_index = $hole_index; 2465 $max_hole_length = $hole_length; 2466 } 2467 } 2468 } 2469 if (isset ($max_hole_index)) 2470 { 2471 array_splice ($result, $max_hole_index, $max_hole_length, array ('')); 2472 if ($max_hole_index == 0 && $max_hole_length == 8) 2473 return '::'; 2474 elseif ($max_hole_index == 0) 2475 return ':' . implode (':', $result); 2476 elseif ($max_hole_index + $max_hole_length == 8) 2477 return implode (':', $result) . ':'; 2478 } 2479 return implode (':', $result); 2480 } while (FALSE); 2481 2482 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2483} 2484 2485function ip_parse ($ip) 2486{ 2487 try 2488 { 2489 if (FALSE !== strpos ($ip, ':')) 2490 return ip6_parse ($ip); 2491 else 2492 return ip4_parse ($ip); 2493 } 2494 catch (InvalidArgException $e) 2495 { 2496 // re-throw with general error message, without specifying an IP family 2497 throw new InvalidArgException ('ip', $ip, "Invalid IP address"); 2498 } 2499} 2500 2501function ip4_parse ($ip) 2502{ 2503 if (is_inet_avail()) 2504 { 2505 if (FALSE !== ($ret = @inet_pton ($ip)) && strlen ($ret) == 4) 2506 return $ret; 2507 } 2508 elseif (FALSE !== ($int = ip2long ($ip))) 2509 return pack ('N', $int); 2510 2511 throw new InvalidArgException ('ip', $ip, "Invalid IPv4 address"); 2512} 2513 2514// returns 16-byte string ip_bin 2515// throws exception if unable to parse 2516function ip6_parse ($ip) 2517{ 2518 do { 2519 if (is_inet6_avail()) 2520 { 2521 if (FALSE !== ($ret = @inet_pton ($ip)) && strlen ($ret) == 16) 2522 return $ret; 2523 break; 2524 } 2525 2526 if (empty ($ip)) 2527 break; 2528 2529 $result = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // 16 bytes 2530 // remove one of double beginning/tailing colons 2531 if (substr ($ip, 0, 2) == '::') 2532 $ip = substr ($ip, 1); 2533 elseif (substr ($ip, -2, 2) == '::') 2534 $ip = substr ($ip, 0, strlen ($ip) - 1); 2535 2536 $tokens = explode (':', $ip); 2537 $last_token = $tokens[count ($tokens) - 1]; 2538 $split = explode ('.', $last_token); 2539 if (count ($split) == 4) 2540 { 2541 $hex_tokens = array(); 2542 $hex_tokens[] = dechex ($split[0] * 256 + $split[1]); 2543 $hex_tokens[] = dechex ($split[2] * 256 + $split[3]); 2544 array_splice ($tokens, -1, 1, $hex_tokens); 2545 } 2546 if (count ($tokens) > 8) 2547 break; 2548 for ($i = 0; $i < count ($tokens); $i++) 2549 { 2550 if ($tokens[$i] != '') 2551 { 2552 if (! set_word_value ($result, $i, $tokens[$i])) 2553 break; 2554 } 2555 else 2556 { 2557 $k = 8; //index in result string (last word) 2558 for ($j = count ($tokens) - 1; $j > $i; $j--) // $j is an index in $tokens for reverse walk 2559 if ($tokens[$j] == '') 2560 break; 2561 elseif (! set_word_value ($result, --$k, $tokens[$j])) 2562 break; 2563 if ($i != $j) 2564 break; //error, more than 1 '::' range 2565 break; 2566 } 2567 } 2568 if (! isset ($k) && count ($tokens) != 8) 2569 break; 2570 return $result; 2571 } while (FALSE); 2572 2573 throw new InvalidArgException ('ip', $ip, "Invalid IPv6 address"); 2574} 2575 2576function ip_get_arpa ($ip_bin) 2577{ 2578 switch (strlen ($ip_bin)) 2579 { 2580 case 4: return ip4_get_arpa ($ip_bin); 2581 case 16: return ip6_get_arpa ($ip_bin); 2582 default: throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2583 } 2584} 2585 2586function ip4_get_arpa ($ip_bin) 2587{ 2588 $ret = ''; 2589 for ($i = 3; $i >= 0; $i--) 2590 $ret .= ord($ip_bin[$i]) . '.'; 2591 return $ret . 'in-addr.arpa'; 2592} 2593 2594function ip6_get_arpa ($ip_bin) 2595{ 2596 $ret = ''; 2597 $hex = implode ('', unpack('H32', $ip_bin)); 2598 for ($i = 31; $i >= 0; $i--) 2599 $ret .= $hex[$i] . '.'; 2600 return $ret . 'ip6.arpa'; 2601} 2602 2603function set_word_value (&$haystack, $nword, $hexvalue) 2604{ 2605 // check that $hexvalue is like /^[0-9a-fA-F]*$/ 2606 for ($i = 0; $i < strlen ($hexvalue); $i++) 2607 { 2608 $char = ord ($hexvalue[$i]); 2609 if (! ($char >= 0x30 && $char <= 0x39 || $char >= 0x41 && $char <= 0x46 || $char >=0x61 && $char <= 0x66)) 2610 return FALSE; 2611 } 2612 $haystack = substr_replace ($haystack, pack ('n', hexdec ($hexvalue)), $nword * 2, 2); 2613 return TRUE; 2614} 2615 2616// returns binary IP or FALSE 2617function ip_checkparse ($ip) 2618{ 2619 try 2620 { 2621 return ip_parse ($ip); 2622 } 2623 catch (InvalidArgException $e) 2624 { 2625 return FALSE; 2626 } 2627} 2628 2629// returns binary IP or FALSE 2630function ip4_checkparse ($ip) 2631{ 2632 try 2633 { 2634 return ip4_parse ($ip); 2635 } 2636 catch (InvalidArgException $e) 2637 { 2638 return FALSE; 2639 } 2640} 2641 2642// returns binary IP or FALSE 2643function ip6_checkparse ($ip) 2644{ 2645 try 2646 { 2647 return ip6_parse ($ip); 2648 } 2649 catch (InvalidArgException $e) 2650 { 2651 return FALSE; 2652 } 2653} 2654 2655function ip4_int2bin ($ip_int) 2656{ 2657 return pack ('N', $ip_int + 0); 2658} 2659 2660function ip4_bin2int ($ip_bin) 2661{ 2662 if (4 != strlen ($ip_bin)) 2663 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2664 $ret = array_first (unpack ('N', $ip_bin)); 2665 if (PHP_INT_SIZE > 4 && $ret < 0) 2666 $ret = $ret & 0xffffffff; 2667 return $ret; 2668} 2669 2670// Use this function only when you need to export binary ip out of PHP running context (e.g., DB) 2671// !DO NOT perform arithmetic and bitwise operations with the result of this function! 2672function ip4_bin2db ($ip_bin) 2673{ 2674 $ip_int = ip4_bin2int ($ip_bin); 2675 return $ip_int >= 0 ? $ip_int : sprintf ('%u', 0x00000000 + $ip_int); 2676} 2677 2678function ip_last ($net) 2679{ 2680 return $net['ip_bin'] | ~$net['mask_bin']; 2681} 2682 2683function ip_next ($ip_bin) 2684{ 2685 $ret = $ip_bin; 2686 $p = 1; 2687 for ($i = strlen ($ret) - 1; $i >= 0; $i--) 2688 { 2689 $oct = $p + ord ($ret[$i]); 2690 $ret[$i] = chr ($oct & 0xff); 2691 if ($oct <= 255 && $oct >= 0) 2692 break; 2693 } 2694 return $ret; 2695} 2696 2697function ip_prev ($ip_bin) 2698{ 2699 $ret = $ip_bin; 2700 $p = -1; 2701 for ($i = strlen ($ret) - 1; $i >= 0; $i--) 2702 { 2703 $oct = $p + ord ($ret[$i]); 2704 $ret[$i] = chr ($oct & 0xff); 2705 if ($oct <= 255 && $oct >= 0) 2706 break; 2707 } 2708 return $ret; 2709} 2710 2711function ip4_range_size ($range) 2712{ 2713 return ip4_mask_size ($range['mask']); 2714} 2715 2716function ip4_mask_size ($mask) 2717{ 2718 switch (TRUE) 2719 { 2720 case ($mask > 1 && $mask <= 32): 2721 return (0x7fffffff >> ($mask - 1)) + 1; 2722 // constants below are not representable in 32-bit PHP's int type, 2723 // so they are literally hardcoded and returned as strings on 32-bit architecture. 2724 case ($mask == 1): 2725 return 2147483648; 2726 case ($mask == 0): 2727 return 4294967296; 2728 default: 2729 throw new InvalidArgException ('mask', $mask, 'Invalid IPv4 prefix length'); 2730 } 2731} 2732 2733// returns array with keys 'ip', 'ip_bin', 'mask', 'mask_bin' 2734function constructIPRange ($ip_bin, $mask = NULL) 2735{ 2736 $node = array(); 2737 switch (strlen ($ip_bin)) 2738 { 2739 case 4: // IPv4 2740 if ($mask === NULL) 2741 $mask = 32; 2742 elseif (! isUnsignedInteger ($mask) || $mask > 32) 2743 throw new InvalidArgException ('mask', $mask, "Invalid v4 prefix length"); 2744 $node['mask_bin'] = ip4_mask ($mask); 2745 $node['mask'] = $mask; 2746 $node['ip_bin'] = $ip_bin & $node['mask_bin']; 2747 $node['ip'] = ip4_format ($node['ip_bin']); 2748 break; 2749 case 16: // IPv6 2750 if ($mask === NULL) 2751 $mask = 128; 2752 elseif (! isUnsignedInteger ($mask) || $mask > 128) 2753 throw new InvalidArgException ('mask', $mask, "Invalid v6 prefix length"); 2754 $node['mask_bin'] = ip6_mask ($mask); 2755 $node['mask'] = $mask; 2756 $node['ip_bin'] = $ip_bin & $node['mask_bin']; 2757 $node['ip'] = ip6_format ($node['ip_bin']); 2758 break; 2759 default: 2760 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2761 } 2762 return $node; 2763} 2764 2765// Return minimal IP address structure 2766function constructIPAddress ($ip_bin) 2767{ 2768 // common v4/v6 part 2769 $ret = array 2770 ( 2771 'ip' => ip_format ($ip_bin), 2772 'ip_bin' => $ip_bin, 2773 'name' => '', 2774 'comment' => '', 2775 'reserved' => 'no', 2776 'allocs' => array(), 2777 'vslist' => array(), 2778 'vsglist' => array(), 2779 'rsplist' => array(), 2780 ); 2781 2782 // specific v4 part 2783 if (strlen ($ip_bin) == 4) 2784 $ret = array_merge 2785 ( 2786 $ret, 2787 array 2788 ( 2789 'outpf' => array(), 2790 'inpf' => array(), 2791 ) 2792 ); 2793 return $ret; 2794} 2795 2796function treeApplyFunc1 (&$tree, $func) 2797{ 2798 $self = __FUNCTION__; 2799 foreach (array_keys ($tree) as $key) 2800 { 2801 $func ($tree[$key]); 2802 $self ($tree[$key]['kids'], $func); 2803 } 2804} 2805 2806function treeApplyFunc2 (&$tree, $func, $stopfunc) 2807{ 2808 $self = __FUNCTION__; 2809 foreach (array_keys ($tree) as $key) 2810 { 2811 $func ($tree[$key]); 2812 if (! $stopfunc ($tree[$key])) 2813 $self ($tree[$key]['kids'], $func); 2814 } 2815} 2816 2817// Note that the stop function is called after processing a tree item, not before. 2818// In other words, for a given tree node either all its sub-nodes are processed or 2819// none at all. 2820// XXX: Perhaps instead of a separate stop function it would be better to convey the 2821// feedback through the applied function's return value. This would also leave it up 2822// to the applied function whether to stop before or after modifying the current node. 2823function treeApplyFunc (&$tree, $func, $stopfunc = NULL) 2824{ 2825 if (! is_callable ($func)) 2826 throw new InvalidArgException ('func', $func, 'is not callable'); 2827 if ($stopfunc === NULL) 2828 treeApplyFunc1 ($tree, $func); 2829 else 2830 { 2831 if (! is_callable ($stopfunc)) 2832 throw new InvalidArgException ('stopfunc', $stopfunc, 'is not callable'); 2833 treeApplyFunc2 ($tree, $func, $stopfunc); 2834 } 2835} 2836 2837function nodeIsCollapsed ($node) 2838{ 2839 return $node['symbol'] == 'node-collapsed'; 2840} 2841 2842// returns those addresses from $addrlist that do not belong to $net's subsequent networks 2843function filterOwnAddrList ($net, $addrlist) 2844{ 2845 if ($net['kidc'] == 0) 2846 return $addrlist; 2847 2848 // net has children 2849 $ret = array(); 2850 foreach ($net['spare_ranges'] as $mask => $spare_list) 2851 foreach ($spare_list as $spare_ip) 2852 { 2853 $spare_mask = ip_mask ($mask, strlen ($net['ip_bin']) == 16); 2854 foreach ($addrlist as $bin_ip => $addr) 2855 if (($bin_ip & $spare_mask) == $spare_ip) 2856 $ret[$bin_ip] = $addr; 2857 } 2858 return $ret; 2859} 2860 2861function getIPAddrList ($net, $flags = IPSCAN_ANY) 2862{ 2863 $addrlist = scanIPNet ($net, $flags); 2864 return filterOwnAddrList ($net, $addrlist); 2865} 2866 2867// sets 'addrlist', 'own_addrlist', 'addrc', 'own_addrc' keys of $node 2868// 'addrc' and 'own_addrc' are sizes of 'addrlist' and 'own_addrlist', respectively 2869function loadIPAddrList (&$node) 2870{ 2871 $node['addrlist'] = scanIPNet ($node); 2872 2873 if (! isset ($node['id'])) 2874 $node['own_addrlist'] = $node['addrlist']; 2875 else 2876 $node['own_addrlist'] = filterOwnAddrList ($node, $node['addrlist']); 2877 2878 $node['addrc'] = count ($node['addrlist']); 2879 $node['own_addrc'] = count ($node['own_addrlist']); 2880} 2881 2882// returns list of PtP-typed allocs from $addrlist indexed by ip_bin 2883function getPtPNeighbors ($ip_bin, $addrlist) 2884{ 2885 $ret = array(); 2886 if (isset ($addrlist[$ip_bin])) 2887 { 2888 $found_ptp_alloc = FALSE; 2889 foreach ($addrlist[$ip_bin]['allocs'] as $alloc) 2890 if ($alloc['type'] == 'point2point') 2891 { 2892 $found_ptp_alloc = TRUE; 2893 break; 2894 } 2895 if ($found_ptp_alloc) 2896 foreach ($addrlist as $i_ip_bin => $i_address) 2897 if ($ip_bin !== $i_ip_bin) 2898 foreach ($i_address['allocs'] as $alloc) 2899 if ($alloc['type'] == 'point2point') 2900 $ret[$i_ip_bin][] = $alloc; 2901 } 2902 return $ret; 2903} 2904 2905// returns the array of structure described by constructIPAddress 2906function getIPAddress ($ip_bin) 2907{ 2908 $scanres = scanIPSpace (array (array ('first' => $ip_bin, 'last' => $ip_bin))); 2909 if (empty ($scanres)) 2910 $scanres[$ip_bin] = constructIPAddress ($ip_bin); 2911 markupIPAddrList ($scanres); 2912 return $scanres[$ip_bin]; 2913} 2914 2915function getIPv4Address ($ip_bin) 2916{ 2917 if (strlen ($ip_bin) != 4) 2918 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2919 return getIPAddress ($ip_bin); 2920} 2921 2922function getIPv6Address ($ip_bin) 2923{ 2924 if (strlen ($ip_bin) != 16) 2925 throw new InvalidArgException ('ip_bin', $ip_bin, "Invalid binary IP"); 2926 return getIPAddress ($ip_bin); 2927} 2928 2929function makeIPTree ($netlist) 2930{ 2931 // treeFromList() requires parent_id to be correct for an item to get onto the tree, 2932 // so perform necessary pre-processing to calculate parent_id of each item of $netlist. 2933 $stack = array(); 2934 foreach ($netlist as $net_id => &$net) 2935 { 2936 while (count ($stack)) 2937 { 2938 $top_id = $stack[count ($stack) - 1]; 2939 if (! IPNetContains ($netlist[$top_id], $net)) // unless $net is a child of stack top 2940 array_pop ($stack); 2941 else 2942 { 2943 $net['parent_id'] = $top_id; 2944 break; 2945 } 2946 } 2947 if (! count ($stack)) 2948 $net['parent_id'] = NULL; 2949 array_push ($stack, $net_id); 2950 } 2951 unset ($stack); 2952 2953 return treeFromList (addTraceToNodes ($netlist)); 2954} 2955 2956function prepareIPTree ($netlist, $expanded_id = 0) 2957{ 2958 $tree = makeIPTree ($netlist); 2959 // complement the tree before markup to make the spare networks have "symbol" set 2960 treeApplyFunc ($tree, 'iptree_fill'); 2961 iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id); 2962 return $tree; 2963} 2964 2965# Traverse IPv4/IPv6 tree and return a list of all networks that 2966# exist in DB and don't have any sub-networks. 2967function getTerminalNetworks ($tree) 2968{ 2969 $self = __FUNCTION__; 2970 $ret = array(); 2971 foreach ($tree as $node) 2972 if ($node['kidc'] == 0 && isset ($node['realm'])) 2973 $ret[] = $node; 2974 else 2975 $ret = array_merge ($ret, $self ($node['kids'])); 2976 return $ret; 2977} 2978 2979// Check all items of the tree recursively, until the requested target id is 2980// found. Mark all items leading to this item as "expanded", collapsing all 2981// the rest that exceed the given threshold (if the threshold is given). 2982function iptree_markup_collapsion (&$tree, $threshold = 1024, $target = 0) 2983{ 2984 $self = __FUNCTION__; 2985 $ret = FALSE; 2986 foreach (array_keys ($tree) as $key) 2987 { 2988 $here = $target === 'ALL' || ($target > 0 && isset ($tree[$key]['id']) && $tree[$key]['id'] == $target); 2989 $below = $self ($tree[$key]['kids'], $threshold, $target); 2990 $expand_enabled = ($target !== 'NONE'); 2991 if (!$tree[$key]['kidc']) // terminal node 2992 $tree[$key]['symbol'] = 'spacer'; 2993 elseif ($expand_enabled && $tree[$key]['kidc'] < $threshold) 2994 $tree[$key]['symbol'] = 'node-expanded-static'; 2995 elseif ($expand_enabled && ($here || $below)) 2996 $tree[$key]['symbol'] = 'node-expanded'; 2997 else 2998 $tree[$key]['symbol'] = 'node-collapsed'; 2999 $ret = $ret || $here || $below; 3000 } 3001 return $ret; 3002} 3003 3004// Convert entity name to human-readable value 3005function formatRealmName ($realm) 3006{ 3007 $realmstr = array 3008 ( 3009 'ipv4net' => 'IPv4 Network', 3010 'ipv6net' => 'IPv6 Network', 3011 'ipv4rspool' => 'IPv4 RS Pool', 3012 'ipv4vs' => 'IPv4 Virtual Service', 3013 'ipvs' => 'IP Virtual Service', 3014 'object' => 'Object', 3015 'rack' => 'Rack', 3016 'row' => 'Row', 3017 'location' => 'Location', 3018 'user' => 'User', 3019 'file' => 'File', 3020 'vst' => 'VLAN switch template', 3021 ); 3022 return array_fetch ($realmstr, $realm, 'invalid'); 3023} 3024 3025// Convert filesize to appropriate unit and make it human-readable 3026function formatFileSize ($bytes) 3027{ 3028 // bytes 3029 if($bytes < 1024) // bytes 3030 return "${bytes} bytes"; 3031 3032 // kilobytes 3033 if ($bytes < 1024000) 3034 return sprintf ("%.1fk", round (($bytes / 1024), 1)); 3035 3036 // megabytes 3037 return sprintf ("%.1f MB", round (($bytes / 1024000), 1)); 3038} 3039 3040// Reverse of formatFileSize, it converts human-readable value to bytes 3041function convertToBytes ($value) 3042{ 3043 $value = trim($value); 3044 $last = strtolower($value[strlen($value)-1]); 3045 switch ($last) 3046 { 3047 case 'g': 3048 $value *= 1024; 3049 case 'm': 3050 $value *= 1024; 3051 case 'k': 3052 $value *= 1024; 3053 } 3054 3055 return $value; 3056} 3057 3058// make "A" HTML element 3059function mkA ($text, $nextpage, $bypass = NULL, $nexttab = NULL, $attrs = array()) 3060{ 3061 global $page, $tab; 3062 if ($text == '') 3063 throw new InvalidArgException ('text', $text, 'must not be empty'); 3064 if (! array_key_exists ($nextpage, $page)) 3065 throw new InvalidArgException ('nextpage', $nextpage, 'not a valid page name'); 3066 $args = array ('page' => $nextpage); 3067 if ($nexttab !== NULL) 3068 { 3069 if (! array_key_exists ($nexttab, $tab[$nextpage])) 3070 throw new InvalidArgException ('nexttab', $nexttab, 'not a valid tab name'); 3071 $args['tab'] = $nexttab; 3072 } 3073 if (array_key_exists ('bypass', $page[$nextpage])) 3074 { 3075 if ($bypass === NULL) 3076 throw new InvalidArgException ('bypass', '(NULL)', 'must be specified for the given page name'); 3077 $args[$page[$nextpage]['bypass']] = $bypass; 3078 } 3079 $attrs['href'] = makeHref ($args); 3080 $ret = '<a'; 3081 foreach ($attrs as $attr_name => $attr_value) 3082 $ret .= " $attr_name=" . '"' . htmlspecialchars ($attr_value, ENT_QUOTES) . '"'; 3083 return $ret . '>' . $text . '</a>'; 3084} 3085 3086// make "HREF" HTML attribute 3087function makeHref ($params = array()) 3088{ 3089 $tmp = array(); 3090 foreach ($params as $key => $value) 3091 { 3092 if (is_array ($value)) 3093 $key .= "[]"; 3094 else 3095 $value = array ($value); 3096 if (!count ($value)) 3097 $tmp[] = urlencode ($key) . '='; 3098 else 3099 foreach ($value as $sub_value) 3100 $tmp[] = urlencode ($key) . '=' . urlencode ($sub_value); 3101 } 3102 return 'index.php?' . implode ('&', $tmp); 3103} 3104 3105function makePageParams ($params = array()) 3106{ 3107 global $pageno, $tabno; 3108 $ret = array(); 3109 // assure that page and tab keys go first 3110 $ret['page'] = isset ($params['page']) ? $params['page'] : $pageno; 3111 $ret['tab'] = isset ($params['tab']) ? $params['tab'] : $tabno; 3112 $ret += $params; 3113 if ($ret['page'] === $pageno) 3114 fillBypassValues ($pageno, $ret); 3115 return $ret; 3116} 3117 3118function makeHrefProcess ($params = array()) 3119{ 3120 return makeHref (array ('module' => 'redirect') + makePageParams ($params)); 3121} 3122 3123// Process the given list of records to build data suitable for printNiftySelect() 3124// (like it was formerly executed by printSelect()). Screen out vendors according 3125// to VENDOR_SIEVE, if object type ID is provided. However, the OPTGROUP with already 3126// selected OPTION is protected from being screened. 3127function cookOptgroups ($recordList, $object_type_id = 0, $existing_value = 0) 3128{ 3129 $ret = array(); 3130 $therest = array(); 3131 foreach ($recordList as $dict_key => $dict_value) 3132 if (preg_match ('/^(.*)%(GPASS|GSKIP)%/', $dict_value, $m)) 3133 $ret[$m[1]][$dict_key] = execGMarker ($dict_value); 3134 else 3135 $therest[$dict_key] = $dict_value; 3136 3137 // Always keep "other" OPTGROUP at the SELECT bottom. 3138 $ret['other'] = $therest; 3139 3140 if ($object_type_id != 0) 3141 { 3142 $screenlist = array(); 3143 foreach (explode (';', getConfigVar ('VENDOR_SIEVE')) as $sieve) 3144 if (preg_match ("/^([^@]+)(@${object_type_id})?\$/", trim ($sieve), $regs)) 3145 $screenlist[] = $regs[1]; 3146 3147 foreach (array_keys ($ret) as $vendor) 3148 if (in_array ($vendor, $screenlist)) 3149 { 3150 $ok_to_screen = TRUE; 3151 if ($existing_value) 3152 foreach (array_keys ($ret[$vendor]) as $recordkey) 3153 if ($recordkey == $existing_value) 3154 { 3155 $ok_to_screen = FALSE; 3156 break; 3157 } 3158 if ($ok_to_screen) 3159 unset ($ret[$vendor]); 3160 } 3161 } 3162 return $ret; 3163} 3164 3165function dos2unix ($text) 3166{ 3167 return str_replace ("\r\n", "\n", $text); 3168} 3169 3170function unix2dos ($text) 3171{ 3172 return str_replace ("\n", "\r\n", $text); 3173} 3174 3175function buildPredicateTable (&$rackCode) 3176{ 3177 $ret = array(); 3178 $new_rackCode = array(); 3179 3180 foreach ($rackCode as $sentence) 3181 if ($sentence['type'] == 'SYNT_DEFINITION') 3182 $ret[$sentence['term']] = $sentence['definition']; 3183 else 3184 $new_rackCode[] = $sentence; 3185 3186 // remove SYNT_DEFINITION statements from the original rackCode to 3187 // make permitted() calls faster. 3188 $rackCode = $new_rackCode; 3189 3190 // The latest (and the only, as the interpreter must not allow to redefine predicates) 3191 // definition of every predicate is now in the predicate table, which will remain 3192 // intact until the end of run-time. 3193 return $ret; 3194} 3195 3196// Take a list of records and filter against given RackCode expression. Return 3197// the original list intact, if there was no filter requested, but return an 3198// empty list, if there was an error. 3199function filterCellList ($list_in, $expression = array()) 3200{ 3201 if ($expression === NULL) 3202 return array(); 3203 if (!count ($expression)) 3204 return $list_in; 3205 $list_out = array(); 3206 foreach ($list_in as $item_key => $item_value) 3207 if (TRUE === judgeCell ($item_value, $expression)) 3208 $list_out[$item_key] = $item_value; 3209 return $list_out; 3210} 3211 3212function eval_expression ($expr, $tagchain, $silent = FALSE) 3213{ 3214 $self = __FUNCTION__; 3215 global $pTable; 3216 3217 switch ($expr['type']) 3218 { 3219 // Return true, if given tag is present on the tag chain. 3220 case 'LEX_TAG': 3221 return isset ($tagchain[$expr['load']]); 3222 case 'LEX_PREDICATE': // Find given predicate in the symbol table and evaluate it. 3223 $pname = $expr['load']; 3224 if (!isset ($pTable[$pname])) 3225 { 3226 if (!$silent) 3227 showWarning ("Undefined predicate [${pname}]"); 3228 return NULL; 3229 } 3230 return $self ($pTable[$pname], $tagchain, $silent); 3231 case 'LEX_BOOL': 3232 return $expr['load']; 3233 case 'SYNT_NOT_EXPR': // logical NOT 3234 $tmp = $self ($expr['load'], $tagchain, $silent); 3235 return is_bool($tmp) ? !$tmp : $tmp; 3236 case 'SYNT_AND_EXPR': // logical AND 3237 foreach ($expr['tag_args'] as $tag) 3238 if (! isset ($tagchain[$tag])) 3239 return FALSE; // early failure 3240 foreach ($expr['expr_args'] as $sub_expr) 3241 if (! $self ($sub_expr, $tagchain, $silent)) 3242 return FALSE; // early failure 3243 return TRUE; 3244 case 'SYNT_EXPR': // logical OR 3245 foreach ($expr['tag_args'] as $tag) 3246 if (isset ($tagchain[$tag])) 3247 return TRUE; // early success 3248 foreach ($expr['expr_args'] as $sub_expr) 3249 if ($self ($sub_expr, $tagchain, $silent)) 3250 return TRUE; // early success 3251 return FALSE; 3252 default: 3253 if (!$silent) 3254 showWarning ("Evaluation error, cannot process expression type '${expr['type']}'"); 3255 return NULL; 3256 } 3257} 3258 3259// Tell, if the given expression is true for the given entity. Take complete record on input. 3260function judgeCell ($cell, $expression) 3261{ 3262 $context = array_merge ($cell['etags'], $cell['itags'], $cell['atags']); 3263 $context = reindexById ($context, 'tag', TRUE); 3264 return eval_expression 3265 ( 3266 $expression, 3267 $context, 3268 TRUE 3269 ); 3270} 3271 3272function judgeContext ($expression) 3273{ 3274 global $expl_tags, $impl_tags, $auto_tags; 3275 $context = array_merge ($expl_tags, $impl_tags, $auto_tags); 3276 $context = reindexById ($context, 'tag', TRUE); 3277 return eval_expression 3278 ( 3279 $expression, 3280 $context, 3281 TRUE 3282 ); 3283} 3284 3285// Tell, if a constraint from config option permits given record. 3286// An undefined $cell means current context. 3287function considerConfiguredConstraint ($cell, $varname) 3288{ 3289 // Do not mask any exceptions thrown in getConfigVar(). 3290 $text = getConfigVar ($varname); 3291 try 3292 { 3293 return considerGivenConstraint ($cell, $text); 3294 } 3295 catch (InvalidArgException $e) 3296 { 3297 return FALSE; // constraint set, but cannot be used due to compilation error 3298 } 3299} 3300 3301// Tell, if the given arbitrary RackCode text addresses the given record 3302// (an empty text matches any record). 3303// An undefined $cell means current context. 3304function considerGivenConstraint ($cell, $filter) 3305{ 3306 if ($filter == '') 3307 return TRUE; 3308 if (! $expr = compileExpression ($filter)) 3309 throw new InvalidArgException ('filter', $filter, 'not a valid RackCode expression'); 3310 if (isset ($cell)) 3311 return judgeCell ($cell, $expr); 3312 else 3313 return judgeContext ($expr); 3314} 3315 3316// Return list of records in the given realm that conform to 3317// the given RackCode expression. If the realm is unknown or text 3318// doesn't validate as a RackCode expression, return NULL. 3319// Otherwise (successful scan) return a list of all matched 3320// records, even if the list is empty (array() !== NULL). If the 3321// text is an empty string, return all found records in the given 3322// realm. 3323function scanRealmByText ($realm, $ftext = '') 3324{ 3325 if ('' == $ftext = trim ($ftext)) 3326 $fexpr = array(); 3327 else 3328 { 3329 $fexpr = compileExpression ($ftext); 3330 if (! $fexpr) 3331 return NULL; 3332 } 3333 return filterCellList (listCells ($realm), $fexpr); 3334} 3335 3336function getVSTOptions() 3337{ 3338 return reduceSubarraysToColumn (reindexById (listCells ('vst')), 'description'); 3339} 3340 3341# Return an array in the format understood by getNiftySelect() and getOptionTree(), 3342# so that the options stand for all VLANs grouped by respective VLAN domains, except 3343# those listed on the "except" array. 3344function getAllVLANOptions ($except = array()) 3345{ 3346 $ret = array(); 3347 foreach (getVLANDomainOptions() as $domain_id => $domain_descr) 3348 { 3349 $domain_list = array(); 3350 foreach (getDomainVLANList ($domain_id, TRUE) as $vlan) 3351 $domain_list["${domain_id}-${vlan['vlan_id']}"] = "${vlan['vlan_id']} ${vlan['vlan_descr']}"; 3352 if (isset ($except[$domain_id])) 3353 foreach ($except[$domain_id] as $vid) 3354 if (isset ($domain_list["${domain_id}-${vid}"])) 3355 unset ($domain_list["${domain_id}-${vid}"]); 3356 $ret[$domain_descr] = $domain_list; 3357 } 3358 return $ret; 3359} 3360 3361// This debugging helper does not depend on interface.php. 3362function dump ($var) 3363{ 3364 echo '<div align=left><pre>'; 3365 var_dump ($var); 3366 echo '</pre></div>'; 3367} 3368 3369function getTagChart ($limit = 0, $realm = 'total', $special_tags = array()) 3370{ 3371 $taglist_usage = getTagUsage(); 3372 // first build top-N chart... 3373 $toplist = array(); 3374 foreach ($taglist_usage as $taginfo) 3375 if (isset ($taginfo['refcnt'][$realm])) 3376 $toplist[$taginfo['id']] = $taginfo['refcnt'][$realm]; 3377 arsort ($toplist, SORT_NUMERIC); 3378 $ret = array(); 3379 $done = 0; 3380 foreach (array_keys ($toplist) as $tag_id) 3381 { 3382 $ret[$tag_id] = $taglist_usage[$tag_id]; 3383 if (++$done == $limit) 3384 break; 3385 } 3386 // ...then make sure that every item of the special list is shown 3387 // (using the same sort order) 3388 $extra = array(); 3389 foreach ($special_tags as $taginfo) 3390 if (!array_key_exists ($taginfo['id'], $ret)) 3391 $extra[$taginfo['id']] = $taglist_usage[$taginfo['id']]['refcnt'][$realm]; 3392 arsort ($extra, SORT_NUMERIC); 3393 foreach (array_keys ($extra) as $tag_id) 3394 $ret[] = $taglist_usage[$tag_id]; 3395 return $ret; 3396} 3397 3398// $style is deprecated and unused 3399function decodeObjectType ($objtype_id, $style = '') 3400{ 3401 static $types; 3402 if (! isset ($types)) 3403 $types = readChapter (CHAP_OBJTYPE, 'a'); 3404 return $types[$objtype_id]; 3405} 3406 3407// This wrapper makes it possible to call permitted() with the security context 3408// containing the given object of temporary interest without the [previously loaded] 3409// main subject. 3410function isolatedPermission ($p, $t, $cell) 3411{ 3412 $saved = getContext(); 3413 // retarget 3414 fixContext ($cell); 3415 // remember decision 3416 $ret = permitted ($p, $t); 3417 restoreContext ($saved); 3418 return $ret; 3419} 3420 3421function getPortListPrefs() 3422{ 3423 $ret = array(); 3424 if (0 >= ($ret['iif_pick'] = getConfigVar ('DEFAULT_PORT_IIF_ID'))) 3425 $ret['iif_pick'] = 1; 3426 $ret['oif_picks'] = array(); 3427 foreach (explode (';', getConfigVar ('DEFAULT_PORT_OIF_IDS')) as $tmp) 3428 { 3429 $tmp = explode ('=', trim ($tmp)); 3430 if (count ($tmp) == 2 && $tmp[0] > 0 && $tmp[1] > 0) 3431 $ret['oif_picks'][$tmp[0]] = $tmp[1]; 3432 } 3433 // enforce default value 3434 if (!array_key_exists (1, $ret['oif_picks'])) 3435 $ret['oif_picks'][1] = 24; 3436 $ret['selected'] = $ret['iif_pick'] . '-' . $ret['oif_picks'][$ret['iif_pick']]; 3437 return $ret; 3438} 3439 3440function getNewPortTypeOptions() 3441{ 3442 return getUnlinkedPortTypeOptions (NULL); 3443} 3444 3445// Return data for printNiftySelect() with port type options. All OIF options 3446// for the default or current (passed) IIFs will be shown, but only the default 3447// OIFs will be present for each other IIFs. IIFs for that there is no default 3448// OIF will not be listed. 3449// This SELECT will be used in "manage object ports" form. 3450function getUnlinkedPortTypeOptions ($port_iif_id) 3451{ 3452 static $cache; 3453 static $prefs; 3454 static $compat; 3455 if (! isset ($cache)) 3456 { 3457 $cache = array(); 3458 $prefs = getPortListPrefs(); 3459 $compat = getPortInterfaceCompat(); 3460 } 3461 3462 if (isset ($cache[$port_iif_id])) 3463 return $cache[$port_iif_id]; 3464 3465 $ret = array(); 3466 $seen_oifs = array(); 3467 $ambiguous_oifs = array(); 3468 foreach ($compat as $row) 3469 { 3470 if (isset ($seen_oifs[$row['oif_id']])) 3471 $ambiguous_oifs[$row['oif_id']] = 1; 3472 $seen_oifs[$row['oif_id']] = 1; 3473 } 3474 foreach ($compat as $row) 3475 { 3476 if ($row['iif_id'] == $prefs['iif_pick'] || $row['iif_id'] == $port_iif_id) 3477 $optgroup = $row['iif_name']; 3478 elseif (array_key_exists ($row['iif_id'], $prefs['oif_picks']) && $prefs['oif_picks'][$row['iif_id']] == $row['oif_id']) 3479 $optgroup = 'other'; 3480 else 3481 continue; 3482 if (!array_key_exists ($optgroup, $ret)) 3483 $ret[$optgroup] = array(); 3484 if (isset ($ambiguous_oifs[$row['oif_id']]) && $optgroup == 'other') 3485 $name = $row['iif_name'] . ' / ' . $row['oif_name']; 3486 else 3487 $name = $row['oif_name']; 3488 $ret[$optgroup][$row['iif_id'] . '-' . $row['oif_id']] = $name; 3489 } 3490 3491 $cache[$port_iif_id] = $ret; 3492 return $ret; 3493} 3494 3495// Return a serialized version of VLAN configuration for a port. 3496// If a native VLAN is defined, print it first. All other VLANs 3497// are tagged and are listed after a plus sign. When no configuration 3498// is set for a port, return "default" string. 3499function serializeVLANPack ($vlanport) 3500{ 3501 if (!array_key_exists ('mode', $vlanport)) 3502 return 'error'; 3503 switch ($vlanport['mode']) 3504 { 3505 case 'none': 3506 return 'none'; 3507 case 'access': 3508 $ret = 'A'; 3509 break; 3510 case 'trunk': 3511 $ret = 'T'; 3512 break; 3513 case 'uplink': 3514 $ret = 'U'; 3515 break; 3516 case 'downlink': 3517 $ret = 'D'; 3518 break; 3519 default: 3520 return 'error'; 3521 } 3522 if ($vlanport['native']) 3523 $ret .= $vlanport['native']; 3524 $tagged_bits = groupIntsToRanges ($vlanport['allowed'], $vlanport['native']); 3525 if (count ($tagged_bits)) 3526 $ret .= '+' . implode (', ', $tagged_bits); 3527 return $ret != '' ? $ret : 'default'; 3528} 3529 3530function groupIntsToRanges ($list, $exclude_value = NULL) 3531{ 3532 $result = array(); 3533 sort ($list); 3534 $id_from = $id_to = 0; 3535 $list[] = -1; 3536 foreach ($list as $next_id) 3537 if (! isset ($exclude_value) || $next_id != $exclude_value) 3538 if ($id_to && $next_id == $id_to + 1) 3539 $id_to = $next_id; // merge 3540 else 3541 { 3542 if ($id_to) 3543 $result[] = $id_from == $id_to ? $id_from : "${id_from}-${id_to}"; // flush 3544 $id_from = $id_to = $next_id; // start next pair 3545 } 3546 return $result; 3547} 3548 3549// Decode VLAN compound key (which is a string formatted DOMAINID-VLANID) and 3550// return the numbers as an array of two. 3551function decodeVLANCK ($string) 3552{ 3553 $matches = array(); 3554 if (1 != preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $string, $matches)) 3555 throw new InvalidArgException ('VLAN compound key', $string, 'format error'); 3556 if (! isNaturalNumber ($matches[1])) 3557 throw new InvalidArgException ('VLAN compound key', $string, 'domain ID cannot be 0'); 3558 if (! isValidVLANID ($matches[2])) 3559 throw new InvalidArgException ('VLAN compound key', $string, 'invalid VLAN ID'); 3560 return array ($matches[1], $matches[2]); 3561} 3562 3563// Return VLAN name formatted for HTML output (note that input 3564// argument comes from database unescaped). 3565function formatVLANAsOption ($vlaninfo) 3566{ 3567 $ret = $vlaninfo['vlan_id']; 3568 if ($vlaninfo['vlan_descr'] != '') 3569 $ret .= ' ' . $vlaninfo['vlan_descr']; 3570 return $ret; 3571} 3572 3573function formatVLANAsLabel ($vlaninfo) 3574{ 3575 $ret = $vlaninfo['vlan_id']; 3576 if ($vlaninfo['vlan_descr'] != '') 3577 $ret .= ' <i>(' . stringForLabel ($vlaninfo['vlan_descr']) . ')</i>'; 3578 return $ret; 3579} 3580 3581function formatVLANAsPlainText ($vlaninfo) 3582{ 3583 $ret = 'VLAN' . $vlaninfo['vlan_id']; 3584 if ($vlaninfo['vlan_descr'] != '') 3585 $ret .= ' (' . stringForLabel ($vlaninfo['vlan_descr'], 20) . ')'; 3586 return $ret; 3587} 3588 3589function formatVLANAsHyperlink ($vlaninfo) 3590{ 3591 return mkA (formatVLANAsRichText ($vlaninfo), 'vlan', $vlaninfo['domain_id'] . '-' . $vlaninfo['vlan_id']); 3592} 3593 3594function formatVLANAsShortLink ($vlaninfo) 3595{ 3596 $title = sprintf ('VLAN %d @ %s', $vlaninfo['vlan_id'], $vlaninfo['domain_descr']); 3597 if ($vlaninfo['vlan_descr'] != '') 3598 $title .= ' (' . $vlaninfo['vlan_descr'] . ')'; 3599 $attrs = array ('title' => $title); 3600 return mkA ($vlaninfo['vlan_id'], 'vlan', $vlaninfo['domain_id'] . '-' . $vlaninfo['vlan_id'], NULL, $attrs); 3601} 3602 3603function formatVLANAsRichText ($vlaninfo) 3604{ 3605 $ret = 'VLAN' . $vlaninfo['vlan_id']; 3606 $ret .= ' @' . stringForLabel ($vlaninfo['domain_descr']); 3607 if ($vlaninfo['vlan_descr'] != '') 3608 $ret .= ' <i>(' . stringForLabel ($vlaninfo['vlan_descr']) . ')</i>'; 3609 return $ret; 3610} 3611 3612# Produce a list of integers from a string in the following format: 3613# A,B,C-D,E-F,G,H,I-J,K ... 3614function iosParseVLANString ($string) 3615{ 3616 $ret = array(); 3617 foreach (explode (',', $string) as $item) 3618 { 3619 $matches = array(); 3620 $item = trim ($item, ' '); 3621 if (preg_match ('/^([[:digit:]]+)$/', $item, $matches)) 3622 $ret[] = intval ($matches[1]); 3623 elseif (preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $item, $matches)) 3624 $ret = array_merge ($ret, range ($matches[1], $matches[2])); 3625 else 3626 throw new InvalidArgException ('string', $string, 'format mismatch'); 3627 } 3628 return $ret; 3629} 3630 3631// Scan given array and return the key that addresses the first item 3632// with requested column set to given value (or NULL if there is none such). 3633// Note that 0 and NULL mean completely different things and thus 3634// require strict checking (=== and !===). 3635// Also note that this is not a 1:1 reinvention of PHP's array_search() as 3636// this function looks one level deeper (which could be done by means of 3637// array_column(), which appears only in PHP 5 >= 5.5.0). 3638function scanArrayForItem ($table, $scan_column, $scan_value) 3639{ 3640 foreach ($table as $key => $row) 3641 if ($row[$scan_column] == $scan_value) 3642 return $key; 3643 return NULL; 3644} 3645 3646// Return TRUE, if every value of A1 is present in A2 and vice versa, 3647// regardless of each array's sort order and indexing. 3648// Any duplicate values in either of the arguments would be treated same 3649// as a single occurrence, IOW, there is an implicit array_unique() here. 3650function array_values_same ($a1, $a2) 3651{ 3652 if (! is_array ($a1)) 3653 throw new InvalidArgException ('a1', $a1, 'is not an array'); 3654 if (! is_array ($a2)) 3655 throw new InvalidArgException ('a2', $a2, 'is not an array'); 3656 return ! count (array_diff ($a1, $a2)) && ! count (array_diff ($a2, $a1)); 3657} 3658 3659# Reindex provided array of arrays by a column value that is present in 3660# each sub-array and is assumed to be unique. Most often, make "id" column in 3661# a list of cells into the key space. 3662function reindexById ($input, $column_name = 'id', $ignore_dups = FALSE) 3663{ 3664 $ret = array(); 3665 if (! is_array ($input)) 3666 throw new InvalidArgException ('input', $input, 'must be an array'); 3667 foreach ($input as $item) 3668 { 3669 if (! isset ($item[$column_name])) 3670 throw new InvalidArgException ('input', '(array)', 'ID column missing'); 3671 if (isset ($ret[$item[$column_name]])) 3672 { 3673 if (!$ignore_dups) 3674 throw new InvalidArgException ('column_name', $column_name, 'duplicate ID value ' . $item[$column_name]); 3675 } 3676 else 3677 $ret[$item[$column_name]] = $item; 3678 } 3679 return $ret; 3680} 3681 3682# Given an array consisting of subarrays, each typically with the same set 3683# of keys, produce a result indexed the same way as the input array, but 3684# having each subarray replaced with one of the subarray values (using the 3685# provided subindex name, e.g.: 3686# array (10 => array ('a' => 'x1', 'b' => 'y1'), 20 => array ('a' => 'x2', 'b' => 'y2')) 3687# would map to (using subindex 'b'): array (10 => 'y1', 20 => 'y2') 3688# 3689# A similar array_column() function is available in PHP 5 >= 5.5.0. 3690function reduceSubarraysToColumn ($input, $column) 3691{ 3692 $ret = array(); 3693 if (! is_array ($input)) 3694 throw new InvalidArgException ('input', $input, 'must be an array'); 3695 foreach ($input as $key => $item) 3696 if (array_key_exists ($column, $item)) 3697 $ret[$key] = $item[$column]; 3698 else 3699 throw new InvalidArgException ('input', '(array)', "column '${column}' is not set for subarray at index '${key}'"); 3700 return $ret; 3701} 3702 3703// Use the VLAN switch template to set VST role for each port of 3704// the provided list. Return resulting list. 3705function apply8021QOrder ($vswitch, $portlist) 3706{ 3707 $hook_result = callHook ('apply8021Qrder_hook', $vswitch, $portlist); 3708 if (isset ($hook_result)) 3709 return $hook_result; 3710 3711 $vst_id = $vswitch['template_id']; 3712 $vst = spotEntity ('vst', $vswitch['template_id']); 3713 amplifyCell ($vst); 3714 3715 // warm the vlan_filter cache for every rule 3716 foreach ($vst['rules'] as $i_rule => $rule) 3717 $vst['rules'][$i_rule]['vlan_filter'] = buildVLANFilter ($rule['port_role'], $rule['wrt_vlans']); 3718 3719 foreach (array_keys ($portlist) as $port_name) 3720 { 3721 foreach ($vst['rules'] as $rule) 3722 if (preg_match ($rule['port_pcre'], $port_name)) 3723 { 3724 $portlist[$port_name]['vst_role'] = $rule['port_role']; 3725 $portlist[$port_name]['wrt_vlans'] = $rule['vlan_filter']; 3726 continue 2; 3727 } 3728 $portlist[$port_name]['vst_role'] = 'none'; 3729 } 3730 return $portlist; 3731} 3732 3733// Return a sequence of integer ranges for given port role and VLAN filter string. 3734// FIXME: for an "anymode" port the same filter currently applies to the "trunk" 3735// and "access" configurations of the port, i.e. such a port cannot be set to "A1" 3736// by means of the user interface though as far as the data model goes "A1" is a 3737// valid configuration for an "anymode" port. 3738function buildVLANFilter ($role, $string) 3739{ 3740 // set base 3741 switch ($role) 3742 { 3743 case 'access': // 1-4094 3744 case 'anymode': 3745 $min = VLAN_MIN_ID; 3746 $max = VLAN_MAX_ID; 3747 break; 3748 case 'trunk': // 2-4094 3749 case 'uplink': 3750 case 'downlink': 3751 $min = VLAN_MIN_ID + 1; 3752 $max = VLAN_MAX_ID; 3753 break; 3754 default: // none 3755 return array(); 3756 } 3757 if ($string == '') // fast track 3758 return array (array ('from' => $min, 'to' => $max)); 3759 // transform 3760 $vlanidlist = array(); 3761 foreach (iosParseVLANString ($string) as $vlan_id) 3762 if ($min <= $vlan_id && $vlan_id <= $max) 3763 $vlanidlist[] = $vlan_id; 3764 return listToRanges ($vlanidlist); 3765} 3766 3767// pack set of integers into list of integer ranges 3768// e.g. (1, 2, 3, 5, 6, 7, 9, 11) => ((1, 3), (5, 7), (9, 9), (11, 11)) 3769// The second argument, when it is different from 0, limits amount of 3770// items in each generated range. 3771function listToRanges ($vlanidlist, $limit = 0) 3772{ 3773 sort ($vlanidlist); 3774 $ret = array(); 3775 $from = $to = NULL; 3776 foreach ($vlanidlist as $vlan_id) 3777 if ($from == NULL) 3778 { 3779 if ($limit == 1) 3780 $ret[] = array ('from' => $vlan_id, 'to' => $vlan_id); 3781 else 3782 $from = $to = $vlan_id; 3783 } 3784 elseif ($to + 1 == $vlan_id) 3785 { 3786 $to = $vlan_id; 3787 if ($to - $from + 1 == $limit) 3788 { 3789 // cut accumulated range and start over 3790 $ret[] = array ('from' => $from, 'to' => $to); 3791 $from = $to = NULL; 3792 } 3793 } 3794 else 3795 { 3796 $ret[] = array ('from' => $from, 'to' => $to); 3797 $from = $to = $vlan_id; 3798 } 3799 if ($from != NULL) 3800 $ret[] = array ('from' => $from, 'to' => $to); 3801 return $ret; 3802} 3803 3804// return TRUE, if given VLAN ID belongs to one of filter's ranges 3805function matchVLANFilter ($vlan_id, $vfilter) 3806{ 3807 foreach ($vfilter as $range) 3808 if ($range['from'] <= $vlan_id && $vlan_id <= $range['to']) 3809 return TRUE; 3810 return FALSE; 3811} 3812 3813function filterVLANList ($vlan_list, $vfilter) 3814{ 3815 $ret = array(); 3816 foreach ($vlan_list as $vid) 3817 if (matchVLANFilter ($vid, $vfilter)) 3818 $ret[] = $vid; 3819 return $ret; 3820} 3821 3822function generate8021QDeployOps ($vswitch, $device_vlanlist, $before, $changes) 3823{ 3824 $domain_vlanlist = getDomainVLANList ($vswitch['domain_id']); 3825 $employed_vlans = getEmployedVlans ($vswitch['object_id'], $domain_vlanlist); 3826 3827 // only ignore VLANs that exist and are explicitly shown as "alien" 3828 $old_managed_vlans = array(); 3829 foreach ($device_vlanlist as $vlan_id) 3830 if 3831 ( 3832 ! array_key_exists ($vlan_id, $domain_vlanlist) || 3833 $domain_vlanlist[$vlan_id]['vlan_type'] != 'alien' 3834 ) 3835 $old_managed_vlans[] = $vlan_id; 3836 $old_managed_vlans = array_unique ($old_managed_vlans); 3837 3838 $ports_to_do = array(); 3839 $ports_to_do_queue1 = array(); 3840 $ports_to_do_queue2 = array(); 3841 $after = $before; 3842 foreach ($changes as $port_name => $port) 3843 { 3844 $changeset = array 3845 ( 3846 'old_mode' => $before[$port_name]['mode'], 3847 'old_allowed' => $before[$port_name]['allowed'], 3848 'old_native' => $before[$port_name]['native'], 3849 'new_mode' => $port['mode'], 3850 'new_allowed' => $port['allowed'], 3851 'new_native' => $port['native'], 3852 ); 3853 // put the ports with employed vlans first, the others - below them 3854 if (! count (array_intersect ($changeset['old_allowed'], $employed_vlans))) 3855 $ports_to_do_queue2[$port_name] = $changeset; 3856 else 3857 $ports_to_do_queue1[$port_name] = $changeset; 3858 $after[$port_name] = $port; 3859 } 3860 # Two arrays without common keys get merged with "+" operator just fine, 3861 # with an important difference from array_merge() in that the latter 3862 # renumbers numeric keys, and "+" does not. This matters, when port name 3863 # is a number (like in XOS12 system). 3864 $ports_to_do = $ports_to_do_queue1 + $ports_to_do_queue2; 3865 // New VLAN table is a union of: 3866 // 1. all compulsory VLANs 3867 // 2. all "current" non-alien allowed VLANs of those ports that are left 3868 // intact (regardless if a VLAN exists in VLAN domain, but looking, 3869 // if it is present in device's own VLAN table) 3870 // 3. all "new" allowed VLANs of the ports to be "pushed" now 3871 // Like for old_managed_vlans, a VLANs is never listed, only if it 3872 // exists and belongs to "alien" type. 3873 $new_managed_vlans = array(); 3874 // It is necessary to count down the number of ports still using a specific VLAN 3875 // in order to unconfigure the VLAN on the switch as soon as the VLAN membership 3876 // is removed from its last port. 3877 // This array tracks port count: 3878 // * keys are vlan_id's; 3879 // * values are the number of changed ports that were using this vlan in old configuration 3880 $used_vlans = array(); 3881 foreach ($employed_vlans as $vlan_id) 3882 $used_vlans[$vlan_id] = 1; // prevent deletion of an employed vlan 3883 3884 // 1 3885 foreach ($domain_vlanlist as $vlan_id => $vlan) 3886 if ($vlan['vlan_type'] == 'compulsory') 3887 $new_managed_vlans[] = $vlan_id; 3888 // 2 3889 foreach ($before as $port_name => $port) 3890 if (!array_key_exists ($port_name, $changes)) 3891 foreach ($port['allowed'] as $vlan_id) 3892 { 3893 if (in_array ($vlan_id, $new_managed_vlans)) 3894 continue; 3895 if 3896 ( 3897 array_key_exists ($vlan_id, $domain_vlanlist) && 3898 $domain_vlanlist[$vlan_id]['vlan_type'] == 'alien' 3899 ) 3900 continue; 3901 if (in_array ($vlan_id, $device_vlanlist)) 3902 $new_managed_vlans[] = $vlan_id; 3903 } 3904 else 3905 foreach ($port['allowed'] as $vlan_id) 3906 { 3907 if (!array_key_exists ($vlan_id, $used_vlans)) 3908 $used_vlans[$vlan_id] = 0; 3909 $used_vlans[$vlan_id]++; 3910 } 3911 // 3 3912 foreach ($changes as $port) 3913 foreach ($port['allowed'] as $vlan_id) 3914 if 3915 ( 3916 isset ($domain_vlanlist[$vlan_id]) && 3917 $domain_vlanlist[$vlan_id]['vlan_type'] == 'ondemand' && 3918 !in_array ($vlan_id, $new_managed_vlans) 3919 ) 3920 $new_managed_vlans[] = $vlan_id; 3921 $new_managed_vlans = array_unique ($new_managed_vlans); 3922 3923 $vlans_to_add = array_diff ($new_managed_vlans, $old_managed_vlans); 3924 $vlans_to_del = array_diff ($old_managed_vlans, $new_managed_vlans); 3925 $crq = array(); 3926 3927 // destroy unused VLANs on device 3928 $deleted_vlans = array_diff ($vlans_to_del, array_keys ($used_vlans)); 3929 foreach ($deleted_vlans as $vlan_id) 3930 $crq[] = array 3931 ( 3932 'opcode' => 'destroy VLAN', 3933 'arg1' => $vlan_id, 3934 ); 3935 $vlans_to_del = array_diff ($vlans_to_del, $deleted_vlans); 3936 3937 foreach (sortPortList ($ports_to_do) as $port_name => $port) 3938 { 3939 // Before removing each old VLAN as such it is necessary to unassign 3940 // ports from it (to remove VLAN from each ports' list of "allowed" 3941 // VLANs). This change in turn requires that a port's "native" 3942 // VLAN isn't set to the one being removed from its "allowed" list. 3943 switch ($port['old_mode'] . '->' . $port['new_mode']) 3944 { 3945 case 'trunk->trunk': 3946 // "old" native is set and differs from the "new" native 3947 if ($port['old_native'] && $port['old_native'] != $port['new_native']) 3948 $crq[] = array 3949 ( 3950 'opcode' => 'unset native', 3951 'arg1' => $port_name, 3952 'arg2' => $port['old_native'], 3953 ); 3954 $vlans_to_remove = array_diff ($port['old_allowed'], $port['new_allowed']); 3955 $queues = array(); 3956 $queues[] = array_intersect ($employed_vlans, $vlans_to_remove); // remove employed vlans first 3957 $queues[] = array_diff ($vlans_to_remove, $employed_vlans);// remove other vlans afterwards 3958 foreach ($queues as $queue) 3959 if (count ($queue)) 3960 { 3961 $crq[] = array 3962 ( 3963 'opcode' => 'rem allowed', 3964 'port' => $port_name, 3965 'vlans' => $queue, 3966 ); 3967 foreach ($queue as $vlan_id) 3968 $used_vlans[$vlan_id]--; 3969 } 3970 break; 3971 case 'access->access': 3972 if ($port['old_native'] && $port['old_native'] != $port['new_native']) 3973 { 3974 $crq[] = array 3975 ( 3976 'opcode' => 'unset access', 3977 'arg1' => $port_name, 3978 'arg2' => $port['old_native'], 3979 ); 3980 $used_vlans[$port['old_native']]--; 3981 } 3982 break; 3983 case 'access->trunk': 3984 $crq[] = array 3985 ( 3986 'opcode' => 'unset access', 3987 'arg1' => $port_name, 3988 'arg2' => $port['old_native'], 3989 ); 3990 $used_vlans[$port['old_native']]--; 3991 break; 3992 case 'trunk->access': 3993 if ($port['old_native']) 3994 $crq[] = array 3995 ( 3996 'opcode' => 'unset native', 3997 'arg1' => $port_name, 3998 'arg2' => $port['old_native'], 3999 ); 4000 $vlans_to_remove = $port['old_allowed']; 4001 $queues = array(); 4002 $queues[] = array_intersect ($employed_vlans, $vlans_to_remove); // remove employed vlans first 4003 $queues[] = array_diff ($vlans_to_remove, $employed_vlans);// remove other vlans afterwards 4004 foreach ($queues as $queue) 4005 if (count ($queue)) 4006 { 4007 $crq[] = array 4008 ( 4009 'opcode' => 'rem allowed', 4010 'port' => $port_name, 4011 'vlans' => $queue, 4012 ); 4013 foreach ($queue as $vlan_id) 4014 $used_vlans[$vlan_id]--; 4015 } 4016 break; 4017 default: 4018 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure'); 4019 } 4020 4021 // destroy unneeded VLANs on device 4022 $deleted_vlans = array(); 4023 foreach ($vlans_to_del as $vlan_id) 4024 if ($used_vlans[$vlan_id] == 0) 4025 { 4026 $crq[] = array 4027 ( 4028 'opcode' => 'destroy VLAN', 4029 'arg1' => $vlan_id, 4030 ); 4031 $deleted_vlans[] = $vlan_id; 4032 } 4033 $vlans_to_del = array_diff ($vlans_to_del, $deleted_vlans); 4034 4035 // create new VLANs on device 4036 $added_vlans = array_intersect ($vlans_to_add, $port['new_allowed']); 4037 foreach ($added_vlans as $vlan_id) 4038 $crq[] = array 4039 ( 4040 'opcode' => 'create VLAN', 4041 'arg1' => $vlan_id, 4042 ); 4043 $vlans_to_add = array_diff ($vlans_to_add, $added_vlans); 4044 4045 // change port mode if needed 4046 if ($port['old_mode'] != $port['new_mode']) 4047 $crq[] = array 4048 ( 4049 'opcode' => 'set mode', 4050 'arg1' => $port_name, 4051 'arg2' => $port['new_mode'], 4052 ); 4053 4054 // Now, when all new VLANs are created (queued), it is safe to assign (queue) 4055 // ports to the new VLANs. 4056 switch ($port['old_mode'] . '->' . $port['new_mode']) 4057 { 4058 case 'trunk->trunk': 4059 // For each allowed VLAN that is present on the "new" list and missing from 4060 // the "old" one, queue a command to assign current port to that VLAN. 4061 if (count ($tmp = array_diff ($port['new_allowed'], $port['old_allowed']))) 4062 $crq[] = array 4063 ( 4064 'opcode' => 'add allowed', 4065 'port' => $port_name, 4066 'vlans' => $tmp, 4067 ); 4068 // One of the "allowed" VLANs for this port may probably be "native". 4069 // "new native" is set and differs from "old native" 4070 if ($port['new_native'] && $port['new_native'] != $port['old_native']) 4071 $crq[] = array 4072 ( 4073 'opcode' => 'set native', 4074 'arg1' => $port_name, 4075 'arg2' => $port['new_native'], 4076 ); 4077 break; 4078 case 'access->access': 4079 if ($port['new_native'] && $port['new_native'] != $port['old_native']) 4080 $crq[] = array 4081 ( 4082 'opcode' => 'set access', 4083 'arg1' => $port_name, 4084 'arg2' => $port['new_native'], 4085 ); 4086 break; 4087 case 'access->trunk': 4088 if (count ($port['new_allowed'])) 4089 $crq[] = array 4090 ( 4091 'opcode' => 'add allowed', 4092 'port' => $port_name, 4093 'vlans' => $port['new_allowed'], 4094 ); 4095 if ($port['new_native']) 4096 $crq[] = array 4097 ( 4098 'opcode' => 'set native', 4099 'arg1' => $port_name, 4100 'arg2' => $port['new_native'], 4101 ); 4102 break; 4103 case 'trunk->access': 4104 $crq[] = array 4105 ( 4106 'opcode' => 'set access', 4107 'arg1' => $port_name, 4108 'arg2' => $port['new_native'], 4109 ); 4110 break; 4111 default: 4112 throw new InvalidArgException ('ports_to_do', '(hidden)', 'error in structure'); 4113 } 4114 } 4115 4116 // add the rest of VLANs to device (compulsory VLANs) 4117 foreach ($vlans_to_add as $vlan_id) 4118 $crq[] = array 4119 ( 4120 'opcode' => 'create VLAN', 4121 'arg1' => $vlan_id, 4122 ); 4123 4124 return $crq; 4125} 4126 4127function exportSwitch8021QConfig 4128( 4129 $vswitch, 4130 $running_config, 4131 $changes 4132) 4133{ 4134 $crq = generate8021QDeployOps ($vswitch, $running_config['vlanlist'], $running_config['portdata'], $changes); 4135 if (count ($crq)) 4136 { 4137 array_unshift ($crq, array ('opcode' => 'begin configuration')); 4138 $crq[] = array ('opcode' => 'end configuration'); 4139 if (considerConfiguredConstraint (spotEntity ('object', $vswitch['object_id']), '8021Q_WRI_AFTER_CONFT_LISTSRC')) 4140 $crq[] = array ('opcode' => 'save configuration'); 4141 setVLANSwitchTimestamp ($vswitch['object_id'], 'last_push_started'); 4142 setDevice8021QConfig ($vswitch['object_id'], $crq, $running_config['vlannames']); 4143 setVLANSwitchTimestamp ($vswitch['object_id'], 'last_push_finished'); 4144 } 4145 return count ($crq); 4146} 4147 4148// filter list of changed ports to cancel changes forbidden by VST and domain 4149function filter8021QChangeRequests 4150( 4151 $domain_vlanlist, 4152 $before, // current saved configuration of all ports 4153 $changes // changed ports with VST markup 4154) 4155{ 4156 $domain_immune_vlans = array(); 4157 foreach ($domain_vlanlist as $vlan_id => $vlan) 4158 if ($vlan['vlan_type'] == 'alien') 4159 $domain_immune_vlans[] = $vlan_id; 4160 $ret = array(); 4161 foreach ($changes as $port_name => $port) 4162 { 4163 // VST violation ? 4164 if (!goodModeForVSTRole ($port['mode'], $port['vst_role'])) 4165 continue; // ignore change request 4166 // find and cancel any changes regarding immune VLANs 4167 switch ($port['mode']) 4168 { 4169 case 'access': 4170 foreach ($domain_immune_vlans as $immune) 4171 // Reverting an attempt to set an access port from 4172 // "normal" VLAN to immune one (or vice versa) requires 4173 // special handling, becase the calling function has 4174 // discarded the old contents of 'allowed' for current port. 4175 if 4176 ( 4177 $before[$port_name]['native'] == $immune || 4178 $port['native'] == $immune 4179 ) 4180 { 4181 $port['native'] = $before[$port_name]['native']; 4182 $port['allowed'] = array ($port['native']); 4183 // Such reversal happens either once or never for an 4184 // access port. 4185 break; 4186 } 4187 break; 4188 case 'trunk': 4189 // restrict adding alien vlans into trunk 4190 $before_vlans = isset ($before[$port_name]) ? $before[$port_name]['allowed'] : array(); 4191 $added = array_diff ($port['allowed'], $before_vlans); 4192 $restricted = array_diff ($added, array_keys ($domain_vlanlist)); 4193 $port['allowed'] = array_diff ($port['allowed'], $restricted); 4194 if (in_array ($port['native'], $restricted)) 4195 $port['native'] = 0; 4196 4197 foreach ($domain_immune_vlans as $immune) 4198 if (in_array ($immune, $before[$port_name]['allowed'])) // was allowed before 4199 { 4200 if (!in_array ($immune, $port['allowed'])) 4201 $port['allowed'][] = $immune; // restore 4202 if ($before[$port_name]['native'] == $immune) // and was native 4203 $port['native'] = $immune; // also restore 4204 } 4205 else // wasn't 4206 { 4207 if (in_array ($immune, $port['allowed'])) 4208 unset ($port['allowed'][array_search ($immune, $port['allowed'])]); // cancel 4209 if ($port['native'] == $immune) 4210 $port['native'] = $before[$port_name]['native']; 4211 } 4212 break; 4213 default: 4214 throw new InvalidArgException ('mode', $port['mode']); 4215 } 4216 // save work 4217 $ret[$port_name] = $port; 4218 } 4219 return $ret; 4220} 4221 4222function getEmployedVlans ($object_id, $domain_vlanlist) 4223{ 4224 $employed = array(); // keyed by vlan_id. Value is dummy int 4225 // find persistent VLANs in domain 4226 foreach ($domain_vlanlist as $vlan_id => $vlan) 4227 if ($vlan['vlan_type'] == 'compulsory') 4228 $employed[$vlan_id] = 1; 4229 4230 // find VLANs for object's L3 allocations 4231 foreach (getObjectIPAllocationList ($object_id) as $ip_bin => $alloc) 4232 if ($net = spotNetworkByIP ($ip_bin)) 4233 foreach ($net['8021q'] as $vlan) 4234 if (isset ($domain_vlanlist[$vlan['vlan_id']]) && ! isset ($employed[$vlan['vlan_id']])) 4235 $employed[$vlan['vlan_id']] = 1; 4236 $ret = array_keys ($employed); 4237 $override = callHook ('getEmployedVlans_hook', $ret, $object_id, $domain_vlanlist); 4238 if (isset ($override)) 4239 $ret = $override; 4240 return $ret; 4241} 4242 4243// take port list with order applied and return uplink ports in the same format 4244function produceUplinkPorts ($domain_vlanlist, $portlist, $object_id) 4245{ 4246 $ret = array(); 4247 4248 $employed = array(); 4249 foreach (getEmployedVlans ($object_id, $domain_vlanlist) as $vlan_id) 4250 $employed[$vlan_id] = $vlan_id; 4251 4252 foreach ($portlist as $port_name => $port) 4253 if ($port['vst_role'] != 'uplink') 4254 foreach ($port['allowed'] as $vlan_id) 4255 if (! isset ($employed[$vlan_id]) && isset ($domain_vlanlist[$vlan_id])) 4256 $employed[$vlan_id] = $vlan_id; 4257 4258 foreach ($portlist as $port_name => $port) 4259 if ($port['vst_role'] == 'uplink') 4260 $ret[$port_name] = array 4261 ( 4262 'vst_role' => 'uplink', 4263 'mode' => 'trunk', 4264 'allowed' => filterVLANList ($employed, $port['wrt_vlans']), 4265 'native' => 0, 4266 ); 4267 return $ret; 4268} 4269 4270function same8021QConfigs ($a, $b) 4271{ 4272 return $a['mode'] == $b['mode'] && 4273 array_values_same ($a['allowed'], $b['allowed']) && 4274 $a['native'] == $b['native']; 4275} 4276 4277// Return TRUE, if the port can be edited by the user. 4278function editable8021QPort ($port) 4279{ 4280 return in_array ($port['vst_role'], array ('trunk', 'access', 'anymode')); 4281} 4282 4283// Decide, whether the given 802.1Q port mode is permitted by 4284// VST port role. 4285function goodModeForVSTRole ($mode, $role) 4286{ 4287 switch ($mode) 4288 { 4289 case 'access': 4290 return in_array ($role, array ('access', 'anymode')); 4291 case 'trunk': 4292 return in_array ($role, array ('trunk', 'uplink', 'downlink', 'anymode')); 4293 default: 4294 throw new InvalidArgException ('mode', $mode); 4295 } 4296} 4297 4298/* 4299 4300Relation between desired (D), cached (C) and running (R) 4301copies of switch ports (P) list. 4302 4303 D C R 4304+---+ +---+ +---+ 4305| P |-----| P |-? +--| P | 4306+---+ +---+ / +---+ 4307| P |-----| P |--+ ?-| P | 4308+---+ +---+ +---+ 4309| P |-----| P |-------| P | 4310+---+ +---+ +---+ 4311| P |-----| P |--+ ?-| P | 4312+---+ +---+ \ +---+ 4313| P |-----| P |--+ +--| P | 4314+---+ +---+ \ +---+ 4315 +--| P | 4316 +---+ 4317 ?-| P | 4318 +---+ 4319 4320A modified local version of a port in "conflict" state ignores remote 4321changes until remote change maintains its difference. Once both edits 4322match, the local copy "locks" on the remote and starts tracking it. 4323 4324v 4325a "o" -- remOte version 4326l "l" -- Local version 4327u "b" -- Both versions 4328e 4329 4330^ 4331| o b 4332| o 4333| l l l l l l b b 4334| o o b 4335| o b 4336| 4337| o 4338| 4339| 43400----------------------------------------------> time 4341 4342*/ 4343function get8021QSyncOptions 4344( 4345 $vswitch, 4346 $D, // desired config 4347 $C, // cached config 4348 $R // running-config 4349) 4350{ 4351 $default_port = array 4352 ( 4353 'mode' => 'access', 4354 'allowed' => array (VLAN_DFL_ID), 4355 'native' => VLAN_DFL_ID, 4356 ); 4357 $ret = array(); 4358 $allports = array(); 4359 foreach (array_unique (array_merge (array_keys ($C), array_keys ($R))) as $pn) 4360 $allports[$pn] = array(); 4361 foreach (apply8021QOrder ($vswitch, $allports) as $pn => $port) 4362 { 4363 // catch anomalies early 4364 if ($port['vst_role'] == 'none') 4365 { 4366 if ((! array_key_exists ($pn, $R) || $R[$pn]['mode'] == 'none') && ! array_key_exists ($pn, $C)) 4367 $ret[$pn] = array ('status' => 'none'); 4368 else 4369 $ret[$pn] = array 4370 ( 4371 'status' => 'martian_conflict', 4372 'left' => array_fetch ($C, $pn, array ('mode' => 'none')), 4373 'right' => array_fetch ($R, $pn, array ('mode' => 'none')), 4374 ); 4375 continue; 4376 } 4377 elseif ((! array_key_exists ($pn, $R) || $R[$pn]['mode'] == 'none') && array_key_exists ($pn, $C)) 4378 { 4379 $ret[$pn] = array 4380 ( 4381 'status' => 'martian_conflict', 4382 'left' => array_fetch ($C, $pn, array ('mode' => 'none')), 4383 'right' => array_fetch ($R, $pn, array ('mode' => 'none')), 4384 ); 4385 continue; 4386 } 4387 // (DC_): port missing from device 4388 if (!array_key_exists ($pn, $R)) 4389 { 4390 $ret[$pn] = array ('left' => $D[$pn]); 4391 if (same8021QConfigs ($D[$pn], $default_port)) 4392 $ret[$pn]['status'] = 'ok_to_delete'; 4393 else 4394 { 4395 $ret[$pn]['status'] = 'delete_conflict'; 4396 $ret[$pn]['lastseen'] = $C[$pn]; 4397 } 4398 continue; 4399 } 4400 // (__R): port missing from DB 4401 if (!array_key_exists ($pn, $C)) 4402 { 4403 // Allow importing any configuration that passes basic 4404 // validation. If port mode doesn't match its VST role, 4405 // this will be handled later WRT each port. 4406 $ret[$pn] = array 4407 ( 4408 'status' => acceptable8021QConfig ($R[$pn]) ? 'ok_to_add' : 'add_conflict', 4409 'right' => $R[$pn], 4410 ); 4411 continue; 4412 } 4413 $D_eq_C = same8021QConfigs ($D[$pn], $C[$pn]); 4414 $C_eq_R = same8021QConfigs ($C[$pn], $R[$pn]); 4415 // (DCR), D = C = R: data in sync 4416 if ($D_eq_C && $C_eq_R) // implies D == R 4417 { 4418 $ret[$pn] = array 4419 ( 4420 'status' => 'in_sync', 4421 'both' => $R[$pn], 4422 ); 4423 continue; 4424 } 4425 // (DCR), D = C: no local edit in the way 4426 if ($D_eq_C) 4427 $ret[$pn] = array 4428 ( 4429 'status' => 'ok_to_pull', 4430 'left' => $D[$pn], 4431 'right' => $R[$pn], 4432 ); 4433 // (DCR), C = R: no remote edit in the way 4434 elseif ($C_eq_R) 4435 $ret[$pn] = array 4436 ( 4437 'status' => 'ok_to_push', 4438 'left' => $D[$pn], 4439 'right' => $R[$pn], 4440 ); 4441 // (DCR), D = R: end of version conflict, restore tracking 4442 elseif (same8021QConfigs ($D[$pn], $R[$pn])) 4443 $ret[$pn] = array 4444 ( 4445 'status' => 'ok_to_merge', 4446 'both' => $R[$pn], 4447 ); 4448 else // D != C, C != R, D != R: version conflict 4449 $ret[$pn] = array 4450 ( 4451 'status' => editable8021QPort ($port) ? 4452 // In case the port is normally updated by user, let him 4453 // resolve the conflict. If the system manages this port, 4454 // arrange the data to let remote version go down. 4455 'merge_conflict' : 'ok_to_push_with_merge', 4456 'left' => $D[$pn], 4457 'right' => $R[$pn], 4458 ); 4459 } 4460 return $ret; 4461} 4462 4463// return number of records updated successfully of FALSE, if a conflict was in the way 4464function exec8021QDeploy ($object_id, $do_push) 4465{ 4466 $nsaved = $npushed = $nsaved_uplinks = 0; 4467 if (NULL === $vswitch = getVLANSwitchInfo ($object_id)) 4468 throw new InvalidArgException ('object_id', $object_id, 'VLAN domain is not set for this object'); 4469 if (! tryDBMutex (__FUNCTION__ . "-$object_id", 10)) 4470 throw new RTGatewayError ("802.1Q sync is already active for object #$object_id"); 4471 4472 try 4473 { 4474 $R = getRunning8021QConfig ($vswitch['object_id']); 4475 } 4476 catch (RTGatewayError $e) 4477 { 4478 setVLANSwitchError ($object_id, E_8021Q_PULL_REMOTE_ERROR); 4479 throw $e; 4480 } 4481 4482 global $dbxlink; 4483 $dbxlink->beginTransaction(); 4484 $vswitch = getVLANSwitchInfo ($object_id, 'FOR UPDATE'); 4485 $D = getStored8021QConfig ($vswitch['object_id'], 'desired'); 4486 $Dnew = $D; 4487 $C = getStored8021QConfig ($vswitch['object_id'], 'cached'); 4488 $conflict = FALSE; 4489 $ok_to_push = array(); 4490 foreach (get8021QSyncOptions ($vswitch, $D, $C, $R['portdata']) as $pn => $port) 4491 { 4492 // always update cache with new data from switch 4493 switch ($port['status']) 4494 { 4495 case 'ok_to_merge': 4496 // FIXME: this can be logged 4497 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['both'], $C[$pn]); 4498 break; 4499 case 'ok_to_delete': 4500 $nsaved += del8021QPort ($vswitch['object_id'], $pn); 4501 unset ($Dnew[$pn]); 4502 break; 4503 case 'ok_to_add': 4504 $nsaved += add8021QPort ($vswitch['object_id'], $pn, $port['right']); 4505 $Dnew[$pn] = $port['right']; 4506 break; 4507 case 'delete_conflict': 4508 case 'merge_conflict': 4509 case 'add_conflict': 4510 case 'martian_conflict': 4511 $conflict = TRUE; 4512 break; 4513 case 'ok_to_pull': 4514 // FIXME: this can be logged 4515 $nsaved += upd8021QPort ('desired', $vswitch['object_id'], $pn, $port['right'], $D[$pn]); 4516 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right'], $C[$pn]); 4517 $Dnew[$pn] = $port['right']; 4518 break; 4519 case 'ok_to_push_with_merge': 4520 upd8021QPort ('cached', $vswitch['object_id'], $pn, $port['right'], $C[$pn]); 4521 // fall through 4522 case 'ok_to_push': 4523 $ok_to_push[$pn] = $port['left']; 4524 break; 4525 } 4526 } 4527 // redo uplinks if some changes were pulled 4528 if ($nsaved) 4529 { 4530 $domain_vlanlist = getDomainVLANList ($vswitch['domain_id']); 4531 $Dnew = apply8021QOrder ($vswitch, $Dnew); 4532 // Take new "desired" configuration and derive uplink port configuration 4533 // from it. Then cancel changes to immune VLANs and save resulting 4534 // changes (if any left). 4535 $new_uplinks = filter8021QChangeRequests ($domain_vlanlist, $Dnew, produceUplinkPorts ($domain_vlanlist, $Dnew, $vswitch['object_id'])); 4536 $nsaved_uplinks += replace8021QPorts ('desired', $vswitch['object_id'], $Dnew, $new_uplinks); 4537 } 4538 4539 $out_of_sync = FALSE; 4540 $errno = E_8021Q_NOERROR; 4541 if ($nsaved + $nsaved_uplinks || count ($ok_to_push)) 4542 $out_of_sync = TRUE; 4543 if ($conflict) 4544 { 4545 $errno = E_8021Q_VERSION_CONFLICT; 4546 $out_of_sync = TRUE; 4547 } 4548 setVLANSwitchError ($object_id, $errno); 4549 4550 $mutex_rev = $vswitch['mutex_rev']; 4551 if ($vswitch['out_of_sync'] == "yes" && ! $out_of_sync) 4552 detouchVLANSwitch ($object_id, $mutex_rev); 4553 elseif ($vswitch['out_of_sync'] == "no" && $out_of_sync) 4554 { 4555 touchVLANSwitch ($object_id); 4556 ++$mutex_rev; 4557 } 4558 $dbxlink->commit(); 4559 4560 if ($out_of_sync && $do_push) 4561 { 4562 try 4563 { 4564 $npushed += exportSwitch8021QConfig ($vswitch, $R, $ok_to_push); 4565 } 4566 catch (RTGatewayError $r) 4567 { 4568 setVLANSwitchError ($object_id, E_8021Q_PUSH_REMOTE_ERROR); 4569 callHook ('pushErrorHandler', $object_id, $r); 4570 throw $r; 4571 } 4572 4573 // update cache for ports deployed 4574 $dbxlink->beginTransaction(); 4575 replace8021QPorts ('cached', $vswitch['object_id'], $R['portdata'], $ok_to_push); 4576 setVLANSwitchError ($object_id, E_8021Q_NOERROR); 4577 detouchVLANSwitch ($object_id, $mutex_rev); 4578 $dbxlink->commit(); 4579 } 4580 // start downlink work only after unlocking current object to make deadlocks less likely to happen 4581 // TODO: only process changed uplink ports 4582 if ($nsaved_uplinks) 4583 initiateUplinksReverb ($vswitch['object_id'], $new_uplinks); 4584 return $conflict ? FALSE : $nsaved + $npushed + $nsaved_uplinks; 4585} 4586 4587function strerror8021Q ($errno) 4588{ 4589 $errstr = array 4590 ( 4591 E_8021Q_VERSION_CONFLICT => 'pull failed due to version conflict', 4592 E_8021Q_PULL_REMOTE_ERROR => 'pull failed due to remote error', 4593 E_8021Q_PUSH_REMOTE_ERROR => 'push failed due to remote error', 4594 E_8021Q_SYNC_DISABLED => 'sync disabled by operator', 4595 ); 4596 return array_fetch ($errstr, $errno, "unknown error code ${errno}"); 4597} 4598 4599function saveDownlinksReverb ($object_id, $requested_changes) 4600{ 4601 $nsaved = 0; 4602 global $dbxlink; 4603 $dbxlink->beginTransaction(); 4604 if (NULL === $vswitch = getVLANSwitchInfo ($object_id, 'FOR UPDATE')) // not configured, bail out 4605 { 4606 $dbxlink->rollBack(); 4607 return; 4608 } 4609 $domain_vlanlist = getDomainVLANList ($vswitch['domain_id']); 4610 // aplly VST to the smallest set necessary 4611 $requested_changes = apply8021QOrder ($vswitch, $requested_changes); 4612 $before = getStored8021QConfig ($object_id, 'desired', array_keys ($requested_changes)); 4613 $changes_to_save = array(); 4614 // first filter by wrt_vlans constraint 4615 foreach ($requested_changes as $pn => $requested) 4616 if (array_key_exists ($pn, $before) && $requested['vst_role'] == 'downlink') 4617 $changes_to_save[$pn] = array 4618 ( 4619 'vst_role' => 'downlink', 4620 'mode' => 'trunk', 4621 'allowed' => filterVLANList ($requested['allowed'], $requested['wrt_vlans']), 4622 'native' => 0, 4623 ); 4624 // immune VLANs filter 4625 foreach (filter8021QChangeRequests ($domain_vlanlist, $before, $changes_to_save) as $pn => $finalconfig) 4626 $nsaved += upd8021QPort ('desired', $vswitch['object_id'], $pn, $finalconfig, $before[$pn]); 4627 if ($nsaved) 4628 touchVLANSwitch ($vswitch['object_id']); 4629 $dbxlink->commit(); 4630 return $nsaved; 4631} 4632 4633// Use records from Port and Link tables to run a series of tasks on remote 4634// objects. These device-specific tasks will adjust downlink ports according to 4635// the current configuration of given uplink ports. 4636function initiateUplinksReverb ($object_id, $uplink_ports) 4637{ 4638 // Filter and regroup all requests (regardless of how many will succeed) 4639 // to end up with no more than one execution per remote object. 4640 $upstream_config = array(); 4641 foreach (getObjectPortsAndLinks ($object_id, FALSE) as $portinfo) 4642 if 4643 ( 4644 $portinfo['linked'] && 4645 array_key_exists ($portinfo['name'], $uplink_ports) 4646 ) 4647 $upstream_config[$portinfo['remote_object_id']][$portinfo['remote_name']] = $uplink_ports[$portinfo['name']]; 4648 // Note that when current object has several Port records inder same name 4649 // (but with unique IIF-OIF pair), these ports can be Link'ed to different 4650 // remote objects (using different media types, perhaps). Such a case can 4651 // be considered as normal, and each remote object will show up on the 4652 // task list (with its actual remote port name, of course). 4653 $done = 0; 4654 foreach ($upstream_config as $remote_object_id => $remote_ports) 4655 if ($changed = saveDownlinksReverb ($remote_object_id, $remote_ports)) 4656 { 4657 $done += $changed; 4658 $done += apply8021qChangeRequest ($remote_object_id, array(), FALSE); 4659 } 4660 return $done; 4661} 4662 4663// returns the first port from $ports that is connected and its name equals to $name 4664function findConnectedPort ($ports, $name) 4665{ 4666 foreach ($ports as $portinfo) 4667 if ($portinfo['linked'] && $portinfo['name'] == $name) 4668 return $portinfo; 4669} 4670 4671// checks if the desired config of all uplink/downlink ports of that switch, and 4672// his neighbors, equals to the recalculated config. If not, 4673// sets the recalculated configs as desired and puts switches into out-of-sync state. 4674// Returns an array with object_id as key and portname subkey 4675function recalc8021QPorts ($switch_id) 4676{ 4677 $ret = array 4678 ( 4679 'switches' => 0, 4680 'ports' => 0, 4681 ); 4682 $vswitch = getVLANSwitchInfo ($switch_id); 4683 if (! $vswitch) 4684 return $ret; 4685 4686 $ports = array(); // only linked ports appear here 4687 foreach (getObjectPortsAndLinks ($switch_id, FALSE) as $portinfo) 4688 if ($portinfo['linked']) 4689 $ports[$portinfo['name']] = $portinfo; 4690 4691 $order = apply8021QOrder ($vswitch, getStored8021QConfig ($switch_id, 'desired', array_keys ($ports))); 4692 4693 $self_processed = FALSE; 4694 4695 // calculate remote uplinks and copy them to local downlinks 4696 foreach ($ports as $portinfo) 4697 if 4698 ( 4699 isset ($order[$portinfo['name']]) && 4700 $order[$portinfo['name']]['vst_role'] == 'downlink' 4701 ) 4702 { 4703 // if there is a link with remote side type 'uplink', use its vlan mask 4704 $remote_pn = $portinfo['remote_name']; 4705 $remote_vswitch = getVLANSwitchInfo ($portinfo['remote_object_id']); 4706 if (! $remote_vswitch) 4707 continue; 4708 $n = apply8021qChangeRequest ($remote_vswitch['object_id'], array(), FALSE, NULL, array($portinfo['remote_name'] => '')); 4709 $ret['switches'] += $n ? 1 : 0; 4710 $ret['ports'] += $n; 4711 if ($n > 1) 4712 $self_processed = TRUE; 4713 } 4714 4715 // if no connected downlinks found, re-calculate local uplinks 4716 // (otherwise the remote switch has already called apply8021qChangeRequest for us) 4717 if ($self_processed) 4718 $ret['switches'] += 1; 4719 elseif ($ret['ports'] == 0) 4720 { 4721 $n = apply8021qChangeRequest ($switch_id, array(), FALSE, NULL, array()); 4722 $ret['switches'] += $n ? 1 : 0; 4723 $ret['ports'] += $n; 4724 } 4725 return $ret; 4726} 4727 4728// This function takes 802.1q order and the order of corresponding remote uplink port. 4729// It returns assotiative array with single row. Key = $portname, value - produced port 4730// order based on $order, and having vlan list replaced based on $uplink_order, but filtered. 4731function produceDownlinkPort ($domain_vlanlist, $portname, $order, $uplink_order) 4732{ 4733 $new_order = array ($portname => $order[$portname]); 4734 $new_order[$portname]['mode'] = 'trunk'; 4735 $new_order[$portname]['allowed'] = filterVLANList ($uplink_order['allowed'], $new_order[$portname]['wrt_vlans']); 4736 $new_order[$portname]['native'] = 0; 4737 return filter8021QChangeRequests ($domain_vlanlist, $order, $new_order); 4738} 4739 4740function detectVLANSwitchQueue ($vswitch) 4741{ 4742 if ($vswitch['out_of_sync'] == 'no' && $vswitch['last_errno'] != E_8021Q_SYNC_DISABLED) 4743 return 'done'; 4744 switch ($vswitch['last_errno']) 4745 { 4746 case E_8021Q_NOERROR: 4747 $last_change_age = time() - $vswitch['last_change']; 4748 if ($last_change_age > getConfigVar ('8021Q_DEPLOY_MAXAGE')) 4749 return 'sync_ready'; 4750 elseif ($last_change_age < getConfigVar ('8021Q_DEPLOY_MINAGE')) 4751 return 'sync_aging'; 4752 else 4753 return 'sync_ready'; 4754 case E_8021Q_VERSION_CONFLICT: 4755 case E_8021Q_PULL_REMOTE_ERROR: 4756 case E_8021Q_PUSH_REMOTE_ERROR: 4757 $last_error_age = time() - $vswitch['last_error_ts']; 4758 if ($last_error_age < getConfigVar ('8021Q_DEPLOY_RETRY')) 4759 return 'resync_aging'; 4760 else 4761 return 'resync_ready'; 4762 case E_8021Q_SYNC_DISABLED: 4763 return 'disabled'; 4764 } 4765 return ''; 4766} 4767 4768function get8021QDeployQueues() 4769{ 4770 global $dqtitle; 4771 $ret = array(); 4772 foreach (array_keys ($dqtitle) as $qcode) 4773 $ret[$qcode] = array(); 4774 foreach (getVLANSwitches() as $object_id) 4775 { 4776 $vswitch = getVLANSwitchInfo ($object_id); 4777 if ('' != $qcode = detectVLANSwitchQueue ($vswitch)) 4778 $ret[$qcode][] = $vswitch; 4779 } 4780 return $ret; 4781} 4782 4783function acceptable8021QConfig ($port) 4784{ 4785 switch ($port['mode']) 4786 { 4787 case 'trunk': 4788 return $port['native'] == 0 || in_array ($port['native'], $port['allowed']); 4789 case 'access': 4790 return $port['native'] > 0 && count ($port['allowed']) == 1 && 4791 in_array ($port['native'], $port['allowed']); 4792 default: 4793 return FALSE; 4794 } 4795} 4796 4797function nativeVlanChangePermitted ($pn, $from_vid, $to_vid, $op = NULL) 4798{ 4799 $before = array ($pn => array ( 4800 'mode' => 'access', 4801 'native' => $from_vid, 4802 'allowed' => array ($from_vid), 4803 )); 4804 $changes = array ($pn => array ( 4805 'mode' => 'access', 4806 'native' => $to_vid, 4807 'allowed' => array ($to_vid), 4808 )); 4809 4810 return count (authorize8021QChangeRequests ($before, $changes, $op)) != 0; 4811} 4812 4813function authorize8021QChangeRequests ($before, $changes, $op = NULL) 4814{ 4815 if (NULL !== $ret = callHook ('authorize8021QChangeRequests_hook', $before, $changes, $op)) 4816 return $ret; 4817 global $script_mode; 4818 if (isset ($script_mode) && $script_mode) 4819 return $changes; 4820 $ret = array(); 4821 foreach ($changes as $pn => $new) 4822 { 4823 if (! array_key_exists($pn, $before)) 4824 { 4825 $removed = array(); 4826 $added = $new['allowed']; 4827 } 4828 else 4829 { 4830 $old = $before[$pn]; 4831 $removed = array_diff ($old['allowed'], $new['allowed']); 4832 $added = array_diff ($new['allowed'], $old['allowed']); 4833 // treat native vlan replacement as removing/adding of old/new vlans, 4834 // even if they persist in both $old and $new allowed list 4835 if ($new['native'] != $old['native']) 4836 { 4837 if ($old['native'] && ! in_array($old['native'], $removed)) 4838 $removed[] = $old['native']; 4839 if ($new['native'] && ! in_array($new['native'], $added)) 4840 $added[] = $new['native']; 4841 } 4842 } 4843 foreach ($removed as $vid) 4844 if (!permitted (NULL, NULL, $op, array (array ('tag' => "\$fromvlan_{$vid}"), array ('tag' => "\$vlan_{$vid}")))) 4845 continue 2; // next port 4846 foreach ($added as $vid) 4847 if (!permitted (NULL, NULL, $op, array (array ('tag' => "\$tovlan_{$vid}"), array ('tag' => "\$vlan_{$vid}")))) 4848 continue 2; // next port 4849 $ret[$pn] = $new; 4850 } 4851 return $ret; 4852} 4853 4854function formatPortIIFOIF ($port) 4855{ 4856 $ret = ''; 4857 if ($port['iif_id'] != 1) 4858 $ret .= $port['iif_name'] . '/'; 4859 $ret .= $port['oif_name']; 4860 return $ret; 4861} 4862 4863// Not an equivalent of the above but related. 4864function parsePortIIFOIF ($port_type) 4865{ 4866 if (preg_match ('/^([[:digit:]]+)-([[:digit:]]+)$/', $port_type, $matches)) 4867 return array ($matches[1], $matches[2]); 4868 if (preg_match ('/^([[:digit:]]+)$/', $port_type, $matches)) 4869 return array (1, $matches[1]); 4870 throw new InvalidArgException ('port_type', $port_type, 'format error'); 4871} 4872 4873// returns '<a...</a>' html string containing a link to specified port or object. 4874// link title is "hostname portname" if both parts are defined 4875function formatPortLink($host_id, $hostname, $port_id, $portname, $a_class = '') 4876{ 4877 $href = 'index.php?page=object&object_id=' . urlencode($host_id); 4878 $additional = ''; 4879 if (isset ($port_id)) 4880 { 4881 $href .= '&hl_port_id=' . urlencode($port_id); 4882 $additional = "name=\"port-$port_id\""; 4883 } 4884 if (! empty($a_class)) 4885 $additional .= ($additional == '' ? '' : ' '). "class='$a_class'"; 4886 4887 $text_items = array(); 4888 if (isset ($hostname)) 4889 $text_items[] = $hostname; 4890 if (isset ($portname)) 4891 $text_items[] = $portname; 4892 4893 return "<a $additional href=\"$href\">" . implode(' ', $text_items) . '</a>'; 4894} 4895 4896// function returns a HTML-formatted link to the specified port 4897function formatPort ($port_info, $a_class = '') 4898{ 4899 return formatPortLink 4900 ( 4901 $port_info['object_id'], 4902 $port_info['object_name'], 4903 $port_info['id'], 4904 $port_info['name'], 4905 $a_class 4906 ); 4907} 4908 4909// function returns a HTML-formatted link to remote port, connected to the specified port 4910function formatLinkedPort ($port_info, $a_class = '') 4911{ 4912 return formatPortLink 4913 ( 4914 $port_info['remote_object_id'], 4915 $port_info['remote_object_name'], 4916 $port_info['remote_id'], 4917 $port_info['remote_name'], 4918 $a_class 4919 ); 4920} 4921 4922function compareDecomposedPortNames ($porta, $portb) 4923{ 4924 $ret = 0; 4925 4926 $prefix_diff = strcmp ($porta['prefix'], $portb['prefix']); 4927 4928 // concatenation of 0..(n-1) numeric indices 4929 $a_parent = $porta['idx_parent']; 4930 $b_parent = $portb['idx_parent']; 4931 4932 $index_diff = 0; 4933 for ($i = 0; $i < $porta['numidx']; $i++) 4934 { 4935 if ($i >= $portb['numidx']) 4936 { 4937 $index_diff = 1; // a > b 4938 break; 4939 } 4940 if ($porta['index'][$i] != $portb['index'][$i]) 4941 { 4942 $index_diff = $porta['index'][$i] - $portb['index'][$i]; 4943 break; 4944 } 4945 } 4946 if ($index_diff == 0 && $porta['numidx'] < $portb['numidx']) 4947 $index_diff = -1; // a < b 4948 4949 // compare by portname fields 4950 if ($prefix_diff != 0 && ($porta['numidx'] <= 1 || $portb['numidx'] <= 1)) // if index count is lte 1, sort by prefix 4951 { 4952 $ret = $porta['numidx'] - $portb['numidx']; 4953 if ($ret == 0) 4954 $ret = $prefix_diff; 4955 } 4956 // if index count > 1 and ports have different prefixes in intersecting index sections, sort by prefix 4957 elseif ($prefix_diff != 0 && $a_parent != '' && $a_parent == $b_parent) 4958 $ret = $prefix_diff; 4959 // if indices are not equal, sort by index 4960 elseif ($index_diff != 0) 4961 $ret = $index_diff; 4962 // if all of name fields are equal, compare by some additional port fields 4963 elseif ($porta['iif_id'] != $portb['iif_id']) 4964 $ret = $porta['iif_id'] - $portb['iif_id']; 4965 elseif (0 != $result = strcmp ($porta['label'], $portb['label'])) 4966 $ret = $result; 4967 elseif (0 != $result = strcmp ($porta['l2address'], $portb['l2address'])) 4968 $ret = $result; 4969 elseif ($porta['id'] != $portb['id']) 4970 $ret = $porta['id'] - $portb['id']; 4971 4972 return ($ret > 0) - ($ret < 0); 4973} 4974 4975// Sort provided port list in a way based on natural. For example, 4976// switches can have ports: 4977// * fa0/1~48, gi0/1~4 (in this case 'gi' should come after 'fa' 4978// * fa1, gi0/1~48, te1/49~50 (type matters, then index) 4979// * gi5/1~3, te5/4~5 (here index matters more than type) 4980// This implementation makes port type (prefix) matter for all 4981// interfaces that have less than 2 indices, but for other ports 4982// their indices matter more than type (unless there is a clash 4983// of indices). 4984// When $name_in_value is TRUE, port name determines as $plist[$key]['name'] 4985// Otherwise portname is the key of $plist 4986function sortPortList ($plist, $name_in_value = FALSE) 4987{ 4988 $ret = array(); 4989 $to_sort = array(); 4990 $prefix_re = '/^([^0-9]*)[0-9].*$/'; 4991 foreach ($plist as $pkey => $pvalue) 4992 { 4993 $pn = $name_in_value ? $pvalue['name'] : $pkey; 4994 $numbers = preg_split ('/[^0-9]+/', $pn, -1, PREG_SPLIT_NO_EMPTY); 4995 $parent = implode ('-', array_slice ($numbers, 0, count ($numbers) - 1)); 4996 $to_sort[] = array 4997 ( 4998 'key' => $pkey, 4999 'prefix' => preg_replace ($prefix_re, '\\1', $pn), 5000 'numidx' => count ($numbers), 5001 'index' => $numbers, 5002 'idx_parent' => $parent, 5003 'iif_id' => array_fetch ($pvalue, 'iif_id', 0), 5004 'label' => array_fetch ($pvalue, 'label', ''), 5005 'l2address' => array_fetch ($pvalue, 'l2address', ''), 5006 'id' => array_fetch ($pvalue, 'id', 0), 5007 'name' => $pn, 5008 ); 5009 } 5010 usort ($to_sort, 'compareDecomposedPortNames'); 5011 foreach ($to_sort as $pvalue) 5012 $ret[$pvalue['key']] = $plist[$pvalue['key']]; 5013 return $ret; 5014} 5015 5016// This function works like standard php usort function and uses sortPortList. 5017function usort_portlist (&$portnames) 5018{ 5019 $portnames = array_keys (sortPortList (array_fill_keys ($portnames, array()))); 5020} 5021 5022// Return a "?, ?, ?, ... ?, ?" string consisting of N question marks. N > 0 because 5023// the string will be a part of an SQL query. 5024function questionMarks ($count) 5025{ 5026 if (! isNaturalNumber ($count)) 5027 throw new InvalidArgException ('count', $count, 'must be greater than zero'); 5028 return implode (', ', array_fill (0, $count, '?')); 5029} 5030 5031// returns search results as an array keyed with realm name 5032// groups found entities by realms in 'summary' 5033function searchEntitiesByText ($terms) 5034{ 5035 $summary = array(); 5036 5037 if (FALSE !== ($ip_bin = ip4_checkparse ($terms))) 5038 // Search for IPv4 address. 5039 { 5040 if ($net_id = getIPv4AddressNetworkId ($ip_bin)) 5041 $summary['ipv4addressbydq'][$ip_bin] = array ('net_id' => $net_id, 'ip' => $ip_bin); 5042 } 5043 elseif (FALSE !== ($ip_bin = ip6_checkparse ($terms))) 5044 // Search for IPv6 address 5045 { 5046 if ($net_id = getIPv6AddressNetworkId ($ip_bin)) 5047 $summary['ipv6addressbydq'][$ip_bin] = array ('net_id' => $net_id, 'ip' => $ip_bin); 5048 } 5049 elseif (preg_match (RE_IP4_NET, $terms)) 5050 // Search for IPv4 network 5051 { 5052 list ($base, $len) = explode ('/', $terms); 5053 if (NULL !== ($net_id = getIPv4AddressNetworkId (ip4_parse ($base), $len + 1))) 5054 $summary['ipv4net'][$net_id] = spotEntity('ipv4net', $net_id); 5055 } 5056 elseif (preg_match ('@(.*)/(\d+)$@', $terms, $matches) && FALSE !== ($ip_bin = ip6_checkparse ($matches[1]))) 5057 // Search for IPv6 network 5058 { 5059 if (NULL !== ($net_id = getIPv6AddressNetworkId ($ip_bin, $matches[2] + 1))) 5060 $summary['ipv6net'][$net_id] = spotEntity('ipv6net', $net_id); 5061 } 5062 elseif (preg_match ('/^vlan\s*(\d+)$/i', $terms, $matches)) 5063 { 5064 $byID = getSearchResultByField 5065 ( 5066 'VLANDescription', 5067 array ('domain_id', 'vlan_id'), 5068 'vlan_id', 5069 $matches[1], 5070 'domain_id', 5071 1 5072 ); 5073 foreach ($byID as $vlan) 5074 { 5075 // add vlans to results 5076 $vlan_ck = $vlan['domain_id'] . '-' . $vlan['vlan_id']; 5077 $vlan['id'] = $vlan_ck; 5078 $summary['vlan'][$vlan_ck] = $vlan; 5079 // add linked networks to results 5080 $vlan_info = getVLANInfo ($vlan_ck); 5081 foreach ($vlan_info['ipv4nets'] as $net_id) 5082 $summary['ipv4net'][$net_id] = spotEntity ("ipv4net", $net_id); 5083 foreach ($vlan_info['ipv6nets'] as $net_id) 5084 $summary['ipv6net'][$net_id] = spotEntity ("ipv6net", $net_id); 5085 } 5086 } 5087 else 5088 // Search for objects, addresses, networks, virtual services and RS pools by their description. 5089 { 5090 // search by FQDN has special treatment - if single object found, do not search by other fields 5091 $object_id_by_fqdn = NULL; 5092 $domains = preg_split ('/\s*,\s*/', mb_strtolower (getConfigVar ('SEARCH_DOMAINS'))); 5093 if (! empty ($domains) && $object_id = searchByMgmtHostname ($terms)) 5094 { 5095 // get FQDN 5096 $attrs = getAttrValues ($object_id); 5097 $fqdn = ''; 5098 if (isset ($attrs[3]['value'])) 5099 $fqdn = mb_strtolower (trim ($attrs[3]['value'])); 5100 foreach ($domains as $domain) 5101 if ('.' . $domain === substr ($fqdn, -strlen ($domain) - 1)) 5102 { 5103 $object_id_by_fqdn = $object_id; 5104 break; 5105 } 5106 } 5107 if ($object_id_by_fqdn) 5108 { 5109 $summary['object'][$object_id_by_fqdn] = array 5110 ( 5111 'id' => $object_id_by_fqdn, 5112 'method' => 'fqdn', 5113 ); 5114 } 5115 else 5116 { 5117 $summary['object'] = getObjectSearchResults ($terms); 5118 $summary['ipv4addressbydescr'] = getIPv4AddressSearchResult ($terms); 5119 $summary['ipv6addressbydescr'] = getIPv6AddressSearchResult ($terms); 5120 $summary['ipv4net'] = getIPv4PrefixSearchResult ($terms); 5121 $summary['ipv6net'] = getIPv6PrefixSearchResult ($terms); 5122 $summary['ipv4rspool'] = getIPv4RSPoolSearchResult ($terms); 5123 $summary['ipvs'] = getVServiceSearchResult ($terms); 5124 $summary['ipv4vs'] = getIPv4VServiceSearchResult ($terms); 5125 $summary['user'] = getAccountSearchResult ($terms); 5126 $summary['file'] = getFileSearchResult ($terms); 5127 $summary['rack'] = getRackSearchResult ($terms); 5128 $summary['row'] = getRowSearchResult ($terms); 5129 $summary['location'] = getLocationSearchResult ($terms); 5130 $summary['vlan'] = getVLANSearchResult ($terms); 5131 } 5132 } 5133 # Filter search results in a way in some realms to omit records that the 5134 # user would not be able to browse anyway. 5135 foreach (array ('object', 'ipv4net', 'ipv6net', 'ipv4rspool', 'ipv4vs', 'ipvs', 'file', 'rack', 'row', 'location') as $realm) 5136 if (isset ($summary[$realm])) 5137 foreach ($summary[$realm] as $key => $record) 5138 if (! isolatedPermission ($realm, 'default', spotEntity ($realm, $record['id']))) 5139 unset ($summary[$realm][$key]); 5140 // clear empty search result realms 5141 return array_filter ($summary, 'count'); 5142} 5143 5144// returns URL to redirect to, or NULL if $result_type is unknown 5145function buildSearchRedirectURL ($result_type, $record) 5146{ 5147 global $pageno_by_etype, $page; 5148 $id = isset ($record['id']) ? $record['id'] : NULL; 5149 $params = array(); 5150 switch ($result_type) 5151 { 5152 case 'ipv4addressbydq': 5153 case 'ipv6addressbydq': 5154 case 'ipv4addressbydescr': 5155 case 'ipv6addressbydescr': 5156 $address = getIPAddress ($record['ip']); 5157 if (count ($address['allocs']) == 1 && isIPAddressEmpty ($address, array ('allocs'))) 5158 { 5159 $next_page = 'object'; 5160 $id = $address['allocs'][0]['object_id']; 5161 $params['hl_ip'] = ip_format ($record['ip']); 5162 } 5163 elseif (count ($address['vsglist'] + $address['vslist']) && isIPAddressEmpty ($address, array ('vslist', 'vsglist'))) 5164 { 5165 $next_page = 'ipaddress'; 5166 $id = ip_format ($record['ip']); 5167 } 5168 else 5169 { 5170 $next_page = strlen ($record['ip']) == 16 ? 'ipv6net' : 'ipv4net'; 5171 $id = isset ($record['net_id']) ? $record['net_id'] : getIPAddressNetworkId ($record['ip']); 5172 $params['hl_ip'] = ip_format ($record['ip']); 5173 } 5174 break; 5175 case 'vlan': 5176 $next_page = 'vlan'; 5177 $id = $record['id']; 5178 break; 5179 default: 5180 if (! isset ($pageno_by_etype[$result_type])) 5181 return NULL; 5182 $next_page = $pageno_by_etype[$result_type]; 5183 if ($result_type == 'object') 5184 if (isset ($record['by_port']) && 1 == count ($record['by_port'])) 5185 { 5186 $found_ports_ids = array_keys ($record['by_port']); 5187 $params['hl_port_id'] = $found_ports_ids[0]; 5188 } 5189 break; 5190 } 5191 if (array_key_exists ($next_page, $page) && isset ($page[$next_page]['bypass'])) 5192 $key = $page[$next_page]['bypass']; 5193 if (! isset ($key) || ! isset ($id)) 5194 return NULL; 5195 $params[$key] = $id; 5196 if (isset ($_REQUEST['last_tab']) && isset ($_REQUEST['last_page']) && $next_page == $_REQUEST['last_page']) 5197 $next_tab = assertStringArg('last_tab'); 5198 return buildRedirectURL ($next_page, isset ($next_tab) ? $next_tab : 'default', $params); 5199} 5200 5201// This works like explode() with space as a separator with the added difference 5202// that anything in double quotes is returned as a single word. 5203function parseSearchTerms ($terms) 5204{ 5205 $ret = array(); 5206 if (mb_substr_count ($terms, '"') % 2 != 0) 5207 throw new InvalidArgException ('terms', $terms, 'contains odd number of quotes'); 5208 $state = 'whitespace'; 5209 $buffer = ''; 5210 $len = mb_strlen ($terms); 5211 for ($i = 0; $i < $len; $i++) 5212 { 5213 $c = mb_substr ($terms, $i, 1); 5214 switch ($state) 5215 { 5216 case 'whitespace': 5217 switch ($c) 5218 { 5219 case ' ': 5220 break; // nom-nom 5221 case '"': 5222 $buffer = ''; 5223 $state = 'quoted_string'; 5224 break; 5225 default: 5226 $buffer = $c; 5227 $state = 'word'; 5228 } 5229 break; 5230 5231 case 'word': 5232 switch ($c) 5233 { 5234 case '"': 5235 throw new InvalidArgException ('terms', $terms, 'punctuation error'); 5236 case ' ': 5237 $ret[] = $buffer; 5238 $buffer = ''; 5239 $state = 'whitespace'; 5240 break; 5241 default: 5242 $buffer .= $c; 5243 } 5244 break; 5245 5246 case 'quoted_string': 5247 switch ($c) 5248 { 5249 case '"': 5250 if (trim ($buffer) == '') 5251 throw new InvalidArgException ('terms', $terms, 'punctuation error'); 5252 $ret[] = trim ($buffer); 5253 $buffer = ''; 5254 $state = 'whitespace'; 5255 // FIXME: this does not detect missing whitespace that would be reasonable 5256 // to expect between the closing quote and the next token, if any. 5257 break; 5258 default: 5259 $buffer .= $c; 5260 } 5261 break; 5262 } 5263 } 5264 if ($buffer != '') 5265 $ret[] = $buffer; 5266 5267 return $ret; 5268} 5269 5270// Take a parse tree and figure out if it is a valid payload or not. 5271// Depending on that return either NULL or an array filled with the load 5272// of that expression. 5273define('PARSER_ABI_VER', 2); 5274function spotPayload ($text, $reqtype = 'SYNT_CODETEXT') 5275{ 5276 require_once 'code.php'; 5277 try 5278 { 5279 $parser = new RackCodeParser(); 5280 $tree = $parser->parse ($text, $reqtype == 'SYNT_EXPR' ? 'expr' : 'prog'); 5281 return array ('result' => 'ACK', 'ABI_ver' => PARSER_ABI_VER, 'load' => $tree); 5282 } 5283 catch (RCParserError $e) 5284 { 5285 $msg = $e->getMessage(); 5286 if ($reqtype != 'SYNT_EXPR' || $e->lineno != 1) 5287 $msg .= ", line {$e->lineno}"; 5288 return array ('result' => 'NAK', 'ABI_ver' => PARSER_ABI_VER, 'load' => $msg); 5289 } 5290} 5291 5292// Top-level wrapper for most of the code in this file. Get a text, return a parse tree 5293// (or error message). 5294function getRackCode ($text) 5295{ 5296 if ($text == '') 5297 return array ('result' => 'NAK', 'ABI_ver' => PARSER_ABI_VER, 'load' => 'The RackCode text was found empty in ' . __FUNCTION__); 5298 $text = str_replace ("\r", '', $text) . "\n"; 5299 $synt = spotPayload ($text, 'SYNT_CODETEXT'); 5300 if ($synt['result'] != 'ACK') 5301 return $synt; 5302 // An empty sentence list is semantically valid, yet senseless, 5303 // so checking intermediate result once more won't hurt. 5304 if (!count ($synt['load'])) 5305 return array ('result' => 'NAK', 'ABI_ver' => PARSER_ABI_VER, 'load' => 'Empty parse tree found in ' . __FUNCTION__); 5306 return $synt; 5307} 5308 5309// returns array with 'from', 'length' keys. 5310// if not found, 'from' is NULL; 5311// if length is not defined (to the end of line), length is -1 5312function getColumnCoordinates ($line, $column_name, $align = 'left') 5313{ 5314 $result = array ('from' => NULL, 'length' => -1); 5315 $items = preg_split('/\s+/', $line); 5316 for ($i = 0; $i < count ($items); $i++) 5317 { 5318 $item = $items[$i]; 5319 if ($column_name == $item) 5320 { 5321 $current_start = strpos ($line, $items[$i]); 5322 if ($align == 'left') 5323 { 5324 $result['from'] = $current_start; 5325 if ($i < count ($items) - 1) 5326 { 5327 $next_start = strpos ($line, $items[$i + 1]); 5328 $result['length'] = $next_start - $result['from'] - 1; 5329 } 5330 else 5331 $result['length'] = -1; 5332 } 5333 elseif ($align == 'right') 5334 { 5335 if ($i > 0) 5336 $prev_end = strpos ($line, $items[$i - 1]) + strlen ($items[$i - 1]); 5337 else 5338 $prev_end = -1; 5339 $result['from'] = $prev_end + 1; 5340 $result['length'] = $current_start - $result['from'] + strlen ($column_name); 5341 } 5342 break; 5343 } 5344 } 5345 return $result; 5346} 5347 5348// Messages in the top of the page should be shown using these functions. 5349// You can call them multiple times to show multiple messages. 5350// $option can be 'inline' to echo message div, instead of putting it into $_SESSION and draw on next index page show 5351// These functions always return NULL 5352function showError ($message, $option = '') 5353{ 5354 setMessage ('error', $message, $option == 'inline'); 5355} 5356 5357function showWarning ($message, $option = '') 5358{ 5359 setMessage ('warning', $message, $option == 'inline'); 5360} 5361 5362function showSuccess ($message, $option = '') 5363{ 5364 setMessage ('success', $message, $option == 'inline'); 5365} 5366 5367function showNotice ($message, $option = '') 5368{ 5369 setMessage ('neutral', $message, $option == 'inline'); 5370} 5371 5372// do not call this directly, use showError and its siblings instead 5373// $type could be 'error', 'warning', 'success' or 'neutral' 5374function setMessage ($type, $message, $direct_rendering) 5375{ 5376 global $script_mode, $message_buffering; 5377 if ($direct_rendering) 5378 echo '<div class="msg_' . $type . '">' . $message . '</div>'; 5379 elseif (isset ($script_mode) && $script_mode && !$message_buffering) 5380 { 5381 if ($type == 'warning' || $type == 'error') 5382 file_put_contents ('php://stderr', strtoupper ($type) . ': ' . strip_tags ($message) . "\n"); 5383 } 5384 else 5385 { 5386 switch ($type) 5387 { 5388 case 'error': 5389 $code = 100; 5390 break; 5391 case 'warning': 5392 $code = 200; 5393 break; 5394 case 'success'; 5395 $code = 0; 5396 break; 5397 case 'neutral': 5398 default: 5399 $code = 300; 5400 break; 5401 } 5402 showOneLiner ($code, array ($message)); 5403 } 5404} 5405 5406function showOneLiner ($code, $args = array()) 5407{ 5408 global $log_messages; 5409 $line = array ('c' => $code); 5410 if (! empty ($args)) 5411 $line['a'] = $args; 5412 $log_messages[] = $line; 5413} 5414 5415// Works only in $script_mode == TRUE 5416function setMessageBuffering ($state) 5417{ 5418 global $message_buffering; 5419 $message_buffering = $state; 5420} 5421 5422function flushMessageBuffer() 5423{ 5424 global $log_messages, $script_mode; 5425 5426 if (!isset ($script_mode) || !$script_mode) 5427 return; 5428 5429 $code_str_map = array 5430 ( 5431 100 => 'ERROR', 5432 200 => 'WARNING', 5433 ); 5434 5435 foreach ($log_messages as $line) 5436 if (isset ($code_str_map[$line['c']])) 5437 { 5438 $type = $code_str_map[$line['c']]; 5439 $message = strip_tags (implode ("\n", $line['a'])); 5440 file_put_contents ('php://stderr', $type . ': ' . $message . "\n"); 5441 } 5442 $log_messages = array(); 5443} 5444 5445function clearMessageBuffer() 5446{ 5447 global $log_messages; 5448 $log_messages = array(); 5449} 5450 5451function showFuncMessage ($callfunc, $status, $log_args = array()) 5452{ 5453 global $msgcode; 5454 if (isset ($msgcode[$callfunc][$status])) 5455 showOneLiner ($msgcode[$callfunc][$status], $log_args); 5456 else 5457 showWarning ("Message '$status' is lost in $callfunc"); 5458} 5459 5460// function returns integer count of unshown messages in log buffer. 5461// message_type can be 'all', 'success', 'error', 'warning', 'neutral'. 5462function getMessagesCount ($message_type = 'all') 5463{ 5464 global $log_messages; 5465 $result = 0; 5466 foreach ($log_messages as $msg) 5467 if ($msg['c'] < 100) 5468 { 5469 if ($message_type == 'success' || $message_type == 'all') 5470 ++$result; 5471 } 5472 elseif ($msg['c'] < 200) 5473 { 5474 if ($message_type == 'error' || $message_type == 'all') 5475 ++$result; 5476 } 5477 elseif ($msg['c'] < 300) 5478 { 5479 if ($message_type == 'warning' || $message_type == 'all') 5480 ++$result; 5481 } 5482 else 5483 { 5484 if ($message_type == 'neutral' || $message_type == 'all') 5485 ++$result; 5486 } 5487 return $result; 5488} 5489 5490function isEthernetPort($port) 5491{ 5492 return ($port['iif_id'] != 1 || preg_match('/Base|LACP/i', $port['oif_name'])); 5493} 5494 5495function loadConfigDefaults() 5496{ 5497 $ret = loadConfigCache(); 5498 if (!count ($ret)) 5499 throw new RackTablesError ('Failed to load configuration from the database.', RackTablesError::INTERNAL); 5500 foreach (array_keys ($ret) as $varname) 5501 { 5502 $ret[$varname]['is_altered'] = 'no'; 5503 if ($ret[$varname]['vartype'] == 'uint') 5504 $ret[$varname]['varvalue'] = intval ($ret[$varname]['varvalue']); 5505 $ret[$varname]['defaultvalue'] = $ret[$varname]['varvalue']; 5506 } 5507 return $ret; 5508} 5509 5510function alterConfigWithUserPreferences() 5511{ 5512 global $configCache; 5513 global $remote_username; 5514 foreach (loadUserConfigCache($remote_username) as $key => $row) 5515 if ($configCache[$key]['is_userdefined'] == 'yes') 5516 { 5517 $configCache[$key]['varvalue'] = $row['varvalue']; 5518 $configCache[$key]['is_altered'] = 'yes'; 5519 } 5520} 5521 5522// Returns true if varname has a different value or varname is new 5523function isConfigVarChanged ($varname, $varvalue) 5524{ 5525 global $configCache; 5526 if (!isset ($configCache)) 5527 throw new RackTablesError ('configuration cache is unavailable', RackTablesError::INTERNAL); 5528 if ($varname == '') 5529 throw new InvalidArgException('varname', $varname, 'Empty variable name'); 5530 if (!isset ($configCache[$varname])) 5531 return TRUE; 5532 if ($configCache[$varname]['vartype'] == 'uint') 5533 return $configCache[$varname]['varvalue'] !== intval ($varvalue); 5534 else 5535 return $configCache[$varname]['varvalue'] !== $varvalue; 5536} 5537 5538// This function depends on init.php to have the cache array initialized. 5539function getConfigVar ($varname) 5540{ 5541 global $configCache; 5542 if (! isset ($configCache)) 5543 throw new RackTablesError ('configuration cache is unavailable', RackTablesError::INTERNAL); 5544 if (! array_key_exists ($varname, $configCache)) 5545 throw new InvalidArgException ('varname', $varname, 'no such configuration variable'); 5546 return $configCache[$varname]['varvalue']; 5547} 5548 5549// return portinfo array if object has a port with such name, or NULL 5550// in strict mode the resulting port name is always equal to the $portname. 5551// in non-strict mode names are compared using shortenIfName() 5552function getPortinfoByName (&$object, $portname, $strict_mode = TRUE) 5553{ 5554 if (! isset ($object['ports'])) 5555 $object['ports'] = getObjectPortsAndLinks ($object['id']); 5556 if (! $strict_mode) 5557 { 5558 $breed = detectDeviceBreed ($object['id']); 5559 $portname = shortenIfName ($portname, $breed); 5560 } 5561 $ret = NULL; 5562 foreach ($object['ports'] as $portinfo) 5563 if ($portname == ($strict_mode ? $portinfo['name'] : shortenIfName ($portinfo['name'], $breed))) 5564 { 5565 $ret = $portinfo; 5566 if ($ret['linked']) 5567 break; 5568 } 5569 elseif (isset ($ret)) 5570 break; 5571 return $ret; 5572} 5573 5574// exclude location-related object types 5575function withoutLocationTypes ($objtypes) 5576{ 5577 global $location_obj_types; 5578 return array_diff_key ($objtypes, array_fill_keys ($location_obj_types, 0)); 5579} 5580 5581# For the given object ID return a getSelect-suitable list of object types 5582# compatible with the object's attributes that have an assigned value in 5583# AttributeValue (no assigned values mean full compatibility). Being compatible 5584# with an attribute means having a record in AttributeMap (with the same chapter 5585# ID, if the attribute is dictionary-based). This knowledge is required to allow 5586# the user changing object type ID in a way that leaves data in AttributeValue 5587# meeting constraints in AttributeMap upon the change. 5588function getObjectTypeChangeOptions ($object_id) 5589{ 5590 $map = getAttrMap(); 5591 $used = array(); 5592 $ret = array(); 5593 foreach (getAttrValues ($object_id) as $attr) 5594 { 5595 if (! array_key_exists ($attr['id'], $map)) 5596 return array(); // inconsistent current data 5597 if ($attr['value'] != '') 5598 $used[] = $attr; 5599 } 5600 foreach (withoutLocationTypes (readChapter (CHAP_OBJTYPE, 'o')) as $test_id => $text) 5601 { 5602 foreach ($used as $attr) 5603 { 5604 $app = $map[$attr['id']]['application']; 5605 if 5606 ( 5607 (NULL === $appidx = scanArrayForItem ($app, 'objtype_id', $test_id)) || 5608 ($attr['type'] == 'dict' && $attr['chapter_id'] != $app[$appidx]['chapter_no']) 5609 ) 5610 continue 2; // next type ID 5611 } 5612 $ret[$test_id] = $text; 5613 } 5614 return $ret; 5615} 5616 5617// Gets the timestamp and returns human-friendly short message describing the time difference 5618// between the current system time and the specified timestamp (like '2d 5h ago') 5619function formatAgeTimestamp ($timestamp) 5620{ 5621 return formatAgeSeconds (time() - $timestamp); 5622} 5623 5624// For backward compatibility. 5625function formatAge ($timestamp) 5626{ 5627 return formatAgeTimestamp ($timestamp); 5628} 5629 5630function formatAgeSeconds ($seconds) 5631{ 5632 switch (TRUE) 5633 { 5634 case $seconds < 1: 5635 return 'just now'; 5636 case $seconds < 60: 5637 return "${seconds}s" . ' ago'; 5638 case $seconds <= 300: 5639 $mins = intval ($seconds / 60); 5640 $secs = $seconds % 60; 5641 return ($secs ? "{$mins}min ${secs}s" : "{$mins}min") . ' ago'; 5642 case $seconds < 3600: 5643 return round ($seconds / 60) . 'min' . ' ago'; 5644 case $seconds < 3 * 3600: 5645 $hrs = intval ($seconds / 3600); 5646 $mins = round (($seconds % 3600) / 60) . ''; 5647 return ($mins ? "${hrs}h ${mins}min" : "${hrs}h") . ' ago'; 5648 case $seconds < 86400: 5649 return round ($seconds / 3600) . 'h' . ' ago'; 5650 case $seconds < 86400 * 3: 5651 $days = intval ($seconds / 86400); 5652 $hrs = round (($seconds - $days * 86400) / 3600); 5653 return ($hrs ? "${days}d ${hrs}h" : "${days}d") . ' ago'; 5654 case $seconds < 86400 * 30.4375: 5655 return round ($seconds / 86400) . 'd' . ' ago'; 5656 case $seconds < 86400 * 30.4375 * 4 : 5657 $mon = intval ($seconds / 86400 / 30.4375); 5658 $days = round (($seconds - $mon * 86400 * 30.4375) / 86400); 5659 return ($days ? "${mon}mo ${days}d" : "${mon}mo") . ' ago'; 5660 case $seconds < 365.25 * 86400: 5661 return (round ($seconds / 86400 / 30.4375) . 'mo') . ' ago'; 5662 case $seconds < 2 * 365.25 * 86400: 5663 $yrs = intval ($seconds / 86400 / 365.25); 5664 $mon = round (($seconds - $yrs * 86400 * 365.25) / 86400 / 30.4375); 5665 return ($mon ? "${yrs}y ${mon}mo" : "${yrs}y") . ' ago'; 5666 default: 5667 return (round ($seconds / 86400 / 365.25) . 'y') . ' ago'; 5668 } 5669} 5670 5671// proxy function returning the output of another function. Takes any number of additional parameters 5672function getOutputOf ($func_name) 5673{ 5674 ob_start(); 5675 try 5676 { 5677 $params = func_get_args(); 5678 array_shift($params); 5679 call_user_func_array ($func_name, $params); 5680 return ob_get_clean(); 5681 } 5682 catch (Exception $e) 5683 { 5684 ob_end_clean(); 5685 throw $e; 5686 } 5687} 5688 5689// Calls a function taking into account the overrides specified in the 5690// $hook array. Takes any number of additional parameters. 5691function callHook ($hook_name) 5692{ 5693 global $hook; 5694 $callback = $hook_name; 5695 if (isset ($hook[$hook_name])) 5696 $callback = $hook[$hook_name]; 5697 $params = func_get_args(); 5698 if ($callback !== 'universalHookHandler') 5699 array_shift ($params); 5700 if (is_callable ($callback)) 5701 return call_user_func_array ($callback, $params); 5702} 5703 5704// function to parse text table header, aligned by left side 5705// returns array suitable to be used by explodeTableLine 5706function guessTableStructure ($line) 5707{ 5708 $ret = array(); 5709 $i = 0; 5710 while ($line != '') 5711 { 5712 if (! preg_match ('/^(\s*\S+\s*)/', $line, $m)) 5713 break; 5714 $header = trim ($m[1]); 5715 $ret[$header] = array ('begin' => $i, 'length' => strlen ($m[1])); 5716 $line = substr ($line, strlen ($m[1])); 5717 $i += strlen ($m[1]); 5718 } 5719 return $ret; 5720} 5721 5722// takes text-formatted table line and an array returned by guessTableStructure 5723// returns array indexed by cell name. Works for left-aligned tables only 5724function explodeTableLine ($line, $table_schema) 5725{ 5726 $ret = array(); 5727 foreach ($table_schema as $header => $constraints) 5728 { 5729 $value = substr ($line, $constraints['begin'], $constraints['length']); 5730 $ret[$header] = trim ($value); 5731 } 5732 return $ret; 5733} 5734 5735// returns number of changed ports (both local and remote) 5736// shows error messages unconditionally, and success messages respecting to $verbose setting 5737// if $mutex_rev is set, checks if it is outdated 5738// if $uplinks_filter is not empty, changes only those uplink ports that are keys of this array 5739// NOTE: this function is calling itself through initiateUplinksReverb. It is important that 5740// the call to initiateUplinksReverb is outside of DB transaction scope. 5741function apply8021qChangeRequest ($switch_id, $changes, $verbose = TRUE, $mutex_rev = NULL, $uplinks_filter = array()) 5742{ 5743 global $dbxlink; 5744 $dbxlink->beginTransaction(); 5745 try 5746 { 5747 if (NULL === $vswitch = getVLANSwitchInfo ($switch_id, 'FOR UPDATE')) 5748 throw new InvalidArgException ('object_id', $switch_id, 'VLAN domain is not set for this object'); 5749 if (isset ($mutex_rev) && $vswitch['mutex_rev'] != $mutex_rev) 5750 throw new InvalidRequestArgException ('mutex_rev', $mutex_rev, 'expired form data'); 5751 $after = $before = apply8021QOrder ($vswitch, getStored8021QConfig ($vswitch['object_id'], 'desired')); 5752 $domain_vlanlist = getDomainVLANList ($vswitch['domain_id']); 5753 $changes = filter8021QChangeRequests 5754 ( 5755 $domain_vlanlist, 5756 $before, 5757 apply8021QOrder ($vswitch, $changes) 5758 ); 5759 $desired_ports_count = count ($changes); 5760 $changes = authorize8021QChangeRequests ($before, $changes); 5761 if (count ($changes) < $desired_ports_count) 5762 showWarning (sprintf ("Permission denied to change %d ports", $desired_ports_count - count ($changes))); 5763 foreach ($changes as $port_name => $port) 5764 $after[$port_name] = $port; 5765 $new_uplinks = filter8021QChangeRequests ($domain_vlanlist, $after, produceUplinkPorts ($domain_vlanlist, $after, $vswitch['object_id'])); 5766 if ($uplinks_filter) 5767 $new_uplinks = array_intersect_key ($new_uplinks, $uplinks_filter); 5768 $npulled = replace8021QPorts ('desired', $vswitch['object_id'], $before, $changes); 5769 $nsaved_uplinks = replace8021QPorts ('desired', $vswitch['object_id'], $before, $new_uplinks); 5770 if ($npulled + $nsaved_uplinks) 5771 touchVLANSwitch ($vswitch['object_id']); 5772 $dbxlink->commit(); 5773 } 5774 catch (Exception $e) 5775 { 5776 $dbxlink->rollBack(); 5777 showError (sprintf ("Failed to update switchports: %s", $e->getMessage())); 5778 return 0; 5779 } 5780 $nsaved_downlinks = initiateUplinksReverb ($vswitch['object_id'], $new_uplinks); 5781 // instant deploy to that switch if configured 5782 $done = 0; 5783 if ($npulled + $nsaved_uplinks > 0 && getConfigVar ('8021Q_INSTANT_DEPLOY') == 'yes') 5784 { 5785 try 5786 { 5787 if (FALSE === $done = exec8021QDeploy ($vswitch['object_id'], TRUE)) 5788 showError ("deploy was blocked due to conflicting configuration versions"); 5789 elseif ($verbose) 5790 showSuccess (sprintf ("Configuration for %u port(s) have been deployed", $done)); 5791 } 5792 catch (Exception $e) 5793 { 5794 showError (sprintf ("Failed to deploy changes to switch: %s", $e->getMessage())); 5795 } 5796 } 5797 // report number of changed ports 5798 $total = $npulled + $nsaved_uplinks + $nsaved_downlinks; 5799 if ($verbose) 5800 { 5801 $message = sprintf ('%u port(s) have been changed', $total); 5802 if ($total > 0) 5803 showSuccess ($message); 5804 else 5805 showNotice ($message); 5806 } 5807 return $total; 5808} 5809 5810// takes a full sublist of ipv4net entities ordered by (ip,mask) 5811// fills ['spare_ranges'] and ['kidc'] fields of each item of $nets. 5812function fillIPNetsCorrelation (&$nets, $max_depth = 0) 5813{ 5814 $stack = array(); 5815 foreach ($nets as &$net) 5816 { 5817 $last = NULL; // last element popped from the stack 5818 while (count ($stack)) // spin stack leaving only the parents of the $net. 5819 { 5820 $top = &$stack[count ($stack) - 1]; 5821 if (IPNetContains ($top, $net)) 5822 { 5823 // skip the network if max_depth exceeded 5824 if ($max_depth && count ($stack) > $max_depth) 5825 continue 2; 5826 $top['kidc']++; 5827 break; 5828 } 5829 if (isset ($last)) 5830 // possible hole in the end of $top 5831 fillIPSpareListBstr ($top, ip_next (ip_last ($last)), ip_last ($top)); 5832 $last = array_pop ($stack); 5833 } 5834 if (count ($stack)) 5835 { 5836 $top = &$stack[count ($stack) - 1]; 5837 if (isset ($last)) 5838 // possible hole in the middle of $top 5839 fillIPSpareListBstr ($top, ip_next (ip_last ($last)), ip_prev ($net['ip_bin'])); 5840 else 5841 // possible hole in the beginning of $top 5842 fillIPSpareListBstr ($top, $top['ip_bin'], ip_prev ($net['ip_bin'])); 5843 } 5844 $stack[] = &$net; 5845 } 5846 // final stack spin 5847 $last = NULL; 5848 while (count ($stack)) 5849 { 5850 $top = &$stack[count ($stack) - 1]; 5851 if (isset ($last)) 5852 { 5853 // possible hole in the end of $top 5854 $last = ip_last ($last); 5855 $a = ip_next ($last); 5856 // check for crossing 0 5857 if (0 > strcmp ($a, $last)) 5858 break; 5859 fillIPSpareListBstr ($top, $a, ip_last ($top)); 5860 } 5861 $last = array_pop ($stack); 5862 } 5863} 5864 5865// $a, $b - binary strings (IP addresses) meaning the beginning and the end of IP range. 5866// $net is an entity for hole autotags to be set to. 5867function fillIPSpareListBstr (&$net, $a, $b) 5868{ 5869 $len = strlen ($a) * 8; 5870 while (0 >= strcmp ($a, $b)) 5871 { 5872 $max_mask = 0; // the number of common binary bits in the major of $a and $b 5873 $xor = $a ^ $b; 5874 for ($i = 0; $i < strlen ($xor); $i++) 5875 { 5876 $max_mask += 8; 5877 if ($xor[$i] != "\0") 5878 { 5879 $byte = ord ($xor[$i]); 5880 do 5881 { 5882 $max_mask--; 5883 $byte >>= 1; 5884 } while ($byte != 0); 5885 break; 5886 } 5887 } 5888 5889 for ($mask = $max_mask; $mask <= $len; $mask++) 5890 { 5891 $bmask = ip_mask ($mask, $len == 128); 5892 $last_a = $a | ~ $bmask; 5893 if ($a == ($a & $bmask) && 0 >= strcmp ($last_a, $b)) 5894 { 5895 $net['spare_ranges'][$mask][] = $a; 5896 $a = ip_next ($last_a); 5897 // check for crossing 0 5898 if (0 > strcmp ($a, $last_a)) 5899 break 2; 5900 break; 5901 } 5902 } 5903 } 5904} 5905 5906// returns TRUE if all of the fields set by constructIPAddress are empty 5907function isIPAddressEmpty ($addrinfo, $except_fields = array()) 5908{ 5909 // string fields 5910 $check_fields = array ('name', 'comment'); 5911 foreach (array_diff ($check_fields, $except_fields) as $field) 5912 if (array_key_exists ($field, $addrinfo) && $addrinfo[$field] != '') 5913 return FALSE; 5914 5915 // "boolean" fields 5916 if (! in_array ('reserved', $except_fields) && $addrinfo['reserved'] != 'no') 5917 return FALSE; 5918 5919 // array fields 5920 $check_fields = array ('allocs', 'rsplist', 'vslist', 'vsglist'); 5921 if (strlen ($addrinfo['ip_bin']) == 4) 5922 $check_fields = array_merge ($check_fields, array ('inpf', 'outpf')); 5923 foreach (array_diff ($check_fields, $except_fields) as $field) 5924 if (array_key_exists ($field, $addrinfo) && is_array ($addrinfo[$field]) && count ($addrinfo[$field]) > 0) 5925 return FALSE; 5926 return TRUE; 5927} 5928 5929// returns TRUE if the network cell is allowed to be deleted, FALSE otherwise 5930// $netinfo could be either ipv4net or ipv6net entity. 5931// in case of returning FALSE, $netinfo['addrlist'] is set 5932function isIPNetworkEmpty (&$netinfo) 5933{ 5934 if (getConfigVar ('IPV4_JAYWALK') == 'yes') 5935 return TRUE; 5936 if (! isset ($netinfo['addrlist'])) 5937 loadIPAddrList ($netinfo); 5938 $pure_array = ($netinfo['realm'] == 'ipv4net') ? 5939 array ($netinfo['ip_bin'] => 'network', ip_last ($netinfo) => 'broadcast') : // v4 5940 array ($netinfo['ip_bin'] => 'Subnet-Router anycast'); // v6 5941 $pure_auto = 0; 5942 foreach ($pure_array as $ip => $comment) 5943 if 5944 ( 5945 array_key_exists ($ip, $netinfo['addrlist']) && 5946 $netinfo['addrlist'][$ip]['name'] == $comment && 5947 $netinfo['addrlist'][$ip]['reserved'] == 'yes' && 5948 isIPAddressEmpty ($netinfo['addrlist'][$ip], array ('name', 'reserved')) 5949 ) 5950 $pure_auto++; 5951 return ($netinfo['own_addrc'] <= $pure_auto); 5952} 5953 5954// returns the first element of given array, or NULL if array is empty 5955function array_first ($array) 5956{ 5957 $single = array_slice (array_values ($array), 0, 1); 5958 if (count ($single)) 5959 return $single[0]; 5960} 5961 5962// returns the last element of given array, or NULL if array is empty 5963function array_last ($array) 5964{ 5965 $single = array_slice (array_values ($array), -1, 1); 5966 if (count ($single)) 5967 return $single[0]; 5968} 5969 5970// FIXME: Remove this function at some point because it is a reimplementation 5971// of array_diff_key(). 5972// returns array of key-value pairs from array $a such that keys are not present in $b 5973function array_sub ($a, $b) 5974{ 5975 $ret = array(); 5976 foreach ($a as $key => $value) 5977 if (! array_key_exists($key, $b)) 5978 $ret[$key] = $value; 5979 return $ret; 5980} 5981 5982// returns the requested element value or the default value if not found 5983function array_fetch ($array, $key, $default_value) 5984{ 5985 if (! is_array ($array)) 5986 throw new InvalidArgException ('array', $array, 'is not an array'); 5987 return array_key_exists ($key, $array) ? $array[$key] : $default_value; 5988} 5989 5990// Registers additional ophandler on page-tab-opname triplet. 5991// Valid $method values are 'before' and 'after'. 5992// 'before' puts your ophandler in the beginning of the list (and thus before the default) 5993// 'after' puts your ophandler to the end of the list (and thus after the default) 5994function registerOpHandler ($page, $tab, $opname, $callback, $method = 'before') 5995{ 5996 global $ophandlers_stack; 5997 global $ophandler; 5998 if (! isset ($ophandler[$page][$tab][$opname])) 5999 $ophandlers_stack[$page][$tab][$opname] = array(); 6000 if (isset ($ophandler[$page][$tab][$opname]) && $ophandler[$page][$tab][$opname] != 'universalOpHandler') 6001 { 6002 $ophandlers_stack[$page][$tab][$opname] = array ($ophandler[$page][$tab][$opname]); 6003 $ophandler[$page][$tab][$opname] = 'universalOpHandler'; 6004 } 6005 $ophandler[$page][$tab][$opname] = 'universalOpHandler'; 6006 if ($method == 'before') 6007 array_unshift ($ophandlers_stack[$page][$tab][$opname], $callback); 6008 elseif ($method == 'after') 6009 array_push ($ophandlers_stack[$page][$tab][$opname], $callback); 6010 else 6011 throw new RacktablesError ("unknown ophandler injection method '$method'", RackTablesError::INTERNAL); 6012} 6013 6014// call this from custom ophandler registered by registerOpHandler 6015// to prevent the rest of the ophanlers to run 6016function stopOpPropagation() 6017{ 6018 global $ophandler_propagation_stop; 6019 $ophandler_propagation_stop = TRUE; 6020} 6021 6022function universalOpHandler() 6023{ 6024 global $ophandler_propagation_stop; 6025 $ophandler_propagation_stop = FALSE; 6026 global $ophandlers_stack; 6027 global $pageno, $tabno, $op; 6028 $op_stack = $ophandlers_stack[$pageno][$tabno][$op]; 6029 $ret = NULL; 6030 foreach ($op_stack as $callback) 6031 { 6032 $ret_i = call_user_func ($callback); 6033 if ($ret_i != '' || ! isset ($ret)) 6034 $ret = $ret_i; 6035 if ($ophandler_propagation_stop) 6036 break; 6037 } 6038 return $ret; 6039} 6040 6041// Registers additional tabhandler on page-tab pair. 6042// Valid $method values are 'before', 'after' and 'replace'. 6043// 'before' puts your tabhandler in the beginning of the list (and thus before the default) 6044// 'after' puts your tabhandler to the end of the list (and thus after the default) 6045// 'replace': the same as 'after', but the rendered tab is replaced by your output, not appended. See also getRenderedTab 6046function registerTabHandler ($page, $tab, $callback, $method = 'after') 6047{ 6048 global $tabhandlers_stack; 6049 global $tabhandler; 6050 6051 if (! isset ($tabhandlers_stack[$page][$tab])) 6052 $tabhandlers_stack[$page][$tab] = array(); 6053 6054 if (isset ($tabhandler[$page][$tab]) && $tabhandler[$page][$tab] != 'universalTabHandler') 6055 array_push ($tabhandlers_stack[$page][$tab], $tabhandler[$page][$tab]); 6056 $tabhandler[$page][$tab] = 'universalTabHandler'; 6057 6058 if ($method == 'before') 6059 array_unshift ($tabhandlers_stack[$page][$tab], $callback); 6060 elseif ($method == 'after') 6061 array_push ($tabhandlers_stack[$page][$tab], $callback); 6062 elseif ($method == 'replace') 6063 array_push ($tabhandlers_stack[$page][$tab], '!' . $callback); 6064 else 6065 throw new RacktablesError ("unknown tabhandler injection method '$method'", RackTablesError::INTERNAL); 6066} 6067 6068// Returns tab content already rendered by previous tabhandlers in the chain registered by registerTabHandler. 6069// It is useful in custom tabhandlers registered with 'replace' method. 6070function getRenderedTab() 6071{ 6072 global $tabhandler_output; 6073 return $tabhandler_output; 6074} 6075 6076// call this from custom tabhandler registered by registerTabHandler 6077// to prevent the rest of the tabhanlers to run 6078function stopTabPropagation() 6079{ 6080 global $tabhandler_propagation_stop; 6081 $tabhandler_propagation_stop = TRUE; 6082} 6083 6084function universalTabHandler($bypass = NULL) 6085{ 6086 global $tabhandler_propagation_stop; 6087 $tabhandler_propagation_stop = FALSE; 6088 global $tabhandlers_stack; 6089 global $tabhandler_output; 6090 global $pageno, $tabno; 6091 $tab_stack = $tabhandlers_stack[$pageno][$tabno]; 6092 $ret = NULL; 6093 $tabhandler_output = ''; 6094 foreach ($tab_stack as $callback) 6095 { 6096 $do_replace = FALSE; 6097 if ($callback[0] == '!') 6098 { 6099 $callback = substr ($callback, 1); 6100 $do_replace = TRUE; 6101 } 6102 ob_start(); 6103 $ret = call_user_func ($callback, $bypass); 6104 $current_output = ob_get_contents(); 6105 ob_end_clean(); 6106 if ($do_replace) 6107 $tabhandler_output = $current_output; 6108 else 6109 $tabhandler_output .= $current_output; 6110 if ($tabhandler_propagation_stop) 6111 break; 6112 } 6113 echo $tabhandler_output; 6114 return $ret; 6115} 6116 6117// $method could be 'before', 'after', 'chain' 6118function registerHook ($hook_name, $callback, $method = 'after') 6119{ 6120 global $hooks_stack, $hook; 6121 6122 if (! isset ($hooks_stack[$hook_name])) 6123 $hooks_stack[$hook_name] = array(); 6124 6125 if (isset ($hook[$hook_name]) && $hook[$hook_name] != 'universalHookHandler') 6126 array_push ($hooks_stack[$hook_name], $hook[$hook_name]); 6127 $hook[$hook_name] = 'universalHookHandler'; 6128 6129 // If the hook name is a built-in function, the function needs to be the first on the stack. 6130 if (empty ($hooks_stack[$hook_name]) && is_callable ($hook_name)) 6131 array_push ($hooks_stack[$hook_name], $hook_name); 6132 6133 switch ($method) 6134 { 6135 case 'before': 6136 array_unshift ($hooks_stack[$hook_name], $callback); 6137 break; 6138 case 'after': 6139 array_push ($hooks_stack[$hook_name], $callback); 6140 break; 6141 case 'chain': 6142 array_push ($hooks_stack[$hook_name], '!' . $callback); 6143 break; 6144 default: 6145 throw new InvalidArgException ('method', $method, 'Invalid hook method'); 6146 } 6147} 6148 6149// hook handlers dispatcher. registerHook leaves 'universalHookHandler' in $hook 6150function universalHookHandler() 6151{ 6152 global $hook_propagation_stop; 6153 if (! isset ($hook_propagation_stop)) 6154 $hook_propagation_stop = array(); 6155 array_unshift ($hook_propagation_stop, FALSE); 6156 global $hooks_stack; 6157 $ret = NULL; 6158 $bk_params = func_get_args(); 6159 $hook_name = array_shift ($bk_params); 6160 if (! array_key_exists ($hook_name, $hooks_stack) || ! is_array ($hooks_stack[$hook_name])) 6161 throw new RackTablesError ("malformed structure in hooks_stack['{$hook_name}']", RackTablesError::INTERNAL); 6162 foreach ($hooks_stack[$hook_name] as $callback) 6163 { 6164 $params = $bk_params; 6165 if ('!' === substr ($callback, 0, 1)) 6166 { 6167 $callback = substr ($callback, 1); 6168 array_unshift ($params, $ret); 6169 } 6170 if (is_callable ($callback)) 6171 $ret = call_user_func_array ($callback, $params); 6172 else 6173 throw new RackTablesError ("Call of non-existant callback '$callback'", RackTablesError::INTERNAL); 6174 if ($hook_propagation_stop[0]) 6175 break; 6176 } 6177 array_shift ($hook_propagation_stop); 6178 return $ret; 6179} 6180 6181// call this from custom hook registered by registerHook 6182// to prevent the rest of the hooks to run 6183function stopHookPropagation() 6184{ 6185 global $hook_propagation_stop; 6186 if ($hook_propagation_stop) 6187 $hook_propagation_stop[0] = TRUE; 6188} 6189 6190function arePortTypesCompatible ($oif1, $oif2) 6191{ 6192 static $map = NULL; 6193 if (! isset ($map)) 6194 { 6195 $map = array(); 6196 foreach (getPortOIFCompat() as $item) 6197 $map[$item['type1']][$item['type2']] = 1; 6198 } 6199 return isset ($map[$oif1][$oif2]); 6200} 6201 6202function arePortsCompatible ($portinfo_a, $portinfo_b) 6203{ 6204 return arePortTypesCompatible ($portinfo_a['oif_id'], $portinfo_b['oif_id']); 6205} 6206 6207// returns HTML-formatted link to the given entity 6208function mkCellA ($cell, $title = NULL) 6209{ 6210 global $pageno_by_etype; 6211 if (! isset ($pageno_by_etype[$cell['realm']])) 6212 throw new RackTablesError ("Internal structure error in array \$pageno_by_etype. Page for realm '${cell['realm']}' is not set", RackTablesError::INTERNAL); 6213 $cell_page = $pageno_by_etype[$cell['realm']]; 6214 $cell_key = $cell[$cell['realm'] == 'user' ? 'user_id' : 'id']; 6215 if ($title === NULL) 6216 switch ($cell['realm']) 6217 { 6218 case 'object': 6219 case 'ipv4vs': 6220 case 'ipv4net': 6221 case 'ipv6net': 6222 $title = formatEntityName ($cell); 6223 break; 6224 default: 6225 $title = formatRealmName ($cell['realm']) . ' ' . formatEntityName ($cell); 6226 break; 6227 } 6228 return mkA ($title, $cell_page, $cell_key); 6229} 6230 6231// Returns a list of entities of a given realm, like listCells. 6232// An optional $varname is the name of config option with constraint in RackCode. 6233function listConstraint ($realm, $varname = '') 6234{ 6235 $wideList = listCells ($realm); 6236 if ($varname != '' && ('' != $filter = getConfigVar ($varname))) 6237 { 6238 $expr = compileExpression ($filter); 6239 if (! $expr) 6240 return array(); 6241 $wideList = filterCellList ($wideList, $expr); 6242 } 6243 return $wideList; 6244} 6245 6246// Return a simple object list w/o related information, so that the returned value 6247// can be directly used by printSelect(). An optional argument is the name of config 6248// option with constraint in RackCode. 6249function getNarrowObjectList ($varname = '') 6250{ 6251 return formatEntityList (listConstraint ('object', $varname)); 6252} 6253 6254// takes an array of cells, 6255// returns an array indexed by cell id, values are simple text representation of a cell. 6256// Intended to pass its return value to printSelect routine. 6257function formatEntityList ($list) 6258{ 6259 $ret = array(); 6260 foreach ($list as $entity) 6261 $ret[$entity['id']] = formatEntityName ($entity); 6262 asort ($ret); 6263 return $ret; 6264} 6265 6266function formatEntityName ($entity) 6267{ 6268 $ret = ''; 6269 switch ($entity['realm']) 6270 { 6271 case 'object': 6272 $ret = $entity['dname']; 6273 break; 6274 case 'ipv4vs': 6275 $ret = $entity['name'] . ($entity['name'] != '' ? ' ' : '') . '(' . $entity['dname'] . ')'; 6276 break; 6277 case 'ipv4net': 6278 case 'ipv6net': 6279 $ret = $entity['ip'] . '/' . $entity['mask']; 6280 break; 6281 case 'user': 6282 $ret = $entity['user_name']; 6283 break; 6284 case 'ipvs': 6285 case 'ipv4rspool': 6286 case 'file': 6287 case 'rack': 6288 case 'row': 6289 case 'location': 6290 $ret = $entity['name']; 6291 break; 6292 } 6293 if ($ret == '') 6294 $ret = '[unnamed] #' . $entity['id']; 6295 return $ret; 6296} 6297 6298// Returns reversed (top-to-bottom) $unit_no if the rack is configured to be 6299// that way (the calling function tells this), or unchanged $unit_no otherwise. 6300function inverseRackUnit ($height, $unit_no, $reverse) 6301{ 6302 return $reverse ? ($height - $unit_no + 1) : $unit_no; 6303} 6304 6305// returns true either if given domains are the same 6306// or if one is a group and other is its member 6307function sameDomains ($domain_id_1, $domain_id_2) 6308{ 6309 if ($domain_id_1 == $domain_id_2) 6310 return TRUE; 6311 static $cache = array(); 6312 if (! isset ($cache[$domain_id_1])) 6313 $cache[$domain_id_1] = getDomainGroupMembers ($domain_id_1); 6314 if (! isset ($cache[$domain_id_2])) 6315 $cache[$domain_id_2] = getDomainGroupMembers ($domain_id_2); 6316 return in_array ($domain_id_1, $cache[$domain_id_2]) || in_array ($domain_id_2, $cache[$domain_id_1]); 6317} 6318 6319// Checks if 802.1Q port uplink/downlink feature is misconfigured. 6320// Returns FALSE if 802.1Q port role/linking is wrong, TRUE otherwise. 6321function checkPortRole ($vswitch, $portinfo, $port_name, $port_order) 6322{ 6323 if (! $portinfo || ! $portinfo['linked']) 6324 return TRUE; // not linked port 6325 6326 // find linked port with the same name 6327 if ($port_name != $portinfo['name']) 6328 return FALSE; // typo in local port name 6329 6330 $local_auto = ($port_order['vst_role'] == 'uplink' || $port_order['vst_role'] == 'downlink') ? 6331 $port_order['vst_role'] : 6332 FALSE; 6333 $remote_vswitch = getVLANSwitchInfo ($portinfo['remote_object_id']); 6334 if (! $remote_vswitch) 6335 return ! $local_auto; 6336 6337 $remote_pn = $portinfo['remote_name']; 6338 $remote_ports = apply8021QOrder ($remote_vswitch, getStored8021QConfig ($remote_vswitch['object_id'], 'desired', array ($remote_pn))); 6339 if (! array_key_exists($remote_pn, $remote_ports)) 6340 // linked auto-port must have corresponding remote 802.1Q port 6341 return 6342 ! $local_auto && 6343 ! isset ($remote_ports[shortenIfName ($remote_pn, NULL, $portinfo['remote_object_id'])]); // typo in remote port name 6344 $remote = $remote_ports[$remote_pn]; 6345 6346 $remote_auto = ($remote['vst_role'] == 'uplink' || $remote['vst_role'] == 'downlink') ? 6347 $remote['vst_role'] : 6348 FALSE; 6349 6350 if (! $remote_auto && ! $local_auto) 6351 return TRUE; 6352 if ($remote_auto && $local_auto && $local_auto != $remote_auto && sameDomains ($vswitch['domain_id'], $remote_vswitch['domain_id'])) 6353 return TRUE; // auto-calc link ends must belong to the same domain 6354 return FALSE; 6355} 6356 6357# Convert InvalidArgException to InvalidRequestArgException with a choice of 6358# replacing the reference to the failed argument or leaving it unchanged. 6359# 6360# DEPRECATED, use InvalidArgException::newIRAE() 6361function convertToIRAE ($iae, $override_argname = NULL) 6362{ 6363 if (! ($iae instanceof InvalidArgException)) 6364 throw new InvalidArgException ('iae', '(object)', 'not an instance of InvalidArgException class'); 6365 return $iae->newIRAE ($override_argname); 6366} 6367 6368# Produce a textual date/time from a given UNIX timestamp 6369# If timestamp is omitted, time() value is used 6370function datetimestrFromTimestamp ($ts = NULL) 6371{ 6372 if (! isset ($ts)) 6373 $ts = time(); 6374 return strftime (getConfigVar ('DATETIME_FORMAT'), $ts); 6375} 6376 6377# vice versa 6378function timestampFromDatetimestr ($s) 6379{ 6380 $format = getConfigVar ('DATETIME_FORMAT'); 6381 if (FALSE === $tmp = strptime ($s, $format)) 6382 throw new InvalidArgException ('s', $s, "not a date in format '${format}'"); 6383 $ret = mktime 6384 ( 6385 $tmp['tm_hour'], # 0~23 6386 $tmp['tm_min'], # 0~59 6387 $tmp['tm_sec'], # 0~59 6388 $tmp['tm_mon'] + 1, # 0~11 -> 1~12 6389 $tmp['tm_mday'], # 1~31 6390 $tmp['tm_year'] + 1900 # 0~n -> 1900~n 6391 ); 6392 # PHP UNIX time has a wider (at least on 64-bit systems) range than the unsigned 6393 # 32-bit integer type RackTables allocates in the database for UNIX time. 6394 if ($ret < 0) 6395 throw new InvalidArgException ('s', $s, 'is before 1970-01-01 00:00:00 UTC'); 6396 if ($ret >= 0xFFFFFFFF) 6397 throw new InvalidArgException ('s', $s, 'is on or after 2106-02-07 06:28:15 UTC'); 6398 return $ret; 6399} 6400 6401function SQLDateFromDateStr ($s, $format = NULL) 6402{ 6403 if ($format === NULL) 6404 $format = getConfigVar ('DATEONLY_FORMAT'); 6405 if (FALSE === $tmp = strptime ($s, $format)) 6406 throw new InvalidArgException ('s', $s, "not a date in format '${format}'"); 6407 $y = $tmp['tm_year'] + 1900; 6408 $m = $tmp['tm_mon'] + 1; 6409 $d = $tmp['tm_mday']; 6410 if ($y < 1000 || $y > 9999) 6411 throw new InvalidArgException ('s', $s, 'year out of range'); 6412 if (! checkdate ($m, $d, $y)) 6413 throw new InvalidArgException ('s', $s, 'not a Gregorian calendar date'); 6414 return sprintf ('%4u-%02u-%02u', $y, $m, $d); 6415} 6416 6417// This function returns either -1 or 0 or 1, it can be used with usort(). 6418function cmpSQLDates ($date1, $date2) 6419{ 6420 if 6421 ( 6422 ! preg_match ('/^(\d\d\d\d)-(\d\d)-(\d\d)$/', $date1, $m1) || 6423 ! checkdate ($m1[2], $m1[3], $m1[1]) 6424 ) 6425 throw new InvalidArgException ('date1', $date1, 'not a valid SQL date'); 6426 if 6427 ( 6428 ! preg_match ('/^(\d\d\d\d)-(\d\d)-(\d\d)$/', $date2, $m2) || 6429 ! checkdate ($m2[2], $m2[3], $m2[1]) 6430 ) 6431 throw new InvalidArgException ('date2', $date2, 'not a valid SQL date'); 6432 if (0 != $ret = numCompare ($m1[1], $m2[1])) 6433 return $ret; 6434 if (0 != $ret = numCompare ($m1[2], $m2[2])) 6435 return $ret; 6436 return numCompare ($m1[3], $m2[3]); 6437} 6438 6439# Produce a human-readable clue, such as 'YYYY-MM-DD' for '%Y-%m-%d'. 6440function datetimeFormatHint ($format) 6441{ 6442 $subst = array 6443 ( 6444 # leave ISO-8601:1988 week-numbering years (%g, %G) alone 6445 '%y' => 'YY', 6446 '%Y' => 'YYYY', 6447 '%m' => 'MM', 6448 '%d' => 'DD', 6449 ); 6450 return str_replace (array_keys ($subst), $subst, $format); 6451} 6452 6453// Return TRUE, if the object belongs to specified type and has 6454// specified attribute belonging to the given set of values. 6455function checkTypeAndAttribute ($object_id, $type_id, $attr_id, $values) 6456{ 6457 $object = spotEntity ('object', $object_id); 6458 if ($object['objtype_id'] == $type_id) 6459 foreach (getAttrValues ($object_id) as $record) 6460 if ($record['id'] == $attr_id && in_array ($record['key'], $values)) 6461 return TRUE; 6462 return FALSE; 6463} 6464 6465// The old name, remove at a later point. 6466function nullEmptyStr ($str) 6467{ 6468 return nullIfEmptyStr ($str); 6469} 6470 6471function nullIfEmptyStr ($str) 6472{ 6473 return $str != '' ? $str : NULL; 6474} 6475 6476function nullIfFalse ($x) 6477{ 6478 return $x === FALSE ? NULL : $x; 6479} 6480 6481function nullIfZero ($x) 6482{ 6483 return $x == 0 ? NULL : $x; 6484} 6485 6486function emptyStrIfZero ($x) 6487{ 6488 return ($x === 0 || $x === '0') ? '' : $x; 6489} 6490 6491function printLocationChildrenSelectOptions ($location, $parent_id, $location_id = NULL, $level = 0) 6492{ 6493 $self = __FUNCTION__; 6494 $level++; 6495 foreach ($location['kids'] as $subLocation) 6496 { 6497 if ($subLocation['id'] == $location_id) 6498 continue; 6499 echo "<option value=${subLocation['id']}"; 6500 if ($subLocation['id'] == $parent_id) 6501 echo ' selected'; 6502 echo '>' . str_repeat ('» ', $level) . "${subLocation['name']}</option>\n"; 6503 if ($subLocation['kidc'] > 0) 6504 $self ($subLocation, $parent_id, $location_id, $level); 6505 } 6506} 6507 6508function validTagName ($s, $allow_autotag = FALSE) 6509{ 6510 return preg_match (TAGNAME_REGEXP, $s) || 6511 ($allow_autotag && preg_match (AUTOTAGNAME_REGEXP, $s)); 6512} 6513 6514// returns html string with parent location names 6515// link: if each name should be wrapped in an href 6516function getLocationTrail ($location_id, $link = TRUE, $spacer = ' : ') 6517{ 6518 // XXX: $location_tree is an array, not a tree 6519 static $location_tree = array (); 6520 if (count ($location_tree) == 0) 6521 foreach (listCells ('location') as $location) 6522 $location_tree[$location['id']] = array ('parent_id' => $location['parent_id'], 'name' => $location['name']); 6523 6524 // prepend parent location(s) to given location string 6525 $names = array(); 6526 $id = $location_id; 6527 $locationIdx = 0; 6528 while (isset ($id)) 6529 { 6530 if ($locationIdx == 20) 6531 { 6532 showWarning ('Warning: There is likely a circular reference in the location tree.'); 6533 break; 6534 } 6535 $name = $location_tree[$id]['name']; 6536 array_unshift ($names, $link ? mkA ($name, 'location', $id) : $name); 6537 $id = $location_tree[$id]['parent_id']; 6538 $locationIdx++; 6539 } 6540 return implode ($spacer, $names); 6541} 6542 6543function cmp_array_sizes ($a, $b) 6544{ 6545 return numCompare (count ($a), count ($b)); 6546} 6547 6548// parses the value of MGMT_PROTOS config variable and returns an array 6549// indexed by protocol name with corresponding textual RackCode values 6550function getMgmtProtosConfig ($ignore_cache = FALSE) 6551{ 6552 static $cache = NULL; 6553 if (!$ignore_cache && isset ($cache)) 6554 return $cache; 6555 6556 $cache = array(); 6557 $config = getConfigVar ('MGMT_PROTOS'); 6558 foreach (explode (';', $config) as $item) 6559 { 6560 $item = trim ($item); 6561 if ($item == '') 6562 continue; 6563 if (preg_match('/^(\S+)\s*:\s*(.*)$/', $item, $m)) 6564 $cache[$m[1]] = $m[2]; 6565 } 6566 return $cache; 6567} 6568 6569// returns compiled RackCode expression or NULL if syntax error occurs 6570// caches the result in $exprCache global 6571function compileExpression ($code, $do_cache_lookup = TRUE) 6572{ 6573 global $exprCache; 6574 if (! is_array ($exprCache)) 6575 $exprCache = array(); 6576 if ($do_cache_lookup && array_key_exists($code, $exprCache)) 6577 return $exprCache[$code]; 6578 6579 $ret = NULL; 6580 $parse = spotPayload ($code, 'SYNT_EXPR'); 6581 if ($parse['result'] == 'ACK') 6582 $ret = $parse['load']; 6583 $exprCache[$code] = $ret; 6584 return $ret; 6585} 6586 6587// a caching wrapper around detectDeviceBreed and shortenIfName 6588function shortenPortName ($if_name, $object_id) 6589{ 6590 static $breed_cache = array(); 6591 if (! array_key_exists($object_id, $breed_cache)) 6592 $breed_cache[$object_id] = detectDeviceBreed ($object_id); 6593 $breed = $breed_cache[$object_id]; 6594 return $breed == '' ? $if_name : shortenIfName ($if_name, $breed); 6595} 6596 6597// returns an array of IP ranges of size $dst_mask > $netinfo['mask'], or array ($netinfo) 6598function splitNetworkByMask ($netinfo, $dst_mask) 6599{ 6600 $self = __FUNCTION__; 6601 if ($netinfo['mask'] >= $dst_mask) 6602 return array ($netinfo); 6603 6604 return array_merge 6605 ( 6606 $self (constructIPRange ($netinfo['ip_bin'], $netinfo['mask'] + 1), $dst_mask), 6607 $self (constructIPRange (ip_last ($netinfo), $netinfo['mask'] + 1), $dst_mask) 6608 ); 6609} 6610 6611// this function is used both to remember and to retrieve the last created entity's ID 6612// it stores given id in the static var, and returns the stored value is called without args 6613// used in plugins to make additional work on created entity in the chained ophandler 6614// returns an array of realm-ID pairs 6615function lastCreated ($realm = NULL, $id = NULL) 6616{ 6617 static $last_ids = array(); 6618 if (isset ($realm) && isset ($id)) 6619 $last_ids[] = array('realm' => $realm, 'id' => $id); 6620 return $last_ids; 6621} 6622 6623// returns last id of a given type from lastCreated() result array 6624function getLastCreatedId ($realm) 6625{ 6626 foreach (array_reverse (lastCreated()) as $item) 6627 if ($item['realm'] == $realm) 6628 return $item['id']; 6629} 6630 6631function formatPatchCableHeapAsPlainText ($heap) 6632{ 6633 $text = "${heap['amount']} pcs: [${heap['end1_connector']}] ${heap['pctype']} [${heap['end2_connector']}]"; 6634 if ($heap['description'] != '') 6635 $text .= " (${heap['description']})"; 6636 return stringForOption ($text, 512); 6637} 6638 6639// takes a list of structures and the field name in those structures. 6640// returns a two-dimentional list indexed by the value of the given field 6641// the subsequent index value is taken from the index of the original $list. 6642function groupBy ($list, $group_field) 6643{ 6644 $ret = array(); 6645 if (! is_array ($list)) 6646 throw new InvalidArgException ('list', $list, 'must be an array'); 6647 foreach ($list as $index => $item) 6648 { 6649 if (! is_array ($item)) 6650 throw new InvalidArgException ("list[${index}]", $item, 'must be an array'); 6651 $key = ''; 6652 if (isset ($item[$group_field])) 6653 $key = (string) $item[$group_field]; 6654 $ret[$key][$index] = $item; 6655 } 6656 return $ret; 6657} 6658 6659// returns the associative $array sorted by its keys 6660// sort order is taken from the $order array: 6661// $array[i] is arranged with $array[j] conforming to 6662// numeric comparison of $order[i] and $order[j]. 6663function customKsort ($array, $order) 6664{ 6665 $ret = array(); 6666 foreach ($array as $key => $value) 6667 $ret[$key] = isset ($order[$key]) ? $order[$key] : array_last ($order) + 1; 6668 6669 asort ($ret, SORT_NUMERIC); 6670 foreach (array_keys ($ret) as $key) 6671 $ret[$key] = $array[$key]; 6672 return $ret; 6673} 6674 6675// RT uses PHP sessions on demand and tries to minimize session lifetime 6676// to allow concurrent operations for a single user. 6677// You should call session_commit after each call of this function. 6678function startSession() 6679{ 6680 if (session_status() != PHP_SESSION_ACTIVE) 6681 session_start(); 6682} 6683 6684// loads session data. Use if you need to only read from _SESSION. 6685function startROSession() 6686{ 6687 startSession(); 6688 session_commit(); 6689} 6690 6691// removes a VLAN from ports that contain it, but keep a VLAN amongst others in a range 6692function pinpointDeleteVlan ($domain_id, $vlan_id) 6693{ 6694 $ret = 0; 6695 $vlan_ck = $domain_id . '-' . $vlan_id; 6696 $domain_vlanlist = getDomainVLANList ($domain_id); 6697 $used_ports = getVLANConfiguredPorts ($vlan_ck); 6698 foreach ($used_ports as $object_id => $port_list) 6699 { 6700 $vswitch = getVLANSwitchInfo ($object_id); // get mutex rev 6701 $D = getStored8021QConfig ($object_id, 'desired', $port_list); 6702 $changes = array(); 6703 foreach ($port_list as $pn) 6704 { 6705 if (! isset ($D[$pn])) 6706 continue; 6707 // remove vlan from a port only if its range does not contain foreign vlans 6708 foreach (listToRanges ($D[$pn]['allowed']) as $range) 6709 if (matchVLANFilter ($vlan_id, array ($range))) 6710 { 6711 for ($i = $range['from']; $i <= $range['to']; ++$i) 6712 if (! isset ($domain_vlanlist[$i])) 6713 continue 3; // keep vlan, skip to next port 6714 } 6715 // remove vlan from port 6716 $conf = $D[$pn]; 6717 $conf['allowed'] = array_diff ($conf['allowed'], array ($vlan_id)); 6718 if ($conf['mode'] == 'access') 6719 $conf['mode'] = 'trunk'; 6720 if ($conf['native'] == $vlan_id) 6721 $conf['native'] = 0; 6722 $changes[$pn] = $conf; 6723 } 6724 if ($changes) 6725 $ret += apply8021qChangeRequest ($object_id, $changes, FALSE, $vswitch['mutex_rev']); 6726 } 6727 6728 return $ret; 6729} 6730 6731function etypeByPageno ($pg = NULL) 6732{ 6733 global $etype_by_pageno, $pageno; 6734 if ($pg === NULL) 6735 $pg = $pageno; 6736 if (! array_key_exists ($pg, $etype_by_pageno)) 6737 throw new RackTablesError ('key not found', RackTablesError::INTERNAL); 6738 return $etype_by_pageno[$pg]; 6739} 6740 6741function requireListOfFiles ($x) 6742{ 6743 if (! is_array ($x)) 6744 require_once $x; 6745 else 6746 foreach ($x as $filename) 6747 require_once $filename; 6748} 6749 6750function requireExtraFiles ($reqlist, $pageno, $tabno) 6751{ 6752 if (array_key_exists ("${pageno}-${tabno}", $reqlist)) 6753 requireListOfFiles ($reqlist["${pageno}-${tabno}"]); 6754 if (array_key_exists ("${pageno}-*", $reqlist)) 6755 requireListOfFiles ($reqlist["${pageno}-*"]); 6756} 6757 6758// Return the text as a list of lines after removing CRs, empty lines 6759// and leading/trailing whitespace. 6760function textareaCooked ($text) 6761{ 6762 $ret = dos2unix ($text); 6763 $ret = explode ("\n", $ret); 6764 $ret = array_map ('trim', $ret); 6765 $ret = array_diff ($ret, array ('')); 6766 $ret = array_values ($ret); // reindex to be consistent enough for the tests 6767 return $ret; 6768} 6769 6770// Used to fill $desiredPorts argument for replaceObjectPorts. 6771// Call this function just like commitAddPort except the first argument 6772function addDesiredPort (&$desiredPorts, $port_name, $port_type_id, $port_label, $port_l2address) 6773{ 6774 list ($iif_id, $oif_id) = parsePortIIFOIF ($port_type_id); 6775 $desiredPorts["{$port_name}-{$iif_id}"] = array ( 6776 'name' => $port_name, 6777 'iif_id' => $iif_id, 6778 'oif_id' => $oif_id, 6779 'label' => $port_label, 6780 'l2address' => $port_l2address, 6781 ); 6782} 6783 6784// synchronizes the list of the object ports to the desired list of ports 6785// $desiredPorts is a list of ports in getObjectPortsAndLinks format. 6786// required port fields are name, iif_id, oif_id, label and l2address. 6787// $desiredPorts can be filled by addDesiredPort function using commitAddPort format 6788function replaceObjectPorts ($object_id, $desiredPorts) 6789{ 6790 global $dbxlink; 6791 $to_delete = $to_update = $real_ports = array(); 6792 6793 // The check that does not require access to the database goes first. 6794 foreach (array_keys ($desiredPorts) as $k) 6795 $desiredPorts[$k]['l2address'] = l2addressForDatabase ($desiredPorts[$k]['l2address']); 6796 6797 // Further processing must be done with exclusive access to the table. Even when the 6798 // only changes requested are to add ports w/o MAC addresses or to update existing 6799 // ports in a way that does not introduce new MAC addresses, it is impossible to 6800 // tell reliably which ports require which actions without locking the table first. 6801 $dbxlink->exec ('LOCK TABLES Port WRITE, PortLog WRITE, Link READ'); 6802 foreach (getObjectPortsAndLinksTerse ($object_id) as $port) 6803 { 6804 $key = "{$port['name']}-{$port['iif_id']}"; 6805 if (! array_key_exists ($key, $desiredPorts)) 6806 { 6807 $to_delete[] = $port; 6808 continue; 6809 } 6810 if ($port['l2address'] != $desiredPorts[$key]['l2address'] || $port['label'] != $desiredPorts[$key]['label']) 6811 $to_update[$key] = $port; 6812 $real_ports[$key] = 1; 6813 } 6814 $to_add = array_diff_key ($desiredPorts, $real_ports); 6815 6816 try 6817 { 6818 assertUniqueL2Addresses (reduceSubarraysToColumn (array_merge ($to_update, $to_add), 'l2address'), $object_id); 6819 // Make the actual changes. 6820 foreach ($to_delete as $port) 6821 if ($port['link_count'] != 0) 6822 showWarning (sprintf ("Port %s should be deleted, but it's used", formatPort ($port))); 6823 else 6824 usePreparedDeleteBlade ('Port', array ('id' => $port['id'])); 6825 foreach ($to_update as $key => $port) 6826 commitUpdatePortReal 6827 ( 6828 $object_id, 6829 $port['id'], 6830 $port['name'], 6831 $port['iif_id'], 6832 $port['oif_id'], 6833 $desiredPorts[$key]['label'], 6834 $desiredPorts[$key]['l2address'], 6835 $port['reservation_comment'] 6836 ); 6837 foreach ($to_add as $key => $port) 6838 commitAddPortReal 6839 ( 6840 $object_id, 6841 $port['name'], 6842 $port['iif_id'], 6843 $port['oif_id'], 6844 $port['label'], 6845 $port['l2address'] 6846 ); 6847 } 6848 catch (Exception $e) 6849 { 6850 $dbxlink->exec ('UNLOCK TABLES'); 6851 throw $e; 6852 } 6853 6854 $dbxlink->exec ('UNLOCK TABLES'); 6855 showSuccess (sprintf ('Added ports: %u, changed: %u, deleted: %u', count ($to_add), count ($to_update), count ($to_delete))); 6856} 6857 6858function formatPluginState ($state) 6859{ 6860 $map = array 6861 ( 6862 'disabled' => 'Disabled', 6863 'enabled' => 'Enabled', 6864 'not_installed' => 'Not installed', 6865 ); 6866 return array_fetch ($map, $state, 'unknown'); 6867} 6868