OSDN Git Service

Initial implementation for "remove" feature.
[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, MinGW 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
41 #define PKGERR_INVALID_MANIFEST( REASON )  \
42   PKGMSG_INVALID_MANIFEST, tarname, REASON
43
44 #define PKGMSG_INVALID_MANIFEST         "%s: invalid manifest; %s\n"
45 #define PKGMSG_NO_RELEASE_KEY           "no release key assigned"
46 #define PKGMSG_RELEASE_KEY_MISMATCH     "release key mismatch"
47
48 static const char *request_key = "request";
49
50 static __inline__ __attribute__((__always_inline__))
51 pkgXmlNode *sysroot_lookup( pkgXmlNode *pkg, const char *tarname )
52 {
53   /* A local helper function, to identify the sysroot association
54    * for any package which is to be uninstalled.
55    */
56   pkgSpecs lookup( tarname );
57   return pkg->GetSysRoot( lookup.GetSubSystemName() );
58 }
59
60 static __inline__ __attribute__((__always_inline__))
61 const char *id_lookup( pkgXmlNode *reftag, const char *fallback )
62 {
63   /* A local convenience function, to retrieve the value of
64    * the "id" attribute associated with any pkgXmlNode element.
65    */
66   return reftag->GetPropVal( id_key, fallback );
67 }
68
69 static __inline__ __attribute__((__always_inline__))
70 const char *pathname_lookup( pkgXmlNode *reftag, const char *fallback )
71 {
72   /* A local convenience function, to retrieve the value of
73    * the "id" attribute associated with any pkgXmlNode element.
74    */
75   return reftag->GetPropVal( pathname_key, fallback );
76 }
77
78 int pkgActionItem::SetAuthorities( pkgActionItem *current )
79 {
80   /* Helper method to either grant or revoke authority for removal of
81    * any package, either permanently when the user requests that it be
82    * removed, or temporarily in preparation for replacement be a newer
83    * version, when scheduled for upgrade.
84    *
85    * This is a multiple pass method, iterating over the entire list
86    * of scheduled actions within each pass, until the entire schedule
87    * of authorities has been established.
88    */
89   if( (flags & ACTION_PREFLIGHT) == 0 )
90   {
91     /* This applies exclusively in the first pass, which is effectively
92      * a "preflight" checking pass only.
93      */
94     while( current != NULL )
95     {
96       /* Each scheduled action is inspected in turn...
97        */
98       pkgXmlNode *ref;
99       if( ((current->flags & ACTION_REMOVE) != 0)
100       &&  ((ref = current->Selection( to_remove )) != NULL)  )
101       {
102         /* ...and, when it specifies a "remove" action relating to a
103          * package which has been identified as "installed"...
104          */
105         const char *tarname = ref->GetPropVal( tarname_key, value_unknown );
106         DEBUG_INVOKE_IF( DEBUGLEVEL && DEBUG_TRACE_INIT,
107             dmh_printf( "%s: selected for %s\n",
108               (tarname = ref->GetPropVal( tarname_key, value_unknown )),
109               (current->flags & ACTION_INSTALL) ? "upgrade" : "removal"
110           ));
111
112         /* ...we identify its associated entry in the installed
113          * package manifest...
114          */
115         if( (ref = ref->GetInstallationRecord( tarname )) != NULL )
116         {
117           /* ...and then, having confirmed its validity...
118            */
119           DEBUG_INVOKE_IF( DEBUGLEVEL && DEBUG_TRACE_INIT,
120               dmh_printf( "%s: marked for %s\n",
121                 ref->GetPropVal( tarname_key, value_unknown ),
122                 (current->flags & ACTION_INSTALL) ? "upgrade" : "removal"
123             ));
124
125           /* ...we mark it as a candidate for removal...
126            */
127           ref->SetAttribute( request_key, action_name( ACTION_REMOVE ) );
128
129           /* ...and assign a provisional grant of authority
130            * to proceed with the removal...
131            */
132           current->flags |= ACTION_PREFLIGHT;
133         }
134       }
135       /* ...then, we move on to perform the same "preflight" check
136        * for the next scheduled action, if any.
137        */
138       current = current->next;
139     }
140     /* When we get to here, we have completed the "preflight" checks
141      * for all scheduled actions.  The "current" pointer has advanced,
142      * but the "this" pointer still refers to the head of the actions
143      * list; terminate this "preflight" pass, marking the associated
144      * entry to confirm that it has been completed.
145      */
146     return flags |= ACTION_PREFLIGHT;
147   }
148
149   /* We will get to here only in second and subsequent passes, after
150    * the initial "preflight" checks have been completed.  For actions
151    * which schedule a package removal, (whether explicitly, or as an
152    * implicit prerequisite in preparation for upgrade), the initial
153    * pass will have provisionally authorised removal; in subsequent
154    * passes, we either ratify that authority, or we revoke it in the
155    * event that it would break a dependency of another package.
156    *
157    * FIXME: we need to develop the code to perform the dependency
158    * analysis; for the time being, we simply return zero, ratifying
159    * authority for all removal requests.
160    */
161   return 0;
162 }
163
164 pkgXmlNode *pkgManifest::GetSysRootReference( const char *key )
165 {
166   /* Method to verify that a package manifest includes
167    * a reference to any sysroot which claims it, returning
168    * a pointer to the first such reference found.
169    */
170   if( (this != NULL) && (manifest != NULL) && (key != NULL) )
171   {
172     /* We appear to have a valid manifest, and a valid sysroot
173      * key to match; locate this manifest's first, (and nominally
174      * its only), references section.
175      */
176     pkgXmlNode *grp = manifest->GetRoot()->FindFirstAssociate( reference_key );
177     while( grp != NULL )
178     {
179       /* Having identified a references section, locate the
180        * first sysroot reference contained therein...
181        */
182       pkgXmlNode *ref = grp->FindFirstAssociate( sysroot_key );
183       while( ref != NULL )
184       {
185         /* ...then retrieve the sysroot ID key value,
186          * for comparison with the requested key...
187          */
188         const char *chk;
189         if( ((chk = ref->GetPropVal( id_key, NULL )) != NULL)
190         &&  (strcmp( chk, key ) == 0)                        )
191           /*
192            * ...returning immediately, if a match is found...
193            */
194           return ref;
195
196         /* ...otherwise, repeat check for any further sysroot
197          * references which may be present...
198          */
199         ref = ref->FindNextAssociate( sysroot_key );
200       }
201       /* ...ultimately extending the search into any further
202        * (unlikely) references sections which might be present.
203        */
204       grp = grp->FindNextAssociate( reference_key );
205     }
206   }
207   /* If we fell through the preceding loop, then the expected reference
208    * was not present; return NULL, to report this unexpected result.
209    */
210   return NULL;
211 }
212
213 void pkgManifest::DetachSysRoot( const char *sysroot )
214 {
215   /* Method to remove all references to a specified sysroot
216    * from a package manifest; (note that it would be unusual
217    * for a manifest to refer to a given sysroot more than once,
218    * but we repeat the request until we are sure no more such
219    * references exist...
220    */
221   pkgXmlNode *ref;
222   while( (ref = GetSysRootReference( sysroot )) != NULL )
223     /*
224      * ...deleting each one we do find, as we go.
225      */
226     ref->GetParent()->DeleteChild( ref );
227 }
228
229 /* Format string used to construct absolute path names from the
230  * "sysroot" relative "pathname" maintained in package manifests;
231  * (shared by "pkg_rmdir()" and "pkg_unlink()" functions).
232  */
233 static const char *pkg_path_format = "%s/%s";
234
235 static __inline__ __attribute__((__always_inline__))
236 int pkg_rmdir( const char *sysroot, const char *pathname )
237 {
238   /* Local helper, used to remove directories which become empty
239    * during scheduled package removal; "pathname" is specified from
240    * the package manifest, and is relative to "sysroot", (thus both
241    * must be specified and non-NULL).  Return value is non-zero
242    * when the specified directory is successfully removed, or
243    * zero otherwise.
244    */
245   int retval = 0;
246   if( (sysroot != NULL) && (pathname != NULL) )
247   {
248     /* "sysroot" and "pathname" are both specified.  Construct
249      * the absolute path name, and attempt to "rmdir" it, setting
250      * return value as appropriate; silently ignore failure.
251      */
252     char fullpath[ 1 + snprintf( NULL, 0, pkg_path_format, sysroot, pathname ) ];
253     snprintf( fullpath, sizeof( fullpath ), pkg_path_format, sysroot, pathname );
254
255     DEBUG_INVOKE_IF( DEBUGLEVEL && DEBUG_TRACE_TRANSACTIONS,
256         dmh_printf( "  %s: rmdir\n", fullpath )
257       );
258
259     retval = rmdir( fullpath ) == 0;
260   }
261   return retval;
262 }
263
264 static __inline__ __attribute__((__always_inline__))
265 int pkg_unlink( const char *sysroot, const char *pathname )
266 {
267   /* Local helper, used to delete files during scheduled package
268    * removal; "pathname" is specified within the package manifest,
269    * and is relative to "sysroot", (thus both must be specified and
270    * non-NULL).  Return value is non-zero when the specified file
271    * is successfully deleted, or zero otherwise.
272    */
273   int retval = 0;
274   if( (sysroot != NULL) && (pathname != NULL) )
275   {
276     char filepath[ 1 + snprintf( NULL, 0, pkg_path_format, sysroot, pathname ) ];
277     snprintf( filepath, sizeof( filepath ), pkg_path_format, sysroot, pathname );
278
279     DEBUG_INVOKE_IF( DEBUGLEVEL && DEBUG_TRACE_TRANSACTIONS,
280         dmh_printf( "  %s: unlink file\n", filepath )
281       );
282
283     if( ((retval = unlink( filepath )) != 0) && (errno != ENOENT) )
284       dmh_notify( DMH_WARNING, "%s:unlink failed; %s\n", filepath, strerror( errno ) );
285   }
286   return retval;
287 }
288
289 EXTERN_C void pkgRemove( pkgActionItem *current )
290 {
291   /* Common handler for all package removal tasks...
292    */
293   pkgXmlNode *pkg;
294   if( (pkg = current->Selection( to_remove )) != NULL )
295   {
296     /* We've identified a candidate package for removal;
297      * first, identify the canonical tarname for the package,
298      * and the sysroot with which it is associated.
299      */
300     const char *tarname = pkg->GetPropVal( tarname_key, value_unknown );
301     pkgXmlNode *sysroot = sysroot_lookup( pkg, tarname );
302
303     dmh_printf( " removing %s %s\n", pkg->GetName(), tarname );
304
305     /* Removal of virtual (meta) packages is comparitively simple;
306      * identified by having an associated archive name of "none", they
307      * have no associated archive file, no installed footprint on disk,
308      * and no associated content manifest to process; thus...
309      */
310     if( ! match_if_explicit( pkg->ArchiveName(), value_none ) )
311     {
312       /* ...only in the case of packages identified as "real", (which
313        * we expect to be in a substantial majority), do we need to refer
314        * to any installation manifest, to identify actual disk files to
315        * be removed.
316        */
317       const char *refname;
318       pkgManifest inventory( package_key, tarname );
319       pkgXmlNode *ref, *manifest = inventory.GetRoot();
320
321       /* Perform some sanity checks on the retrieved manifest...
322        */
323       if( ((ref = manifest) == NULL)
324       ||  ((ref = ref->FindFirstAssociate( release_key )) == NULL)  )
325         /*
326          * Mostly "belt-and-braces": this should never happen, because
327          * the manifest constructor should never return a manifest which
328          * lacks a "release" element.  If we see this, there is something
329          * seriously wrong; hopefully we will get a bug report.
330          */
331         dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( PKGMSG_NO_RELEASE_KEY ) );
332
333       else if( ((refname = ref->GetPropVal( tarname_key, NULL )) == NULL)
334       ||        (strcmp( tarname, refname ) != 0)                          )
335       {
336         /* Another "belt-and-braces" case: once again, it should never
337          * happen, because the manifest constructor should never return
338          * a manifest with a "tarname" attribute in the "release" element
339          * which doesn't match the "tarname" requested.
340          */
341         dmh_control( DMH_BEGIN_DIGEST );
342         dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( PKGMSG_RELEASE_KEY_MISMATCH ) );
343         if( refname != NULL )
344           dmh_notify( DMH_ERROR, "%s: found %s instead\n", tarname, refname );
345         dmh_control( DMH_END_DIGEST );
346       }
347
348       else if( ref->FindNextAssociate( release_key ) != NULL )
349         /*
350          * Yet another "belt-and-braces" case: the constructor should
351          * never create a manifest which does not have exactly one, and
352          * no more than one, "release" element.
353          */
354         dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( "too many release keys" ) );
355
356       else if( ((ref = manifest->FindFirstAssociate( reference_key )) == NULL)
357       ||        (ref->FindFirstAssociate( sysroot_key ) == NULL)                )
358         dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( "no references" ) );
359
360       else
361       { /* We have a manifest which we may likely be able to process;
362          * before proceeding, perform a few sanity checks, and report
363          * any anomalies which may be recoverable.
364          */
365         const char *sysname = id_lookup( sysroot, NULL );
366         if( inventory.GetSysRootReference( sysname ) == NULL )
367         {
368           /* This indicates that the manifest file itself is lacking
369            * a reference to the sysroot.  The sysroot record indicates
370            * that such a reference should be present; diagnose the
371            * anomaly, and proceed anyway.
372            *
373            * FIXME: we should probably make this an error condition,
374            * which will suppress processing unless it is overridden by
375            * a user specified option.
376            */
377           dmh_notify( DMH_WARNING,
378               "%s: unreferenced in %s\n", sysname, id_lookup( manifest, value_unknown )
379             );
380         }
381         /* Now, we've validated the manifest, and confirmed that it
382          * correctly records its association with the current sysroot,
383          * (or we've reported the inconsistency; we may proceed with
384          * removal of the associated files.
385          */
386         if( (manifest = manifest->FindFirstAssociate( manifest_key )) != NULL )
387         {
388           /* The manifest records file pathnames relative to sysroot;
389            * thus, first identify the pathname prefix which identifies
390            * the absolute locations of the files and directories which
391            * are to be purged.
392            */
393           const char *syspath = pathname_lookup( sysroot, value_unknown );
394
395           /* Read the package manifest...
396            */
397           ref = manifest;
398           while( ref != NULL )
399           {
400             /* ...selecting records identifying installed files...
401              */
402             pkgXmlNode *files = ref->FindFirstAssociate( filename_key );
403             while( files != NULL )
404             {
405               /* ...and delete each in turn...
406                */
407               pkg_unlink( syspath, pathname_lookup( files, NULL ) );
408               /*
409                * ...before moving on to the next in the list.
410                */
411               files = files->FindNextAssociate( filename_key );
412             }
413             /* It should not be, but allow for the possibility that
414              * the manifest is subdivided into multiple sections.
415              */
416             ref = ref->FindNextAssociate( manifest_key );
417           }
418
419           /* Having deleted all files associated with the package,
420            * we attempt to prune any directories, which may have been
421            * created during the installation of this package, from the
422            * file system tree.  We note that we may remove only those
423            * directories which no longer contain any files or other
424            * subdirectories, (i.e. those which are leaf directories
425            * within the file system).  We also note that many of the
426            * directories associated with the package being removed
427            * may also contain files belonging to other packages; thus
428            * we do not consider it to be an error if we are unable to
429            * remove any directory specified in the package manifest.
430            *
431            * Removal of any leaf directory may expose its own parent
432            * as a new leaf, which may then itself become a candidate
433            * for removal; thus we adopt an iterative removal procedure,
434            * restarting with a further iteration after any pass through
435            * the manifest in which any directory is removed.
436            */
437           bool restart;
438           do {
439                /* Process the entire manifest on each iteration;
440                 * initially assume that no restart will be required.
441                 */
442                ref = manifest; restart = false;
443                while( ref != NULL )
444                {
445                  /* Select manifest records which specify directories...
446                   */
447                  pkgXmlNode *dir = ref->FindFirstAssociate( dirname_key );
448                  while( dir != NULL )
449                  {
450                    /* ...attempting to remove each in turn; request a
451                     * restart when at least one such attempt succeeds...
452                     */
453                    restart |= pkg_rmdir( syspath, pathname_lookup( dir, NULL ) );
454                    /*
455                     * ...then move on to the next record, if any.
456                     */
457                    dir = dir->FindNextAssociate( dirname_key );
458                  }
459                  /* As in the case of file removal, allow for the
460                   * possibility of a multisectional manifest.
461                   */
462                  ref = ref->FindNextAssociate( manifest_key );
463                }
464                /* Restart the directory removal process, with a new
465                 * iteration through the entire manifest, until no more
466                 * listed directories can be removed.
467                 */
468              } while( restart );
469
470           /* Finally, disassociate the package manifest from the active sysroot;
471            * this will automatically delete the manifest itself, unless it has a
472            * further association with any other sysroot, (e.g. in an alternative
473            * system map).
474            */
475           inventory.DetachSysRoot( sysname );
476         }
477       }
478     }
479     /* In the case of both real and virtual packages, the final phase of removal
480      * is to expunge the installation record from the associated sysroot element
481      * within the system map; (that is, any record of type "installed" contained
482      * within the sysroot element referenced by the "sysroot" pointer identified
483      * above, with a tarname attribute which matches "tarname").
484      */
485     pkgXmlNode *expunge, *instrec = sysroot->FindFirstAssociate( installed_key );
486     while( (expunge = instrec) != NULL )
487     {
488       /* Consider each installation record in turn, as a possible candidate for
489        * deletion; in any case, always locate the NEXT candidate, BEFORE deleting
490        * a matched record, so we don't destroy our point of reference, whence we
491        * must continue the search.
492        */
493       instrec = instrec->FindNextAssociate( installed_key );
494       if( strcmp( tarname, expunge->GetPropVal( tarname_key, value_unknown )) == 0 )
495       {
496         /* The CURRENT candidate matches the "tarname" criterion for deletion;
497          * we may delete it, also marking the sysroot record as "modified", so
498          * that the change will be committed to disk.
499          */
500         sysroot->DeleteChild( expunge );
501         sysroot->SetAttribute( modified_key, value_yes );
502       }
503     }
504   }
505 }
506
507 /* $RCSfile$: end of file */