XBPS Library API  0.19
The X Binary Package System
package_remove.c
1 /*-
2  * Copyright (c) 2009-2012 Juan Romero Pardines.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include <stdio.h>
27 #include <stdbool.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <dirent.h>
32 #include <libgen.h>
33 
34 #include "xbps_api_impl.h"
35 
36 /**
37  * @file lib/package_remove.c
38  * @brief Package removal routines
39  * @defgroup pkg_remove Package removal functions
40  *
41  * These functions will remove a package or only a subset of its
42  * files. Package removal steps:
43  * -# Its <b>pre-remove</b> target specified in the REMOVE script
44  * will be executed.
45  * -# Its links, files, conf_files and dirs will be removed.
46  * Modified files (not matchings its sha256 hash) are preserved, unless
47  * XBPS_FLAG_FORCE_REMOVE_FILES flag is set via xbps_init::flags member.
48  * -# Its <b>post-remove</b> target specified in the REMOVE script
49  * will be executed.
50  * -# Its state will be changed to XBPS_PKG_STATE_HALF_REMOVED.
51  * -# Its <b>purge-remove</b> target specified in the REMOVE script
52  * will be executed.
53  * -# Its package metadata file will be removed.
54  * -# Package will be unregistered from package database.
55  *
56  * @note
57  * -# If a package is going to be updated, only steps <b>1</b> and <b>4</b>
58  * will be executed.
59  * -# If a package is going to be removed, all steps will be executed.
60  *
61  * The following image shows the structure of an internalized package's
62  * files.plist dictionary:
63  *
64  * @image html images/xbps_pkg_files_dictionary.png
65  *
66  * Legend:
67  * - <b>Salmon bg box</b>: XBPS_PKGFILES plist file.
68  * - <b>White bg box</b>: mandatory objects.
69  * - <b>Grey bg box</b>: optional objects.
70  *
71  * Text inside of white boxes are the key associated with the object, its
72  * data type is specified on its edge, i.e string, array, integer, dictionary.
73  */
74 int
76  prop_dictionary_t dict,
77  const char *key,
78  const char *pkgver)
79 {
80  struct stat st;
81  prop_array_t array;
82  prop_object_iterator_t iter;
83  prop_object_t obj;
84  const char *file, *sha256, *version, *curobj = NULL;
85  char *path = NULL, *pkgname = NULL;
86  char buf[PATH_MAX];
87  int rv = 0;
88 
89  assert(prop_object_type(dict) == PROP_TYPE_DICTIONARY);
90  assert(key != NULL);
91 
92  array = prop_dictionary_get(dict, key);
93  if ((prop_object_type(array) != PROP_TYPE_ARRAY) ||
94  prop_array_count(array) == 0)
95  return 0;
96 
97  iter = xbps_array_iter_from_dict(dict, key);
98  if (iter == NULL)
99  return ENOMEM;
100 
101  if (strcmp(key, "files") == 0)
102  curobj = "file";
103  else if (strcmp(key, "conf_files") == 0)
104  curobj = "configuration file";
105  else if (strcmp(key, "links") == 0)
106  curobj = "link";
107  else if (strcmp(key, "dirs") == 0)
108  curobj = "directory";
109 
110  pkgname = xbps_pkg_name(pkgver);
111  assert(pkgname);
112  version = xbps_pkg_version(pkgver);
113 
114  while ((obj = prop_object_iterator_next(iter))) {
115  prop_dictionary_get_cstring_nocopy(obj, "file", &file);
116  path = xbps_xasprintf("%s/%s", xhp->rootdir, file);
117 
118  if ((strcmp(key, "files") == 0) ||
119  (strcmp(key, "conf_files") == 0)) {
120  /*
121  * Check SHA256 hash in regular files and
122  * configuration files.
123  */
124  prop_dictionary_get_cstring_nocopy(obj,
125  "sha256", &sha256);
126  rv = xbps_file_hash_check(path, sha256);
127  if (rv == ENOENT) {
128  /* missing file, ignore it */
129  xbps_set_cb_state(xhp,
130  XBPS_STATE_REMOVE_FILE_HASH_FAIL,
131  rv, pkgname, version,
132  "%s: failed to check hash for %s `%s': %s",
133  pkgver, curobj, file, strerror(rv));
134  free(path);
135  rv = 0;
136  continue;
137  } else if (rv == ERANGE) {
138  rv = 0;
139  if ((xhp->flags &
140  XBPS_FLAG_FORCE_REMOVE_FILES) == 0) {
141  xbps_set_cb_state(xhp,
142  XBPS_STATE_REMOVE_FILE_HASH_FAIL,
143  0, pkgname, version,
144  "%s: %s `%s' SHA256 mismatch, "
145  "preserving file", pkgver,
146  curobj, file);
147  free(path);
148  continue;
149  } else {
150  xbps_set_cb_state(xhp,
151  XBPS_STATE_REMOVE_FILE_HASH_FAIL,
152  0, pkgname, version,
153  "%s: %s `%s' SHA256 mismatch, "
154  "forcing removal", pkgver,
155  curobj, file);
156  }
157  } else if (rv != 0 && rv != ERANGE) {
158  xbps_set_cb_state(xhp,
159  XBPS_STATE_REMOVE_FILE_HASH_FAIL,
160  rv, pkgname, version,
161  "%s: [remove] failed to check hash for "
162  "%s `%s': %s", pkgver, curobj, file,
163  strerror(rv));
164  free(path);
165  break;
166  }
167  } else if (strcmp(key, "links") == 0) {
168  /*
169  * All regular files from package were removed at this
170  * point, so we will only remove dangling symlinks.
171  */
172  if (realpath(path, buf) == NULL) {
173  if (errno != ENOENT) {
174  free(path);
175  rv = errno;
176  break;
177  }
178  }
179  if (stat(buf, &st) == 0) {
180  free(path);
181  continue;
182  }
183  }
184  /*
185  * Remove the object if possible.
186  */
187  if (remove(path) == -1) {
188  xbps_set_cb_state(xhp, XBPS_STATE_REMOVE_FILE_FAIL,
189  errno, pkgname, version,
190  "%s: failed to remove %s `%s': %s", pkgver,
191  curobj, file, strerror(errno));
192  errno = 0;
193  } else {
194  /* success */
195  xbps_set_cb_state(xhp, XBPS_STATE_REMOVE_FILE,
196  0, pkgname, version,
197  "Removed %s `%s'", curobj, file);
198  }
199  free(path);
200  }
201  prop_object_iterator_release(iter);
202  free(pkgname);
203 
204  return rv;
205 }
206 
207 int
209  const char *pkgver,
210  bool update,
211  bool soft_replace)
212 {
213  prop_dictionary_t pkgd = NULL;
214  char *pkgname, *buf = NULL;
215  const char *version;
216  int rv = 0;
217  pkg_state_t state = 0;
218 
219  assert(xhp);
220  assert(pkgver);
221 
222  pkgname = xbps_pkg_name(pkgver);
223  assert(pkgname);
224  version = xbps_pkg_version(pkgver);
225  assert(version);
226 
227  if ((rv = xbps_pkg_state_installed(xhp, pkgname, &state)) != 0)
228  goto out;
229 
230  xbps_dbg_printf(xhp, "attempting to remove %s state %d\n",
231  pkgver, state);
232 
233  if (!update)
234  xbps_set_cb_state(xhp, XBPS_STATE_REMOVE, 0, pkgname, version, NULL);
235 
236  pkgver = xbps_xasprintf("%s-%s", pkgname, version);
237 
238  if (chdir(xhp->rootdir) == -1) {
239  rv = errno;
240  xbps_set_cb_state(xhp, XBPS_STATE_REMOVE_FAIL,
241  rv, pkgname, version,
242  "%s: [remove] failed to chdir to rootdir `%s': %s",
243  pkgver, xhp->rootdir, strerror(rv));
244  goto out;
245  }
246 
247  /* internalize pkg dictionary from metadir */
248  buf = xbps_xasprintf("%s/.%s.plist", xhp->metadir, pkgname);
249  pkgd = prop_dictionary_internalize_from_file(buf);
250  free(buf);
251  if (pkgd == NULL)
252  xbps_dbg_printf(xhp, "WARNING: metaplist for %s "
253  "doesn't exist!\n", pkgname);
254 
255  /* If package was "half-removed", remove it fully. */
256  if (state == XBPS_PKG_STATE_HALF_REMOVED)
257  goto purge;
258  /*
259  * Run the pre remove action.
260  */
261  if (pkgd) {
262  rv = xbps_pkg_exec_script(xhp, pkgd, "remove", "pre", update);
263  if (rv != 0) {
264  xbps_set_cb_state(xhp, XBPS_STATE_REMOVE_FAIL,
265  errno, pkgname, version,
266  "%s: [remove] REMOVE script failed to "
267  "execute pre ACTION: %s",
268  pkgver, strerror(errno));
269  rv = errno;
270  goto out;
271  }
272  }
273  /*
274  * If updating a package, we just need to execute the current
275  * pre-remove action target and we are done. Its files will be
276  * overwritten later in unpack phase.
277  */
278  if (update) {
279  if (pkgd)
280  prop_object_release(pkgd);
281  free(pkgname);
282  return 0;
283  } else if (soft_replace) {
284  /*
285  * Soft replace a package. Do not remove its files, but
286  * execute PURGE action, remove metadata files and unregister
287  * from pkgdb.
288  */
289  goto softreplace;
290  }
291 
292  if (pkgd) {
293  /* Remove regular files */
294  if ((rv = xbps_remove_pkg_files(xhp, pkgd, "files", pkgver)) != 0)
295  goto out;
296  /* Remove configuration files */
297  if ((rv = xbps_remove_pkg_files(xhp, pkgd, "conf_files", pkgver)) != 0)
298  goto out;
299  /* Remove links */
300  if ((rv = xbps_remove_pkg_files(xhp, pkgd, "links", pkgver)) != 0)
301  goto out;
302  /* Remove dirs */
303  if ((rv = xbps_remove_pkg_files(xhp, pkgd, "dirs", pkgver)) != 0)
304  goto out;
305  /*
306  * Execute the post REMOVE action if file exists and we aren't
307  * updating the package.
308  */
309  rv = xbps_pkg_exec_script(xhp, pkgd, "remove-script", "post", false);
310  if (rv != 0) {
311  xbps_set_cb_state(xhp, XBPS_STATE_REMOVE_FAIL,
312  rv, pkgname, version,
313  "%s: [remove] REMOVE script failed to execute "
314  "post ACTION: %s", pkgver, strerror(rv));
315  goto out;
316  }
317  }
318 
319 softreplace:
320  /*
321  * Set package state to "half-removed".
322  */
323  rv = xbps_set_pkg_state_installed(xhp, pkgname, version,
324  XBPS_PKG_STATE_HALF_REMOVED);
325  if (rv != 0) {
326  xbps_set_cb_state(xhp, XBPS_STATE_REMOVE_FAIL,
327  rv, pkgname, version,
328  "%s: [remove] failed to set state to half-removed: %s",
329  pkgver, strerror(rv));
330  goto out;
331  }
332 
333 purge:
334  /*
335  * Execute the purge REMOVE action if file exists.
336  */
337  if (pkgd) {
338  rv = xbps_pkg_exec_script(xhp, pkgd, "remove-script", "purge", false);
339  if (rv != 0) {
340  xbps_set_cb_state(xhp, XBPS_STATE_REMOVE_FAIL,
341  rv, pkgname, version,
342  "%s: REMOVE script failed to execute "
343  "purge ACTION: %s", pkgver, strerror(rv));
344  goto out;
345  }
346  prop_object_release(pkgd);
347  }
348  /*
349  * Remove package metadata plist.
350  */
351  buf = xbps_xasprintf("%s/.%s.plist", xhp->metadir, pkgname);
352  if (remove(buf) == -1) {
353  if (errno != ENOENT) {
354  xbps_set_cb_state(xhp, XBPS_STATE_REMOVE_FAIL,
355  rv, pkgname, version,
356  "%s: failed to remove metadata file: %s",
357  pkgver, strerror(errno));
358  }
359  }
360  /*
361  * Unregister package from pkgdb.
362  */
363  if ((rv = xbps_unregister_pkg(xhp, pkgver, true)) != 0)
364  goto out;
365 
366  xbps_dbg_printf(xhp, "[remove] unregister %s returned %d\n", pkgver, rv);
367 
368  xbps_set_cb_state(xhp, XBPS_STATE_REMOVE_DONE,
369  0, pkgname, version, NULL);
370 out:
371  if (buf != NULL)
372  free(buf);
373  if (pkgname != NULL)
374  free(pkgname);
375 
376  return rv;
377 }