+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.
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)
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) \
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
* $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
*/
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;
}
* $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;
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
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.
*/
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.
*/
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 );
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */