OSDN Git Service

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