Allows for safer, more fine-grained control for overwriting files than --force's all-or-nothing approach. Implements FS#31549. Signed-off-by: Andrew Gregory <andrew.gregory.8@gmail.com> --- doc/pacman.8.txt | 12 ++++++++++++ lib/libalpm/alpm.h | 5 +++++ lib/libalpm/conflict.c | 14 ++++++++++---- lib/libalpm/handle.c | 21 +++++++++++++++++++++ lib/libalpm/handle.h | 1 + src/pacman/conf.c | 2 ++ src/pacman/conf.h | 2 ++ src/pacman/pacman.c | 6 ++++++ test/pacman/tests/TESTS | 3 +++ test/pacman/tests/overwrite-files-match-negated.py | 13 +++++++++++++ test/pacman/tests/overwrite-files-match.py | 13 +++++++++++++ test/pacman/tests/overwrite-files-nonmatch.py | 13 +++++++++++++ 12 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 test/pacman/tests/overwrite-files-match-negated.py create mode 100644 test/pacman/tests/overwrite-files-match.py create mode 100644 test/pacman/tests/overwrite-files-nonmatch.py diff --git a/doc/pacman.8.txt b/doc/pacman.8.txt index f940d05c..19b959b1 100644 --- a/doc/pacman.8.txt +++ b/doc/pacman.8.txt @@ -266,6 +266,18 @@ Upgrade Options (apply to '-S' and '-U')[[UO]] *\--needed*:: Do not reinstall the targets that are already up-to-date. +*\--overwrite* <glob>:: + Bypass file conflict checks and overwrite conflicting files. If the + package that is about to be installed contains files that are already + installed and match 'glob', this option will cause all those files to be + overwritten. Using '\--overwrite' will not allow overwriting a directory + with a file or installing packages with conflicting files and directories. + Multiple patterns can be specified by separating them with a comma. May be + specified multiple times. Patterns can be negated, such that files + matching them will not be overwritten, by prefixing them with an + exclamation mark. Subsequent matches will override previous ones. A leading + literal exclamation mark or backslash needs to be escaped. + Query Options (apply to '-Q')[[QO]] ----------------------------------- diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h index 49cf200a..de0adc1e 100644 --- a/lib/libalpm/alpm.h +++ b/lib/libalpm/alpm.h @@ -830,6 +830,11 @@ int alpm_option_add_hookdir(alpm_handle_t *handle, const char *hookdir); int alpm_option_remove_hookdir(alpm_handle_t *handle, const char *hookdir); /** @} */ +alpm_list_t *alpm_option_get_overwrite_files(alpm_handle_t *handle); +int alpm_option_set_overwrite_files(alpm_handle_t *handle, alpm_list_t *globs); +int alpm_option_add_overwrite_file(alpm_handle_t *handle, const char *glob); +int alpm_option_remove_overwrite_file(alpm_handle_t *handle, const char *glob); + /** Returns the logfile name. */ const char *alpm_option_get_logfile(alpm_handle_t *handle); /** Sets the logfile name. */ diff --git a/lib/libalpm/conflict.c b/lib/libalpm/conflict.c index b1666dfe..0e37419f 100644 --- a/lib/libalpm/conflict.c +++ b/lib/libalpm/conflict.c @@ -385,6 +385,12 @@ static alpm_list_t *alpm_db_find_file_owners(alpm_db_t* db, const char *path) return owners; } +static int _alpm_can_overwrite_file(alpm_handle_t *handle, const char *path) +{ + return handle->trans->flags & ALPM_TRANS_FLAG_FORCE + || _alpm_fnmatch_patterns(handle->overwrite_files, path) == 0; +} + /** * @brief Find file conflicts that may occur during the transaction. * @@ -448,8 +454,8 @@ alpm_list_t *_alpm_db_find_fileconflicts(alpm_handle_t *handle, /* can skip file-file conflicts when forced * * checking presence in p2_files detects dir-file or file-dir * conflicts as the path from p1 is returned */ - if((handle->trans->flags & ALPM_TRANS_FLAG_FORCE) && - alpm_filelist_contains(p2_files, filename)) { + if(_alpm_can_overwrite_file(handle, filename) + && alpm_filelist_contains(p2_files, filename)) { _alpm_log(handle, ALPM_LOG_DEBUG, "%s exists in both '%s' and '%s'\n", filename, p1->name, p2->name); @@ -654,8 +660,8 @@ alpm_list_t *_alpm_db_find_fileconflicts(alpm_handle_t *handle, } /* skip file-file conflicts when being forced */ - if((handle->trans->flags & ALPM_TRANS_FLAG_FORCE) && - !S_ISDIR(lsbuf.st_mode)) { + if(!S_ISDIR(lsbuf.st_mode) + && _alpm_can_overwrite_file(handle, filestr)) { _alpm_log(handle, ALPM_LOG_DEBUG, "conflict with file on filesystem being forced\n"); resolved_conflict = 1; diff --git a/lib/libalpm/handle.c b/lib/libalpm/handle.c index 5a5cd448..9f1b179a 100644 --- a/lib/libalpm/handle.c +++ b/lib/libalpm/handle.c @@ -283,6 +283,12 @@ alpm_list_t SYMEXPORT *alpm_option_get_ignoregroups(alpm_handle_t *handle) return handle->ignoregroup; } +alpm_list_t SYMEXPORT *alpm_option_get_overwrite_files(alpm_handle_t *handle) +{ + CHECK_HANDLE(handle, return NULL); + return handle->overwrite_files; +} + alpm_list_t SYMEXPORT *alpm_option_get_assumeinstalled(alpm_handle_t *handle) { CHECK_HANDLE(handle, return NULL); @@ -657,6 +663,21 @@ int SYMEXPORT alpm_option_remove_ignoregroup(alpm_handle_t *handle, const char * return _alpm_option_strlist_rem(handle, &(handle->ignoregroup), grp); } +int SYMEXPORT alpm_option_add_overwrite_file(alpm_handle_t *handle, const char *glob) +{ + return _alpm_option_strlist_add(handle, &(handle->overwrite_files), glob); +} + +int SYMEXPORT alpm_option_set_overwrite_files(alpm_handle_t *handle, alpm_list_t *globs) +{ + return _alpm_option_strlist_set(handle, &(handle->overwrite_files), globs); +} + +int SYMEXPORT alpm_option_remove_overwrite_file(alpm_handle_t *handle, const char *glob) +{ + return _alpm_option_strlist_rem(handle, &(handle->overwrite_files), glob); +} + int SYMEXPORT alpm_option_add_assumeinstalled(alpm_handle_t *handle, const alpm_depend_t *dep) { alpm_depend_t *depcpy; diff --git a/lib/libalpm/handle.h b/lib/libalpm/handle.h index e096bfb3..4c9a0759 100644 --- a/lib/libalpm/handle.h +++ b/lib/libalpm/handle.h @@ -83,6 +83,7 @@ struct __alpm_handle_t { char *gpgdir; /* Directory where GnuPG files are stored */ alpm_list_t *cachedirs; /* Paths to pacman cache directories */ alpm_list_t *hookdirs; /* Paths to hook directories */ + alpm_list_t *overwrite_files; /* Paths that may be overwritten */ /* package lists */ alpm_list_t *noupgrade; /* List of packages NOT to be upgraded */ diff --git a/src/pacman/conf.c b/src/pacman/conf.c index 52151c56..cd68aff8 100644 --- a/src/pacman/conf.c +++ b/src/pacman/conf.c @@ -781,6 +781,8 @@ static int setup_libalpm(void) alpm_option_set_cachedirs(handle, config->cachedirs); } + alpm_option_set_overwrite_files(handle, config->overwrite_files); + alpm_option_set_default_siglevel(handle, config->siglevel); config->localfilesiglevel = merge_siglevel(config->siglevel, diff --git a/src/pacman/conf.h b/src/pacman/conf.h index 636fec61..e6ab8379 100644 --- a/src/pacman/conf.h +++ b/src/pacman/conf.h @@ -122,6 +122,7 @@ typedef struct __config_t { alpm_list_t *assumeinstalled; alpm_list_t *noupgrade; alpm_list_t *noextract; + alpm_list_t *overwrite_files; char *xfercommand; /* our connection to libalpm */ @@ -171,6 +172,7 @@ enum { OP_GPGDIR, OP_DBONLY, OP_FORCE, + OP_OVERWRITE_FILES, OP_COLOR, OP_DBPATH, OP_CASCADE, diff --git a/src/pacman/pacman.c b/src/pacman/pacman.c index a459cf00..2e31fcfc 100644 --- a/src/pacman/pacman.c +++ b/src/pacman/pacman.c @@ -190,6 +190,8 @@ static void usage(int op, const char * const myname) case PM_OP_SYNC: case PM_OP_UPGRADE: addlist(_(" --force force install, overwrite conflicting files\n")); + addlist(_(" --overwrite <path>\n" + " overwrite conflicting files (can be used more than once)\n")); addlist(_(" --asdeps install packages as non-explicitly installed\n")); addlist(_(" --asexplicit install packages as explicitly installed\n")); addlist(_(" --ignore <pkg> ignore a package upgrade (can be used more than once)\n")); @@ -703,6 +705,9 @@ static int parsearg_upgrade(int opt) case OP_FORCE: config->flags |= ALPM_TRANS_FLAG_FORCE; break; + case OP_OVERWRITE_FILES: + parsearg_util_addlist(&(config->overwrite_files)); + break; case OP_ASDEPS: config->flags |= ALPM_TRANS_FLAG_ALLDEPS; break; @@ -924,6 +929,7 @@ static int parseargs(int argc, char *argv[]) {"assume-installed", required_argument, 0, OP_ASSUMEINSTALLED}, {"debug", optional_argument, 0, OP_DEBUG}, {"force", no_argument, 0, OP_FORCE}, + {"overwrite", required_argument, 0, OP_OVERWRITE_FILES}, {"noprogressbar", no_argument, 0, OP_NOPROGRESSBAR}, {"noscriptlet", no_argument, 0, OP_NOSCRIPTLET}, {"ask", required_argument, 0, OP_ASK}, diff --git a/test/pacman/tests/TESTS b/test/pacman/tests/TESTS index 11d1c38c..eadb63b4 100644 --- a/test/pacman/tests/TESTS +++ b/test/pacman/tests/TESTS @@ -80,6 +80,9 @@ TESTS += test/pacman/tests/mode001.py TESTS += test/pacman/tests/mode002.py TESTS += test/pacman/tests/mode003.py TESTS += test/pacman/tests/noupgrade-inverted.py +TESTS += test/pacman/tests/overwrite-files-match-negated.py +TESTS += test/pacman/tests/overwrite-files-match.py +TESTS += test/pacman/tests/overwrite-files-nonmatch.py TESTS += test/pacman/tests/pacman001.py TESTS += test/pacman/tests/pacman002.py TESTS += test/pacman/tests/pacman003.py diff --git a/test/pacman/tests/overwrite-files-match-negated.py b/test/pacman/tests/overwrite-files-match-negated.py new file mode 100644 index 00000000..42b1be2d --- /dev/null +++ b/test/pacman/tests/overwrite-files-match-negated.py @@ -0,0 +1,13 @@ +self.description = "Install a package with an existing file matching a negated --overwrite pattern" + +p = pmpkg("dummy") +p.files = ["foobar"] +self.addpkg(p) + +self.filesystem = ["foobar*"] + +self.args = "-U --overwrite=foobar --overwrite=!foo* %s" % p.filename() + +self.addrule("!PACMAN_RETCODE=0") +self.addrule("!PKG_EXIST=dummy") +self.addrule("!FILE_MODIFIED=foobar") diff --git a/test/pacman/tests/overwrite-files-match.py b/test/pacman/tests/overwrite-files-match.py new file mode 100644 index 00000000..004155c3 --- /dev/null +++ b/test/pacman/tests/overwrite-files-match.py @@ -0,0 +1,13 @@ +self.description = "Install a package with an existing file matching an --overwrite pattern" + +p = pmpkg("dummy") +p.files = ["foobar"] +self.addpkg(p) + +self.filesystem = ["foobar*"] + +self.args = "-U --overwrite=foobar %s" % p.filename() + +self.addrule("PACMAN_RETCODE=0") +self.addrule("PKG_EXIST=dummy") +self.addrule("FILE_MODIFIED=foobar") diff --git a/test/pacman/tests/overwrite-files-nonmatch.py b/test/pacman/tests/overwrite-files-nonmatch.py new file mode 100644 index 00000000..38932d5f --- /dev/null +++ b/test/pacman/tests/overwrite-files-nonmatch.py @@ -0,0 +1,13 @@ +self.description = "Install a package with an existing file not matching --overwrite patterns" + +p = pmpkg("dummy") +p.files = ["foobar"] +self.addpkg(p) + +self.filesystem = ["foobar*"] + +self.args = "-U --overwrite=foo %s" % p.filename() + +self.addrule("!PACMAN_RETCODE=0") +self.addrule("!PKG_EXIST=dummy") +self.addrule("!FILE_MODIFIED=foobar") -- 2.12.1