OSDN Git Service

Eliminate invalid comparisons of "this" with nullptr.
[mingw/mingw-get.git] / src / pkginst.cpp
1 /*
2  * pkginst.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2010, 2011, 2012, MinGW.org Project
8  *
9  *
10  * Implementation of the primary package installation and package
11  * manifest recording methods.
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 <unistd.h>
32
33 #include "dmh.h"
34
35 #include "pkginfo.h"
36 #include "pkgkeys.h"
37 #include "pkgproc.h"
38 #include "pkgtask.h"
39
40 EXTERN_C const char *hashed_name( int, const char *, const char * );
41
42 pkgManifest::pkgManifest( const char *tag, const char *tarname )
43 {
44   /* Construct an in-memory image for processing a package manifest.
45    *
46    * We begin by initialising this pair of reference pointers,
47    * assuming that this manifest may become invalid...
48    */
49   manifest = NULL;
50   inventory = NULL;
51
52   /* Then we check that a package tarname has been provided...
53    */
54   if( tarname != NULL )
55   {
56     /* ...in which case, we proceed to set up the reference data...
57      */
58     int retry = 0;
59     while( retry < 16 )
60     {
61       /* Generate a hashed signature for the package manifest
62        * record, and derive an associated database file path name
63        * from it.  Note that this hash is returned in 'malloc'ed
64        * memory, which we must later free.  Also note that there
65        * are eight possible hashes, to mitigate hash collision,
66        * each of which is denoted by retry modulo eight; we make
67        * an initial pass through the possible hashes, looking for
68        * an existing installation map for this sysroot, loading
69        * it immediately if we find it.  Otherwise, we continue
70        * with a second cycle, (retry = 8..15), looking for the
71        * first generated hash with no associated file; we then
72        * use this to create a new installation record file.
73        */
74       pkgXmlDocument *chkfile;
75       const char *signame = hashed_name( retry++, manifest_key, tarname );
76       const char *sigfile = xmlfile( signame, NULL );
77
78       /* Check for an existing file associated with the hash value...
79        */
80       if( (chkfile = new pkgXmlDocument( sigfile ))->IsOk() )
81       {
82         /* ...such a file does exist, but we must still check
83          * that it relates to the specified package...
84          */
85         if( retry < 9 )
86         {
87           /* ...however, we only perform this check during the
88            * first pass through the possible hashes; (second time
89            * through, we are only interested in a hash which does
90            * not have an associated file; note that the first pass
91            * through is for retry = 0..7, but by the time we get
92            * to here we have already incremented 7 to become 8,
93            * hence the check for retry < 9).
94            */
95           pkgXmlNode *root, *rel;
96           const char *pkg_id, *pkg_tarname;
97           if( ((root = chkfile->GetRoot()) != NULL)
98           &&  ((root->IsElementOfType( tag )))
99           &&  ((pkg_id = root->GetPropVal( id_key, NULL )) != NULL)
100           &&  ((strcmp( pkg_id, signame ) == 0))
101           &&  ((rel = root->FindFirstAssociate( release_key )) != NULL)
102           &&  ((pkg_tarname = rel->GetPropVal( tarname_key, NULL )) != NULL)
103           &&  ((pkg_strcmp( pkg_tarname, tarname )))  )
104           {
105             /* This is the manifest file we require...
106              * assign it for return, and force an early exit
107              * from the retry loop.
108              */
109             manifest = chkfile;
110             retry = 16;
111           }
112         }
113       }
114
115       /* Once more noting the prior increment of retry, such
116        * that it has now become 8 for the hash generation with
117        * retry = 7...
118        */
119       else if( retry > 8 )
120       {
121         /* ...we have exhausted all possible hash references,
122          * finding no existing manifest for the specified package;
123          * immediately assign the current (unused) manifest name,
124          * and initialise its root element...
125          */
126         pkgXmlNode *root = new pkgXmlNode( tag );
127         (manifest = chkfile)->AddDeclaration( "1.0", "UTF-8", value_yes );
128         root->SetAttribute( id_key, signame );
129         manifest->SetRoot( root );
130
131         /* ...a container, to be filled later, for recording the
132          * data associated with the specific release of the package
133          * to which this manifest will refer...
134          */
135         pkgXmlNode *ref = new pkgXmlNode( release_key );
136         ref->SetAttribute( tarname_key, tarname );
137         root->AddChild( ref );
138
139         /* ...a further container, in which to record the sysroot
140          * associations for installed instances of this package...
141          */
142         ref = new pkgXmlNode( reference_key );
143         root->AddChild( ref );
144
145         /* ...and one in which to accumulate the content manifest.
146          */
147         inventory = new pkgXmlNode( manifest_key );
148         root->AddChild( inventory );
149
150         /* Finally, having constructed a skeletal manifest,
151          * force an immediate exit from the retry loop...
152          */
153         retry = 16;
154       }
155
156       /* Before abandoning our references to the current hash
157        * signature, and the path name for the associated XML file,
158        * free the memory allocated for them.
159        */
160       free( (void *)(sigfile) );
161       free( (void *)(signame) );
162
163       /* If we have not yet exhausted all possible hashed file names...
164        */
165       if( retry < 16 )
166         /*
167          * ...free the heap memory allocated for the current (unsuitable)
168          * association, so making its "chkfile" reference pointer available
169          * for the next trial, without incurring a memory leak.
170          */
171         delete chkfile;
172     }
173   }
174 }
175
176 void pkgManifest::BindSysRoot( pkgXmlNode *sysroot, const char *reference_tag )
177 {
178   /* Identify the package associated with the current manifest as
179    * having been installed within the specified sysroot, by tagging
180    * with a manifest reference to the sysroot identification key.
181    */
182   const char *id;
183   if( ((id = sysroot->GetPropVal( id_key, NULL )) != NULL) && (*id != '\0') )
184   {
185     /* The specified sysroot has a non-NULL, non-blank key;
186      * check for a prior reference to the same key, within the
187      * "references" section of the manifest...
188      */
189     pkgXmlNode *map, *ref;
190     if(  ((ref = manifest->GetRoot()) != NULL)
191     &&   ((ref->IsElementOfType( reference_tag )))
192     &&   ((map = ref->FindFirstAssociate( reference_key )) == NULL)  )
193     {
194       /* This manifest doesn't yet have a "references" section;
195        * create and add one now...
196        */
197       map = new pkgXmlNode( reference_key );
198       ref->AddChild( map );
199     }
200
201     /* Examine any existing sysroot references within this manifest...
202      */
203     ref = map->FindFirstAssociate( sysroot_key );
204     while( (ref != NULL) && (strcmp( id, ref->GetPropVal( id_key, id )) != 0) )
205       /*
206        * ...progressing to the next reference, if any, until...
207        */
208       ref = ref->FindNextAssociate( sysroot_key );
209
210     /* ...we either matched the required sysroot key, or we
211      * ran out of existing references, without finding a match.
212      */
213     if( ref == NULL )
214     {
215       /* When no existing reference was found, add one.
216        */
217       ref = new pkgXmlNode( sysroot_key );
218       ref->SetAttribute( id_key, id );
219       map->AddChild( ref );
220     }
221   }
222 }
223
224 void pkgManifest::AddEntry( const char *key, const char *pathname )
225 {
226   /* Method invoked by package installers, to add file or directory
227    * entries to the tracked inventory of package content.
228    *
229    * Tracking is enabled only if the manifest structure is valid,
230    * (which must have been verified by the caller), AND an inventory
231    * table has been allocated...
232    */
233   if( inventory != NULL )
234   {
235     /* ...in which case we allocate a new tracking record, with
236      * "dir" or "file" reference key as appropriate, fill it out
237      * with the associated path name attribute, and insert it in
238      * the inventory table.
239      */
240     pkgXmlNode *entry = new pkgXmlNode( key );
241     entry->SetAttribute( pathname_key, pathname );
242     inventory->AddChild( entry );
243   }
244 }
245
246 pkgManifest::~pkgManifest()
247 {
248   /* Destructor for package manifest images; it releases
249    * the memory used while processing a manifest, after first
250    * committing the image to disk storage, or deleting such
251    * a disk image as appropriate.
252    */
253   pkgXmlNode *ref;
254   const char *sigfile;
255
256   /* First confirm that an identification signature has been
257    * assigned for this manifest...
258    */
259   if(  ((manifest != NULL)) && ((ref = manifest->GetRoot()) != NULL)
260   &&   ((sigfile = ref->GetPropVal( id_key, NULL )) != NULL)          )
261   {
262     /* ...and map this to a file system reference path name.
263      */
264     sigfile = xmlfile( sigfile );
265
266     /* Check if any current installation, as identified by
267      * its sysroot key, refers to this manifest...
268      */
269     if(  ((ref = ref->FindFirstAssociate( reference_key )) != NULL)
270     &&   ((ref = ref->FindFirstAssociate( sysroot_key )) != NULL)    )
271       /*
272        * ...and if so, commit this manifest to disk...
273        */
274       manifest->Save( sigfile );
275
276     else
277       /* ...otherwise, this manifest is defunct, so
278        * delete any current disk copy.
279        */
280       unlink( sigfile );
281
282     /* Release the memory used to identify the path name for
283      * the on-disk copy of this manifest...
284      */
285     free( (void *)(sigfile) );
286   }
287
288   /* ...and finally, expunge its in-memory image.
289    */
290   delete manifest;
291 }
292
293 static
294 void record_dependencies( pkgXmlNode *origin, pkgXmlNode *list )
295 {
296   /* Helper function to record dependency call-outs for the package
297    * which is specified by the XML descriptor reference at "origin",
298    * (which should nominally represent a "release" specification for
299    * the package); the call-out references are collected in the XML
300    * container referenced by "list".
301    */
302   if( (origin != NULL) && (list != NULL) )
303   {
304     /* Assume "origin" and "list" represent appropriate XML objects...
305      */
306     if( ! origin->IsElementOfType( package_key ) )
307       /*
308        * ...and walk back through the "origin" tree, until we locate
309        * the top level node in the "package" specification.
310        */
311       record_dependencies( origin->GetParent(), list );
312
313     /* As we unwind this recursive walk back search, we copy all
314      * "requires" elements, at each intervening level, from the top
315      * "package" node until we return to the original "release"...
316      */
317     pkgXmlNode *dep = origin->FindFirstAssociate( requires_key );
318     while( dep != NULL )
319     {
320       /* ...each as a simple clone within the "list" container,
321        * (and all at a single level within the "list")...
322        */
323       list->AddChild( dep->Clone() );
324
325       /* ...repeating for all specified "requires" elements
326        * at each level.
327        */
328       dep = dep->FindNextAssociate( requires_key );
329     }
330   }
331 }
332
333 EXTERN_C void pkgRegister
334 ( pkgXmlNode *sysroot, pkgXmlNode *origin, const char *tarname, const char *pkgfile )
335 {
336   /* Search the installation records for the current sysroot...
337    */
338   const char *pkg_tarname = NULL;
339   pkgXmlNode *ref = sysroot->FindFirstAssociate( installed_key );
340   while(  (ref != NULL)
341     &&   ((pkg_tarname = ref->GetPropVal( tarname_key, NULL )) != NULL)
342     && !  (pkg_strcmp( tarname, pkg_tarname ))  )
343       /*
344        * ...continuing until we either run out of installation records,
345        * or we find an already existing reference to this package.
346        */
347       ref = ref->FindNextAssociate( installed_key );
348
349   /* When we didn't find an appropriate existing installation record,
350    * we instantiate a new one...
351    */
352   if( (ref == NULL) && ((ref = new pkgXmlNode( installed_key )) != NULL) )
353   {
354     /* Fill out its "tarname" attribution...
355      */
356     ref->SetAttribute( tarname_key, tarname );
357     if( pkgfile != tarname )
358     {
359       /* When the real package tarball name isn't identically
360        * the same as the canonical name, then we record the real
361        * file name too.
362        */
363       pkgXmlNode *dl = new pkgXmlNode( download_key );
364       dl->SetAttribute( tarname_key, pkgfile );
365       ref->AddChild( dl );
366     }
367
368     /* Record dependency call-outs for the installed package.
369      */
370     record_dependencies( origin, ref );
371
372     /* Set the 'modified' flag for, and attach the installation
373      * record to, the relevant sysroot record.
374      */
375     sysroot->SetAttribute( modified_key, value_yes );
376     sysroot->AddChild( ref );
377   }
378 }
379
380 EXTERN_C void pkgInstall( pkgActionItem *current )
381 {
382   /* Common handler for all package installation tasks; note that we
383    * initially assert failure, and will revert this assertion in the
384    * event of subsequent successful installation...
385    */
386   pkgXmlNode *pkg;
387   current->Assert( ACTION_INSTALL_FAILED );
388   if( (pkg = current->Selection()) != NULL )
389   {
390     /* The current action item has a valid package association...
391      */
392     if( current->HasAttribute( ACTION_DOWNLOAD ) == 0 )
393     {
394       /* ...and the required package has been successfully downloaded.
395        */
396       if( current->Selection( to_remove ) == NULL )
397       {
398         /* The selected package has either not yet been installed,
399          * or any prior installation has been removed in preparation
400          * for re-installation or upgrade.
401          */
402         const char *pkgfile, *tarname;
403
404         /* Before proceeding with the installation, we should invoke
405          * any associated pre-install script.
406          */
407         pkg->InvokeScript( "pre-install" );
408
409         /* Now, we may proceed with package installation...
410          *
411          * FIXME: the notification here is somewhat redundant, but it
412          * does maintain symmetry with the "remove" operation, and will
413          * make "upgrade" notifications more logical; in any event, it
414          * should ultimately be made conditional on a "verbose" mode
415          * option selection.
416          */
417         dmh_printf( " installing %s\n",
418             pkg->GetPropVal( tarname_key, value_unknown )
419           );
420         if(  match_if_explicit( pkgfile = pkg->ArchiveName(), value_none )
421         && ((tarname = pkg->GetPropVal( tarname_key, NULL )) != NULL)       )
422         {
423           /* In this case, the selected package has no associated archive,
424            * (i.e. it is a "virtual" package); provided we can identify an
425            * associated "sysroot"...
426            */
427           pkgXmlNode *sysroot;
428           pkgSpecs lookup( tarname );
429           if( (sysroot = pkg->GetSysRoot( lookup.GetSubSystemName() )) != NULL )
430             /*
431              * ...the installation process becomes a simple matter of
432              * recording the state of this virtual package as "installed",
433              * in the sysroot manifest, and itemising its prerequisites.
434              */
435             pkgRegister( sysroot, pkg, tarname, pkgfile );
436         }
437         else
438         { /* Here we have a "real" (physical) package to install;
439            * for the time being, we assume it is packaged in our
440            * standard "tar" archive format.
441            */
442           pkgTarArchiveInstaller install( pkg );
443           if( install.IsOk() )
444             install.Process();
445         }
446         /* Update the internal record of installed state; although no
447          * running CLI instance will return to any point where it needs
448          * this, we may have been called from the GUI, and it requires
449          * consistency here, if the user revisits this package within
450          * any single active session.
451          */
452         pkg->SetAttribute( installed_key, value_yes );
453
454         /* Whether we just installed a virtual package or a real package,
455          * we may now run its post-install script, (if any).
456          */
457         pkg->InvokeScript( "post-install" );
458
459         /* When we get to here, the package should have been installed
460          * successfully; revert our original assertion of failure.
461          */
462         current->Assert( 0UL, ~ACTION_INSTALL_FAILED );
463       }
464       else
465         /* There is a prior installation of the selected package, which
466          * prevents us from proceeding; diagnose and otherwise ignore...
467          */
468         dmh_notify( DMH_ERROR,
469             "package %s is already installed\n",
470             current->Selection()->GetPropVal( tarname_key, value_unknown )
471           );
472     }
473     else
474     { /* We have a valid package selection, but the required package is
475        * not present in the local cache; this indicates that the package
476        * has never been successfully downloaded.
477        */
478       int action = current->HasAttribute( ACTION_MASK );
479       dmh_notify( DMH_ERROR, "required package file is not available\n" );
480       dmh_notify( DMH_ERROR, "cannot %s%s%s\n", action_name( action ),
481           (action == ACTION_UPGRADE) ? " to " : " ",
482           pkg->GetPropVal( tarname_key, value_unknown )
483         );
484       dmh_notify( DMH_ERROR, "due to previous download failure\n" );
485     }
486   }
487 }
488
489 /* $RCSfile$: end of file */