OSDN Git Service

Support assignment of DEBUGLEVEL at configure time.
[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, 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 void pkgXmlDocument::LoadSystemMap()
117 {
118   /* Load an initial, or a replacement, system map into the
119    * internal XML database image space.
120    */
121   pkgXmlNode *dbase = GetRoot();
122   pkgXmlNode *sysmap = dbase->FindFirstAssociate( sysmap_key );
123   pkgXmlNode *sysroot = dbase->FindFirstAssociate( sysroot_key );
124
125   /* First, we clear out any pre-existing sysroot mappings,
126    * which may have been inherited from a previous system map...
127    */
128   while( sysroot != NULL )
129   {
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.
133      */
134     pkgXmlNode *to_clear = sysroot;
135     sysroot = sysroot->FindNextAssociate( sysroot_key );
136     dbase->DeleteChild( to_clear );
137   }
138
139   /* Next, we identify the system map to be loaded, by inspection
140    * of the profile entries already loaded into the XML database.
141    */
142   while( sysmap != NULL )
143   {
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.
147      */
148     pkgXmlNode *to_clear = sysmap;
149
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...
153      */
154     if( sysroot == NULL )
155     {
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...
159        *
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.
163        */
164       const char *id = sysmap->GetPropVal( id_key, "<default>" );
165       if( match_if_explicit( id, NULL ) )
166       {
167         /* This system map is a candidate for loading;
168          * process all of its subsystem specific sysroot declarations...
169          */
170         DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_INIT ),
171             dmh_printf( "Load system map: id = %s\n", id )
172           );
173         pkgXmlNode *subsystem = sysmap->FindFirstAssociate( sysroot_key );
174         while( subsystem != NULL )
175         {
176           /* ...to identify all unique sysroot path specifications,
177            * (ignoring any for which no path has been specified).
178            */
179           const char *path;
180           if( (path = subsystem->GetPropVal( pathname_key, NULL )) != NULL )
181           {
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).
185              */
186             sysroot = dbase->FindFirstAssociate( sysroot_key );
187             while( (sysroot != NULL)
188             && ! samepath( path, sysroot->GetPropVal( pathname_key, NULL )) )
189               sysroot = sysroot->FindNextAssociate( sysroot_key );
190
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
194               ));
195
196             if( sysroot == NULL )
197             {
198               /* This sysroot has not yet been registered...
199                */
200               int retry = 0;
201
202               while( retry < 16 )
203               {
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.
216                  */
217                 const char *sig = hashed_name( retry++, sysroot_key, path );
218                 const char *sigfile = xmlfile( sig, NULL );
219
220                 /* Check for an existing sysroot file associated with the
221                  * current hash value...
222                  */
223                 pkgXmlDocument check( sigfile );
224                 if( check.IsOk() )
225                 {
226                   /* ...such a file does exist, but we must still check
227                    * that it relates to the path for the desired sysroot...
228                    */
229                   if( retry < 9 )
230                   {
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).
238                      */
239                     pkgXmlNode *root;
240                     if( ((root = check.GetRoot()) != NULL)
241                     &&  samepath( root->GetPropVal( pathname_key, NULL ), path )  )
242                     {
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.
246                        */
247                       dbase->AddChild( root->Clone() );
248                       retry = 16;
249                     }
250                   }
251                 }
252
253                 /* Once more noting the prior increment of retry, such
254                  * that it has now become 8 for the hash generation with
255                  * retry = 7...
256                  */
257                 else if( retry > 8 )
258                 {
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.
265                    *
266                    * FIXME: perhaps we should not do this arbitrarily for
267                    * any non-default system root.
268                    */
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 );
274
275                   /* Finally, force termination of the sysroot search.
276                    */
277                   retry = 16;
278                 }
279
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.
283                  */
284                 free( (void *)(sigfile) );
285                 free( (void *)(sig) );
286               }
287             }
288           }
289
290           /* Repeat, to map the sysroots for any additional subsystems
291            * which may be specified.
292            */
293           subsystem = subsystem->FindNextAssociate( sysroot_key );
294         }
295
296         /* Cancel the 'clear' request for the system map we just loaded.
297          */
298         to_clear = NULL;
299       }
300     }
301
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.
306      */
307     sysmap = sysmap->FindNextAssociate( sysmap_key );
308
309     /* Finally, if the current system map was not loaded...
310      */
311     if( to_clear != NULL )
312       /*
313        * ...then we delete its declaration from the active data space.
314        */
315       dbase->DeleteChild( to_clear );
316   }
317 }
318
319 void pkgXmlDocument::UpdateSystemMap()
320 {
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.
324    */
325   pkgXmlNode *entry = GetRoot()->FindFirstAssociate( sysroot_key );
326
327   while( entry != NULL )
328   {
329     /* We found a sysroot record...
330      * evaluate and clear its 'modified' attribute...
331      */
332     const char *modified = entry->GetPropVal( modified_key, no_value );
333     entry->RemoveAttribute( modified_key );
334
335     if(  (strcmp( modified, yes_value ) == 0)
336     &&  ((modified = entry->GetPropVal( id_key, NULL )) != NULL)  )
337     {
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.
341        */
342       const char *mapfile = xmlfile( modified, NULL );
343
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.
347        */
348       pkgXmlDocument map;
349       map.AddDeclaration( "1.0", "UTF-8", yes_value );
350       map.SetRoot( entry->Clone() );
351       map.Save( mapfile );
352
353       /* The 'xmlfile()' look-up for the 'mapfile' path name used
354        * the heap to return the result; free the space allocated.
355        */
356       free( (void *)(mapfile) );
357     }
358
359     /* Repeat for the next sysroot record, if any...
360      */
361     entry = entry->FindNextAssociate( sysroot_key );
362   }
363 }
364
365 pkgXmlNode* pkgXmlNode::GetSysRoot( const char *subsystem )
366 {
367   /* Retrieve the installation records for the system root associated
368    * with the specified software subsystem.
369    *
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.
373    */
374   pkgXmlNode *dbase, *sysmap;
375   if( ((dbase = GetDocumentRoot()) != NULL)
376   &&  ((sysmap = dbase->FindFirstAssociate( sysmap_key )) != NULL)  )
377   {
378     /* We've located the system map; walk its list of sysroot entries...
379      */
380     pkgXmlNode *sysroot = sysmap->FindFirstAssociate( sysroot_key );
381     while( sysroot != NULL )
382     {
383       /* ...until we find one for the subsystem of interest...
384        */
385       if( subsystem_strcmp( subsystem, sysroot->GetPropVal( subsystem_key, NULL )) )
386       {
387         /* ...from which we retrieve the sysroot path specification...
388          */
389         const char *sysroot_path;
390         if( (sysroot_path = sysroot->GetPropVal( pathname_key, NULL )) != NULL )
391         {
392           /* ...which we then use as an identifying reference...
393            */
394           pkgXmlNode *lookup = dbase->FindFirstAssociate( sysroot_key );
395           while( lookup != NULL )
396           {
397             /* ...to select and return the associated sysroot record
398              * (if any) at the top level in the internal XML database.
399              */
400             if( samepath( sysroot_path, lookup->GetPropVal( pathname_key, NULL )) )
401               return lookup;
402
403             /* We haven't found the required sysroot record yet...
404              * move on to the next available.
405              */
406             lookup = lookup->FindNextAssociate( sysroot_key );
407           }
408         }
409       }
410
411       /* We are still looking for a matching sysroot entry in the
412        * system map; move on to the next candidate.
413        */
414       sysroot = sysroot->FindNextAssociate( sysroot_key );
415     }
416   }
417
418   /* If we get to here, we didn't find any appropriate system root
419    * record; return NULL to signal this.
420    */
421   return NULL;
422 }
423
424 /* $RCSfile$: end of file */