OSDN Git Service

3789da5406349a5997dbab9c5d198f9d0d0a020b
[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    * AND an inventory table has been allocated...
231    */
232   if( (this != NULL) && (inventory != NULL) )
233   {
234     /* ...in which case we allocate a new tracking record, with
235      * "dir" or "file" reference key as appropriate, fill it out
236      * with the associated path name attribute, and insert it in
237      * the inventory table.
238      */
239     pkgXmlNode *entry = new pkgXmlNode( key );
240     entry->SetAttribute( pathname_key, pathname );
241     inventory->AddChild( entry );
242   }
243 }
244
245 pkgManifest::~pkgManifest()
246 {
247   /* Destructor for package manifest images; it releases
248    * the memory used while processing a manifest, after first
249    * committing the image to disk storage, or deleting such
250    * a disk image as appropriate.
251    */
252   pkgXmlNode *ref;
253   const char *sigfile;
254
255   /* First confirm that an identification signature has been
256    * assigned for this manifest...
257    */
258   if(  ((manifest != NULL)) && ((ref = manifest->GetRoot()) != NULL)
259   &&   ((sigfile = ref->GetPropVal( id_key, NULL )) != NULL)          )
260   {
261     /* ...and map this to a file system reference path name.
262      */
263     sigfile = xmlfile( sigfile );
264
265     /* Check if any current installation, as identified by
266      * its sysroot key, refers to this manifest...
267      */
268     if(  ((ref = ref->FindFirstAssociate( reference_key )) != NULL)
269     &&   ((ref = ref->FindFirstAssociate( sysroot_key )) != NULL)    )
270       /*
271        * ...and if so, commit this manifest to disk...
272        */
273       manifest->Save( sigfile );
274
275     else
276       /* ...otherwise, this manifest is defunct, so
277        * delete any current disk copy.
278        */
279       unlink( sigfile );
280
281     /* Release the memory used to identify the path name for
282      * the on-disk copy of this manifest...
283      */
284     free( (void *)(sigfile) );
285   }
286
287   /* ...and finally, expunge its in-memory image.
288    */
289   delete manifest;
290 }
291
292 static
293 void record_dependencies( pkgXmlNode *origin, pkgXmlNode *list )
294 {
295   /* Helper function to record dependency call-outs for the package
296    * which is specified by the XML descriptor reference at "origin",
297    * (which should nominally represent a "release" specification for
298    * the package); the call-out references are collected in the XML
299    * container referenced by "list".
300    */
301   if( (origin != NULL) && (list != NULL) )
302   {
303     /* Assume "origin" and "list" represent appropriate XML objects...
304      */
305     if( ! origin->IsElementOfType( package_key ) )
306       /*
307        * ...and walk back through the "origin" tree, until we locate
308        * the top level node in the "package" specification.
309        */
310       record_dependencies( origin->GetParent(), list );
311
312     /* As we unwind this recursive walk back search, we copy all
313      * "requires" elements, at each intervening level, from the top
314      * "package" node until we return to the original "release"...
315      */
316     pkgXmlNode *dep = origin->FindFirstAssociate( requires_key );
317     while( dep != NULL )
318     {
319       /* ...each as a simple clone within the "list" container,
320        * (and all at a single level within the "list")...
321        */
322       list->AddChild( dep->Clone() );
323
324       /* ...repeating for all specified "requires" elements
325        * at each level.
326        */
327       dep = dep->FindNextAssociate( requires_key );
328     }
329   }
330 }
331
332 EXTERN_C void pkgRegister
333 ( pkgXmlNode *sysroot, pkgXmlNode *origin, const char *tarname, const char *pkgfile )
334 {
335   /* Search the installation records for the current sysroot...
336    */
337   const char *pkg_tarname = NULL;
338   pkgXmlNode *ref = sysroot->FindFirstAssociate( installed_key );
339   while(  (ref != NULL)
340     &&   ((pkg_tarname = ref->GetPropVal( tarname_key, NULL )) != NULL)
341     && !  (pkg_strcmp( tarname, pkg_tarname ))  )
342       /*
343        * ...continuing until we either run out of installation records,
344        * or we find an already existing reference to this package.
345        */
346       ref = ref->FindNextAssociate( installed_key );
347
348   /* When we didn't find an appropriate existing installation record,
349    * we instantiate a new one...
350    */
351   if( (ref == NULL) && ((ref = new pkgXmlNode( installed_key )) != NULL) )
352   {
353     /* Fill out its "tarname" attribution...
354      */
355     ref->SetAttribute( tarname_key, tarname );
356     if( pkgfile != tarname )
357     {
358       /* When the real package tarball name isn't identically
359        * the same as the canonical name, then we record the real
360        * file name too.
361        */
362       pkgXmlNode *dl = new pkgXmlNode( download_key );
363       dl->SetAttribute( tarname_key, pkgfile );
364       ref->AddChild( dl );
365     }
366
367     /* Record dependency call-outs for the installed package.
368      */
369     record_dependencies( origin, ref );
370
371     /* Set the 'modified' flag for, and attach the installation
372      * record to, the relevant sysroot record.
373      */
374     sysroot->SetAttribute( modified_key, value_yes );
375     sysroot->AddChild( ref );
376   }
377 }
378
379 EXTERN_C void pkgInstall( pkgActionItem *current )
380 {
381   /* Common handler for all package installation tasks; note that we
382    * initially assert failure, and will revert this assertion in the
383    * event of subsequent successful installation...
384    */
385   pkgXmlNode *pkg;
386   current->Assert( ACTION_INSTALL_FAILED );
387   if( (pkg = current->Selection()) != NULL )
388   {
389     /* The current action item has a valid package association...
390      */
391     if( current->HasAttribute( ACTION_DOWNLOAD ) == 0 )
392     {
393       /* ...and the required package has been successfully downloaded.
394        */
395       if( current->Selection( to_remove ) == NULL )
396       {
397         /* The selected package has either not yet been installed,
398          * or any prior installation has been removed in preparation
399          * for re-installation or upgrade.
400          */
401         const char *pkgfile, *tarname;
402
403         /* Before proceeding with the installation, we should invoke
404          * any associated pre-install script.
405          */
406         pkg->InvokeScript( "pre-install" );
407
408         /* Now, we may proceed with package installation...
409          *
410          * FIXME: the notification here is somewhat redundant, but it
411          * does maintain symmetry with the "remove" operation, and will
412          * make "upgrade" notifications more logical; in any event, it
413          * should ultimately be made conditional on a "verbose" mode
414          * option selection.
415          */
416         dmh_printf( " installing %s\n",
417             pkg->GetPropVal( tarname_key, value_unknown )
418           );
419         if(  match_if_explicit( pkgfile = pkg->ArchiveName(), value_none )
420         && ((tarname = pkg->GetPropVal( tarname_key, NULL )) != NULL)       )
421         {
422           /* In this case, the selected package has no associated archive,
423            * (i.e. it is a "virtual" package); provided we can identify an
424            * associated "sysroot"...
425            */
426           pkgXmlNode *sysroot;
427           pkgSpecs lookup( tarname );
428           if( (sysroot = pkg->GetSysRoot( lookup.GetSubSystemName() )) != NULL )
429             /*
430              * ...the installation process becomes a simple matter of
431              * recording the state of this virtual package as "installed",
432              * in the sysroot manifest, and itemising its prerequisites.
433              */
434             pkgRegister( sysroot, pkg, tarname, pkgfile );
435         }
436         else
437         { /* Here we have a "real" (physical) package to install;
438            * for the time being, we assume it is packaged in our
439            * standard "tar" archive format.
440            */
441           pkgTarArchiveInstaller install( pkg );
442           if( install.IsOk() )
443             install.Process();
444         }
445         /* Update the internal record of installed state; although no
446          * running CLI instance will return to any point where it needs
447          * this, we may have been called from the GUI, and it requires
448          * consistency here, if the user revisits this package within
449          * any single active session.
450          */
451         pkg->SetAttribute( installed_key, value_yes );
452
453         /* Whether we just installed a virtual package or a real package,
454          * we may now run its post-install script, (if any).
455          */
456         pkg->InvokeScript( "post-install" );
457
458         /* When we get to here, the package should have been installed
459          * successfully; revert our original assertion of failure.
460          */
461         current->Assert( 0UL, ~ACTION_INSTALL_FAILED );
462       }
463       else
464         /* There is a prior installation of the selected package, which
465          * prevents us from proceeding; diagnose and otherwise ignore...
466          */
467         dmh_notify( DMH_ERROR,
468             "package %s is already installed\n",
469             current->Selection()->GetPropVal( tarname_key, value_unknown )
470           );
471     }
472     else
473     { /* We have a valid package selection, but the required package is
474        * not present in the local cache; this indicates that the package
475        * has never been successfully downloaded.
476        */
477       int action = current->HasAttribute( ACTION_MASK );
478       dmh_notify( DMH_ERROR, "required package file is not available\n" );
479       dmh_notify( DMH_ERROR, "cannot %s%s%s\n", action_name( action ),
480           (action == ACTION_UPGRADE) ? " to " : " ",
481           pkg->GetPropVal( tarname_key, value_unknown )
482         );
483       dmh_notify( DMH_ERROR, "due to previous download failure\n" );
484     }
485   }
486 }
487
488 /* $RCSfile$: end of file */