6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2010, 2011, MinGW Project
10 * Implementation of the system map loader, sysroot management and
11 * installation tracking functions.
14 * This is free software. Permission is granted to copy, modify and
15 * redistribute this software, under the provisions of the GNU General
16 * Public License, Version 3, (or, at your option, any later version),
17 * as published by the Free Software Foundation; see the file COPYING
18 * for licensing details.
20 * Note, in particular, that this software is provided "as is", in the
21 * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
22 * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
23 * PARTICULAR PURPOSE. Under no circumstances will the author, or the
24 * MinGW Project, accept liability for any damages, however caused,
25 * arising from the use of this software.
35 * Work around a PATH_MAX declaration anomaly in MinGW.
38 # define PATH_MAX _MAX_PATH
49 EXTERN_C char *hashed_name( int, const char*, const char* );
51 static bool samepath( const char *tstpath, const char *refpath )
53 /* Helper to determine equivalence of two path name references.
55 * We begin by checking that both of the path name references are
56 * actually defined, with non-NULL values.
58 if( (tstpath == NULL) || (refpath == NULL) )
60 * If either path is undefined, then they are equivalent only
61 * if both are undefined.
63 return (tstpath == refpath);
65 /* Convert both input path name strings to canonical forms,
66 * before comparing them for equivalence.
68 char canonical_tstpath[PATH_MAX], canonical_refpath[PATH_MAX];
70 if( (_fullpath( canonical_tstpath, tstpath, PATH_MAX) != NULL)
71 && (_fullpath( canonical_refpath, refpath, PATH_MAX) != NULL) )
73 /* We successfully obtained canonical forms for both paths
74 * which are to be compared; we return the result of a case
75 * insensitive comparison; (note that it is not necessary to
76 * consider '/' vs. '\' directory name separator distinctions
77 * here, because all such separators are normalised to '\' in
78 * the canonical forms of the path names).
80 return stricmp( canonical_tstpath, canonical_refpath ) == 0;
83 /* If we get to here, then we failed to get the canonical forms
84 * for both input path name strings; fall back to a less reliable
85 * comparison of the non-canonical forms, ignoring case and any
86 * distinction between '/' and '\' as directory separators...
88 while( *tstpath && *refpath )
90 /* Hence, comparing character by character, while we have not
91 * reached the terminating '\0' on at least one path name string...
93 if( ((*tstpath == '/') || (*tstpath == '\\'))
94 && ((*refpath == '/') || (*refpath == '\\')) )
96 /* Here we simply step over a matched directory separator,
97 * regardless of '/' vs. '\' distinction.
101 else if( tolower( *tstpath++ ) != tolower( *refpath++ ) )
103 * Here we force a 'false' return, on a case-insensitive
104 * MISMATCH between the two path name strings.
109 /* Finally, if we get to here, we found the '\0' terminator
110 * for at least one of the non-canonical path name strings;
111 * for equivalence, we must have reached it for both.
113 return (*tstpath == *refpath);
116 void pkgXmlDocument::LoadSystemMap()
118 /* Load an initial, or a replacement, system map into the
119 * internal XML database image space.
121 pkgXmlNode *dbase = GetRoot();
122 pkgXmlNode *sysmap = dbase->FindFirstAssociate( sysmap_key );
123 pkgXmlNode *sysroot = dbase->FindFirstAssociate( sysroot_key );
125 /* First, we clear out any pre-existing sysroot mappings,
126 * which may have been inherited from a previous system map...
128 while( sysroot != NULL )
130 /* This has the side effect of leaving the sysroot pointer
131 * initialised, as required, to NULL, while also locating and
132 * deleting the pre-existing database entries.
134 pkgXmlNode *to_clear = sysroot;
135 sysroot = sysroot->FindNextAssociate( sysroot_key );
136 dbase->DeleteChild( to_clear );
139 /* Next, we identify the system map to be loaded, by inspection
140 * of the profile entries already loaded into the XML database.
142 while( sysmap != NULL )
144 /* Consider all specified system maps...
145 * Any which are not selected for loading are to be purged
146 * from the internal XML database image.
148 pkgXmlNode *to_clear = sysmap;
150 /* Only the first system map which matches the specified selection
151 * `id' criterion, and which registers at least one sysroot for which
152 * the installation is to be managed, can be loaded...
154 if( sysroot == NULL )
156 /* We have not yet registered any sysroot for a managed installation;
157 * this implies that no system map has yet been selected for loading,
158 * so check if the current one matches the selection criterion...
160 * FIXME: match_if_explicit( id, NULL ) must always return true;
161 * this is a place holder for a match on a system map selector,
162 * which will be specified by a future command line option.
164 const char *id = sysmap->GetPropVal( id_key, "<default>" );
165 if( match_if_explicit( id, NULL ) )
167 /* This system map is a candidate for loading;
168 * process all of its subsystem specific sysroot declarations...
170 DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_INIT ),
171 dmh_printf( "Load system map: id = %s\n", id )
173 pkgXmlNode *subsystem = sysmap->FindFirstAssociate( sysroot_key );
174 while( subsystem != NULL )
176 /* ...to identify all unique sysroot path specifications,
177 * (ignoring any for which no path has been specified).
180 if( (path = subsystem->GetPropVal( pathname_key, NULL )) != NULL )
182 /* This subsystem has a valid sysroot path specification;
183 * check for a prior registration, (i.e. of a common sysroot
184 * which is shared by a preceding subsystem declaration).
186 sysroot = dbase->FindFirstAssociate( sysroot_key );
187 while( (sysroot != NULL)
188 && ! samepath( path, sysroot->GetPropVal( pathname_key, NULL )) )
189 sysroot = sysroot->FindNextAssociate( sysroot_key );
191 DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_INIT ),
192 dmh_printf( "Bind subsystem %s: sysroot = %s\n",
193 subsystem->GetPropVal( subsystem_key, "<unknown>" ), path
196 if( sysroot == NULL )
198 /* This sysroot has not yet been registered...
204 /* Generate a hashed signature for the sysroot installation
205 * record, and derive an associated database file path name
206 * from it. Note that this hash is returned in 'malloc'ed
207 * memory, which we must later free. Also note that there
208 * are eight possible hashes, to mitigate hash collision,
209 * each of which is denoted by retry modulo eight; we make
210 * an initial pass through the possible hashes, looking for
211 * an existing installation map for this sysroot, loading
212 * it immediately if we find it. Otherwise, we continue
213 * with a second cycle, (retry = 8..15), looking for the
214 * first generated hash with no associated file; we then
215 * use this to create a new installation record file.
217 const char *sig = hashed_name( retry++, sysroot_key, path );
218 const char *sigfile = xmlfile( sig, NULL );
220 /* Check for an existing sysroot file associated with the
221 * current hash value...
223 pkgXmlDocument check( sigfile );
226 /* ...such a file does exist, but we must still check
227 * that it relates to the path for the desired sysroot...
231 /* ...however, we only perform this check during the
232 * first pass through the possible hashes; (second time
233 * through, we are only interested in a hash which does
234 * not have an associated file; note that the first pass
235 * through is for retry = 0..7, but by the time we get
236 * to here we have already incremented 7 to become 8,
237 * hence the check for retry < 9).
240 if( ((root = check.GetRoot()) != NULL)
241 && samepath( root->GetPropVal( pathname_key, NULL ), path ) )
243 /* This is the sysroot record we require...
244 * Copy its root element into the internal database,
245 * and force an early exit from the retry loop.
247 dbase->AddChild( root->Clone() );
253 /* Once more noting the prior increment of retry, such
254 * that it has now become 8 for the hash generation with
259 /* ...we have exhausted all possible hash references,
260 * finding no existing mapping database for this sysroot...
261 * The current hashed file name has not yet been assigned,
262 * so create a new entry in the internal XML database,
263 * marking it as "modified", so that it will be written
264 * to disk, when the system map is updated.
266 * FIXME: perhaps we should not do this arbitrarily for
267 * any non-default system root.
269 pkgXmlNode *record = new pkgXmlNode( sysroot_key );
270 record->SetAttribute( modified_key, yes_value );
271 record->SetAttribute( id_key, sig );
272 record->SetAttribute( pathname_key, path );
273 dbase->AddChild( record );
275 /* Finally, force termination of the sysroot search.
280 /* Before abandoning our references to the current hash
281 * signature, and the path name for the associated XML file,
282 * free the memory allocated for them.
284 free( (void *)(sigfile) );
285 free( (void *)(sig) );
290 /* Repeat, to map the sysroots for any additional subsystems
291 * which may be specified.
293 subsystem = subsystem->FindNextAssociate( sysroot_key );
296 /* Cancel the 'clear' request for the system map we just loaded.
302 /* Select the next system map declaration, if any, to be processed;
303 * note that we must do this BEFORE we clear the current map, (if it
304 * was unused), since the clear action would destroy the contained
305 * reference for the next system map element.
307 sysmap = sysmap->FindNextAssociate( sysmap_key );
309 /* Finally, if the current system map was not loaded...
311 if( to_clear != NULL )
313 * ...then we delete its declaration from the active data space.
315 dbase->DeleteChild( to_clear );
319 void pkgXmlDocument::UpdateSystemMap()
321 /* Inspect all sysroot records in the current system map;
322 * save copies of any marked with the 'modified' attribute
323 * to the appropriate disk files.
325 pkgXmlNode *entry = GetRoot()->FindFirstAssociate( sysroot_key );
327 while( entry != NULL )
329 /* We found a sysroot record...
330 * evaluate and clear its 'modified' attribute...
332 const char *modified = entry->GetPropVal( modified_key, no_value );
333 entry->RemoveAttribute( modified_key );
335 if( (strcmp( modified, yes_value ) == 0)
336 && ((modified = entry->GetPropVal( id_key, NULL )) != NULL) )
338 /* The 'modified' attribute for this record was set,
339 * and the 'id' attribute is valid; establish the path
340 * name for the file in which to save the record.
342 const char *mapfile = xmlfile( modified, NULL );
344 /* Create a copy of the sysroot record, as the content of
345 * a new freestanding XML document, and write it out to the
346 * nominated record file.
349 map.AddDeclaration( "1.0", "UTF-8", yes_value );
350 map.SetRoot( entry->Clone() );
353 /* The 'xmlfile()' look-up for the 'mapfile' path name used
354 * the heap to return the result; free the space allocated.
356 free( (void *)(mapfile) );
359 /* Repeat for the next sysroot record, if any...
361 entry = entry->FindNextAssociate( sysroot_key );
365 pkgXmlNode* pkgXmlNode::GetSysRoot( const char *subsystem )
367 /* Retrieve the installation records for the system root associated
368 * with the specified software subsystem.
370 * Note that, by the time this is called, there should be exactly
371 * one system map entry remaining in the internal XML database, so
372 * we need only find the first such entry.
374 pkgXmlNode *dbase, *sysmap;
375 if( ((dbase = GetDocumentRoot()) != NULL)
376 && ((sysmap = dbase->FindFirstAssociate( sysmap_key )) != NULL) )
378 /* We've located the system map; walk its list of sysroot entries...
380 pkgXmlNode *sysroot = sysmap->FindFirstAssociate( sysroot_key );
381 while( sysroot != NULL )
383 /* ...until we find one for the subsystem of interest...
385 if( subsystem_strcmp( subsystem, sysroot->GetPropVal( subsystem_key, NULL )) )
387 /* ...from which we retrieve the sysroot path specification...
389 const char *sysroot_path;
390 if( (sysroot_path = sysroot->GetPropVal( pathname_key, NULL )) != NULL )
392 /* ...which we then use as an identifying reference...
394 pkgXmlNode *lookup = dbase->FindFirstAssociate( sysroot_key );
395 while( lookup != NULL )
397 /* ...to select and return the associated sysroot record
398 * (if any) at the top level in the internal XML database.
400 if( samepath( sysroot_path, lookup->GetPropVal( pathname_key, NULL )) )
403 /* We haven't found the required sysroot record yet...
404 * move on to the next available.
406 lookup = lookup->FindNextAssociate( sysroot_key );
411 /* We are still looking for a matching sysroot entry in the
412 * system map; move on to the next candidate.
414 sysroot = sysroot->FindNextAssociate( sysroot_key );
418 /* If we get to here, we didn't find any appropriate system root
419 * record; return NULL to signal this.
424 /* $RCSfile$: end of file */