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 <[email protected]>
---
 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

Reply via email to