r2197 - initial spare networks code
[racktables] / inc / functions.php
index 3da481bfeba76f1106239be343c553b6a9eed8cb..b4cc1d909618cf4f2b0f24ffbb32dd6a2ae141c0 100644 (file)
@@ -269,316 +269,86 @@ function mergeGridFormToRack (&$rackData)
                }
 }
 
+// netmask conversion from length to number
 function binMaskFromDec ($maskL)
 {
-       $binmask=0;
-       for ($i=0; $i<$maskL; $i++)
-       {
-               $binmask*=2;
-               $binmask+=1;
-       }
-       for ($i=$maskL; $i<32; $i++)
-       {
-               $binmask*=2;
-       }
-       return $binmask;
-}
-
-function binInvMaskFromDec ($maskL)
-{
-       $binmask=0;
-       for ($i=0; $i<$maskL; $i++)
-       {
-               $binmask*=2;
-       }
-       for ($i=$maskL; $i<32; $i++)
-       {
-               $binmask*=2;
-               $binmask+=1;
-       }
-       return $binmask;
-}
-
-function getIPRange ($id = 0)
-{
-       global $dbxlink;
-       $query =
-               "select ".
-               "id as IPRanges_id, ".
-               "INET_NTOA(ip) as IPRanges_ip, ".
-               "mask as IPRanges_mask, ".
-               "name as IPRanges_name ".
-               "from IPRanges ".
-               "where id = '$id'";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       $ret = array();
-       $row = $result->fetch (PDO::FETCH_ASSOC);
-       if ($row == NULL)
-               return $ret;
-       $ret['id'] = $row['IPRanges_id'];
-       $ret['ip'] = $row['IPRanges_ip'];
-       $ret['ip_bin'] = ip2long ($row['IPRanges_ip']);
-       $ret['mask_bin'] = binMaskFromDec($row['IPRanges_mask']);
-       $ret['mask_bin_inv'] = binInvMaskFromDec($row['IPRanges_mask']);
-       $ret['name'] = $row['IPRanges_name'];
-       $ret['mask'] = $row['IPRanges_mask'];
-       $ret['addrlist'] = array();
-       $result->closeCursor();
-       unset ($result);
-       // We risk losing some significant bits in an unsigned 32bit integer,
-       // unless it is converted to a string.
-       $db_first = "'" . sprintf ('%u', 0x00000000 + $ret['ip_bin'] & $ret['mask_bin']) . "'";
-       $db_last  = "'" . sprintf ('%u', 0x00000000 + $ret['ip_bin'] | ($ret['mask_bin_inv'])) . "'";
-
-       // Don't try to build up the whole structure in a single pass. Request
-       // the list of user comments and reservations and merge allocations in
-       // at a latter point.
-       $query =
-               "select INET_NTOA(ip) as ip, name, reserved from IPAddress " .
-               "where ip between ${db_first} and ${db_last} " .
-               "and (reserved = 'yes' or name != '')";
-       $result = $dbxlink->query ($query);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ip_bin = ip2long ($row['ip']);
-               $ret['addrlist'][$ip_bin] = $row;
-               $tmp = array();
-               foreach (array ('ip', 'name', 'reserved') as $cname)
-                       $tmp[$cname] = $row[$cname];
-               $tmp['references'] = array();
-               $tmp['lbrefs'] = array();
-               $tmp['rsrefs'] = array();
-               $ret['addrlist'][$ip_bin] = $tmp;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query =
-               "select INET_NTOA(ipb.ip) as ip, ro.id as object_id, " .
-               "ro.name as object_name, ipb.name, ipb.type, objtype_id, " .
-               "dict_value as objtype_name from " .
-               "IPBonds as ipb inner join RackObject as ro on ipb.object_id = ro.id " .
-               "left join Dictionary on objtype_id=dict_key natural join Chapter " .
-               "where ip between ${db_first} and ${db_last} " .
-               "and chapter_name = 'RackObjectType'" .
-               "order by ipb.type, object_name";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ip_bin = ip2long ($row['ip']);
-               if (!isset ($ret['addrlist'][$ip_bin]))
-               {
-                       $ret['addrlist'][$ip_bin] = array();
-                       $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
-                       $ret['addrlist'][$ip_bin]['name'] = '';
-                       $ret['addrlist'][$ip_bin]['reserved'] = 'no';
-                       $ret['addrlist'][$ip_bin]['references'] = array();
-                       $ret['addrlist'][$ip_bin]['lbrefs'] = array();
-                       $ret['addrlist'][$ip_bin]['rsrefs'] = array();
-               }
-               $tmp = array();
-               foreach (array ('object_id', 'type', 'name') as $cname)
-                       $tmp[$cname] = $row[$cname];
-               $quasiobject['name'] = $row['object_name'];
-               $quasiobject['objtype_id'] = $row['objtype_id'];
-               $quasiobject['objtype_name'] = $row['objtype_name'];
-               $tmp['object_name'] = displayedName ($quasiobject);
-               $ret['addrlist'][$ip_bin]['references'][] = $tmp;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query = "select vs_id, inet_ntoa(vip) as ip, vport, proto, " .
-               "object_id, objtype_id, ro.name, dict_value as objtype_name from " .
-               "IPVirtualService as vs inner join IPLoadBalancer as lb on vs.id = lb.vs_id " .
-               "inner join RackObject as ro on lb.object_id = ro.id " .
-               "left join Dictionary on objtype_id=dict_key " .
-               "natural join Chapter " .
-               "where vip between ${db_first} and ${db_last} " .
-               "and chapter_name = 'RackObjectType'" .
-               "order by vport, proto, ro.name, object_id";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ip_bin = ip2long ($row['ip']);
-               if (!isset ($ret['addrlist'][$ip_bin]))
-               {
-                       $ret['addrlist'][$ip_bin] = array();
-                       $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
-                       $ret['addrlist'][$ip_bin]['name'] = '';
-                       $ret['addrlist'][$ip_bin]['reserved'] = 'no';
-                       $ret['addrlist'][$ip_bin]['references'] = array();
-                       $ret['addrlist'][$ip_bin]['lbrefs'] = array();
-                       $ret['addrlist'][$ip_bin]['rsrefs'] = array();
-               }
-               $tmp = $qbject = array();
-               foreach (array ('object_id', 'vport', 'proto', 'vs_id') as $cname)
-                       $tmp[$cname] = $row[$cname];
-               foreach (array ('name', 'objtype_id', 'objtype_name') as $cname)
-                       $qobject[$cname] = $row[$cname];
-               $tmp['object_name'] = displayedName ($qobject);
-               $ret['addrlist'][$ip_bin]['lbrefs'][] = $tmp;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query = "select inet_ntoa(rsip) as ip, rsport, rspool_id, rsp.name as rspool_name from " .
-               "IPRealServer as rs inner join IPRSPool as rsp on rs.rspool_id = rsp.id " .
-               "where rsip between ${db_first} and ${db_last} " .
-               "order by ip, rsport, rspool_id";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ip_bin = ip2long ($row['ip']);
-               if (!isset ($ret['addrlist'][$ip_bin]))
-               {
-                       $ret['addrlist'][$ip_bin] = array();
-                       $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
-                       $ret['addrlist'][$ip_bin]['name'] = '';
-                       $ret['addrlist'][$ip_bin]['reserved'] = 'no';
-                       $ret['addrlist'][$ip_bin]['references'] = array();
-                       $ret['addrlist'][$ip_bin]['lbrefs'] = array();
-                       $ret['addrlist'][$ip_bin]['rsrefs'] = array();
-               }
-               $tmp = array();
-               foreach (array ('rspool_id', 'rsport', 'rspool_name') as $cname)
-                       $tmp[$cname] = $row[$cname];
-               $ret['addrlist'][$ip_bin]['rsrefs'][] = $tmp;
-       }
-
-       return $ret;
-}
-
-// Don't require any records in IPAddress, but if there is one,
-// merge the data between getting allocation list. Collect enough data
-// to call displayedName() ourselves.
-function getIPAddress ($ip = 0)
-{
-       $ret = array
-       (
-               'bonds' => array(),
-               'outpf' => array(),
-               'inpf' => array(),
-               'vslist' => array(),
-               'rslist' => array(),
-               'exists' => 0,
-               'name' => '',
-               'reserved' => 'no'
+       $map_straight = array (
+               0  => 0x00000000,
+               1  => 0x80000000,
+               2  => 0xc0000000,
+               3  => 0xe0000000,
+               4  => 0xf0000000,
+               5  => 0xf8000000,
+               6  => 0xfc000000,
+               7  => 0xfe000000,
+               8  => 0xff000000,
+               9  => 0xff800000,
+               10 => 0xffc00000,
+               11 => 0xffe00000,
+               12 => 0xfff00000,
+               13 => 0xfff80000,
+               14 => 0xfffc0000,
+               15 => 0xfffe0000,
+               16 => 0xffff0000,
+               17 => 0xffff8000,
+               18 => 0xffffc000,
+               19 => 0xffffe000,
+               20 => 0xfffff000,
+               21 => 0xfffff800,
+               22 => 0xfffffc00,
+               23 => 0xfffffe00,
+               24 => 0xffffff00,
+               25 => 0xffffff80,
+               26 => 0xffffffc0,
+               27 => 0xffffffe0,
+               28 => 0xfffffff0,
+               29 => 0xfffffff8,
+               30 => 0xfffffffc,
+               31 => 0xfffffffe,
+               32 => 0xffffffff,
        );
-       $query =
-               "select ".
-               "name, reserved ".
-               "from IPAddress ".
-               "where ip = INET_ATON('$ip') and (reserved = 'yes' or name != '')";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       if ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ret['exists'] = 1;
-               $ret['name'] = $row['name'];
-               $ret['reserved'] = $row['reserved'];
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query =
-               "select ".
-               "IPBonds.object_id as object_id, ".
-               "IPBonds.name as name, ".
-               "IPBonds.type as type, ".
-               "objtype_id, dict_value as objtype_name, " .
-               "RackObject.name as object_name ".
-               "from IPBonds join RackObject on IPBonds.object_id=RackObject.id ".
-               "left join Dictionary on objtype_id=dict_key natural join Chapter " .
-               "where IPBonds.ip=INET_ATON('$ip') ".
-               "and chapter_name = 'RackObjectType' " .
-               "order by RackObject.id, IPBonds.name";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       $count = 0;
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ret['bonds'][$count]['object_id'] = $row['object_id'];
-               $ret['bonds'][$count]['name'] = $row['name'];
-               $ret['bonds'][$count]['type'] = $row['type'];
-               $qo = array();
-               $qo['name'] = $row['object_name'];
-               $qo['objtype_id'] = $row['objtype_id'];
-               $qo['objtype_name'] = $row['objtype_name'];
-               $ret['bonds'][$count]['object_name'] = displayedName ($qo);
-               $count++;
-               $ret['exists'] = 1;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query = "select id, vport, proto, name from IPVirtualService where vip = inet_aton('${ip}')";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $new = $row;
-               $new['vip'] = $ip;
-               $ret['vslist'][] = $new;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query = "select inservice, rsport, IPRSPool.id as pool_id, IPRSPool.name as poolname from " .
-               "IPRealServer inner join IPRSPool on rspool_id = IPRSPool.id " .
-               "where rsip = inet_aton('${ip}')";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $new = $row;
-               $new['rsip'] = $ip;
-               $ret['rslist'][] = $new;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query =
-               "select " .
-               "proto, " .
-               "INET_NTOA(localip) as localip, " .
-               "localport, " .
-               "INET_NTOA(remoteip) as remoteip, " .
-               "remoteport, " .
-               "description " .
-               "from PortForwarding " .
-               "where remoteip = inet_aton('${ip}') or localip = inet_aton('${ip}') " .
-               "order by localip, localport, remoteip, remoteport, proto";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               if ($row['remoteip'] == $ip)
-                       $ret['inpf'][] = $row;
-               if ($row['localip'] == $ip)
-                       $ret['outpf'][] = $row;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       return $ret;
+       return $map_straight[$maskL];
 }
 
-function bindIpToObject ($ip = '', $object_id = 0, $name = '', $type = '')
+// complementary value
+function binInvMaskFromDec ($maskL)
 {
-       global $dbxlink;
-
-       $range = getRangeByIp ($ip);
-       if (!$range)
-               return 'Non-existant ip address. Try adding IP range first';
-
-       $result = useInsertBlade
-       (
-               'IPBonds',
-               array
-               (
-                       'ip' => "INET_ATON('$ip')",
-                       'object_id' => "'${object_id}'",
-                       'name' => "'${name}'",
-                       'type' => "'${type}'"
-               )
+       $map_compl = array (
+               0  => 0xffffffff,
+               1  => 0x7fffffff,
+               2  => 0x3fffffff,
+               3  => 0x1fffffff,
+               4  => 0x0fffffff,
+               5  => 0x07ffffff,
+               6  => 0x03ffffff,
+               7  => 0x01ffffff,
+               8  => 0x00ffffff,
+               9  => 0x007fffff,
+               10 => 0x003fffff,
+               11 => 0x001fffff,
+               12 => 0x000fffff,
+               13 => 0x0007ffff,
+               14 => 0x0003ffff,
+               15 => 0x0001ffff,
+               16 => 0x0000ffff,
+               17 => 0x00007fff,
+               18 => 0x00003fff,
+               19 => 0x00001fff,
+               20 => 0x00000fff,
+               21 => 0x000007ff,
+               22 => 0x000003ff,
+               23 => 0x000001ff,
+               24 => 0x000000ff,
+               25 => 0x0000007f,
+               26 => 0x0000003f,
+               27 => 0x0000001f,
+               28 => 0x0000000f,
+               29 => 0x00000007,
+               30 => 0x00000003,
+               31 => 0x00000001,
+               32 => 0x00000000,
        );
-       return $result ? '' : (__FUNCTION__ . '(): useInsertBlade() failed');
+       return $map_compl[$maskL];
 }
 
 // This function looks up 'has_problems' flag for 'T' atoms
@@ -608,48 +378,6 @@ function search_cmpObj ($a, $b)
        return ($a['score'] > $b['score'] ? -1 : 1);
 }
 
-// This function performs search and then calculates score for each result.
-// Given previous search results in $objects argument, it adds new results
-// to the array and updates score for existing results, if it is greater than
-// existing score.
-function mergeSearchResults (&$objects, $terms, $fieldname)
-{
-       global $dbxlink;
-       $query =
-               "select name, label, asset_no, barcode, ro.id, dict_key as objtype_id, " .
-               "dict_value as objtype_name, asset_no from RackObject as ro inner join Dictionary " .
-               "on objtype_id = dict_key natural join Chapter where chapter_name = 'RackObjectType' and ";
-       $count = 0;
-       foreach (explode (' ', $terms) as $term)
-       {
-               if ($count) $query .= ' or ';
-               $query .= "${fieldname} like '%$term%'";
-               $count++;
-       }
-       $query .= " order by ${fieldname}";
-       $result = useSelectBlade ($query, __FUNCTION__);
-// FIXME: this dead call was executed 4 times per 1 object search!
-//     $typeList = getObjectTypeList();
-       $clist = array ('id', 'name', 'label', 'asset_no', 'barcode', 'objtype_id', 'objtype_name');
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               foreach ($clist as $cname)
-                       $object[$cname] = $row[$cname];
-               $object['score'] = 0;
-               $object['dname'] = displayedName ($object);
-               unset ($object['objtype_id']);
-               foreach (explode (' ', $terms) as $term)
-                       if (strstr ($object['name'], $term))
-                               $object['score'] += 1;
-               unset ($object['name']);
-               if (!isset ($objects[$row['id']]))
-                       $objects[$row['id']] = $object;
-               elseif ($objects[$row['id']]['score'] < $object['score'])
-                       $objects[$row['id']]['score'] = $object['score'];
-       }
-       return $objects;
-}
-
 function getObjectSearchResults ($terms)
 {
        $objects = array();
@@ -804,27 +532,6 @@ function sortRacks ($a, $b)
        return sortTokenize($a['row_name'] . ': ' . $a['name'], $b['row_name'] . ': ' . $b['name']);
 }
 
-function eq ($a, $b)
-{
-       return $a==$b;
-}
-
-function neq ($a, $b)
-{
-       return $a!=$b;
-}
-
-function countRefsOfType ($refs, $type, $eq)
-{
-       $count=0;
-       foreach ($refs as $ref)
-       {
-               if ($eq($ref['type'], $type))
-                       $count++;
-       }
-       return $count;
-}
-
 function sortEmptyPorts ($a, $b)
 {
        $objname_cmp = sortTokenize($a['Object_name'], $b['Object_name']);
@@ -850,8 +557,6 @@ function sortObjectAddressesAndNames ($a, $b)
        return $objname_cmp;
 }
 
-
-
 function sortAddresses ($a, $b)
 {
        $name_cmp = sortTokenize($a['name'], $b['name']);
@@ -875,121 +580,6 @@ function buildPortCompatMatrixFromList ($portTypeList, $portCompatList)
        return $matrix;
 }
 
-function newPortForwarding($object_id, $localip, $localport, $remoteip, $remoteport, $proto, $description)
-{
-       global $dbxlink;
-
-       $range = getRangeByIp($localip);
-       if (!$range)
-               return "$localip: Non existant ip";
-       
-       $range = getRangeByIp($remoteip);
-       if (!$range)
-               return "$remoteip: Non existant ip";
-       
-       if ( ($localport <= 0) or ($localport >= 65536) )
-               return "$localport: invaild port";
-
-       if ( ($remoteport <= 0) or ($remoteport >= 65536) )
-               return "$remoteport: invaild port";
-
-       $result = useInsertBlade
-       (
-               'PortForwarding',
-               array
-               (
-                       'object_id' => $object_id,
-                       'localip' => "INET_ATON('${localip}')",
-                       'remoteip' => "INET_ATON('$remoteip')",
-                       'localport' => $localport,
-                       'remoteport' => $remoteport,
-                       'proto' => "'${proto}'",
-                       'description' => "'${description}'",
-               )
-       );
-       if ($result)
-               return '';
-       else
-               return __FUNCTION__ . ': Failed to insert the rule.';
-}
-
-function deletePortForwarding($object_id, $localip, $localport, $remoteip, $remoteport, $proto)
-{
-       global $dbxlink;
-
-       $query =
-               "delete from PortForwarding where object_id='$object_id' and localip=INET_ATON('$localip') and remoteip=INET_ATON('$remoteip') and localport='$localport' and remoteport='$remoteport' and proto='$proto'";
-       $result = $dbxlink->exec ($query);
-       return '';
-}
-
-function updatePortForwarding($object_id, $localip, $localport, $remoteip, $remoteport, $proto, $description)
-{
-       global $dbxlink;
-
-       $query =
-               "update PortForwarding set description='$description' where object_id='$object_id' and localip=INET_ATON('$localip') and remoteip=INET_ATON('$remoteip') and localport='$localport' and remoteport='$remoteport' and proto='$proto'";
-       $result = $dbxlink->exec ($query);
-       return '';
-}
-
-function getNATv4ForObject ($object_id)
-{
-       $ret = array();
-       $ret['out'] = array();
-       $ret['in'] = array();
-       $query =
-               "select ".
-               "proto, ".
-               "INET_NTOA(localip) as localip, ".
-               "localport, ".
-               "INET_NTOA(remoteip) as remoteip, ".
-               "remoteport, ".
-               "ipa1.name as local_addr_name, " .
-               "ipa2.name as remote_addr_name, " .
-               "description ".
-               "from PortForwarding ".
-               "left join IPAddress as ipa1 on PortForwarding.localip = ipa1.ip " .
-               "left join IPAddress as ipa2 on PortForwarding.remoteip = ipa2.ip " .
-               "where object_id='$object_id' ".
-               "order by localip, localport, proto, remoteip, remoteport";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       $count=0;
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               foreach (array ('proto', 'localport', 'localip', 'remoteport', 'remoteip', 'description', 'local_addr_name', 'remote_addr_name') as $cname)
-                       $ret['out'][$count][$cname] = $row[$cname];
-               $count++;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query =
-               "select ".
-               "proto, ".
-               "INET_NTOA(localip) as localip, ".
-               "localport, ".
-               "INET_NTOA(remoteip) as remoteip, ".
-               "remoteport, ".
-               "PortForwarding.object_id as object_id, ".
-               "RackObject.name as object_name, ".
-               "description ".
-               "from ((PortForwarding join IPBonds on remoteip=IPBonds.ip) join RackObject on PortForwarding.object_id=RackObject.id) ".
-               "where IPBonds.object_id='$object_id' ".
-               "order by remoteip, remoteport, proto, localip, localport";
-       $result = useSelectBlade ($query, __FUNCTION__);
-       $count=0;
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               foreach (array ('proto', 'localport', 'localip', 'remoteport', 'remoteip', 'object_id', 'object_name', 'description') as $cname)
-                       $ret['in'][$count][$cname] = $row[$cname];
-               $count++;
-       }
-       $result->closeCursor();
-
-       return $ret;
-}
-
 // This function returns an array of single element of object's FQDN attribute,
 // if FQDN is set. The next choice is object's common name, if it looks like a
 // hostname. Otherwise an array of all 'regular' IP addresses of the
@@ -1000,11 +590,10 @@ function findAllEndpoints ($object_id, $fallback = '')
        foreach ($values as $record)
                if ($record['name'] == 'FQDN' && !empty ($record['value']))
                        return array ($record['value']);
-       $addresses = getObjectAddresses ($object_id);
        $regular = array();
-       foreach ($addresses as $idx => $address)
-               if ($address['type'] == 'regular')
-                       $regular[] = $address['ip'];
+       foreach (getObjectIPv4Allocations ($object_id) as $dottedquad => $alloc)
+               if ($alloc['type'] == 'regular')
+                       $regular[] = $dottedquad;
        if (!count ($regular) && !empty ($fallback))
                return array ($fallback);
        return $regular;
@@ -1097,7 +686,8 @@ function getRSUforRackRow ($rowData = NULL)
        return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
 }
 
-function getObjectCount ($rackData)
+// Return a list of object IDs, which can be found in the given rackspace block.
+function stuffInRackspace ($rackData)
 {
        $objects = array();
        for ($i = $rackData['height']; $i > 0; $i--)
@@ -1108,16 +698,7 @@ function getObjectCount ($rackData)
                                !in_array ($rackData[$i][$locidx]['object_id'], $objects)
                        )
                                $objects[] = $rackData[$i][$locidx]['object_id'];
-       return count ($objects);
-}
-
-// Perform substitutions and return resulting string
-function apply_macros ($macros, $subject)
-{
-       $ret = $subject;
-       foreach ($macros as $search => $replace)
-               $ret = str_replace ($search, $replace, $ret);
-       return $ret;
+       return $objects;
 }
 
 // Make sure the string is always wrapped with LF characters
@@ -1183,32 +764,40 @@ function getAutoPorts ($type_id)
 
 // Find if a particular tag id exists on the tree, then attach the
 // given child tag to it. If the parent tag doesn't exist, return FALSE.
-function attachChildTag (&$tree, $parent_id, $child_id, $child_info)
+function attachChildTag (&$tree, $parent_id, $child_id, $child_info, $threshold = 0)
 {
+       $self = __FUNCTION__;
        foreach ($tree as $tagid => $taginfo)
        {
                if ($tagid == $parent_id)
                {
-                       $tree[$tagid]['kids'][$child_id] = $child_info;
+                       if (!$threshold or ($threshold and $tree[$tagid]['kidc'] + 1 < $threshold))
+                               $tree[$tagid]['kids'][$child_id] = $child_info;
+                       // Reset the list only once.
+                       if (++$tree[$tagid]['kidc'] == $threshold)
+                               $tree[$tagid]['kids'] = array();
                        return TRUE;
                }
-               elseif (attachChildTag ($tree[$tagid]['kids'], $parent_id, $child_id, $child_info))
+               elseif ($self ($tree[$tagid]['kids'], $parent_id, $child_id, $child_info, $threshold))
                        return TRUE;
        }
        return FALSE;
 }
 
-// Build a tree from the tag list and return it.
-function getTagTree ()
+// Build a tree from the item list and return it. Input and output data is
+// indexed by item id (nested items in output are recursively stored in 'kids'
+// key, which is in turn indexed by id. Functions, which are ready to handle
+// tree collapsion/expansion themselves, may request non-zero threshold value
+// for smaller resulting tree.
+function treeFromList ($mytaglist, $threshold = 0)
 {
-       global $taglist;
-       $mytaglist = $taglist;
        $ret = array();
        while (count ($mytaglist) > 0)
        {
                $picked = FALSE;
                foreach ($mytaglist as $tagid => $taginfo)
                {
+                       $taginfo['kidc'] = 0;
                        $taginfo['kids'] = array();
                        if ($taginfo['parent_id'] == NULL)
                        {
@@ -1216,7 +805,7 @@ function getTagTree ()
                                $picked = TRUE;
                                unset ($mytaglist[$tagid]);
                        }
-                       elseif (attachChildTag ($ret, $taginfo['parent_id'], $tagid, $taginfo))
+                       elseif (attachChildTag ($ret, $taginfo['parent_id'], $tagid, $taginfo, $threshold))
                        {
                                $picked = TRUE;
                                unset ($mytaglist[$tagid]);
@@ -1276,6 +865,7 @@ function serializeTags ($chain, $baseurl = '')
 // a helper for getTagChainExpansion()
 function traceTagChain ($tree, $chain)
 {
+       $self = __FUNCTION__;
        // For each tag find its path from the root, then combine items
        // of all paths and add them to the chain, if they aren't there yet.
        $ret = array();
@@ -1290,7 +880,7 @@ function traceTagChain ($tree, $chain)
                        }
                if (count ($taginfo1['kids']) > 0)
                {
-                       $subsearch = traceTagChain ($taginfo1['kids'], $chain);
+                       $subsearch = $self ($taginfo1['kids'], $chain);
                        if (count ($subsearch))
                        {
                                $hit = TRUE;
@@ -1335,6 +925,7 @@ function getImplicitTags ($oldtags)
 // Minimize the chain: exclude all implicit tags and return the result.
 function getExplicitTagsOnly ($chain, $tree = NULL)
 {
+       $self = __FUNCTION__;
        global $tagtree;
        if ($tree === NULL)
                $tree = $tagtree;
@@ -1343,7 +934,7 @@ function getExplicitTagsOnly ($chain, $tree = NULL)
        {
                if (isset ($taginfo['kids']))
                {
-                       $harvest = getExplicitTagsOnly ($chain, $taginfo['kids']);
+                       $harvest = $self ($chain, $taginfo['kids']);
                        if (count ($harvest) > 0)
                        {
                                $ret = array_merge ($ret, $harvest);
@@ -1363,9 +954,10 @@ function getExplicitTagsOnly ($chain, $tree = NULL)
 
 // Maximize the chain: for each tag add all tags, for which it is direct or indirect parent.
 // Unlike other functions, this one accepts and returns a list of integer tag IDs, not
-// a list of tag structures.
+// a list of tag structures. Same structure (tag ID list) is returned after processing.
 function complementByKids ($idlist, $tree = NULL, $getall = FALSE)
 {
+       $self = __FUNCTION__;
        global $tagtree;
        if ($tree === NULL)
                $tree = $tagtree;
@@ -1383,12 +975,24 @@ function complementByKids ($idlist, $tree = NULL, $getall = FALSE)
                                break;
                        }
                if (isset ($taginfo['kids']))
-                       $ret = array_merge ($ret, complementByKids ($idlist, $taginfo['kids'], $getallkids));
+                       $ret = array_merge ($ret, $self ($idlist, $taginfo['kids'], $getallkids));
                $getallkids = FALSE;
        }
        return $ret;
 }
 
+function getUserAutoTags ($username = NULL)
+{
+       global $remote_username, $accounts;
+       if ($username == NULL)
+               $username = $remote_username;
+       $ret = array();
+       $ret[] = array ('tag' => '$username_' . $username);
+       if (isset ($accounts[$username]['user_id']))
+               $ret[] = array ('tag' => '$userid_' . $accounts[$username]['user_id']);
+       return $ret;
+}
+
 function loadRackObjectAutoTags ()
 {
        assertUIntArg ('object_id', __FUNCTION__);
@@ -1396,6 +1000,7 @@ function loadRackObjectAutoTags ()
        $oinfo = getObjectInfo ($object_id);
        $ret = array();
        $ret[] = array ('tag' => '$id_' . $object_id);
+       $ret[] = array ('tag' => '$typeid_' . $oinfo['objtype_id']);
        $ret[] = array ('tag' => '$any_object');
        if (validTagName ($oinfo['name']))
                $ret[] = array ('tag' => '$cn_' . $oinfo['name']);
@@ -1403,10 +1008,11 @@ function loadRackObjectAutoTags ()
 }
 
 // Common code for both prefix and address tag listers.
-function getIPv4PrefixTags ($prefix)
+function getIPv4PrefixTags ($netid)
 {
+       $netinfo = getIPv4NetworkInfo ($netid);
        $ret = array();
-       $ret[] = array ('tag' => '$ip4net-' . str_replace ('.', '-', $prefix['ip']) . '-' . $prefix['mask']);
+       $ret[] = array ('tag' => '$ip4net-' . str_replace ('.', '-', $netinfo['ip']) . '-' . $netinfo['mask']);
        // FIXME: find and list tags for all parent networks?
        $ret[] = array ('tag' => '$any_ip4net');
        $ret[] = array ('tag' => '$any_net');
@@ -1419,7 +1025,7 @@ function loadIPv4PrefixAutoTags ()
        return array_merge
        (
                array (array ('tag' => '$ip4netid_' . $_REQUEST['id'])),
-               getIPv4PrefixTags (getIPRange ($_REQUEST['id']))
+               getIPv4PrefixTags ($_REQUEST['id'])
        );
 }
 
@@ -1429,7 +1035,7 @@ function loadIPv4AddressAutoTags ()
        return array_merge
        (
                array (array ('tag' => '$ip4net-' . str_replace ('.', '-', $_REQUEST['ip']) . '-32')),
-               getIPv4PrefixTags (getRangeByIP ($_REQUEST['ip']))
+               getIPv4PrefixTags (getIPv4AddressNetworkId ($_REQUEST['ip']))
        );
 }
 
@@ -1462,6 +1068,42 @@ function loadIPv4RSPoolAutoTags ()
        return $ret;
 }
 
+// Check, if the given tag is present on the chain (will only work
+// for regular tags with tag ID set.
+function tagOnChain ($taginfo, $tagchain)
+{
+       if (!isset ($taginfo['id']))
+               return FALSE;
+       foreach ($tagchain as $test)
+               if ($test['id'] == $taginfo['id'])
+                       return TRUE;
+       return FALSE;
+}
+
+// Idem, but use ID list instead of chain.
+function tagOnIdList ($taginfo, $tagidlist)
+{
+       if (!isset ($taginfo['id']))
+               return FALSE;
+       foreach ($tagidlist as $tagid)
+               if ($taginfo['id'] == $tagid)
+                       return TRUE;
+       return FALSE;
+}
+
+// Return TRUE, if two tags chains differ (order of tags doesn't matter).
+// Assume, that neither of the lists contains duplicates.
+// FIXME: a faster, than O(x^2) method is possible for this calculation.
+function tagChainCmp ($chain1, $chain2)
+{
+       if (count ($chain1) != count ($chain2))
+               return TRUE;
+       foreach ($chain1 as $taginfo1)
+               if (!tagOnChain ($taginfo1, $chain2))
+                       return TRUE;
+       return FALSE;
+}
+
 // If the page-tab-op triplet is final, make $expl_tags and $impl_tags
 // hold all appropriate (explicit and implicit) tags respectively.
 // Otherwise some limited redirection is necessary (only page and tab
@@ -1483,6 +1125,8 @@ function fixContext ()
        $tmap['objects']['newobj'] = 'addmore';
        $tmap['object']['switchvlans'] = 'livevlans';
        $tmap['object']['slb'] = 'editrspvs';
+       $tmap['object']['portfwrd'] = 'nat4';
+       $tmap['object']['network'] = 'ipv4';
        if (isset ($pmap[$pageno]))
                redirectUser ($pmap[$pageno], $tabno);
        if (isset ($tmap[$pageno][$tabno]))
@@ -1502,21 +1146,13 @@ function fixContext ()
        }
 }
 
-function getUserAutoTags ()
-{
-       global $remote_username, $accounts;
-       $ret = array();
-       $ret[] = array ('tag' => '$username_' . $remote_username);
-       $ret[] = array ('tag' => '$userid_' . $accounts[$remote_username]['user_id']);
-       return $ret;
-}
-
-// Build a tag chain from supplied tag id list and return it.
+// Take a list of user-supplied tag IDs to build a list of valid taginfo
+// records indexed by tag IDs (tag chain).
 function buildTagChainFromIds ($tagidlist)
 {
        global $taglist;
        $ret = array();
-       foreach ($tagidlist as $tag_id)
+       foreach (array_unique ($tagidlist) as $tag_id)
                if (isset ($taglist[$tag_id]))
                        $ret[] = $taglist[$tag_id];
        return $ret;
@@ -1526,6 +1162,7 @@ function buildTagChainFromIds ($tagidlist)
 // (sub)tree will have refcnt leaves on every last branch.
 function getObjectiveTagTree ($tree, $realm)
 {
+       $self = __FUNCTION__;
        $ret = array();
        foreach ($tree as $taginfo)
        {
@@ -1533,7 +1170,7 @@ function getObjectiveTagTree ($tree, $realm)
                $pick = FALSE;
                if (count ($taginfo['kids']))
                {
-                       $subsearch = getObjectiveTagTree ($taginfo['kids'], $realm);
+                       $subsearch = $self ($taginfo['kids'], $realm);
                        $pick = count ($subsearch) > 0;
                }
                if (isset ($taginfo['refcnt'][$realm]))
@@ -1565,28 +1202,43 @@ function getTagFilterStr ($tagfilter = array())
        return $ret;
 }
 
-function buildWideRedirectURL ($log, $p = NULL, $t = NULL)
+function buildWideRedirectURL ($log, $nextpage = NULL, $nexttab = NULL)
 {
        global $root, $page, $pageno, $tabno;
-       if ($p === NULL)
-               $p = $pageno;
-       if ($t === NULL)
-               $t = $tabno;
-       $url = "${root}?page=${pageno}&tab=${tabno}";
-       if (isset ($page[$pageno]['bypass']))
-               $url .= '&' . $page[$pageno]['bypass'] . '=' . $_REQUEST[$page[$pageno]['bypass']];
+       if ($nextpage === NULL)
+               $nextpage = $pageno;
+       if ($nexttab === NULL)
+               $nexttab = $tabno;
+       $url = "${root}?page=${nextpage}&tab=${nexttab}";
+       if (isset ($page[$nextpage]['bypass']))
+               $url .= '&' . $page[$nextpage]['bypass'] . '=' . $_REQUEST[$page[$nextpage]['bypass']];
        $url .= "&log=" . urlencode (base64_encode (serialize ($log)));
        return $url;
 }
 
-function buildRedirectURL_OK ($text, $p = NULL, $t = NULL)
+function buildRedirectURL ($status, $args = array(), $nextpage = NULL, $nexttab = NULL)
+{
+       global $msgcode, $pageno, $tabno, $op;
+       if ($nextpage === NULL)
+               $nextpage = $pageno;
+       if ($nexttab === NULL)
+               $nexttab = $tabno;
+       return buildWideRedirectURL (oneLiner ($msgcode[$pageno][$tabno][$op][$status], $args), $nextpage, $nexttab);
+}
+
+// Return a message log consisting of only one message.
+function oneLiner ($code, $args = array())
 {
-       return buildWideRedirectURL (array (array ('code' => 'success', 'message' => $text)), $p, $t);
+       $ret = array ('v' => 2);
+       $ret['m'][] = count ($args) ? array ('c' => $code, 'a' => $args) : array ('c' => $code);
+       return $ret;
 }
 
-function buildRedirectURL_ERR ($text, $p = NULL, $t = NULL)
+// Return mesage code by status code.
+function getMessageCode ($status)
 {
-       return buildWideRedirectURL (array (array ('code' => 'error', 'message' => $text)), $p, $t);
+       global $pageno, $tabno, $op, $msgcode;
+       return $msgcode[$pageno][$tabno][$op][$status];
 }
 
 function validTagName ($s, $allow_autotag = FALSE)
@@ -1608,4 +1260,259 @@ function redirectUser ($p, $t)
        die;
 }
 
+function getRackCodeStats ()
+{
+       global $rackCode;
+       $defc = $grantc = 0;
+       foreach ($rackCode as $s)
+               switch ($s['type'])
+               {
+                       case 'SYNT_DEFINITION':
+                               $defc++;
+                               break;
+                       case 'SYNT_GRANT':
+                               $grantc++;
+                               break;
+                       default:
+                               break;
+               }
+       $ret = array ('Definition sentences' => $defc, 'Grant sentences' => $grantc);
+       return $ret;
+}
+
+function getRackImageWidth ()
+{
+       return 3 + getConfigVar ('rtwidth_0') + getConfigVar ('rtwidth_1') + getConfigVar ('rtwidth_2') + 3;
+}
+
+function getRackImageHeight ($units)
+{
+       return 3 + 3 + $units * 2;
+}
+
+// Perform substitutions and return resulting string
+// used solely by buildLVSConfig()
+function apply_macros ($macros, $subject)
+{
+       $ret = $subject;
+       foreach ($macros as $search => $replace)
+               $ret = str_replace ($search, $replace, $ret);
+       return $ret;
+}
+
+function buildLVSConfig ($object_id = 0)
+{
+       if ($object_id <= 0)
+       {
+               showError ('Invalid argument', __FUNCTION__);
+               return;
+       }
+       $oInfo = getObjectInfo ($object_id);
+       $lbconfig = getSLBConfig ($object_id);
+       if ($lbconfig === NULL)
+       {
+               showError ('getSLBConfig() failed', __FUNCTION__);
+               return;
+       }
+       $newconfig = "#\n#\n# This configuration has been generated automatically by RackTables\n";
+       $newconfig .= "# for object_id == ${object_id}\n# object name: ${oInfo['name']}\n#\n#\n\n\n";
+       foreach ($lbconfig as $vs_id => $vsinfo)
+       {
+               $newconfig .=  "########################################################\n" .
+                       "# VS (id == ${vs_id}): " . (empty ($vsinfo['vs_name']) ? 'NO NAME' : $vsinfo['vs_name']) . "\n" .
+                       "# RS pool (id == ${vsinfo['pool_id']}): " . (empty ($vsinfo['pool_name']) ? 'ANONYMOUS' : $vsinfo['pool_name']) . "\n" .
+                       "########################################################\n";
+               # The order of inheritance is: VS -> LB -> pool [ -> RS ]
+               $macros = array
+               (
+                       '%VIP%' => $vsinfo['vip'],
+                       '%VPORT%' => $vsinfo['vport'],
+                       '%PROTO%' => $vsinfo['proto'],
+                       '%VNAME%' =>  $vsinfo['vs_name'],
+                       '%RSPOOLNAME%' => $vsinfo['pool_name']
+               );
+               $newconfig .=  "virtual_server ${vsinfo['vip']} ${vsinfo['vport']} {\n";
+               $newconfig .=  "\tprotocol ${vsinfo['proto']}\n";
+               $newconfig .= apply_macros
+               (
+                       $macros,
+                       lf_wrap ($vsinfo['vs_vsconfig']) .
+                       lf_wrap ($vsinfo['lb_vsconfig']) .
+                       lf_wrap ($vsinfo['pool_vsconfig'])
+               );
+               foreach ($vsinfo['rslist'] as $rs)
+               {
+                       if (empty ($rs['rsport']))
+                               $rs['rsport'] = $vsinfo['vport'];
+                       $macros['%RSIP%'] = $rs['rsip'];
+                       $macros['%RSPORT%'] = $rs['rsport'];
+                       $newconfig .=  "\treal_server ${rs['rsip']} ${rs['rsport']} {\n";
+                       $newconfig .= apply_macros
+                       (
+                               $macros,
+                               lf_wrap ($vsinfo['vs_rsconfig']) .
+                               lf_wrap ($vsinfo['lb_rsconfig']) .
+                               lf_wrap ($vsinfo['pool_rsconfig']) .
+                               lf_wrap ($rs['rs_rsconfig'])
+                       );
+                       $newconfig .=  "\t}\n";
+               }
+               $newconfig .=  "}\n\n\n";
+       }
+       // FIXME: deal somehow with Mac-styled text, the below replacement will screw it up
+       return str_replace ("\r", '', $newconfig);
+}
+
+// Indicate occupation state of each IP address: none, ordinary or problematic.
+function markupIPv4AddrList (&$addrlist)
+{
+       foreach (array_keys ($addrlist) as $ip_bin)
+       {
+               $refc = array
+               (
+                       'shared' => 0,  // virtual
+                       'virtual' => 0, // loopback
+                       'regular' => 0, // connected host
+                       'router' => 0   // connected gateway
+               );
+               foreach ($addrlist[$ip_bin]['allocs'] as $a)
+                       $refc[$a['type']]++;
+               $nvirtloopback = ($refc['shared'] + $refc['virtual'] > 0) ? 1 : 0; // modulus of virtual + shared
+               $nreserved = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0; // only one reservation is possible ever
+               $nrealms = $nreserved + $nvirtloopback + $refc['regular'] + $refc['router']; // latter two are connected and router allocations
+               
+               if ($nrealms == 1)
+                       $addrlist[$ip_bin]['class'] = 'trbusy';
+               elseif ($nrealms > 1)
+                       $addrlist[$ip_bin]['class'] = 'trerror';
+               else
+                       $addrlist[$ip_bin]['class'] = '';
+       }
+}
+
+// Scan the given address list (returned by scanIPv4Space) and return a list of all routers found.
+function findRouters ($addrlist)
+{
+       $ret = array();
+       foreach ($addrlist as $addr)
+               foreach ($addr['allocs'] as $alloc)
+                       if ($alloc['type'] == 'router')
+                               $ret[] = array
+                               (
+                                       'id' => $alloc['object_id'],
+                                       'iface' => $alloc['name'],
+                                       'dname' => $alloc['object_name'],
+                                       'addr' => $addr['ip']
+                               );
+       return $ret;
+}
+
+// Assist in tag chain sorting.
+function taginfoCmp ($tagA, $tagB)
+{
+       return $tagA['ci'] - $tagB['ci'];
+}
+
+// Compare networks. When sorting a tree, the records on the list will have
+// distinct base IP addresses.
+function IPv4NetworkCmp ($netA, $netB)
+{
+       return bccomp ("${netA['db_first']}", "${netB['db_first']}");
+}
+
+// Modify the given tag tree so, that each level's items are sorted alphabetically.
+function sortTree (&$tree, $sortfunc = '')
+{
+       if (empty ($sortfunc))
+               return;
+       $self = __FUNCTION__;
+       usort ($tree, $sortfunc);
+       // Don't make a mistake of directly iterating over the items of current level, because this way
+       // the sorting will be performed on a _copy_ if each item, not the item itself.
+       foreach (array_keys ($tree) as $tagid)
+               $self ($tree[$tagid]['kids'], $sortfunc);
+}
+
+function iptree_fill (&$netdata)
+{
+       if (!isset ($netdata['kids']) or empty ($netdata['kids']))
+               return;
+       // If we relly have nested prefixes, they must fit into the tree.
+       $worktree = array
+       (
+               'ip_bin' => $netdata['ip_bin'],
+               'mask' => $netdata['mask']
+       );
+       foreach ($netdata['kids'] as $pfx)
+               iptree_embed ($worktree, $pfx);
+       $netdata['kids'] = iptree_construct ($worktree);
+       $netdata['kidc'] = count ($netdata['kids']);
+}
+
+function iptree_construct ($node)
+{
+       $self = __FUNCTION__;
+
+       if (!isset ($node['right']))
+       {
+               if (!isset ($node['ip']))
+               {
+                       $node['ip'] = long2ip ($node['ip_bin']);
+                       $node['kids'] = array();
+                       $node['name'] = '';
+               }
+               return array ($node);
+       }
+       else
+               return array_merge ($self ($node['left']), $self ($node['right']));
+}
+
+function iptree_embed (&$node, $pfx)
+{
+       $self = __FUNCTION__;
+
+       // hit?
+       if ($node['ip_bin'] == $pfx['ip_bin'] and $node['mask'] == $pfx['mask'])
+       {
+               $node = $pfx;
+               return;
+       }
+       if ($node['mask'] == $pfx['mask'])
+       {
+               showError ('Internal error, the recurring loop lost control', __FUNCTION__);
+               die;
+       }
+
+       // split?
+       if (!isset ($node['right']))
+       {
+               $node['right']['mask'] = $node['left']['mask'] = $node['mask'] + 1;
+               $node['left']['ip_bin'] = $node['ip_bin'];
+               $node['right']['ip_bin'] = $node['ip_bin'] + binInvMaskFromDec ($node['mask'] + 1) + 1;
+       }
+
+       // repeat!
+       if (($node['left']['ip_bin'] & binMaskFromDec ($node['left']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
+               $self ($node['left'], $pfx);
+       elseif (($node['right']['ip_bin'] & binMaskFromDec ($node['right']['mask'])) == ($pfx['ip_bin'] & binMaskFromDec ($node['left']['mask'])))
+               $self ($node['right'], $pfx);
+       else
+       {
+               showError ('Internal error, cannot decide between left and right', __FUNCTION__);
+               die;
+       }
+}
+
+function treeApplyFunc (&$tree, $func)
+{
+       if (empty ($func))
+               return;
+       $self = __FUNCTION__;
+       foreach (array_keys ($tree) as $key)
+       {
+               $func ($tree[$key]);
+               $self ($tree[$key]['kids'], $func);
+       }
+}
+
 ?>