Branch data Line data Source code
1 : : /*-
2 : : * Copyright (c) 2011-2014 Baptiste Daroussin <bapt@FreeBSD.org>
3 : : * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
4 : : * Copyright (c) 2011 Will Andrews <will@FreeBSD.org>
5 : : * Copyright (c) 2011 Philippe Pepiot <phil@philpep.org>
6 : : * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
7 : : * Copyright (c) 2023 Serenity Cyber Security, LLC
8 : : * Author: Gleb Popov <arrowd@FreeBSD.org>
9 : : * All rights reserved.
10 : : *
11 : : * Redistribution and use in source and binary forms, with or without
12 : : * modification, are permitted provided that the following conditions
13 : : * are met:
14 : : * 1. Redistributions of source code must retain the above copyright
15 : : * notice, this list of conditions and the following disclaimer
16 : : * in this position and unchanged.
17 : : * 2. Redistributions in binary form must reproduce the above copyright
18 : : * notice, this list of conditions and the following disclaimer in the
19 : : * documentation and/or other materials provided with the distribution.
20 : : *
21 : : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
22 : : * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 : : * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 : : * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
25 : : * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 : : * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 : : * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 : : * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 : : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 : : * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 : : */
32 : :
33 : : #ifdef HAVE_CONFIG_H
34 : : #include "pkg_config.h"
35 : : #endif
36 : :
37 : : #include <assert.h>
38 : : #include <errno.h>
39 : : #include <string.h>
40 : : #include <unistd.h>
41 : : #include <stdlib.h>
42 : : #include <fcntl.h>
43 : :
44 : : #include <bsd_compat.h>
45 : :
46 : : #include "pkg.h"
47 : : #include "private/event.h"
48 : : #include "private/pkg.h"
49 : : #include "private/pkgdb.h"
50 : : #include "private/utils.h"
51 : :
52 : : #if defined(UF_NOUNLINK)
53 : : #define NOCHANGESFLAGS (UF_IMMUTABLE | UF_APPEND | UF_NOUNLINK | SF_IMMUTABLE | SF_APPEND | SF_NOUNLINK)
54 : : #else
55 : : #define NOCHANGESFLAGS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND)
56 : : #endif
57 : :
58 : : int
59 : 44 : pkg_delete(struct pkg *pkg, struct pkg *rpkg, struct pkgdb *db, int flags,
60 : : struct triggers *t)
61 : : {
62 : 44 : xstring *message = NULL;
63 : 44 : int ret, cancel = 0;
64 : 44 : bool handle_rc = false;
65 : 44 : const unsigned load_flags = PKG_LOAD_RDEPS|PKG_LOAD_FILES|PKG_LOAD_DIRS|
66 : : PKG_LOAD_SCRIPTS|PKG_LOAD_ANNOTATIONS|PKG_LOAD_LUA_SCRIPTS;
67 : :
68 [ + - ]: 44 : assert(pkg != NULL);
69 [ + - ]: 44 : assert(db != NULL);
70 : :
71 [ - + ]: 44 : if (pkgdb_ensure_loaded(db, pkg, load_flags) != EPKG_OK)
72 : 0 : return (EPKG_FATAL);
73 [ + + + - ]: 44 : if (rpkg != NULL && pkgdb_ensure_loaded(db, rpkg, load_flags) != EPKG_OK)
74 : 0 : return (EPKG_FATAL);
75 : :
76 : 44 : pkg_emit_new_action();
77 : 44 : pkg_emit_deinstall_begin(pkg);
78 : :
79 : : /* If the package is locked */
80 [ - + ]: 44 : if (pkg->locked) {
81 : 0 : pkg_emit_locked(pkg);
82 : 0 : return (EPKG_LOCKED);
83 : : }
84 : :
85 : : /*
86 : : * stop the different related services if the users do want that
87 : : * and that the service is running
88 : : */
89 : 44 : handle_rc = pkg_object_bool(pkg_config_get("HANDLE_RC_SCRIPTS"));
90 [ + - ]: 44 : if (handle_rc)
91 : 0 : pkg_start_stop_rc_scripts(pkg, PKG_RC_STOP);
92 : :
93 [ + + ]: 44 : if ((flags & (PKG_DELETE_NOSCRIPT | PKG_DELETE_UPGRADE)) == 0) {
94 : 35 : pkg_open_root_fd(pkg);
95 : 35 : ret = pkg_lua_script_run(pkg, PKG_LUA_PRE_DEINSTALL, false);
96 [ - + # # ]: 35 : if (ret != EPKG_OK && ctx.developer_mode)
97 : 0 : return (ret);
98 : 35 : ret = pkg_script_run(pkg, PKG_SCRIPT_PRE_DEINSTALL, false);
99 [ - + # # ]: 35 : if (ret != EPKG_OK && ctx.developer_mode)
100 : 0 : return (ret);
101 : 35 : }
102 : :
103 : 44 : ret = pkg_delete_files(pkg, rpkg, flags, t);
104 [ - + ]: 44 : if (ret == EPKG_CANCEL)
105 : 0 : cancel = 1;
106 [ - + ]: 44 : else if (ret != EPKG_OK)
107 : 0 : return (ret);
108 : :
109 [ + + ]: 44 : if ((flags & (PKG_DELETE_NOSCRIPT | PKG_DELETE_UPGRADE)) == 0) {
110 : 35 : pkg_lua_script_run(pkg, PKG_LUA_POST_DEINSTALL, false);
111 : 35 : pkg_script_run(pkg, PKG_SCRIPT_POST_DEINSTALL, false);
112 : 35 : }
113 : :
114 : 44 : ret = pkg_delete_dirs(db, pkg, NULL);
115 [ - + ]: 44 : if (ret != EPKG_OK)
116 : 0 : return (ret);
117 : :
118 [ + + ]: 44 : if ((flags & PKG_DELETE_UPGRADE) == 0) {
119 : 35 : pkg_emit_deinstall_finished(pkg);
120 [ + + + + : 56 : tll_foreach(pkg->message, m) {
+ + ]
121 [ + + ]: 21 : if (m->item->type == PKG_MESSAGE_REMOVE) {
122 [ - + ]: 3 : if (message == NULL) {
123 : 3 : message = xstring_new();
124 : 6 : pkg_fprintf(message->fp, "Message from "
125 : 3 : "%n-%v:\n", pkg, pkg);
126 : 3 : }
127 : 3 : fprintf(message->fp, "%s\n", m->item->str);
128 : 3 : }
129 : 21 : }
130 [ + + - + ]: 35 : if (pkg_has_message(pkg) && message != NULL) {
131 : 3 : fflush(message->fp);
132 : 3 : pkg_emit_message(message->buf);
133 : 3 : xstring_free(message);
134 : 3 : }
135 : 35 : }
136 : :
137 : 44 : ret = pkgdb_unregister_pkg(db, pkg->id);
138 [ - + ]: 44 : if (ret != EPKG_OK)
139 : 0 : return ret;
140 : :
141 [ - + ]: 44 : return (cancel ? EPKG_CANCEL : ret);
142 : 44 : }
143 : :
144 : : void
145 : 12 : pkg_add_dir_to_del(struct pkg *pkg, const char *file, const char *dir)
146 : : {
147 : : char path[MAXPATHLEN];
148 : : char *tmp;
149 : : size_t len, len2;
150 : :
151 [ + + ]: 12 : strlcpy(path, file != NULL ? file : dir, MAXPATHLEN);
152 : :
153 [ + + ]: 12 : if (file != NULL) {
154 : 9 : tmp = strrchr(path, '/');
155 : 9 : tmp[1] = '\0';
156 : 9 : }
157 : :
158 : 12 : len = strlen(path);
159 : :
160 : : /* make sure to finish by a / */
161 [ + - + + : 12 : if (len > 0 && path[len - 1] != '/' && len < MAXPATHLEN) {
- + ]
162 : 3 : path[len] = '/';
163 : 3 : len++;
164 : 3 : path[len] = '\0';
165 : 3 : }
166 : :
167 [ + + + + : 13 : tll_foreach(pkg->dir_to_del, d) {
- + ]
168 : 5 : len2 = strlen(d->item);
169 [ + - + + ]: 5 : if (len2 >= len && strncmp(path, d->item, len) == 0)
170 : 4 : return;
171 : :
172 [ + - ]: 1 : if (strncmp(path, d->item, len2) == 0) {
173 : 0 : pkg_debug(1, "Replacing in deletion %s with %s",
174 : 0 : d->item, path);
175 [ # # # # : 0 : tll_remove_and_free(pkg->dir_to_del, d, free);
# # ]
176 : 0 : break;
177 : : }
178 : 1 : }
179 : :
180 : 8 : pkg_debug(1, "Adding to deletion %s", path);
181 [ + + + + : 8 : tll_push_back(pkg->dir_to_del, xstrdup(path));
+ - - + +
+ ]
182 : 12 : }
183 : :
184 : : static void
185 : 9 : rmdir_p(struct pkgdb *db, struct pkg *pkg, char *dir, const char *prefix_r)
186 : : {
187 : : char *tmp;
188 : : int64_t cnt;
189 : : char fullpath[MAXPATHLEN];
190 : : size_t len;
191 : : #ifdef HAVE_STRUCT_STAT_ST_FLAGS
192 : : struct stat st;
193 : : #if !defined(HAVE_CHFLAGSAT)
194 : : int fd;
195 : : #endif
196 : : #endif
197 : :
198 : 9 : len = snprintf(fullpath, sizeof(fullpath), "/%s", dir);
199 [ + + ]: 18 : while (fullpath[len -1] == '/') {
200 : 9 : fullpath[len - 1] = '\0';
201 : 9 : len--;
202 : : }
203 [ - + ]: 9 : if (pkgdb_is_dir_used(db, pkg, fullpath, &cnt) != EPKG_OK)
204 : 0 : return;
205 : :
206 : 9 : pkg_debug(1, "Number of packages owning the directory '%s': %d",
207 : 9 : fullpath, (int)cnt);
208 : : /*
209 : : * At this moment the package we are removing have already been removed
210 : : * from the local database so if anything else is owning the directory
211 : : * that is another package meaning only remove the diretory is cnt == 0
212 : : */
213 [ + + ]: 9 : if (cnt > 0)
214 : 1 : return;
215 : :
216 [ + + ]: 8 : if (STREQ(prefix_r, fullpath + 1))
217 : 3 : return;
218 : :
219 : 5 : pkg_debug(1, "removing directory %s", fullpath);
220 : : #ifdef HAVE_STRUCT_STAT_ST_FLAGS
221 [ - + ]: 5 : if (fstatat(pkg->rootfd, dir, &st, AT_SYMLINK_NOFOLLOW) != -1) {
222 [ + - ]: 5 : if (st.st_flags & NOCHANGESFLAGS) {
223 : : #ifdef HAVE_CHFLAGSAT
224 : : /* Disable all flags*/
225 : 0 : chflagsat(pkg->rootfd, dir, 0, AT_SYMLINK_NOFOLLOW);
226 : : #else
227 : : fd = openat(pkg->rootfd, dir, O_NOFOLLOW);
228 : : if (fd > 0) {
229 : : fchflags(fd, 0);
230 : : close(fd);
231 : : }
232 : : #endif
233 : 0 : }
234 : 5 : }
235 : : #endif
236 : :
237 [ + + ]: 5 : if (unlinkat(pkg->rootfd, dir, AT_REMOVEDIR) == -1) {
238 [ - + # # ]: 2 : if (errno != ENOTEMPTY && errno != EBUSY)
239 : 0 : pkg_emit_errno("unlinkat", dir);
240 : : /* If the directory was already removed by a bogus script, continue removing parents */
241 [ + - ]: 2 : if (errno != ENOENT)
242 : 2 : return;
243 : 0 : }
244 : :
245 : : /* No recursivity for packages out of the prefix */
246 [ - + ]: 3 : if (strncmp(prefix_r, dir, strlen(prefix_r)) != 0)
247 : 0 : return;
248 : :
249 : : /* remove the trailing '/' */
250 : 3 : tmp = strrchr(dir, '/');
251 [ + - ]: 3 : if (tmp == NULL)
252 : 0 : return;
253 [ + - ]: 3 : if (tmp == dir)
254 : 0 : return;
255 : :
256 : 3 : tmp[0] = '\0';
257 : 3 : tmp = strrchr(dir, '/');
258 [ + - ]: 3 : if (tmp == NULL)
259 : 0 : return;
260 : :
261 : 3 : tmp[1] = '\0';
262 : :
263 : 3 : rmdir_p(db, pkg, dir, prefix_r);
264 : 9 : }
265 : :
266 : : static void
267 : 77 : pkg_effective_rmdir(struct pkgdb *db, struct pkg *pkg)
268 : : {
269 : : char prefix_r[MAXPATHLEN];
270 : :
271 [ + - ]: 77 : snprintf(prefix_r, sizeof(prefix_r), "%s", pkg->prefix[0] ? pkg->prefix + 1 : "");
272 [ + + + + : 83 : tll_foreach(pkg->dir_to_del, d) {
- + ]
273 : 6 : rmdir_p(db, pkg, d->item, prefix_r);
274 [ + - - + : 6 : tll_remove_and_free(pkg->dir_to_del, d, free);
- + ]
275 : 6 : }
276 : 77 : }
277 : :
278 : : void
279 : 44 : pkg_delete_file(struct pkg *pkg, struct pkg_file *file)
280 : : {
281 : : const char *path;
282 : : const char *prefix_rel;
283 : : size_t len;
284 : : #ifdef HAVE_STRUCT_STAT_ST_FLAGS
285 : : struct stat st;
286 : : #if !defined(HAVE_CHFLAGSAT)
287 : : int fd;
288 : : #endif
289 : : #endif
290 : 44 : pkg_open_root_fd(pkg);
291 : :
292 : 44 : path = file->path;
293 : 44 : path++;
294 : :
295 : 44 : prefix_rel = pkg->prefix;
296 : 44 : prefix_rel++;
297 : 44 : len = strlen(prefix_rel);
298 [ + + + + : 48 : while (len > 0 && prefix_rel[len - 1] == '/')
+ + ]
299 : 4 : len--;
300 : :
301 : : #ifdef HAVE_STRUCT_STAT_ST_FLAGS
302 [ - + ]: 44 : if (fstatat(pkg->rootfd, path, &st, AT_SYMLINK_NOFOLLOW) != -1) {
303 [ + - ]: 44 : if (st.st_flags & NOCHANGESFLAGS) {
304 : : #ifdef HAVE_CHFLAGSAT
305 : 0 : chflagsat(pkg->rootfd, path,
306 : 0 : st.st_flags & ~NOCHANGESFLAGS,
307 : : AT_SYMLINK_NOFOLLOW);
308 : : #else
309 : : fd = openat(pkg->rootfd, path, O_NOFOLLOW);
310 : : if (fd > 0) {
311 : : fchflags(fd, st.st_flags & ~NOCHANGESFLAGS);
312 : : close(fd);
313 : : }
314 : : #endif
315 : 0 : }
316 : 44 : }
317 : : #endif
318 : 44 : pkg_debug(1, "Deleting file: '%s'", path);
319 [ + - ]: 44 : if (unlinkat(pkg->rootfd, path, 0) == -1) {
320 [ # # ]: 0 : if (errno == ENOENT)
321 : 0 : pkg_emit_file_missing(pkg, file);
322 : : else
323 : 0 : pkg_emit_errno("unlinkat", path);
324 : 0 : return;
325 : : }
326 : :
327 : : /* do not bother about directories not in prefix */
328 [ + + + + ]: 44 : if ((strncmp(prefix_rel, path, len) == 0) && path[len] == '/')
329 : 8 : pkg_add_dir_to_del(pkg, path, NULL);
330 : 44 : }
331 : :
332 : : /*
333 : : * Handle a special case: the package is to be upgraded but is being deleted
334 : : * temporarily to handle a file path conflict. In this situation we shouldn't
335 : : * remove configuration files. For now, keep them if the replacement package
336 : : * contains a configuration file at the same path.
337 : : *
338 : : * Note, this currently doesn't handle the case where a configuration file
339 : : * participates in the conflict, i.e., it moves from one package to another.
340 : : */
341 : : static bool
342 : 36 : pkg_delete_skip_config(struct pkg *pkg, struct pkg *rpkg, struct pkg_file *file,
343 : : int flags)
344 : : {
345 [ + + ]: 36 : if ((flags & PKG_DELETE_UPGRADE) == 0)
346 : 30 : return (false);
347 [ - + ]: 6 : if (pkghash_get(pkg->config_files_hash, file->path) == NULL)
348 : 6 : return (false);
349 [ # # ]: 0 : if (pkghash_get(rpkg->config_files_hash, file->path) == NULL)
350 : 0 : return (false);
351 : 0 : return (true);
352 : 36 : }
353 : :
354 : : int
355 : 44 : pkg_delete_files(struct pkg *pkg, struct pkg *rpkg, int flags,
356 : : struct triggers *t)
357 : : {
358 : 44 : struct pkg_file *file = NULL;
359 : :
360 : 44 : int nfiles, cur_file = 0;
361 : 44 : int retcode = EPKG_OK;
362 : :
363 : 44 : nfiles = pkghash_count(pkg->filehash);
364 [ + + ]: 44 : if (nfiles == 0)
365 : 20 : return (EPKG_OK);
366 : :
367 : 24 : pkg_emit_delete_files_begin(pkg);
368 : 24 : pkg_emit_progress_start(NULL);
369 : :
370 [ + + ]: 60 : while (pkg_files(pkg, &file) == EPKG_OK) {
371 [ - + ]: 36 : if (pkg_delete_skip_config(pkg, rpkg, file, flags))
372 : 0 : continue;
373 : 36 : append_touched_file(file->path);
374 [ + - ]: 36 : if (pkg_emit_progress_tick(cur_file++, nfiles))
375 : 0 : retcode = EPKG_CANCEL;
376 : 36 : trigger_is_it_a_cleanup(t, file->path);
377 : 36 : pkg_delete_file(pkg, file);
378 : : }
379 : :
380 : 24 : pkg_emit_progress_tick(nfiles, nfiles);
381 : 24 : pkg_emit_delete_files_finished(pkg);
382 : :
383 : 24 : return (retcode);
384 : 44 : }
385 : :
386 : : void
387 : 1 : pkg_delete_dir(struct pkg *pkg, struct pkg_dir *dir)
388 : : {
389 : : const char *path;
390 : : const char *prefix_rel;
391 : : size_t len;
392 : :
393 : 1 : pkg_open_root_fd(pkg);
394 : :
395 : 1 : path = dir->path;
396 : : /* remove the first / */
397 : 1 : path++;
398 : :
399 : 1 : prefix_rel = pkg->prefix;
400 : 1 : prefix_rel++;
401 : 1 : len = strlen(prefix_rel);
402 [ + - + + : 2 : while (len > 0 && prefix_rel[len - 1] == '/')
+ + ]
403 : 1 : len--;
404 : :
405 [ + - - + ]: 1 : if ((strncmp(prefix_rel, path, len) == 0) && path[len] == '/') {
406 : 1 : pkg_add_dir_to_del(pkg, NULL, path);
407 : 1 : } else {
408 [ # # # # : 0 : tll_push_back(pkg->dir_to_del, xstrdup(path));
# # # # #
# ]
409 : : }
410 : 1 : }
411 : :
412 : : int
413 : 77 : pkg_delete_dirs(__unused struct pkgdb *db, struct pkg *pkg, struct pkg *new)
414 : : {
415 : 77 : struct pkg_dir *dir = NULL;
416 : :
417 [ + + ]: 78 : while (pkg_dirs(pkg, &dir) == EPKG_OK) {
418 [ - + # # ]: 1 : if (new != NULL && !pkg_has_dir(new, dir->path))
419 : 0 : continue;
420 : 1 : pkg_delete_dir(pkg, dir);
421 : : }
422 : :
423 : 77 : pkg_effective_rmdir(db, pkg);
424 : :
425 : 77 : return (EPKG_OK);
426 : : }
|