6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2010, 2011, 2012, 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 EXTERN_C int pkgPutEnv( int flags, char *varspec )
118 /* A helper routine for registration of sysroot path to
119 * subsystem name mappings in the process environment, so
120 * that they propagate to pre/post-processing hooks.
124 /* The flags allow us to specify certain transformations
125 * to be applied to the varspec.
128 flags |= PKG_PUTENV_SCAN_VARNAME;
129 do { if( (flags & PKG_PUTENV_NAME_TOUPPER) == PKG_PUTENV_NAME_TOUPPER )
131 /* The PKG_PUTENV_NAME_TOUPPER flag specifies that the varname
132 * part of varspec may contain lower case characters, which are
133 * to be converted to their upper case equivalents, before they
134 * are stored in the environment...
138 * ...but such transformation does not apply to the value
139 * part, so stop it when we find the first '=' character,
140 * (which separates the name and value parts of varspec).
142 flags &= ~(PKG_PUTENV_SCAN_VARNAME | PKG_PUTENV_NAME_TOUPPER);
145 /* ...we haven't yet encountered the '=', so apply the
148 *ref = toupper( *ref ); }
150 else if( *ref == '=' )
152 * We may be doing case transformation on the varname, so
153 * note when we've passed the '=' which separates it from
154 * any assigned value.
156 flags &= ~PKG_PUTENV_SCAN_VARNAME;
158 /* Once we've reached the value part of varspec, flags which
159 * include the PKG_PUTENV_DIRSEP_MSW bit, but do not include
160 * the PKG_PUTENV_DIRSEP_POSIX bit, request that POSIX style
161 * directory separators are to be replaced by the MS-Windows
164 if( ((flags & PKG_PUTENV_FLAGS_MASK) == PKG_PUTENV_DIRSEP_MSW) &&
165 (*ref == '/') ) *ref = '\\';
167 /* ...while conversely, the PKG_PUTENV_DIRSEP_POSIX bit,
168 * (without PKG_PUTENV_DIRSEP_MSW), requests MS-Windows to
169 * POSIX style transformation.
171 else if( ((flags & PKG_PUTENV_FLAGS_MASK) == PKG_PUTENV_DIRSEP_POSIX)
172 && (*ref == '\\') ) *ref = '/';
176 /* Finally, we insert the variable specification, (which may have
177 * been transformed above), into the process environment.
179 return putenv( varspec );
182 void pkgXmlDocument::LoadSystemMap()
183 #define PKG_SYSROOT_OPTIONS (PKG_PUTENV_NAME_TOUPPER | PKG_PUTENV_DIRSEP_MSW)
185 /* Load an initial, or a replacement, system map into the
186 * internal XML database image space.
188 pkgXmlNode *dbase = GetRoot();
189 pkgXmlNode *sysmap = dbase->FindFirstAssociate( sysmap_key );
190 pkgXmlNode *sysroot = dbase->FindFirstAssociate( sysroot_key );
192 /* First, we clear out any pre-existing sysroot mappings,
193 * which may have been inherited from a previous system map...
195 while( sysroot != NULL )
197 /* This has the side effect of leaving the sysroot pointer
198 * initialised, as required, to NULL, while also locating and
199 * deleting the pre-existing database entries.
201 pkgXmlNode *to_clear = sysroot;
202 sysroot = sysroot->FindNextAssociate( sysroot_key );
203 dbase->DeleteChild( to_clear );
206 /* Next, we identify the system map to be loaded, by inspection
207 * of the profile entries already loaded into the XML database.
209 while( sysmap != NULL )
211 /* Consider all specified system maps...
212 * Any which are not selected for loading are to be purged
213 * from the internal XML database image.
215 pkgXmlNode *to_clear = sysmap;
217 /* Only the first system map which matches the specified selection
218 * `id' criterion, and which registers at least one sysroot for which
219 * the installation is to be managed, can be loaded...
221 if( sysroot == NULL )
223 /* We have not yet registered any sysroot for a managed installation;
224 * this implies that no system map has yet been selected for loading,
225 * so check if the current one matches the selection criterion...
227 * FIXME: match_if_explicit( id, NULL ) must always return true;
228 * this is a place holder for a match on a system map selector,
229 * which will be specified by a future command line option.
231 const char *id = sysmap->GetPropVal( id_key, "<default>" );
232 if( match_if_explicit( id, NULL ) )
234 /* This system map is a candidate for loading;
235 * process all of its subsystem specific sysroot declarations...
237 DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_INIT ),
238 dmh_printf( "Load system map: id = %s\n", id )
240 pkgXmlNode *subsystem = sysmap->FindFirstAssociate( sysroot_key );
241 while( subsystem != NULL )
243 /* ...to identify all unique sysroot path specifications,
244 * (ignoring any for which no path has been specified).
247 if( (path = subsystem->GetPropVal( pathname_key, NULL )) != NULL )
249 /* This subsystem has a valid sysroot path specification;
250 * check for a prior registration, (i.e. of a common sysroot
251 * which is shared by a preceding subsystem declaration).
253 sysroot = dbase->FindFirstAssociate( sysroot_key );
254 while( (sysroot != NULL)
255 && ! samepath( path, sysroot->GetPropVal( pathname_key, NULL )) )
256 sysroot = sysroot->FindNextAssociate( sysroot_key );
258 /* Identify the subsystem name, logging a trace notification
259 * when running with appropriate debugging traces enabled.
261 const char *sysname = subsystem->GetPropVal( subsystem_key, NULL );
262 DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_INIT ),
263 dmh_printf( "Bind subsystem %s: sysroot = %s\n",
264 (sysname == NULL) ? value_unknown : sysname,
268 if( sysname != NULL )
270 /* Register the associated sysroot in the process environment,
271 * so that it may become available to pre/post-processing hooks;
272 * note that the recorded path name is likely to include macros
273 * such as "%R", so we filter it through mkpath() to ensure
274 * that they are expanded.
276 char varname_template[11 + strlen( path )];
277 sprintf( varname_template, "%%F_SYSROOT=%s", path );
278 char varname[mkpath( NULL, varname_template, sysname, NULL )];
279 mkpath( varname, varname_template, sysname, NULL );
280 pkgPutEnv( PKG_SYSROOT_OPTIONS, varname );
283 if( sysroot == NULL )
285 /* This sysroot has not yet been registered...
291 /* Generate a hashed signature for the sysroot installation
292 * record, and derive an associated database file path name
293 * from it. Note that this hash is returned in 'malloc'ed
294 * memory, which we must later free. Also note that there
295 * are eight possible hashes, to mitigate hash collision,
296 * each of which is denoted by retry modulo eight; we make
297 * an initial pass through the possible hashes, looking for
298 * an existing installation map for this sysroot, loading
299 * it immediately if we find it. Otherwise, we continue
300 * with a second cycle, (retry = 8..15), looking for the
301 * first generated hash with no associated file; we then
302 * use this to create a new installation record file.
304 const char *sig = hashed_name( retry++, sysroot_key, path );
305 const char *sigfile = xmlfile( sig, NULL );
307 /* Check for an existing sysroot file associated with the
308 * current hash value...
310 pkgXmlDocument check( sigfile );
313 /* ...such a file does exist, but we must still check
314 * that it relates to the path for the desired sysroot...
318 /* ...however, we only perform this check during the
319 * first pass through the possible hashes; (second time
320 * through, we are only interested in a hash which does
321 * not have an associated file; note that the first pass
322 * through is for retry = 0..7, but by the time we get
323 * to here we have already incremented 7 to become 8,
324 * hence the check for retry < 9).
327 if( ((root = check.GetRoot()) != NULL)
328 && samepath( root->GetPropVal( pathname_key, NULL ), path ) )
330 /* This is the sysroot record we require...
331 * Copy its root element into the internal database,
332 * and force an early exit from the retry loop.
334 dbase->AddChild( root->Clone() );
340 /* Once more noting the prior increment of retry, such
341 * that it has now become 8 for the hash generation with
346 /* ...we have exhausted all possible hash references,
347 * finding no existing mapping database for this sysroot...
348 * The current hashed file name has not yet been assigned,
349 * so create a new entry in the internal XML database,
350 * marking it as "modified", so that it will be written
351 * to disk, when the system map is updated.
353 * FIXME: perhaps we should not do this arbitrarily for
354 * any non-default system root.
356 pkgXmlNode *record = new pkgXmlNode( sysroot_key );
357 record->SetAttribute( modified_key, yes_value );
358 record->SetAttribute( id_key, sig );
359 record->SetAttribute( pathname_key, path );
360 dbase->AddChild( record );
362 /* Finally, force termination of the sysroot search.
367 /* Before abandoning our references to the current hash
368 * signature, and the path name for the associated XML file,
369 * free the memory allocated for them.
371 free( (void *)(sigfile) );
372 free( (void *)(sig) );
377 /* Repeat, to map the sysroots for any additional subsystems
378 * which may be specified.
380 subsystem = subsystem->FindNextAssociate( sysroot_key );
383 /* Cancel the 'clear' request for the system map we just loaded.
389 /* Select the next system map declaration, if any, to be processed;
390 * note that we must do this BEFORE we clear the current map, (if it
391 * was unused), since the clear action would destroy the contained
392 * reference for the next system map element.
394 sysmap = sysmap->FindNextAssociate( sysmap_key );
396 /* Finally, if the current system map was not loaded...
398 if( to_clear != NULL )
400 /* ...clean up its declaration data...
402 const char *sysname = to_clear->GetPropVal( subsystem_key, NULL );
403 if( sysname != NULL )
405 /* ...deleting its sysroot registration entry, if any,
406 * from the process environment...
408 char varname[9 + strlen( sysname )];
409 sprintf( varname, "%s_SYSROOT", sysname );
410 pkgPutEnv( PKG_PUTENV_NAME_TOUPPER, varname );
412 /* ...and removing its reference from the active XML data space.
414 dbase->DeleteChild( to_clear );
419 void pkgXmlDocument::UpdateSystemMap()
421 /* Inspect all sysroot records in the current system map;
422 * save copies of any marked with the 'modified' attribute
423 * to the appropriate disk files.
425 pkgXmlNode *entry = GetRoot()->FindFirstAssociate( sysroot_key );
427 while( entry != NULL )
429 /* We found a sysroot record...
430 * evaluate and clear its 'modified' attribute...
432 const char *modified = entry->GetPropVal( modified_key, no_value );
433 entry->RemoveAttribute( modified_key );
435 if( (strcmp( modified, yes_value ) == 0)
436 && ((modified = entry->GetPropVal( id_key, NULL )) != NULL) )
438 /* The 'modified' attribute for this record was set,
439 * and the 'id' attribute is valid; establish the path
440 * name for the file in which to save the record.
442 const char *mapfile = xmlfile( modified, NULL );
444 /* Create a copy of the sysroot record, as the content of
445 * a new freestanding XML document, and write it out to the
446 * nominated record file.
449 map.AddDeclaration( "1.0", "UTF-8", yes_value );
450 map.SetRoot( entry->Clone() );
453 /* The 'xmlfile()' look-up for the 'mapfile' path name used
454 * the heap to return the result; free the space allocated.
456 free( (void *)(mapfile) );
459 /* Repeat for the next sysroot record, if any...
461 entry = entry->FindNextAssociate( sysroot_key );
465 pkgXmlNode* pkgXmlNode::GetSysRoot( const char *subsystem )
467 /* Retrieve the installation records for the system root associated
468 * with the specified software subsystem.
470 * Note that, by the time this is called, there should be exactly
471 * one system map entry remaining in the internal XML database, so
472 * we need only find the first such entry.
474 pkgXmlNode *dbase, *sysmap;
475 if( ((dbase = GetDocumentRoot()) != NULL)
476 && ((sysmap = dbase->FindFirstAssociate( sysmap_key )) != NULL) )
478 /* We've located the system map; walk its list of sysroot entries...
480 pkgXmlNode *sysroot = sysmap->FindFirstAssociate( sysroot_key );
481 while( sysroot != NULL )
483 /* ...until we find one for the subsystem of interest...
485 if( subsystem_strcmp( subsystem, sysroot->GetPropVal( subsystem_key, NULL )) )
487 /* ...from which we retrieve the sysroot path specification...
489 const char *sysroot_path;
490 if( (sysroot_path = sysroot->GetPropVal( pathname_key, NULL )) != NULL )
492 /* ...which we then use as an identifying reference...
494 pkgXmlNode *lookup = dbase->FindFirstAssociate( sysroot_key );
495 while( lookup != NULL )
497 /* ...to select and return the associated sysroot record
498 * (if any) at the top level in the internal XML database.
500 if( samepath( sysroot_path, lookup->GetPropVal( pathname_key, NULL )) )
503 /* We haven't found the required sysroot record yet...
504 * move on to the next available.
506 lookup = lookup->FindNextAssociate( sysroot_key );
511 /* We are still looking for a matching sysroot entry in the
512 * system map; move on to the next candidate.
514 sysroot = sysroot->FindNextAssociate( sysroot_key );
518 /* If we get to here, we didn't find any appropriate system root
519 * record; return NULL to signal this.
524 /* $RCSfile$: end of file */