OSDN Git Service

Add subsystem specific sysroot mapping facility.
authorKeith Marshall <keithmarshall@users.sourceforge.net>
Fri, 22 Jan 2010 17:11:48 +0000 (17:11 +0000)
committerKeith Marshall <keithmarshall@users.sourceforge.net>
Fri, 22 Jan 2010 17:11:48 +0000 (17:11 +0000)
ChangeLog
Makefile.in
src/climain.cpp
src/pkgbase.h
src/pkghash.c [new file with mode: 0644]
src/sysroot.cpp [new file with mode: 0644]

index 65590fd..de68eab 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,27 @@
+2010-01-22  Keith Marshall  <keithmarshall@users.sourceforge.net>
+
+       Add subsystem specific sysroot mapping facility.
+
+       * src/pkghash.c: New file; required by...
+       * src/sysroot.cpp: New file; it implements...
+       (pkgXmlDocument::LoadSystemMap, pkgXmlNode::GetSysRoot): New methods.
+
+       * src/pkgbase.h: Update copyright notice.
+       (pkgXmlDocument::LoadSystemMap): Declare it.
+       (pkgXmlDocument::AddDeclaration): New inline method.
+       (pkgXmlDocument::SetRoot, pkgXmlDocument::Save): Likewise.
+       (pkgXmlNode::GetSysRoot): Declare it.
+       (pkgXmlNode::GetDocumentRoot): New inline method.
+       (pkgXmlNode::AddChild, pkgXmlNode::DeleteChild): Likewise.
+
+       * src/climain.cpp: Update copyright notice.
+       (climain): Invoke pkgXmlDocument::LoadSystemMap() as required.
+
+       * Makefile.in (CORE_DLL_OBJECTS): Add ...
+       (pkghash.$(OBJEXT), sysroot.$(OBJEXT)): ...these; upate dependencies.
+       (DEBUGLEVEL): New macro; define it.
+       (CPPFLAGS): Use it.
+
 2010-01-16  Keith Marshall  <keithmarshall@users.sourceforge.net>
 
        Assign standardised keys for XML database lookup.
index 837594c..71e4ae2 100644 (file)
@@ -26,9 +26,11 @@ srcdir = @srcdir@
 
 VPATH = ${srcdir}/src ${srcdir}/src/pkginfo ${srcdir}/tinyxml
 
+DEBUGLEVEL = 0
+
 CC = @CC@
 CFLAGS = @CFLAGS@
-CPPFLAGS = @CPPFLAGS@ $(INCLUDES)
+CPPFLAGS = @CPPFLAGS@ -D DEBUGLEVEL=$(DEBUGLEVEL) $(INCLUDES)
 
 CXX = @CXX@
 CXXFLAGS = $(CFLAGS)
@@ -49,7 +51,7 @@ LIBS = -Wl,-Bstatic -lz -lbz2 -llzma -Wl,-Bdynamic -lwininet
 CORE_DLL_OBJECTS = climain.$(OBJEXT) \
    pkgbind.$(OBJEXT) pkginet.$(OBJEXT) pkgstrm.$(OBJEXT) pkgname.$(OBJEXT) \
    pkgexec.$(OBJEXT) pkgfind.$(OBJEXT) pkginfo.$(OBJEXT) pkgspec.$(OBJEXT) \
-   pkgkeys.$(OBJEXT) \
+   sysroot.$(OBJEXT) pkghash.$(OBJEXT) pkgkeys.$(OBJEXT) \
    mkpath.$(OBJEXT)  xmlfile.$(OBJEXT) keyword.$(OBJEXT) \
    tinyxml.$(OBJEXT) tinyxmlparser.$(OBJEXT) \
    tinystr.$(OBJEXT) tinyxmlerror.$(OBJEXT) \
@@ -74,6 +76,7 @@ mingw-get-0.dll: $(CORE_DLL_OBJECTS)
 
 dmh.$(OBJEXT):     dmh.h
 climain.$(OBJEXT): pkgbase.h pkgtask.h tinyxml.h tinystr.h dmh.h
+sysroot.$(OBJEXT): pkgbase.h pkgkeys.h tinyxml.h tinystr.h mkpath.h dmh.h
 
 pkgname.$(OBJEXT): pkgbase.h pkgkeys.h dmh.h
 pkgfind.$(OBJEXT): pkgbase.h pkgkeys.h tinyxml.h tinystr.h
index 5d1645a..880abc3 100644 (file)
@@ -4,7 +4,7 @@
  * $Id$
  *
  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
- * Copyright (C) 2009, MinGW Project
+ * Copyright (C) 2009, 2010, MinGW Project
  *
  *
  * Implementation of the main program function, which is invoked by
@@ -81,28 +81,30 @@ EXTERN_C int climain( int argc, char **argv )
        */
       dmh_notify( DMH_FATAL, "%s: invalid application profile\n", dbase.Value() );
 
-#if 0
     /* If the requested action was "update", then we've already done it,
      * as a side effect of binding the cached repository catalogues...
      */
     if( action != ACTION_UPDATE )
     {
-      /* ...otherwise, we still need to schedule and execute the action request...
-       *
-       * so, schedule the specified action for each additionally specified command
-       * line argument, (each of which is assumed to represent a package name)...
+      /* ...otherwise, we need to load the system map...
+       */
+      dbase.LoadSystemMap();
+
+#if 0
+      /* ...schedule the specified action for each additional command line
+       * argument, (each of which is assumed to represent a package name)...
        */
       while( --argc )
        dbase.Schedule( (unsigned long)(action), *++argv );
 
-      /* ...and finally, execute all scheduled actions...
+      /* ...and finally, execute all scheduled actions.
        */
       dbase.ExecuteActions();
-    }
 #endif
+    }
 
     /* If we get this far, then all actions completed successfully;
-     * we are done...
+     * we are done.
      */
     return EXIT_SUCCESS;
   }
index 92fd531..3837273 100644 (file)
@@ -5,7 +5,7 @@
  * $Id$
  *
  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
- * Copyright (C) 2009, MinGW Project
+ * Copyright (C) 2009, 2010, MinGW Project
  *
  *
  * Public interface for the package directory management routines;
@@ -93,9 +93,31 @@ class pkgXmlNode : public TiXmlElement
       const char* retval = Attribute( name );
       return retval ? retval : subst;
     }
+    inline pkgXmlNode* AddChild( TiXmlNode *child )
+    {
+      /* This is wxXmlNode's method for adding a child node, it is
+       * equivalent to tinyxml's LinkEndChild() method.
+       */
+      return (pkgXmlNode*)(LinkEndChild( child ));
+    }
+    inline bool DeleteChild( pkgXmlNode *child )
+    {
+      /* Both TiXmlNode and wxXmlNode have RemoveChild methods, but the
+       * implementations are semantically different; for tinyxml, we may
+       * simply use the RemoveChild method here, where for wxXmlNode, we
+       * would use RemoveChild followed by `delete child'.
+       */
+      return RemoveChild( child );
+    }
 
     /* Additional methods specific to the application.
      */
+    inline pkgXmlNode *GetDocumentRoot()
+    {
+      /* Convenience method to retrieve a pointer to the document root.
+       */
+      return (pkgXmlNode*)(GetDocument()->RootElement());
+    }
     inline bool IsElementOfType( const char* tagname )
     {
       /* Confirm if the owner XML node represents a data element
@@ -104,6 +126,11 @@ class pkgXmlNode : public TiXmlElement
       return strcmp( GetName(), tagname ) == 0;
     }
 
+    /* Method for retrieving the system root management records
+     * for a specified installed subsystem.
+     */
+    pkgXmlNode *GetSysRoot( const char *subsystem );
+
     /* The following pair of methods provide an iterator
      * for enumerating the contained nodes, within the owner,
      * which themselves exhibit a specified tagname.
@@ -219,6 +246,33 @@ class pkgXmlDocument : public TiXmlDocument
        */
       return (pkgXmlNode *)(RootElement());
     }
+    inline void AddDeclaration
+    ( const char *version, const char *encoding, const char *standalone )
+    {
+      /* Not a standard method of either wxXmlDocumemnt or TiXmlDocument;
+       * this is a convenience method for setting up a new XML database.
+       */
+      TiXmlDeclaration decl( version, encoding, standalone );
+      LinkEndChild( &decl );
+    }
+    inline void SetRoot( TiXmlNode* root )
+    {
+      /* tinyxml has no direct equivalent for this wxXmlDocument method;
+       * to emulate it, we must first explicitly delete an existing root
+       * node, if any, then link the new root node as a document child.
+       */
+      pkgXmlNode *oldroot;
+      if( (oldroot = GetRoot()) != NULL )
+       delete oldroot;
+      LinkEndChild( root );
+    }
+    inline bool Save( const char *filename )
+    {
+      /* This wxXmlDocument method for saving the database is equivalent
+       * to the corresponding tinyxml SaveFile( const char* ) method.
+       */
+      return SaveFile( filename );
+    }
 
   private:
     /* Properties specifying the schedule of actions.
@@ -237,6 +291,11 @@ class pkgXmlDocument : public TiXmlDocument
      */
     pkgXmlNode* BindRepositories( bool );
 
+    /* Method to load the system map, and the lists of installed
+     * packages associated with each specified sysroot.
+     */
+    void LoadSystemMap();
+
     /* Method to locate the XML database entry for a named package.
      */
     pkgXmlNode* FindPackageByName( const char*, const char* = NULL );
diff --git a/src/pkghash.c b/src/pkghash.c
new file mode 100644 (file)
index 0000000..3632cac
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * pkghash.c
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2009, 2010, MinGW Project
+ *
+ *
+ * Hashing functions, used to generate CRC hashes from path names,
+ * and to derive signature file names from hashed path names.
+ *
+ *
+ * This is free software.  Permission is granted to copy, modify and
+ * redistribute this software, under the provisions of the GNU General
+ * Public License, Version 3, (or, at your option, any later version),
+ * as published by the Free Software Foundation; see the file COPYING
+ * for licensing details.
+ *
+ * Note, in particular, that this software is provided "as is", in the
+ * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
+ * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
+ * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
+ * MinGW Project, accept liability for any damages, however caused,
+ * arising from the use of this software.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined( __MS_DOS__ ) || defined( _WIN32 ) || defined( __CYGWIN__ )
+# include <ctype.h>
+/*
+ * When generating path name hashes for Microsoft file systems, we want
+ * the same result irrespective of case distinctions in the input name,
+ * or of choice of `/' or `\' as the directory name separator; thus we
+ * use this input mapping function which passes each input character to
+ * the hashing algorithm as its lower case equivalent, and `\' as if it
+ * had been specified as `/'.
+ */
+static __inline__ __attribute__((__always_inline__))
+unsigned long input_as_ulong( const char value )
+{
+  if( value == '\\' )
+    return (unsigned long)('/');
+
+  return (unsigned long)(tolower(value));
+}
+#else
+/*
+ * Alternatively, for non-Microsoft file systems, we simply cast each
+ * input character to its literal unsigned long equivalent value.
+ */
+# define input_as_ulong( value )  (unsigned long)(value)
+#endif
+
+static __inline__ unsigned long generic_crc_update
+( unsigned bits, unsigned long poly, unsigned long input, unsigned long hash )
+{
+  /* Helper function; it incorporates the effect of the next input byte
+   * into the hash, as already computed from all preceding input bytes,
+   * using the specified generator polynomial and bit length.
+   */
+  int bitcount = 8;                            /* bits in input byte */
+  unsigned long mask = 1UL << (bits - 1);      /* most significant bit */
+
+  /* The input byte is passed as the least significant 8-bits of the
+   * associated unsigned long argument; we begin by aligning its most
+   * significant bit with the most significant bit of the hash...
+   */
+  input <<= bits - bitcount;
+  while( bitcount-- > 0 )
+  {
+    /* ...then for all eight input bits, from most significant to
+     * least significant...
+     */
+    if( (hash ^ input) & mask )
+      /*
+       * ...a 'one' bit here indicates that appending the current
+       * input bit to the current interim CRC residual makes that
+       * residual modulo-2 divisible by the generator polynomial,
+       * so align and modulo-2 subtract (i.e. XOR) it.
+       */
+      hash = (hash << 1) ^ poly;
+
+    else
+      /* ...otherwise, we simply shift out the most significant bit
+       * of the original hash value, (effectively appending a 'zero'
+       * bit to the CRC quotient, and adjusting the residual hash),
+       * in preparation for processing the next input bit...
+       */
+      hash <<= 1;
+
+    /* ...and, in either case, progress to the next input bit.
+     */
+    input <<= 1;
+  }
+
+  /* Ultimately, we return the new value of the hash...
+   * Note that we don't discard superfluous carry bits here;
+   * the caller *must* mask the return value, to extract the
+   * appropriate number of least significant bits from the
+   * returned value, to obtain the specified CRC hash.
+   */
+  return hash;
+}
+
+unsigned long generic_crc
+( unsigned bits, unsigned long poly, const char *input, size_t length )
+{
+  /* Compute a CRC hash of specified bit length, using the specified
+   * generator polynomial, for the given input byte stream buffer of
+   * specified length.
+   */
+  unsigned long hash = 0UL;    /* Initial value */
+  while( length-- > 0 )
+    /*
+     * Update the hash value, processing each byte of the input buffer
+     * in turn, until all specified bytes have been processed.
+     */
+    hash = generic_crc_update( bits, poly, input_as_ulong( *input++ ), hash );
+
+  /* Note that, for CRCs of fewer than the number of bits in an unsigned
+   * long, the accumulated hash may include unwanted noise, (carried out),
+   * in the more significant bits.  The required hash value is to be found
+   * in the specified number of less significant bits; mask out the noise,
+   * and return the required hash value.
+   */
+  return hash & ((1UL << bits) - 1L);
+}
+
+char *hashed_name( int retry, const char *tag, const char *refname )
+{
+  /* Generate a hashed name, comprising the specified 'tag' prefix,
+   * followed by the collision retry index, the length and a pair of 
+   * distinct CRC hashes, which is representative of the specified
+   * 'refname' string.  The result is returned in a buffer allocated
+   * dynamically on the heap; this should be freed by the caller,
+   * when no longer required.
+   */
+  char *retname;
+  size_t len = strlen( tag );
+
+  /* While hash collision may be improbable, it is not impossible;
+   * to mitigate, we provide a collection of generator polynomials,
+   * selected in pairs indexed by the 'retry' parameter, offering
+   * eight hash possibilities for each input 'refname'.
+   */
+  static unsigned long p16[] = /* 16-bit CRC generator polynomials */
+  {
+    0x1021,                            /* CCITT standard */
+    0x8408,                            /* CCITT reversed */
+    0x8005,                            /* CRC-16 standard */
+    0xA001                             /* CRC-16 reversed */
+  };
+
+  static unsigned long p24[] = /* 24-bit CRC generator polynomials */
+  {
+    0x5d6dcb,                          /* CRC-24 (FlexRay) */
+    0x864cfb                           /* CRC-24 (OpenPGP) */
+  };
+
+  if( (retname = malloc( 19 + len )) != NULL )
+  {
+    /* Return buffer successfully allocated; populate it...
+     */
+    retry &= 7;
+    len = strlen( refname );
+    sprintf( retname, "%s-%u-%03u-%04x-%06x", tag, retry, len,
+       (unsigned)(generic_crc( 16, p16[retry>>1], refname, len )),  /* 16-bit CRC */
+       (unsigned)(generic_crc( 24, p24[retry&1], refname, len ))    /* 24-bit CRC */
+      );
+  }
+
+  /* Return value is either a pointer to the populated buffer,
+   * or NULL on allocation failure.
+   */
+  return retname;
+}
+
+/* $RCSfile$: end of file */
diff --git a/src/sysroot.cpp b/src/sysroot.cpp
new file mode 100644 (file)
index 0000000..21aaa01
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * sysroot.cpp
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2010, MinGW Project
+ *
+ *
+ * Implementation of the system map loader, sysroot management and
+ * installation tracking functions.
+ *
+ *
+ * This is free software.  Permission is granted to copy, modify and
+ * redistribute this software, under the provisions of the GNU General
+ * Public License, Version 3, (or, at your option, any later version),
+ * as published by the Free Software Foundation; see the file COPYING
+ * for licensing details.
+ *
+ * Note, in particular, that this software is provided "as is", in the
+ * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
+ * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
+ * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
+ * MinGW Project, accept liability for any damages, however caused,
+ * arising from the use of this software.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef _MAX_PATH
+/*
+ * Work around a PATH_MAX declaration anomaly in MinGW.
+ */
+# undef  PATH_MAX
+# define PATH_MAX _MAX_PATH
+#endif
+
+#include "dmh.h"
+#include "mkpath.h"
+
+#include "pkgbase.h"
+#include "pkgkeys.h"
+
+EXTERN_C char *hashed_name( int, const char*, const char* );
+
+static bool samepath( const char *tstpath, const char *refpath )
+{
+  /* Helper to determine equivalence of two path name references.
+   *
+   * We begin by checking that both of the path name references are
+   * actually defined, with non-NULL values.
+   */
+  if( (tstpath == NULL) || (refpath == NULL) )
+    /*
+     * If either path is undefined, then they are equivalent only
+     * if both are undefined.
+     */
+    return (tstpath == refpath);
+
+  /* Convert both input path name strings to canonical forms,
+   * before comparing them for equivalence.
+   */
+  char canonical_tstpath[PATH_MAX], canonical_refpath[PATH_MAX];
+
+  if( (_fullpath( canonical_tstpath, tstpath, PATH_MAX) != NULL)
+  &&  (_fullpath( canonical_refpath, refpath, PATH_MAX) != NULL)  )
+  {
+    /* We successfully obtained canonical forms for both paths
+     * which are to be compared; we return the result of a case
+     * insensitive comparison; (note that it is not necessary to
+     * consider '/' vs. '\' directory name separator distinctions
+     * here, because all such separators are normalised to '\' in
+     * the canonical forms of the path names).
+     */
+    return stricmp( canonical_tstpath, canonical_refpath ) == 0;
+  }
+
+  /* If we get to here, then we failed to get the canonical forms
+   * for both input path name strings; fall back to a less reliable
+   * comparison of the non-canonical forms, ignoring case and any
+   * distinction between '/' and '\' as directory separators...
+   */
+  while( *tstpath && *refpath )
+  {
+    /* Hence, comparing character by character, while we have not
+     * reached the terminating '\0' on at least one path name string...
+     */
+    if( ((*tstpath == '/') || (*tstpath == '\\'))
+    &&  ((*refpath == '/') || (*refpath == '\\'))  )
+    {
+      /* Here we simply step over a matched directory separator,
+       * regardless of '/' vs. '\' distinction.
+       */
+      ++tstpath; ++refpath;
+    }
+    else if( tolower( *tstpath++ ) != tolower( *refpath++ ) )
+      /*
+       * Here we force a 'false' return, on a case-insensitive
+       * MISMATCH between the two path name strings.
+       */
+      return false;
+  }
+
+  /* Finally, if we get to here, we found the '\0' terminator
+   * for at least one of the non-canonical path name strings;
+   * for equivalence, we must have reached it for both.
+   */
+  return (*tstpath == *refpath);
+}
+
+void pkgXmlDocument::LoadSystemMap()
+{
+  /* Load an initial, or a replacement, system map into the
+   * internal XML database image space.
+   */
+  pkgXmlNode *dbase = GetRoot();
+  pkgXmlNode *sysmap = dbase->FindFirstAssociate( sysmap_key );
+  pkgXmlNode *sysroot = dbase->FindFirstAssociate( sysroot_key );
+
+  /* First, we clear out any pre-existing sysroot mappings,
+   * which may have been inherited from a previous system map...
+   */
+  while( sysroot != NULL )
+  {
+    /* This has the side effect of leaving the sysroot pointer
+     * initialised, as required, to NULL, while also locating and
+     * deleting the pre-existing database entries.
+     */
+    pkgXmlNode *to_clear = sysroot;
+    sysroot = sysroot->FindNextAssociate( sysroot_key );
+    dbase->DeleteChild( to_clear );
+  }
+
+  /* Next, we identify the system map to be loaded, by inspection
+   * of the profile entries already loaded into the XML database.
+   */
+  while( sysmap != NULL )
+  {
+    /* Consider all specified system maps...
+     * Any which are not selected for loading are to be purged
+     * from the internal XML database image.
+     */
+    pkgXmlNode *to_clear = sysmap;
+
+    /* Only the first system map which matches the specified selection
+     * `id' criterion, and which registers at least one sysroot for which
+     * the installation is to be managed, can be loaded...
+     */
+    if( sysroot == NULL )
+    {
+      /* We have not yet registered any sysroot for a managed installation;
+       * this implies that no system map has yet been selected for loading,
+       * so check if the current one matches the selection criterion...
+       */
+      const char *id = sysmap->GetPropVal( id_key, "<default>" );
+      if( match_if_explicit( id, NULL ) )
+      {
+       /* This system map is a candidate for loading;
+        * process all of its subsystem specific sysroot declarations...
+        */
+#if DEBUGLEVEL
+fprintf( stderr, "Load system map: id = %s\n", id );
+#endif
+       pkgXmlNode *subsystem = sysmap->FindFirstAssociate( sysroot_key );
+       while( subsystem != NULL )
+       {
+         /* ...to identify all unique sysroot path specifications,
+          * (ignoring any for which no path has been specified).
+          */
+         const char *path;
+         if( (path = subsystem->GetPropVal( pathname_key, NULL )) != NULL )
+         {
+           /* This subsystem has a valid sysroot path specification;
+            * check for a prior registration, (i.e. of a common sysroot
+            * which is shared by a preceding subsystem declaration).
+            */
+           sysroot = dbase->FindFirstAssociate( sysroot_key );
+           while( (sysroot != NULL)
+           && ! samepath( path, sysroot->GetPropVal( pathname_key, NULL )) )
+             sysroot = sysroot->FindNextAssociate( sysroot_key );
+
+#if DEBUGLEVEL
+fprintf( stderr, "Bind subsystem %s: sysroot = %s\n",
+    subsystem->GetPropVal( subsystem_key, "<unknown>" ), path
+  );
+#endif
+           if( sysroot == NULL )
+           {
+             /* This sysroot has not yet been registered...
+              */
+             int retry = 0;
+             const char *sigpath = "%R" "var/lib/mingw-get/data/%F.xml";
+
+             while( retry < 16 )
+             {
+               /* Generate a hashed signature for the sysroot installation
+                * record, and derive an associated database file path name
+                * from it.  Note that this hash is returned in 'malloc'ed
+                * memory, which we must later free.  Also note that there
+                * are eight possible hashes, to mitigate hash collision,
+                * each of which is denoted by retry modulo eight; we make
+                * an initial pass through the possible hashes, looking for
+                * an existing installation map for this sysroot, loading
+                * it immediately if we find it.  Otherwise, we continue
+                * with a second cycle, (retry = 8..15), looking for the
+                * first generated hash with no associated file; we then
+                * use this to create a new installation record file.
+                */
+               char *sig = hashed_name( retry++, sysroot_key, path );
+               char sigfile[mkpath( NULL, sigpath, sig, NULL )];
+               mkpath( sigfile, sigpath, sig, NULL );
+
+               /* Check for an existing sysroot file associated with the
+                * current hash value...
+                */
+               pkgXmlDocument check( sigfile );
+               if( check.IsOk() )
+               {
+                 /* ...such a file does exist, but we must still check
+                  * that it relates to the path for the desired sysroot...
+                  */
+                 if( retry < 9 )
+                 {
+                   /* ...however, we only perform this check during the
+                    * first pass through the possible hashes; (second time
+                    * through, we are only interested in a hash which does
+                    * not have an associated file; note that the first pass
+                    * through is for retry = 0..7, but by the time we get
+                    * to here we have already incremented 7 to become 8,
+                    * hence the check for retry < 9).
+                    */
+                   pkgXmlNode *root;
+                   if( ((root = check.GetRoot()) != NULL)
+                   &&  samepath( root->GetPropVal( pathname_key, NULL ), path )  )
+                   {
+                     /* This is the sysroot record we require...
+                      * Copy its root element into the internal database,
+                      * and force an early exit from the retry loop.
+                      */
+                     dbase->AddChild( root->Clone() );
+                     retry = 16;
+                   }
+                 }
+               }
+
+               /* Once more noting the prior increment of retry, such
+                * that it has now become 8 for the hash generation with
+                * retry = 7...
+                */
+               else if( retry > 8 )
+               {
+                 /* ...we have exhausted all possible hash references,
+                  * finding no existing mapping database for this sysroot...
+                  * The current hashed file name has not yet been assigned,
+                  * so initialise it as a new database for this sysroot.
+                  *
+                  * FIXME: perhaps we should not do this arbitrarily for
+                  * any non-default system root.
+                  */
+                 check.AddDeclaration( "1.0", "UTF-8", "yes" );
+
+                 /* Initialise the root element for this new database.
+                  */
+                 pkgXmlNode root( sysroot_key );
+                 root.SetAttribute( id_key, sig );
+                 root.SetAttribute( pathname_key, path );
+                 check.SetRoot( &root );
+
+                 /* Link a copy of it as the corresponding sysroot
+                  * entry in the internal database.
+                  */
+                 dbase->AddChild( root.Clone() );
+
+                 /* Commit the initial state of this sysroot database
+                  * to a disk file, for future reference, and terminate
+                  * the retry loop at the end of this cycle.
+                  */
+                 check.Save( sigfile );
+                 retry = 16;
+               }
+
+               /* Before abandoning our reference to the current hash
+                * signature, free the memory allocated for it.
+                */
+               free( (void *)(sig) );
+             }
+           }
+         }
+
+         /* Repeat, to map the sysroots for any additional subsystems
+          * which may be specified.
+          */
+         subsystem = subsystem->FindNextAssociate( sysroot_key );
+       }
+
+       /* Cancel the 'clear' request for the system map we just loaded.
+        */
+       to_clear = NULL;
+      }
+    }
+
+    /* Select the next system map declaration, if any, to be processed;
+     * note that we must do this BEFORE we clear the current map, (if it
+     * was unused), since the clear action would destroy the contained
+     * reference for the next system map element.
+     */
+    sysmap = sysmap->FindNextAssociate( sysmap_key );
+
+    /* Finally, if the current system map was not loaded...
+     */
+    if( to_clear != NULL )
+      /*
+       * ...then we delete its declaration from the active data space.
+       */
+      dbase->DeleteChild( to_clear );
+  }
+}
+
+pkgXmlNode* pkgXmlNode::GetSysRoot( const char *subsystem )
+{
+  /* Retrieve the installation records for the system root associated
+   * with the specified software subsystem.
+   *
+   * Note that, by the time this is called, there should be exactly
+   * one system map entry remaining in the internal XML database, so
+   * we need only find the first such entry.
+   */
+  pkgXmlNode *dbase, *sysmap;
+  if( ((dbase = GetDocumentRoot()) != NULL)
+  &&  ((sysmap = dbase->FindFirstAssociate( sysmap_key )) != NULL)  )
+  {
+    /* We've located the system map; walk its list of sysroot entries...
+     */
+    pkgXmlNode *sysroot = sysmap->FindFirstAssociate( sysroot_key );
+    while( sysroot != NULL )
+    {
+      /* ...until we find one for the subsystem of interest...
+       */
+      if( match_if_explicit( subsystem, sysroot->GetPropVal( subsystem_key, NULL )) )
+      {
+       /* ...from which we retrieve the sysroot path specification...
+        */
+       const char *sysroot_path;
+       if( (sysroot_path = sysroot->GetPropVal( pathname_key, NULL )) != NULL )
+       {
+         /* ...which we then use as an identifying reference...
+          */
+         pkgXmlNode *lookup = dbase->FindFirstAssociate( sysroot_key );
+         while( lookup != NULL )
+         {
+           /* ...to select and return the associated sysroot record
+            * (if any) at the top level in the internal XML database.
+            */
+           if( samepath( sysroot_path, lookup->GetPropVal( pathname_key, NULL )) )
+             return lookup;
+
+           /* We haven't found the required sysroot record yet...
+            * move on to the next available.
+            */
+           lookup = lookup->FindNextAssociate( sysroot_key );
+         }
+       }
+      }
+
+      /* We are still looking for a matching sysroot entry in the
+       * system map; move on to the next candidate.
+       */
+      sysroot = sysroot->FindNextAssociate( sysroot_key );
+    }
+  }
+
+  /* If we get to here, we didn't find any appropriate system root
+   * record; return NULL to signal this.
+   */
+  return NULL;
+}
+
+/* $RCSfile$: end of file */