1 //
2 // pkgutils
3 //
4 // Copyright (c) 2000-2005 Per Liden
5 // Copyright (c) 2006-2021 by CRUX team (http://crux.nu)
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or
10 // (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
20 // USA.
21 //
22
23 #include "pkgadd.h"
24 #include <fstream>
25 #include <iterator>
26 #include <cstdio>
27 #include <regex.h>
28 #include <unistd.h>
29
30 void pkgadd::run(int argc, char** argv)
31 {
32 //
33 // Check command line options
34 //
35 string o_root;
36 string o_config;
37 string o_package;
38 bool o_upgrade = false;
39 bool o_force = false;
40
41 for (int i = 1; i < argc; i++) {
42 string option(argv[i]);
43 if (option == "-r" || option == "--root") {
44 assert_argument(argv, argc, i);
45 o_root = argv[i + 1];
46 i++;
47 } else if (option == "-c" || option == "--config") {
48 assert_argument(argv, argc, i);
49 o_config = argv[i + 1];
50 i++;
51 } else if (option == "-u" || option == "--upgrade") {
52 o_upgrade = true;
53 } else if (option == "-f" || option == "--force") {
54 o_force = true;
55 } else if (option[0] == '-' || !o_package.empty()) {
56 throw runtime_error("invalid option " + option);
57 } else {
58 o_package = option;
59 }
60 }
61
62 if (o_package.empty())
63 throw runtime_error("option missing");
64
65 //
66 // Check UID
67 //
68 if (getuid())
69 throw runtime_error("only root can install/upgrade packages");
70
71 //
72 // Install/upgrade package
73 //
74 {
75 db_lock lock(o_root, true);
76 db_open(o_root);
77
78 pair<string, pkginfo_t> package = pkg_open(o_package);
79 vector<rule_t> config_rules = read_config(o_config);
80
81 bool installed = db_find_pkg(package.first);
82 if (installed && !o_upgrade)
83 throw runtime_error("package " + package.first + " already installed (use -u to upgrade)");
84 else if (!installed && o_upgrade)
85 throw runtime_error("package " + package.first + " not previously installed (skip -u to install)");
86
87 set<string> non_install_files = apply_install_rules(package.first, package.second, config_rules);
88 set<string> conflicting_files = db_find_conflicts(package.first, package.second);
89
90 if (!conflicting_files.empty()) {
91 if (o_force) {
92 set<string> keep_list;
93 if (o_upgrade) // Don't remove files matching the rules in configuration
94 keep_list = make_keep_list(conflicting_files, config_rules);
95 db_rm_files(conflicting_files, keep_list); // Remove unwanted conflicts
96 } else {
97 copy(conflicting_files.begin(), conflicting_files.end(), ostream_iterator<string>(cerr, "\n"));
98 throw runtime_error("listed file(s) already installed (use -f to ignore and overwrite)");
99 }
100 }
101
102 set<string> keep_list;
103
104 if (o_upgrade) {
105 keep_list = make_keep_list(package.second.files, config_rules);
106 db_rm_pkg(package.first, keep_list);
107 }
108
109 db_add_pkg(package.first, package.second);
110 db_commit();
111 try {
112 pkg_install(o_package, keep_list, non_install_files, installed);
113 } catch (runtime_error&) {
114 if (!installed) {
115 db_rm_pkg(package.first);
116 db_commit();
117 throw runtime_error("failed");
118 }
119 }
120 ldconfig();
121 }
122 }
123
124 void pkgadd::print_help() const
125 {
126 cout << "usage: " << utilname << " [options] <file>" << endl
127 << "options:" << endl
128 << " -u, --upgrade upgrade package with the same name" << endl
129 << " -f, --force force install, overwrite conflicting files" << endl
130 << " -r, --root <path> specify alternative installation root" << endl
131 << " -c, --config <file> use alternate configuration file" << endl
132 << " -v, --version print version and exit" << endl
133 << " -h, --help print help and exit" << endl;
134 }
135
136 vector<rule_t> pkgadd::read_config(string file) const
137 {
138 vector<rule_t> rules;
139 unsigned int linecount = 0;
140 string filename = root + PKGADD_CONF;
141
142 if (!file.empty()) filename = file;
143 ifstream in(filename.c_str());
144
145 if (in) {
146 while (!in.eof()) {
147 string line;
148 getline(in, line);
149 linecount++;
150 if (!line.empty() && line[0] != '#') {
151 if (line.length() >= PKGADD_CONF_MAXLINE)
152 throw runtime_error(filename + ":" + itos(linecount) + ": line too long, aborting");
153
154 char event[PKGADD_CONF_MAXLINE];
155 char pattern[PKGADD_CONF_MAXLINE];
156 char action[PKGADD_CONF_MAXLINE];
157 char dummy[PKGADD_CONF_MAXLINE];
158 if (sscanf(line.c_str(), "%s %s %s %s", event, pattern, action, dummy) != 3)
159 throw runtime_error(filename + ":" + itos(linecount) + ": wrong number of arguments, aborting");
160
161 if (!strcmp(event, "UPGRADE") || !strcmp(event, "INSTALL")) {
162 rule_t rule;
163 rule.event = strcmp(event, "UPGRADE") ? INSTALL : UPGRADE;
164 rule.pattern = pattern;
165 if (!strcmp(action, "YES")) {
166 rule.action = true;
167 } else if (!strcmp(action, "NO")) {
168 rule.action = false;
169 } else
170 throw runtime_error(filename + ":" + itos(linecount) + ": '" +
171 string(action) + "' unknown action, should be YES or NO, aborting");
172
173 rules.push_back(rule);
174 } else
175 throw runtime_error(filename + ":" + itos(linecount) + ": '" +
176 string(event) + "' unknown event, aborting");
177 }
178 }
179 in.close();
180 }
181
182 #ifndef NDEBUG
183 cerr << "Configuration:" << endl;
184 for (vector<rule_t>::const_iterator j = rules.begin(); j != rules.end(); j++) {
185 cerr << "\t" << (*j).pattern << "\t" << (*j).action << endl;
186 }
187 cerr << endl;
188 #endif
189
190 return rules;
191 }
192
193 set<string> pkgadd::make_keep_list(const set<string>& files, const vector<rule_t>& rules) const
194 {
195 set<string> keep_list;
196 vector<rule_t> found;
197
198 find_rules(rules, UPGRADE, found);
199
200 for (set<string>::const_iterator i = files.begin(); i != files.end(); i++) {
201 for (vector<rule_t>::reverse_iterator j = found.rbegin(); j != found.rend(); j++) {
202 if (rule_applies_to_file(*j, *i)) {
203 if (!(*j).action)
204 keep_list.insert(keep_list.end(), *i);
205
206 break;
207 }
208 }
209 }
210
211 #ifndef NDEBUG
212 cerr << "Keep list:" << endl;
213 for (set<string>::const_iterator j = keep_list.begin(); j != keep_list.end(); j++) {
214 cerr << " " << (*j) << endl;
215 }
216 cerr << endl;
217 #endif
218
219 return keep_list;
220 }
221
222 set<string> pkgadd::apply_install_rules(const string& name, pkginfo_t& info, const vector<rule_t>& rules)
223 {
224 // TODO: better algo(?)
225 set<string> install_set;
226 set<string> non_install_set;
227 vector<rule_t> found;
228
229 find_rules(rules, INSTALL, found);
230
231 for (set<string>::const_iterator i = info.files.begin(); i != info.files.end(); i++) {
232 bool install_file = true;
233
234 for (vector<rule_t>::reverse_iterator j = found.rbegin(); j != found.rend(); j++) {
235 if (rule_applies_to_file(*j, *i)) {
236 install_file = (*j).action;
237 break;
238 }
239 }
240
241 if (install_file)
242 install_set.insert(install_set.end(), *i);
243 else
244 non_install_set.insert(*i);
245 }
246
247 info.files.clear();
248 info.files = install_set;
249
250 #ifndef NDEBUG
251 cerr << "Install set:" << endl;
252 for (set<string>::iterator j = info.files.begin(); j != info.files.end(); j++) {
253 cerr << " " << (*j) << endl;
254 }
255 cerr << endl;
256
257 cerr << "Non-Install set:" << endl;
258 for (set<string>::iterator j = non_install_set.begin(); j != non_install_set.end(); j++) {
259 cerr << " " << (*j) << endl;
260 }
261 cerr << endl;
262 #endif
263
264 return non_install_set;
265 }
266
267 void pkgadd::find_rules(const vector<rule_t>& rules, rule_event_t event, vector<rule_t>& found) const
268 {
269 for (vector<rule_t>::const_iterator i = rules.begin(); i != rules.end(); i++)
270 if (i->event == event)
271 found.push_back(*i);
272 }
273
274 bool pkgadd::rule_applies_to_file(const rule_t& rule, const string& file) const
275 {
276 regex_t preg;
277 bool ret;
278
279 if (regcomp(&preg, rule.pattern.c_str(), REG_EXTENDED | REG_NOSUB))
280 throw runtime_error("error compiling regular expression '" + rule.pattern + "', aborting");
281
282 ret = !regexec(&preg, file.c_str(), 0, 0, 0);
283 regfree(&preg);
284
285 return ret;
286 }
|