OSDN Git Service

Note relocation of m4 when packaging source distribution.
[mingw/mingw-get.git] / src / sysroot.cpp
1 /*
2  * sysroot.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2010, 2011, 2012, MinGW Project
8  *
9  *
10  * Implementation of the system map loader, sysroot management and
11  * installation tracking functions.
12  *
13  *
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.
19  *
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.
26  *
27  */
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <ctype.h>
32
33 #ifdef _MAX_PATH
34 /*
35  * Work around a PATH_MAX declaration anomaly in MinGW.
36  */
37 # undef  PATH_MAX
38 # define PATH_MAX _MAX_PATH
39 #endif
40
41 #include "dmh.h"
42 #include "mkpath.h"
43
44 #include "pkgbase.h"
45 #include "pkgkeys.h"
46
47 #include "debug.h"
48
49 EXTERN_C char *hashed_name( int, const char*, const char* );
50
51 static bool samepath( const char *tstpath, const char *refpath )
52 {
53   /* Helper to determine equivalence of two path name references.
54    *
55    * We begin by checking that both of the path name references are
56    * actually defined, with non-NULL values.
57    */
58   if( (tstpath == NULL) || (refpath == NULL) )
59     /*
60      * If either path is undefined, then they are equivalent only
61      * if both are undefined.
62      */
63     return (tstpath == refpath);
64
65   /* Convert both input path name strings to canonical forms,
66    * before comparing them for equivalence.
67    */
68   char canonical_tstpath[PATH_MAX], canonical_refpath[PATH_MAX];
69
70   if( (_fullpath( canonical_tstpath, tstpath, PATH_MAX) != NULL)
71   &&  (_fullpath( canonical_refpath, refpath, PATH_MAX) != NULL)  )
72   {
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).
79      */
80     return stricmp( canonical_tstpath, canonical_refpath ) == 0;
81   }
82
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...
87    */
88   while( *tstpath && *refpath )
89   {
90     /* Hence, comparing character by character, while we have not
91      * reached the terminating '\0' on at least one path name string...
92      */
93     if( ((*tstpath == '/') || (*tstpath == '\\'))
94     &&  ((*refpath == '/') || (*refpath == '\\'))  )
95     {
96       /* Here we simply step over a matched directory separator,
97        * regardless of '/' vs. '\' distinction.
98        */
99       ++tstpath; ++refpath;
100     }
101     else if( tolower( *tstpath++ ) != tolower( *refpath++ ) )
102       /*
103        * Here we force a 'false' return, on a case-insensitive
104        * MISMATCH between the two path name strings.
105        */
106       return false;
107   }
108
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.
112    */
113   return (*tstpath == *refpath);
114 }
115
116 EXTERN_C int pkgPutEnv( int flags, char *varspec )
117 {
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.
121    */
122   if( flags )
123   {
124     /* The flags allow us to specify certain transformations
125      * to be applied to the varspec.
126      */
127     char *ref = varspec;
128     flags |= PKG_PUTENV_SCAN_VARNAME;
129     do { if( (flags & PKG_PUTENV_NAME_TOUPPER) == PKG_PUTENV_NAME_TOUPPER )
130          {
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...
135             */
136            if( *ref == '=' )
137              /*
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).
141               */
142              flags &= ~(PKG_PUTENV_SCAN_VARNAME | PKG_PUTENV_NAME_TOUPPER);
143
144            else
145              /* ...we haven't yet encountered the '=', so apply the
146               * transformation.
147               */
148              *ref = toupper( *ref ); }
149
150          else if( *ref == '=' )
151            /*
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.
155             */
156            flags &= ~PKG_PUTENV_SCAN_VARNAME;
157
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
162           * '\' counterpart...
163           */
164          if( ((flags & PKG_PUTENV_FLAGS_MASK) == PKG_PUTENV_DIRSEP_MSW) &&
165              (*ref == '/')   ) *ref = '\\';
166
167          /* ...while conversely, the PKG_PUTENV_DIRSEP_POSIX bit,
168           * (without PKG_PUTENV_DIRSEP_MSW), requests MS-Windows to
169           * POSIX style transformation.
170           */
171          else if( ((flags & PKG_PUTENV_FLAGS_MASK) == PKG_PUTENV_DIRSEP_POSIX)
172              &&   (*ref == '\\')   ) *ref = '/';
173
174        } while( *++ref ); }
175
176   /* Finally, we insert the variable specification, (which may have
177    * been transformed above), into the process environment.
178    */
179   return putenv( varspec );
180 }
181
182 void pkgXmlDocument::LoadSystemMap()
183 #define PKG_SYSROOT_OPTIONS (PKG_PUTENV_NAME_TOUPPER | PKG_PUTENV_DIRSEP_MSW)
184 {
185   /* Load an initial, or a replacement, system map into the
186    * internal XML database image space.
187    */
188   pkgXmlNode *dbase = GetRoot();
189   pkgXmlNode *sysmap = dbase->FindFirstAssociate( sysmap_key );
190   pkgXmlNode *sysroot = dbase->FindFirstAssociate( sysroot_key );
191
192   /* First, we clear out any pre-existing sysroot mappings,
193    * which may have been inherited from a previous system map...
194    */
195   while( sysroot != NULL )
196   {
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.
200      */
201     pkgXmlNode *to_clear = sysroot;
202     sysroot = sysroot->FindNextAssociate( sysroot_key );
203     dbase->DeleteChild( to_clear );
204   }
205
206   /* Next, we identify the system map to be loaded, by inspection
207    * of the profile entries already loaded into the XML database.
208    */
209   while( sysmap != NULL )
210   {
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.
214      */
215     pkgXmlNode *to_clear = sysmap;
216
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...
220      */
221     if( sysroot == NULL )
222     {
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...
226        *
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.
230        */
231       const char *id = sysmap->GetPropVal( id_key, "<default>" );
232       if( match_if_explicit( id, NULL ) )
233       {
234         /* This system map is a candidate for loading;
235          * process all of its subsystem specific sysroot declarations...
236          */
237         DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_INIT ),
238             dmh_printf( "Load system map: id = %s\n", id )
239           );
240         pkgXmlNode *subsystem = sysmap->FindFirstAssociate( sysroot_key );
241         while( subsystem != NULL )
242         {
243           /* ...to identify all unique sysroot path specifications,
244            * (ignoring any for which no path has been specified).
245            */
246           const char *path;
247           if( (path = subsystem->GetPropVal( pathname_key, NULL )) != NULL )
248           {
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).
252              */
253             sysroot = dbase->FindFirstAssociate( sysroot_key );
254             while( (sysroot != NULL)
255             && ! samepath( path, sysroot->GetPropVal( pathname_key, NULL )) )
256               sysroot = sysroot->FindNextAssociate( sysroot_key );
257
258             /* Identify the subsystem name, logging a trace notification
259              * when running with appropriate debugging traces enabled.
260              */
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,
265                   path
266               ));
267
268             if( sysname != NULL )
269             {
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.
275                */
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 );
281             }
282
283             if( sysroot == NULL )
284             {
285               /* This sysroot has not yet been registered...
286                */
287               int retry = 0;
288
289               while( retry < 16 )
290               {
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.
303                  */
304                 const char *sig = hashed_name( retry++, sysroot_key, path );
305                 const char *sigfile = xmlfile( sig, NULL );
306
307                 /* Check for an existing sysroot file associated with the
308                  * current hash value...
309                  */
310                 pkgXmlDocument check( sigfile );
311                 if( check.IsOk() )
312                 {
313                   /* ...such a file does exist, but we must still check
314                    * that it relates to the path for the desired sysroot...
315                    */
316                   if( retry < 9 )
317                   {
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).
325                      */
326                     pkgXmlNode *root;
327                     if( ((root = check.GetRoot()) != NULL)
328                     &&  samepath( root->GetPropVal( pathname_key, NULL ), path )  )
329                     {
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.
333                        */
334                       dbase->AddChild( root->Clone() );
335                       retry = 16;
336                     }
337                   }
338                 }
339
340                 /* Once more noting the prior increment of retry, such
341                  * that it has now become 8 for the hash generation with
342                  * retry = 7...
343                  */
344                 else if( retry > 8 )
345                 {
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.
352                    *
353                    * FIXME: perhaps we should not do this arbitrarily for
354                    * any non-default system root.
355                    */
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 );
361
362                   /* Finally, force termination of the sysroot search.
363                    */
364                   retry = 16;
365                 }
366
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.
370                  */
371                 free( (void *)(sigfile) );
372                 free( (void *)(sig) );
373               }
374             }
375           }
376
377           /* Repeat, to map the sysroots for any additional subsystems
378            * which may be specified.
379            */
380           subsystem = subsystem->FindNextAssociate( sysroot_key );
381         }
382
383         /* Cancel the 'clear' request for the system map we just loaded.
384          */
385         to_clear = NULL;
386       }
387     }
388
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.
393      */
394     sysmap = sysmap->FindNextAssociate( sysmap_key );
395
396     /* Finally, if the current system map was not loaded...
397      */
398     if( to_clear != NULL )
399     {
400       /* ...clean up its declaration data...
401        */
402       const char *sysname = to_clear->GetPropVal( subsystem_key, NULL );
403       if( sysname != NULL )
404       {
405         /* ...deleting its sysroot registration entry, if any,
406          * from the process environment...
407          */
408         char varname[9 + strlen( sysname )];
409         sprintf( varname, "%s_SYSROOT", sysname );
410         pkgPutEnv( PKG_PUTENV_NAME_TOUPPER, varname );
411       }
412       /* ...and removing its reference from the active XML data space.
413        */
414       dbase->DeleteChild( to_clear );
415     }
416   }
417 }
418
419 void pkgXmlDocument::UpdateSystemMap()
420 {
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.
424    */
425   pkgXmlNode *entry = GetRoot()->FindFirstAssociate( sysroot_key );
426
427   while( entry != NULL )
428   {
429     /* We found a sysroot record...
430      * evaluate and clear its 'modified' attribute...
431      */
432     const char *modified = entry->GetPropVal( modified_key, no_value );
433     entry->RemoveAttribute( modified_key );
434
435     if(  (strcmp( modified, yes_value ) == 0)
436     &&  ((modified = entry->GetPropVal( id_key, NULL )) != NULL)  )
437     {
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.
441        */
442       const char *mapfile = xmlfile( modified, NULL );
443
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.
447        */
448       pkgXmlDocument map;
449       map.AddDeclaration( "1.0", "UTF-8", yes_value );
450       map.SetRoot( entry->Clone() );
451       map.Save( mapfile );
452
453       /* The 'xmlfile()' look-up for the 'mapfile' path name used
454        * the heap to return the result; free the space allocated.
455        */
456       free( (void *)(mapfile) );
457     }
458
459     /* Repeat for the next sysroot record, if any...
460      */
461     entry = entry->FindNextAssociate( sysroot_key );
462   }
463 }
464
465 pkgXmlNode* pkgXmlNode::GetSysRoot( const char *subsystem )
466 {
467   /* Retrieve the installation records for the system root associated
468    * with the specified software subsystem.
469    *
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.
473    */
474   pkgXmlNode *dbase, *sysmap;
475   if( ((dbase = GetDocumentRoot()) != NULL)
476   &&  ((sysmap = dbase->FindFirstAssociate( sysmap_key )) != NULL)  )
477   {
478     /* We've located the system map; walk its list of sysroot entries...
479      */
480     pkgXmlNode *sysroot = sysmap->FindFirstAssociate( sysroot_key );
481     while( sysroot != NULL )
482     {
483       /* ...until we find one for the subsystem of interest...
484        */
485       if( subsystem_strcmp( subsystem, sysroot->GetPropVal( subsystem_key, NULL )) )
486       {
487         /* ...from which we retrieve the sysroot path specification...
488          */
489         const char *sysroot_path;
490         if( (sysroot_path = sysroot->GetPropVal( pathname_key, NULL )) != NULL )
491         {
492           /* ...which we then use as an identifying reference...
493            */
494           pkgXmlNode *lookup = dbase->FindFirstAssociate( sysroot_key );
495           while( lookup != NULL )
496           {
497             /* ...to select and return the associated sysroot record
498              * (if any) at the top level in the internal XML database.
499              */
500             if( samepath( sysroot_path, lookup->GetPropVal( pathname_key, NULL )) )
501               return lookup;
502
503             /* We haven't found the required sysroot record yet...
504              * move on to the next available.
505              */
506             lookup = lookup->FindNextAssociate( sysroot_key );
507           }
508         }
509       }
510
511       /* We are still looking for a matching sysroot entry in the
512        * system map; move on to the next candidate.
513        */
514       sysroot = sysroot->FindNextAssociate( sysroot_key );
515     }
516   }
517
518   /* If we get to here, we didn't find any appropriate system root
519    * record; return NULL to signal this.
520    */
521   return NULL;
522 }
523
524 /* $RCSfile$: end of file */