summaryrefslogtreecommitdiff
path: root/libre/pacman/0001-Add-conflict-for-replacing-owned-empty-directory.patch
blob: 85622aaac875c6d861e704bfb4fa97ef059ee38e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
From 717fdb8ee0fd23cf72fc7d2832317f513caefa2c Mon Sep 17 00:00:00 2001
From: Allan McRae <allan@archlinux.org>
Date: Sun, 8 Jul 2012 21:36:36 +1000
Subject: [PATCH 1/4] Add conflict for replacing owned empty directory

When two packages own an empty directory, pacman finds no conflict when
one of those packages wants to replace the directory with a file or a
symlink.  When it comes to actually extracting the new file/symlink,
pacman sees the directory is still there (we do not remove empty
directories if they are owned by a package) and refuses to extract.

Detect this potential conflict early and bail. Note that it is a
_potential_ conflict and not a guaranteed one as the other package owning
the directory could be updated or removed first which would remove
the conflict.  However, pacman currently can not sort package installation
order to ensure this, so this conflict requires manual upgrade ordering.

Signed-off-by: Allan McRae <allan@archlinux.org>
Signed-off-by: Dan McGee <dan@archlinux.org>
---
 lib/libalpm/conflict.c               | 32 ++++++++++++++++++++++++++------
 test/pacman/tests/fileconflict009.py | 20 ++++++++++++++++++++
 test/pacman/tests/fileconflict010.py | 20 ++++++++++++++++++++
 3 files changed, 66 insertions(+), 6 deletions(-)
 create mode 100644 test/pacman/tests/fileconflict009.py
 create mode 100644 test/pacman/tests/fileconflict010.py

diff --git a/lib/libalpm/conflict.c b/lib/libalpm/conflict.c
index 32f6f30..efa1a87 100644
--- a/lib/libalpm/conflict.c
+++ b/lib/libalpm/conflict.c
@@ -328,15 +328,35 @@ const alpm_file_t *_alpm_filelist_contains(alpm_filelist_t *filelist,
 	return NULL;
 }
 
-static int dir_belongsto_pkg(const char *root, const char *dirpath,
+static int dir_belongsto_pkg(alpm_handle_t *handle, const char *dirpath,
 		alpm_pkg_t *pkg)
 {
+	alpm_list_t *i;
 	struct stat sbuf;
 	char path[PATH_MAX];
 	char abspath[PATH_MAX];
-	struct dirent *ent = NULL;
 	DIR *dir;
+	struct dirent *ent = NULL;
+	const char *root = handle->root;
+
+	/* TODO: this is an overly strict check but currently pacman will not
+	 * overwrite a directory with a file (case 10/11 in add.c). Adjusting that
+	 * is not simple as even if the directory is being unowned by a conflicting
+	 * package, pacman does not sort this to ensure all required directory
+	 * "removals" happen before installation of file/symlink */
+
+	/* check that no other _installed_ package owns the directory */
+	for(i = _alpm_db_get_pkgcache(handle->db_local); i; i = i->next) {
+		if(pkg == i->data) {
+			continue;
+		}
+
+		if(_alpm_filelist_contains(alpm_pkg_get_files(i->data), dirpath)) {
+			return 0;
+		}
+	}
 
+	/* check all files in directory are owned by the package */
 	snprintf(abspath, PATH_MAX, "%s%s", root, dirpath);
 	dir = opendir(abspath);
 	if(dir == NULL) {
@@ -349,13 +369,13 @@ static int dir_belongsto_pkg(const char *root, const char *dirpath,
 		if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
 			continue;
 		}
-		snprintf(path, PATH_MAX, "%s/%s", dirpath, name);
+		snprintf(path, PATH_MAX, "%s%s", dirpath, name);
 		snprintf(abspath, PATH_MAX, "%s%s", root, path);
 		if(stat(abspath, &sbuf) != 0) {
 			continue;
 		}
 		if(S_ISDIR(sbuf.st_mode)) {
-			if(dir_belongsto_pkg(root, path, pkg)) {
+			if(dir_belongsto_pkg(handle, path, pkg)) {
 				continue;
 			} else {
 				closedir(dir);
@@ -529,9 +549,9 @@ alpm_list_t *_alpm_db_find_fileconflicts(alpm_handle_t *handle,
 				sprintf(dir, "%s/", filestr);
 				if(_alpm_filelist_contains(alpm_pkg_get_files(dbpkg), dir)) {
 					_alpm_log(handle, ALPM_LOG_DEBUG,
-							"check if all files in %s belongs to %s\n",
+							"check if all files in %s belong to %s\n",
 							dir, dbpkg->name);
-					resolved_conflict = dir_belongsto_pkg(handle->root, filestr, dbpkg);
+					resolved_conflict = dir_belongsto_pkg(handle, dir, dbpkg);
 				}
 				free(dir);
 			}
diff --git a/test/pacman/tests/fileconflict009.py b/test/pacman/tests/fileconflict009.py
new file mode 100644
index 0000000..904af4a
--- /dev/null
+++ b/test/pacman/tests/fileconflict009.py
@@ -0,0 +1,20 @@
+self.description = "dir->symlink change during package upgrade (directory conflict)"
+
+lp1 = pmpkg("pkg1")
+lp1.files = ["dir/"]
+self.addpkg2db("local", lp1)
+
+lp2 = pmpkg("pkg2")
+lp2.files = ["dir/"]
+self.addpkg2db("local", lp2)
+
+p = pmpkg("pkg1", "1.0-2")
+p.files = ["dir -> /usr/dir"]
+self.addpkg2db("sync", p)
+
+self.args = "-S pkg1"
+
+self.addrule("PACMAN_RETCODE=1")
+self.addrule("PKG_VERSION=pkg1|1.0-1")
+self.addrule("PKG_VERSION=pkg2|1.0-1")
+self.addrule("DIR_EXIST=dir/")
diff --git a/test/pacman/tests/fileconflict010.py b/test/pacman/tests/fileconflict010.py
new file mode 100644
index 0000000..0a3ce83
--- /dev/null
+++ b/test/pacman/tests/fileconflict010.py
@@ -0,0 +1,20 @@
+self.description = "dir->file change during package upgrade (directory conflict)"
+
+lp1 = pmpkg("pkg1")
+lp1.files = ["dir/"]
+self.addpkg2db("local", lp1)
+
+lp2 = pmpkg("pkg2")
+lp2.files = ["dir/"]
+self.addpkg2db("local", lp2)
+
+p = pmpkg("pkg1", "1.0-2")
+p.files = ["dir"]
+self.addpkg2db("sync", p)
+
+self.args = "-S pkg1"
+
+self.addrule("PACMAN_RETCODE=1")
+self.addrule("PKG_VERSION=pkg1|1.0-1")
+self.addrule("PKG_VERSION=pkg2|1.0-1")
+self.addrule("DIR_EXIST=dir/")
-- 
1.7.11.1