OSDN Git Service

e40c829ea5a3d6e2968777976126bf6f213d2ed1
[mingw/mingw-get.git] / src / pkgunst.cpp
1 /*
2  * pkgunst.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2011-2013, MinGW.org Project
8  *
9  *
10  * Implementation of the primary package removal methods.
11  *
12  *
13  * This is free software.  Permission is granted to copy, modify and
14  * redistribute this software, under the provisions of the GNU General
15  * Public License, Version 3, (or, at your option, any later version),
16  * as published by the Free Software Foundation; see the file COPYING
17  * for licensing details.
18  *
19  * Note, in particular, that this software is provided "as is", in the
20  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
21  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
22  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
23  * MinGW Project, accept liability for any damages, however caused,
24  * arising from the use of this software.
25  *
26  */
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <errno.h>
32
33 #include "dmh.h"
34 #include "debug.h"
35
36 #include "pkginfo.h"
37 #include "pkgkeys.h"
38 #include "pkgproc.h"
39 #include "pkgtask.h"
40 #include "pkgstat.h"
41
42 #include "mkpath.h"
43
44 #define PKGERR_INVALID_MANIFEST( REASON )  \
45   PKGMSG_INVALID_MANIFEST, tarname, REASON
46
47 #define PKGMSG_INVALID_MANIFEST         "%s: invalid manifest; %s\n"
48 #define PKGMSG_NO_RELEASE_KEY           "no release key assigned"
49 #define PKGMSG_RELEASE_KEY_MISMATCH     "release key mismatch"
50
51 static const char *request_key = "request";
52
53 static __inline__ __attribute__((__always_inline__))
54 pkgXmlNode *sysroot_lookup( pkgXmlNode *pkg, const char *tarname )
55 {
56   /* A local helper function, to identify the sysroot association
57    * for any package which is to be uninstalled.
58    */
59   pkgSpecs lookup( tarname );
60   return pkg->GetSysRoot( lookup.GetSubSystemName() );
61 }
62
63 static __inline__ __attribute__((__always_inline__))
64 const char *id_lookup( pkgXmlNode *reftag, const char *fallback )
65 {
66   /* A local convenience function, to retrieve the value of
67    * the "id" attribute associated with any pkgXmlNode element.
68    */
69   return reftag->GetPropVal( id_key, fallback );
70 }
71
72 static __inline__ __attribute__((__always_inline__))
73 const char *pathname_lookup( pkgXmlNode *reftag, const char *fallback )
74 {
75   /* A local convenience function, to retrieve the value of
76    * the "id" attribute associated with any pkgXmlNode element.
77    */
78   return reftag->GetPropVal( pathname_key, fallback );
79 }
80
81 unsigned long pkgActionItem::SetAuthorities( pkgActionItem *current )
82 {
83   /* Helper method to either grant or revoke authority for removal of
84    * any package, either permanently when the user requests that it be
85    * removed, or temporarily in preparation for replacement by a newer
86    * version, when scheduled for upgrade.
87    *
88    * This is a multiple pass method, iterating over the entire list
89    * of scheduled actions within each pass, until the entire schedule
90    * of authorities has been established.
91    */
92   if( (flags & ACTION_PREFLIGHT) == 0 )
93   {
94     /* This applies exclusively in the first pass, which is effectively
95      * a "preflight" checking pass only.
96      */
97     while( current != NULL )
98     {
99       /* Each scheduled action is inspected in turn...
100        */
101       pkgXmlNode *ref;
102       if( ((current->flags & ACTION_REMOVE) != 0)
103       &&  ((ref = current->Selection( to_remove )) != NULL)  )
104       {
105         /* ...and, when it specifies a "remove" action relating to a
106          * package which has been identified as "installed"...
107          */
108         const char *tarname = ref->GetPropVal( tarname_key, value_unknown );
109         DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_INIT ),
110             dmh_printf( "%s: selected for %s\n",
111               (tarname = ref->GetPropVal( tarname_key, value_unknown )),
112               (current->flags & ACTION_INSTALL) ? "upgrade" : "removal"
113           ));
114
115         /* ...we identify its associated entry in the installed
116          * package manifest...
117          */
118         if( (ref = ref->GetInstallationRecord( tarname )) != NULL )
119         {
120           /* ...and then, having confirmed its validity...
121            */
122           DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_INIT ),
123               dmh_printf( "%s: marked for %s\n",
124                 ref->GetPropVal( tarname_key, value_unknown ),
125                 (current->flags & ACTION_INSTALL) ? "upgrade" : "removal"
126             ));
127
128           /* ...we mark it as a candidate for removal...
129            */
130           ref->SetAttribute( request_key, action_name( ACTION_REMOVE ) );
131
132           /* ...and assign a provisional grant of authority
133            * to proceed with the removal...
134            */
135           current->flags |= ACTION_PREFLIGHT;
136         }
137       }
138       /* ...then, we move on to perform the same "preflight" check
139        * for the next scheduled action, if any.
140        */
141       current = current->next;
142     }
143     /* When we get to here, we have completed the "preflight" checks
144      * for all scheduled actions.  The "current" pointer has advanced,
145      * but the "this" pointer still refers to the head of the actions
146      * list; terminate this "preflight" pass, marking the associated
147      * entry to confirm that it has been completed.
148      */
149     return flags |= ACTION_PREFLIGHT;
150   }
151
152   /* We will get to here only in second and subsequent passes, after
153    * the initial "preflight" checks have been completed.  For actions
154    * which schedule a package removal, (whether explicitly, or as an
155    * implicit prerequisite in preparation for upgrade), the initial
156    * pass will have provisionally authorised removal; in subsequent
157    * passes, we either ratify that authority, or we revoke it in the
158    * event that it would break a dependency of another package.
159    *
160    * FIXME: we need to develop the code to perform the dependency
161    * analysis; for the time being, we simply return zero, ratifying
162    * authority for all removal requests.
163    */
164   return 0;
165 }
166
167 pkgXmlNode *pkgManifest::GetSysRootReference( const char *key )
168 {
169   /* Method to verify that a package manifest includes
170    * a reference to any sysroot which claims it, returning
171    * a pointer to the first such reference found.
172    */
173   if( (this != NULL) && (manifest != NULL) && (key != NULL) )
174   {
175     /* We appear to have a valid manifest, and a valid sysroot
176      * key to match; locate this manifest's first, (and nominally
177      * its only), references section.
178      */
179     pkgXmlNode *grp = manifest->GetRoot()->FindFirstAssociate( reference_key );
180     while( grp != NULL )
181     {
182       /* Having identified a references section, locate the
183        * first sysroot reference contained therein...
184        */
185       pkgXmlNode *ref = grp->FindFirstAssociate( sysroot_key );
186       while( ref != NULL )
187       {
188         /* ...then retrieve the sysroot ID key value,
189          * for comparison with the requested key...
190          */
191         const char *chk;
192         if( ((chk = ref->GetPropVal( id_key, NULL )) != NULL)
193         &&  (strcmp( chk, key ) == 0)                        )
194           /*
195            * ...returning immediately, if a match is found...
196            */
197           return ref;
198
199         /* ...otherwise, repeat check for any further sysroot
200          * references which may be present...
201          */
202         ref = ref->FindNextAssociate( sysroot_key );
203       }
204       /* ...ultimately extending the search into any further
205        * (unlikely) references sections which might be present.
206        */
207       grp = grp->FindNextAssociate( reference_key );
208     }
209   }
210   /* If we fell through the preceding loop, then the expected reference
211    * was not present; return NULL, to report this unexpected result.
212    */
213   return NULL;
214 }
215
216 void pkgManifest::DetachSysRoot( const char *sysroot )
217 {
218   /* Method to remove all references to a specified sysroot
219    * from a package manifest; (note that it would be unusual
220    * for a manifest to refer to a given sysroot more than once,
221    * but we repeat the request until we are sure no more such
222    * references exist...
223    */
224   pkgXmlNode *ref;
225   while( (ref = GetSysRootReference( sysroot )) != NULL )
226     /*
227      * ...deleting each one we do find, as we go.
228      */
229     ref->GetParent()->DeleteChild( ref );
230 }
231
232 /* Format string used to construct absolute path names from the
233  * "sysroot" relative "pathname" maintained in package manifests;
234  * (shared by "pkg_rmdir()" and "pkg_unlink()" functions).
235  */
236 static const char *pkg_path_format = "%s/%s";
237
238 static __inline__ __attribute__((__always_inline__))
239 int pkg_rmdir( const char *sysroot, const char *pathname )
240 {
241   /* Local helper, used to remove directories which become empty
242    * during scheduled package removal; "pathname" is specified from
243    * the package manifest, and is relative to "sysroot", (thus both
244    * must be specified and non-NULL).  Return value is non-zero
245    * when the specified directory is successfully removed, or
246    * zero otherwise.
247    */
248   int retval = 0;
249   if( (sysroot != NULL) && (pathname != NULL) )
250   {
251     /* "sysroot" and "pathname" are both specified.  Construct
252      * the absolute path name, and attempt to "rmdir" it, setting
253      * return value as appropriate; silently ignore failure.
254      */
255     char fullpath[ mkpath( NULL, sysroot, pathname, NULL ) ];
256     mkpath( fullpath, sysroot, pathname, NULL );
257
258     DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_TRANSACTIONS ),
259         dmh_printf( "  %s: rmdir\n", fullpath )
260       );
261
262     retval = rmdir( fullpath ) == 0;
263   }
264   return retval;
265 }
266
267 /* We want the following "unlink" function to emulate "rm -f"
268  * semantics; thus we need to ensure that each file we attempt
269  * to unlink is writeable.  To do this, we call "chmod()" prior
270  * to "unlink()"; we need sys/stat.h for the S_IWRITE mode we
271  * are required to set.
272  */
273 #include <sys/stat.h>
274
275 static __inline__ __attribute__((__always_inline__))
276 int pkg_unlink( const char *sysroot, const char *pathname )
277 {
278   /* Local helper, used to delete files during scheduled package
279    * removal; "pathname" is specified within the package manifest,
280    * and is relative to "sysroot", (thus both must be specified and
281    * non-NULL).  Return value is non-zero when the specified file
282    * is successfully deleted, or zero otherwise.
283    */
284   int retval = 0;
285   if( (sysroot != NULL) && (pathname != NULL) )
286   {
287     char filepath[ mkpath( NULL, sysroot, pathname, NULL ) ];
288     mkpath( filepath, sysroot, pathname, NULL );
289
290     pkgSpinWait::Report( "Deleting %s", pathname );
291     DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_TRANSACTIONS ),
292         dmh_printf( "  %s: unlink file\n", filepath )
293       );
294
295     chmod( filepath, S_IWRITE );
296     if( ((retval = unlink( filepath )) != 0) && (errno != ENOENT) )
297       dmh_notify( DMH_WARNING, "%s:unlink failed; %s\n", filepath, strerror( errno ) );
298   }
299   return retval;
300 }
301
302 EXTERN_C void pkgRemove( pkgActionItem *current )
303 {
304   /* Common handler for all package removal tasks; note that we
305    * initially assert failure, pending reversion on success...
306    */
307   pkgXmlNode *pkg;
308   current->Assert( ACTION_REMOVE_FAILED );
309   if( ((pkg = current->Selection( to_remove )) != NULL)
310   &&  (current->HasAttribute( ACTION_DOWNLOAD_OK ) == ACTION_REMOVE_OK)  )
311   {
312     /* We've identified a candidate package for removal;
313      * first, identify the canonical tarname for the package,
314      * and the sysroot with which it is associated.
315      */
316     const char *tarname = pkg->GetPropVal( tarname_key, value_unknown );
317     pkgXmlNode *sysroot = sysroot_lookup( pkg, tarname );
318
319     /* If the package we are about to remove has an associated
320      * pre-remove script, now is the time to invoke it...
321      */
322     pkg->InvokeScript( "pre-remove" );
323
324     /* ...before we proceed to removal of actual package content.
325      */
326     dmh_printf( " removing %s %s\n", pkg->GetName(), tarname );
327
328     /* Removal of virtual (meta) packages is comparitively simple;
329      * identified by having an associated archive name of "none", they
330      * have no associated archive file, no installed footprint on disk,
331      * and no associated content manifest to process; thus...
332      */
333     if( match_if_explicit( pkg->ArchiveName(), value_none ) )
334     {
335       /* ...we may simply assert the removal action as successful...
336        */
337       current->Assert( 0UL, ~ACTION_REMOVE_FAILED );
338     }
339     else
340     { /* ...but, in the case of packages identified as "real", (which
341        * we expect to be in a substantial majority), do we need to refer
342        * to any installation manifest, to identify actual disk files to
343        * be removed.
344        */
345       const char *refname;
346       pkgManifest inventory( package_key, tarname );
347       pkgXmlNode *ref, *manifest = inventory.GetRoot();
348
349       /* Perform some sanity checks on the retrieved manifest...
350        */
351       if( ((ref = manifest) == NULL)
352       ||  ((ref = ref->FindFirstAssociate( release_key )) == NULL)  )
353         /*
354          * Mostly "belt-and-braces": this should never happen, because
355          * the manifest constructor should never return a manifest which
356          * lacks a "release" element.  If we see this, there is something
357          * seriously wrong; hopefully we will get a bug report.
358          */
359         dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( PKGMSG_NO_RELEASE_KEY ) );
360
361       else if( ((refname = ref->GetPropVal( tarname_key, NULL )) == NULL)
362       ||        (strcmp( tarname, refname ) != 0)                          )
363       {
364         /* Another "belt-and-braces" case: once again, it should never
365          * happen, because the manifest constructor should never return
366          * a manifest with a "tarname" attribute in the "release" element
367          * which doesn't match the "tarname" requested.
368          */
369         dmh_control( DMH_BEGIN_DIGEST );
370         dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( PKGMSG_RELEASE_KEY_MISMATCH ) );
371         if( refname != NULL )
372           dmh_notify( DMH_ERROR, "%s: found %s instead\n", tarname, refname );
373         dmh_control( DMH_END_DIGEST );
374       }
375
376       else if( ref->FindNextAssociate( release_key ) != NULL )
377         /*
378          * Yet another "belt-and-braces" case: the constructor should
379          * never create a manifest which does not have exactly one, and
380          * no more than one, "release" element.
381          */
382         dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( "too many release keys" ) );
383
384       else if( ((ref = manifest->FindFirstAssociate( reference_key )) == NULL)
385       ||        (ref->FindFirstAssociate( sysroot_key ) == NULL)                )
386         dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( "no references" ) );
387
388       else
389       { /* We have a manifest which we may likely be able to process;
390          * before proceeding, perform a few sanity checks, and report
391          * any anomalies which may be recoverable.
392          */
393         const char *sysname = id_lookup( sysroot, NULL );
394         if( inventory.GetSysRootReference( sysname ) == NULL )
395         {
396           /* This indicates that the manifest file itself is lacking
397            * a reference to the sysroot.  The sysroot record indicates
398            * that such a reference should be present; diagnose the
399            * anomaly, and proceed anyway.
400            *
401            * FIXME: we should probably make this an error condition,
402            * which will suppress processing unless it is overridden by
403            * a user specified option.
404            */
405           dmh_notify( DMH_WARNING,
406               "%s: unreferenced in %s\n", sysname, id_lookup( manifest, value_unknown )
407             );
408         }
409         else
410           /* Removal should be successful: revert our original
411            * assertion of failure...
412            */
413           current->Assert( 0UL, ~ACTION_REMOVE_FAILED );
414
415         /* Now, we've validated the manifest, and confirmed that it
416          * correctly records its association with the current sysroot,
417          * (or we've reported the inconsistency; we may proceed with
418          * removal of the associated files.
419          */
420         if( (manifest = manifest->FindFirstAssociate( manifest_key )) != NULL )
421         {
422           /* The manifest records file pathnames relative to sysroot;
423            * thus, first identify the pathname prefix which identifies
424            * the absolute locations of the files and directories which
425            * are to be purged; (note that we specify this as a template
426            * for use with mkpath(), rather than as a simple path name,
427            * so that macros--esp. "%R"--may be correctly resolved at
428            * point of use).
429            */
430           const char *refpath = pathname_lookup( sysroot, value_unknown );
431           char syspath[4 + strlen( refpath )]; sprintf( syspath, "%s%%/F", refpath );
432
433           /* Read the package manifest...
434            */
435           ref = manifest;
436           while( ref != NULL )
437           {
438             /* ...selecting records identifying installed files...
439              */
440             pkgXmlNode *files = ref->FindFirstAssociate( filename_key );
441             while( files != NULL )
442             {
443               /* ...and delete each in turn...
444                */
445               pkg_unlink( syspath, pathname_lookup( files, NULL ) );
446               /*
447                * ...before moving on to the next in the list.
448                */
449               files = files->FindNextAssociate( filename_key );
450             }
451             /* It should not be, but allow for the possibility that
452              * the manifest is subdivided into multiple sections.
453              */
454             ref = ref->FindNextAssociate( manifest_key );
455           }
456
457           /* Having deleted all files associated with the package,
458            * we attempt to prune any directories, which may have been
459            * created during the installation of this package, from the
460            * file system tree.  We note that we may remove only those
461            * directories which no longer contain any files or other
462            * subdirectories, (i.e. those which are leaf directories
463            * within the file system).  We also note that many of the
464            * directories associated with the package being removed
465            * may also contain files belonging to other packages; thus
466            * we do not consider it to be an error if we are unable to
467            * remove any directory specified in the package manifest.
468            *
469            * Removal of any leaf directory may expose its own parent
470            * as a new leaf, which may then itself become a candidate
471            * for removal; thus we adopt an iterative removal procedure,
472            * restarting with a further iteration after any pass through
473            * the manifest in which any directory is removed.
474            */
475           bool restart;
476           do {
477                /* Process the entire manifest on each iteration;
478                 * initially assume that no restart will be required.
479                 */
480                ref = manifest; restart = false;
481                while( ref != NULL )
482                {
483                  /* Select manifest records which specify directories...
484                   */
485                  pkgXmlNode *dir = ref->FindFirstAssociate( dirname_key );
486                  while( dir != NULL )
487                  {
488                    /* ...attempting to remove each in turn; request a
489                     * restart when at least one such attempt succeeds...
490                     */
491                    restart |= pkg_rmdir( syspath, pathname_lookup( dir, NULL ) );
492                    /*
493                     * ...then move on to the next record, if any.
494                     */
495                    dir = dir->FindNextAssociate( dirname_key );
496                  }
497                  /* As in the case of file removal, allow for the
498                   * possibility of a multisectional manifest.
499                   */
500                  ref = ref->FindNextAssociate( manifest_key );
501                }
502                /* Restart the directory removal process, with a new
503                 * iteration through the entire manifest, until no more
504                 * listed directories can be removed.
505                 */
506              } while( restart );
507
508           /* Finally, disassociate the package manifest from the active sysroot;
509            * this will automatically delete the manifest itself, unless it has a
510            * further association with any other sysroot, (e.g. in an alternative
511            * system map).
512            */
513           inventory.DetachSysRoot( sysname );
514         }
515       }
516     }
517     /* In the case of both real and virtual packages, the final phase of removal
518      * is to expunge the installation record from the associated sysroot element
519      * within the system map; (that is, any record of type "installed" contained
520      * within the sysroot element referenced by the "sysroot" pointer identified
521      * above, with a tarname attribute which matches "tarname").
522      */
523     pkgXmlNode *expunge, *instrec = sysroot->FindFirstAssociate( installed_key );
524     while( (expunge = instrec) != NULL )
525     {
526       /* Consider each installation record in turn, as a possible candidate for
527        * deletion; in any case, always locate the NEXT candidate, BEFORE deleting
528        * a matched record, so we don't destroy our point of reference, whence we
529        * must continue the search.
530        */
531       instrec = instrec->FindNextAssociate( installed_key );
532       if( strcmp( tarname, expunge->GetPropVal( tarname_key, value_unknown )) == 0 )
533       {
534         /* The CURRENT candidate matches the "tarname" criterion for deletion;
535          * we may delete it, also marking the sysroot record as "modified", so
536          * that the change will be committed to disk.
537          */
538         sysroot->DeleteChild( expunge );
539         sysroot->SetAttribute( modified_key, value_yes );
540       }
541     }
542     /* Update the internal record of installed state; although no
543      * running CLI instance will return to any point where it needs
544      * this, we may have been called from the GUI, and it requires
545      * consistency here, if the user revisits this package within
546      * any single active session.
547      */
548     pkg->SetAttribute( installed_key, value_no );
549
550     /* After package removal has been completed, we invoke any
551      * post-remove script which may be associated with the package.
552      */
553     pkg->InvokeScript( "post-remove" );
554   }
555   else if( (pkg != NULL) && current->HasAttribute( ACTION_DOWNLOAD ) )
556   {
557     /* This condition arises only when an upgrade has been requested,
558      * but the package archive for the new version is not available in
559      * the local package cache, and all attempts to download it have
560      * been unsuccessful; diagnose, and otherwise ignore it.
561      */
562     dmh_notify( DMH_WARNING, "not removing installed %s\n", pkg->GetName() );
563     dmh_notify( DMH_WARNING, "%s is still installed\n",
564         pkg->GetPropVal( tarname_key, value_unknown )
565       );
566   }
567 }
568
569 /* $RCSfile$: end of file */