6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2011-2013, MinGW.org Project
10 * Implementation of the primary package removal methods.
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.
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.
44 #define PKGERR_INVALID_MANIFEST( REASON ) \
45 PKGMSG_INVALID_MANIFEST, tarname, REASON
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"
51 static const char *request_key = "request";
53 static __inline__ __attribute__((__always_inline__))
54 pkgXmlNode *sysroot_lookup( pkgXmlNode *pkg, const char *tarname )
56 /* A local helper function, to identify the sysroot association
57 * for any package which is to be uninstalled.
59 pkgSpecs lookup( tarname );
60 return pkg->GetSysRoot( lookup.GetSubSystemName() );
63 static __inline__ __attribute__((__always_inline__))
64 const char *id_lookup( pkgXmlNode *reftag, const char *fallback )
66 /* A local convenience function, to retrieve the value of
67 * the "id" attribute associated with any pkgXmlNode element.
69 return reftag->GetPropVal( id_key, fallback );
72 static __inline__ __attribute__((__always_inline__))
73 const char *pathname_lookup( pkgXmlNode *reftag, const char *fallback )
75 /* A local convenience function, to retrieve the value of
76 * the "id" attribute associated with any pkgXmlNode element.
78 return reftag->GetPropVal( pathname_key, fallback );
81 unsigned long pkgActionItem::SetAuthorities( pkgActionItem *current )
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.
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.
92 if( (flags & ACTION_PREFLIGHT) == 0 )
94 /* This applies exclusively in the first pass, which is effectively
95 * a "preflight" checking pass only.
97 while( current != NULL )
99 /* Each scheduled action is inspected in turn...
102 if( ((current->flags & ACTION_REMOVE) != 0)
103 && ((ref = current->Selection( to_remove )) != NULL) )
105 /* ...and, when it specifies a "remove" action relating to a
106 * package which has been identified as "installed"...
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"
115 /* ...we identify its associated entry in the installed
116 * package manifest...
118 if( (ref = ref->GetInstallationRecord( tarname )) != NULL )
120 /* ...and then, having confirmed its validity...
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"
128 /* ...we mark it as a candidate for removal...
130 ref->SetAttribute( request_key, action_name( ACTION_REMOVE ) );
132 /* ...and assign a provisional grant of authority
133 * to proceed with the removal...
135 current->flags |= ACTION_PREFLIGHT;
138 /* ...then, we move on to perform the same "preflight" check
139 * for the next scheduled action, if any.
141 current = current->next;
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.
149 return flags |= ACTION_PREFLIGHT;
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.
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.
167 pkgXmlNode *pkgManifest::GetSysRootReference( const char *key )
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.
173 if( (this != NULL) && (manifest != NULL) && (key != NULL) )
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.
179 pkgXmlNode *grp = manifest->GetRoot()->FindFirstAssociate( reference_key );
182 /* Having identified a references section, locate the
183 * first sysroot reference contained therein...
185 pkgXmlNode *ref = grp->FindFirstAssociate( sysroot_key );
188 /* ...then retrieve the sysroot ID key value,
189 * for comparison with the requested key...
192 if( ((chk = ref->GetPropVal( id_key, NULL )) != NULL)
193 && (strcmp( chk, key ) == 0) )
195 * ...returning immediately, if a match is found...
199 /* ...otherwise, repeat check for any further sysroot
200 * references which may be present...
202 ref = ref->FindNextAssociate( sysroot_key );
204 /* ...ultimately extending the search into any further
205 * (unlikely) references sections which might be present.
207 grp = grp->FindNextAssociate( reference_key );
210 /* If we fell through the preceding loop, then the expected reference
211 * was not present; return NULL, to report this unexpected result.
216 void pkgManifest::DetachSysRoot( const char *sysroot )
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...
225 while( (ref = GetSysRootReference( sysroot )) != NULL )
227 * ...deleting each one we do find, as we go.
229 ref->GetParent()->DeleteChild( ref );
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).
236 static const char *pkg_path_format = "%s/%s";
238 static __inline__ __attribute__((__always_inline__))
239 int pkg_rmdir( const char *sysroot, const char *pathname )
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
249 if( (sysroot != NULL) && (pathname != NULL) )
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.
255 char fullpath[ mkpath( NULL, sysroot, pathname, NULL ) ];
256 mkpath( fullpath, sysroot, pathname, NULL );
258 DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_TRANSACTIONS ),
259 dmh_printf( " %s: rmdir\n", fullpath )
262 retval = rmdir( fullpath ) == 0;
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.
273 #include <sys/stat.h>
275 static __inline__ __attribute__((__always_inline__))
276 int pkg_unlink( const char *sysroot, const char *pathname )
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.
285 if( (sysroot != NULL) && (pathname != NULL) )
287 char filepath[ mkpath( NULL, sysroot, pathname, NULL ) ];
288 mkpath( filepath, sysroot, pathname, NULL );
290 pkgSpinWait::Report( "Deleting %s", pathname );
291 DEBUG_INVOKE_IF( DEBUG_REQUEST( DEBUG_TRACE_TRANSACTIONS ),
292 dmh_printf( " %s: unlink file\n", filepath )
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 ) );
302 EXTERN_C void pkgRemove( pkgActionItem *current )
304 /* Common handler for all package removal tasks; note that we
305 * initially assert failure, pending reversion on success...
308 current->Assert( ACTION_REMOVE_FAILED );
309 if( ((pkg = current->Selection( to_remove )) != NULL)
310 && (current->HasAttribute( ACTION_DOWNLOAD_OK ) == ACTION_REMOVE_OK) )
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.
316 const char *tarname = pkg->GetPropVal( tarname_key, value_unknown );
317 pkgXmlNode *sysroot = sysroot_lookup( pkg, tarname );
319 /* If the package we are about to remove has an associated
320 * pre-remove script, now is the time to invoke it...
322 pkg->InvokeScript( "pre-remove" );
324 /* ...before we proceed to removal of actual package content.
326 dmh_printf( " removing %s %s\n", pkg->GetName(), tarname );
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...
333 if( match_if_explicit( pkg->ArchiveName(), value_none ) )
335 /* ...we may simply assert the removal action as successful...
337 current->Assert( 0UL, ~ACTION_REMOVE_FAILED );
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
346 pkgManifest inventory( package_key, tarname );
347 pkgXmlNode *ref, *manifest = inventory.GetRoot();
349 /* Perform some sanity checks on the retrieved manifest...
351 if( ((ref = manifest) == NULL)
352 || ((ref = ref->FindFirstAssociate( release_key )) == NULL) )
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.
359 dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( PKGMSG_NO_RELEASE_KEY ) );
361 else if( ((refname = ref->GetPropVal( tarname_key, NULL )) == NULL)
362 || (strcmp( tarname, refname ) != 0) )
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.
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 );
376 else if( ref->FindNextAssociate( release_key ) != NULL )
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.
382 dmh_notify( DMH_ERROR, PKGERR_INVALID_MANIFEST( "too many release keys" ) );
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" ) );
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.
393 const char *sysname = id_lookup( sysroot, NULL );
394 if( inventory.GetSysRootReference( sysname ) == NULL )
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.
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.
405 dmh_notify( DMH_WARNING,
406 "%s: unreferenced in %s\n", sysname, id_lookup( manifest, value_unknown )
410 /* Removal should be successful: revert our original
411 * assertion of failure...
413 current->Assert( 0UL, ~ACTION_REMOVE_FAILED );
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.
420 if( (manifest = manifest->FindFirstAssociate( manifest_key )) != NULL )
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
430 const char *refpath = pathname_lookup( sysroot, value_unknown );
431 char syspath[4 + strlen( refpath )]; sprintf( syspath, "%s%%/F", refpath );
433 /* Read the package manifest...
438 /* ...selecting records identifying installed files...
440 pkgXmlNode *files = ref->FindFirstAssociate( filename_key );
441 while( files != NULL )
443 /* ...and delete each in turn...
445 pkg_unlink( syspath, pathname_lookup( files, NULL ) );
447 * ...before moving on to the next in the list.
449 files = files->FindNextAssociate( filename_key );
451 /* It should not be, but allow for the possibility that
452 * the manifest is subdivided into multiple sections.
454 ref = ref->FindNextAssociate( manifest_key );
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.
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.
477 /* Process the entire manifest on each iteration;
478 * initially assume that no restart will be required.
480 ref = manifest; restart = false;
483 /* Select manifest records which specify directories...
485 pkgXmlNode *dir = ref->FindFirstAssociate( dirname_key );
488 /* ...attempting to remove each in turn; request a
489 * restart when at least one such attempt succeeds...
491 restart |= pkg_rmdir( syspath, pathname_lookup( dir, NULL ) );
493 * ...then move on to the next record, if any.
495 dir = dir->FindNextAssociate( dirname_key );
497 /* As in the case of file removal, allow for the
498 * possibility of a multisectional manifest.
500 ref = ref->FindNextAssociate( manifest_key );
502 /* Restart the directory removal process, with a new
503 * iteration through the entire manifest, until no more
504 * listed directories can be removed.
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
513 inventory.DetachSysRoot( sysname );
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").
523 pkgXmlNode *expunge, *instrec = sysroot->FindFirstAssociate( installed_key );
524 while( (expunge = instrec) != NULL )
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.
531 instrec = instrec->FindNextAssociate( installed_key );
532 if( strcmp( tarname, expunge->GetPropVal( tarname_key, value_unknown )) == 0 )
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.
538 sysroot->DeleteChild( expunge );
539 sysroot->SetAttribute( modified_key, value_yes );
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.
548 pkg->SetAttribute( installed_key, value_no );
550 /* After package removal has been completed, we invoke any
551 * post-remove script which may be associated with the package.
553 pkg->InvokeScript( "post-remove" );
555 else if( (pkg != NULL) && current->HasAttribute( ACTION_DOWNLOAD ) )
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.
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 )
569 /* $RCSfile$: end of file */