r2141 - fix tagOnIdList()
[racktables] / inc / functions.php
index 8b2beafee7cf53ed013ba6d5d34d70c75130711e..c7d0356c935887278fcb41069c9a56ebe2650cbd 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
@@ -877,19 +647,12 @@ function buildPortCompatMatrixFromList ($portTypeList, $portCompatList)
 
 function newPortForwarding($object_id, $localip, $localport, $remoteip, $remoteport, $proto, $description)
 {
-       global $dbxlink;
-
-       $range = getRangeByIp($localip);
-       if (!$range)
+       if (NULL === getIPv4AddressNetworkId ($localip))
                return "$localip: Non existant ip";
-       
-       $range = getRangeByIp($remoteip);
-       if (!$range)
+       if (NULL === getIPv4AddressNetworkId ($localip))
                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";
 
@@ -1000,11 +763,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 +859,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 +871,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
@@ -1389,13 +1143,26 @@ function complementByKids ($idlist, $tree = NULL, $getall = 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__);
        $object_id = $_REQUEST['object_id'];
        $oinfo = getObjectInfo ($object_id);
        $ret = array();
-       $ret[] = array ('tag' => '$objectid_' . $object_id);
+       $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 +1170,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 +1187,7 @@ function loadIPv4PrefixAutoTags ()
        return array_merge
        (
                array (array ('tag' => '$ip4netid_' . $_REQUEST['id'])),
-               getIPv4PrefixTags (getIPRange ($_REQUEST['id']))
+               getIPv4PrefixTags ($_REQUEST['id'])
        );
 }
 
@@ -1429,7 +1197,7 @@ function loadIPv4AddressAutoTags ()
        return array_merge
        (
                array (array ('tag' => '$ip4net-' . str_replace ('.', '-', $_REQUEST['ip']) . '-32')),
-               getIPv4PrefixTags (getRangeByIP ($_REQUEST['ip']))
+               getIPv4PrefixTags (getIPv4AddressNetworkId ($_REQUEST['ip']))
        );
 }
 
@@ -1444,9 +1212,9 @@ function loadRackAutoTags ()
 
 function loadIPv4VSAutoTags ()
 {
-       assertUIntArg ('id', __FUNCTION__);
+       assertUIntArg ('vs_id', __FUNCTION__);
        $ret = array();
-       $ret[] = array ('tag' => '$id_' . $_REQUEST['id']);
+       $ret[] = array ('tag' => '$ipv4vsid_' . $_REQUEST['vs_id']);
        $ret[] = array ('tag' => '$any_ipv4vs');
        $ret[] = array ('tag' => '$any_vs');
        return $ret;
@@ -1456,15 +1224,76 @@ function loadIPv4RSPoolAutoTags ()
 {
        assertUIntArg ('pool_id', __FUNCTION__);
        $ret = array();
-       $ret[] = array ('tag' => '$id_' . $_REQUEST['pool_id']);
-       $ret[] = array ('tag' => '$any_ipv4rspool');
-       $ret[] = array ('tag' => '$any_rspool');
+       $ret[] = array ('tag' => '$ipv4rspid_' . $_REQUEST['pool_id']);
+       $ret[] = array ('tag' => '$any_ipv4rsp');
+       $ret[] = array ('tag' => '$any_rsp');
        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
+// names are preserved, ophandler name change isn't handled).
 function fixContext ()
 {
        global $pageno, $tabno, $auto_tags, $expl_tags, $impl_tags, $page;
+
+       $pmap = array
+       (
+               'accounts' => 'userlist',
+               'rspools' => 'ipv4rsplist',
+               'rspool' => 'ipv4rsp',
+               'vservices' => 'ipv4vslist',
+               'vservice' => 'ipv4vs',
+       );
+       $tmap = array();
+       $tmap['objects']['newmulti'] = 'addmore';
+       $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]))
+               redirectUser ($pageno, $tmap[$pageno][$tabno]);
+
        if (isset ($page[$pageno]['autotagloader']))
                $auto_tags = $page[$pageno]['autotagloader'] ();
        if
@@ -1479,21 +1308,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;
@@ -1542,28 +1363,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']];
-       $url .= "&log=" . base64_encode (serialize ($log));
+       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)
 {
-       return buildWideRedirectURL (array (array ('code' => 'success', 'message' => $text)), $p, $t);
+       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);
 }
 
-function buildRedirectURL_ERR ($text, $p = NULL, $t = NULL)
+// Return a message log consisting of only one message.
+function oneLiner ($code, $args = array())
 {
-       return buildWideRedirectURL (array (array ('code' => 'error', 'message' => $text)), $p, $t);
+       $ret = array ('v' => 2);
+       $ret['m'][] = count ($args) ? array ('c' => $code, 'a' => $args) : array ('c' => $code);
+       return $ret;
+}
+
+// Return mesage code by status code.
+function getMessageCode ($status)
+{
+       global $pageno, $tabno, $op, $msgcode;
+       return $msgcode[$pageno][$tabno][$op][$status];
 }
 
 function validTagName ($s, $allow_autotag = FALSE)
@@ -1575,4 +1411,154 @@ function validTagName ($s, $allow_autotag = FALSE)
        return FALSE;
 }
 
+function redirectUser ($p, $t)
+{
+       global $page, $root;
+       $l = "{$root}?page=${p}&tab=${t}";
+       if (isset ($page[$p]['bypass']) and isset ($_REQUEST[$page[$p]['bypass']]))
+               $l .= '&' . $page[$p]['bypass'] . '=' . $_REQUEST[$page[$p]['bypass']];
+       header ("Location: " . $l);
+       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)
+               {
+                       $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";
+       }
+       return $newconfig;
+}
+
+// Indicate occupation state of each IP address: none, ordinary or problematic.
+function markupIPv4AddrList (&$addrlist)
+{
+       foreach (array_keys ($addrlist) as $ip_bin)
+       {
+               $singlealloc = 0;
+               $nvirtual = countRefsOfType ($addrlist[$ip_bin]['allocs'], 'shared', 'eq');
+               $nloopback = countRefsOfType ($addrlist[$ip_bin]['allocs'], 'virtual', 'eq');
+               $nconnected = countRefsOfType ($addrlist[$ip_bin]['allocs'], 'regular', 'eq');
+               $nrouter = countRefsOfType ($addrlist[$ip_bin]['allocs'], 'router', 'eq');
+               $nsl = ($nvirtual + $nloopback > 0) ? 1 : 0;
+               $nrsv = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0;
+               $nrealms = $nrsv + $nsl + $nconnected + $nrouter;
+               
+               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;
+}
+
 ?>