diff --git a/lib/package_remove.c b/lib/package_remove.c index 1ea5aa30..b782a862 100644 --- a/lib/package_remove.c +++ b/lib/package_remove.c @@ -92,6 +92,43 @@ check_remove_pkg_files(struct xbps_handle *xhp, return fail; } +static char * +symlink_target(struct xbps_handle *xhp, const char *path) +{ + struct stat sb; + char *lnk, *res; + ssize_t r; + + if (lstat(path, &sb) == -1) + return NULL; + + lnk = malloc(sb.st_size + 1); + assert(lnk); + + r = readlink(path, lnk, sb.st_size + 1); + if (r < 0 || r > sb.st_size) { + free(lnk); + return NULL; + } + lnk[sb.st_size] = '\0'; + if ((strncmp(lnk, "../", 3) == 0) || strchr(lnk, '/') == NULL) { + char *p, *dname; + + /* relative */ + p = strdup(path); + assert(p); + dname = dirname(p); + assert(dname); + dname += strlen(xhp->rootdir) + 1; + res = xbps_xasprintf("%s/%s", dname, lnk); + free(lnk); + } else { + /* absolute */ + res = lnk; + } + return res; +} + static int remove_pkg_files(struct xbps_handle *xhp, xbps_dictionary_t dict, @@ -101,7 +138,7 @@ remove_pkg_files(struct xbps_handle *xhp, xbps_array_t array; xbps_object_iterator_t iter; xbps_object_t obj; - const char *file, *sha256, *curobj = NULL; + const char *curobj = NULL; /* These are symlinks in Void and must not be removed */ const char *basesymlinks[] = { "/bin", @@ -113,9 +150,7 @@ remove_pkg_files(struct xbps_handle *xhp, "/usr/lib64", "/var/run", }; - char path[PATH_MAX]; int rv = 0; - bool found; assert(xbps_object_type(dict) == XBPS_TYPE_DICTIONARY); assert(key != NULL); @@ -140,6 +175,10 @@ remove_pkg_files(struct xbps_handle *xhp, xbps_object_iterator_reset(iter); while ((obj = xbps_object_iterator_next(iter))) { + const char *file, *sha256; + char path[PATH_MAX]; + bool found; + xbps_dictionary_get_cstring_nocopy(obj, "file", &file); snprintf(path, sizeof(path), "%s/%s", xhp->rootdir, file); @@ -205,6 +244,29 @@ remove_pkg_files(struct xbps_handle *xhp, if (found) { continue; } + if (strcmp(key, "links") == 0) { + const char *target = NULL; + char *lnk; + + lnk = symlink_target(xhp, path); + if (lnk == NULL) { + xbps_dbg_printf(xhp, "[remove] %s " + "symlink_target: %s\n", path, strerror(errno)); + continue; + } + xbps_dictionary_get_cstring_nocopy(obj, "target", &target); + assert(target); + if (strcmp(lnk, target)) { + xbps_dbg_printf(xhp, "[remove] %s symlink " + "modified (stored %s current %s)\n", path, + target, lnk); + if ((xhp->flags & XBPS_FLAG_FORCE_REMOVE_FILES) == 0) { + free(lnk); + continue; + } + } + free(lnk); + } /* * Remove the object if possible. */ diff --git a/tests/xbps/libxbps/shell/remove_test.sh b/tests/xbps/libxbps/shell/remove_test.sh index 6b91dc1d..fec2be24 100644 --- a/tests/xbps/libxbps/shell/remove_test.sh +++ b/tests/xbps/libxbps/shell/remove_test.sh @@ -105,6 +105,69 @@ remove_symlinks_from_root_body() { atf_check_equal $rv 0 } +atf_test_case keep_modified_symlinks + +keep_modified_symlinks_head() { + atf_set "descr" "Tests for package removal: keep modified symlinks in rootdir" +} + +keep_modified_symlinks_body() { + mkdir some_repo + mkdir -p pkg_A/bin + ln -sf /bin/bash pkg_A/bin/ash + ln -sf /bin/bash pkg_A/bin/sh + + cd some_repo + xbps-create -A noarch -n A-1.0_1 -s "A pkg" ../pkg_A + atf_check_equal $? 0 + xbps-rindex -a *.xbps + atf_check_equal $? 0 + cd .. + xbps-install -r root -C null.conf --repository=$PWD/some_repo -y A + atf_check_equal $? 0 + ln -sf /bin/foo root/bin/sh + ln -sf foo root/bin/ash + + xbps-remove -r root -yvd A + atf_check_equal $? 0 + rv=1 + if [ -h root/bin/sh -a -h root/bin/ash ]; then + rv=0 + fi + atf_check_equal $rv 0 +} + +atf_test_case remove_symlinks_modified + +remove_symlinks_modified_head() { + atf_set "descr" "Tests for package removal: force remove modified symlinks in rootdir" +} + +remove_symlinks_modified_body() { + mkdir some_repo + mkdir -p pkg_A/bin + ln -sf /bin/bash pkg_A/bin/ash + ln -sf /bin/bash pkg_A/bin/sh + + cd some_repo + xbps-create -A noarch -n A-1.0_1 -s "A pkg" ../pkg_A + atf_check_equal $? 0 + xbps-rindex -a *.xbps + atf_check_equal $? 0 + cd .. + xbps-install -r root -C null.conf --repository=$PWD/some_repo -y A + atf_check_equal $? 0 + ln -sf /bin/foo root/bin/sh + + xbps-remove -r root -yvfd A + atf_check_equal $? 0 + rv=0 + if [ -h root/bin/sh -o -h root/bin/ash ]; then + rv=1 + fi + atf_check_equal $rv 0 +} + atf_test_case remove_readonly_files remove_readonly_files_head() { @@ -160,8 +223,10 @@ remove_dups_body() { atf_init_test_cases() { atf_add_test_case keep_base_symlinks + atf_add_test_case keep_modified_symlinks atf_add_test_case remove_readonly_files atf_add_test_case remove_symlinks atf_add_test_case remove_symlinks_from_root + atf_add_test_case remove_symlinks_modified atf_add_test_case remove_dups }