Commit | Line | Data |
---|---|---|
9ac667e6 SR |
1 | // |
2 | // pkgutils | |
3 | // | |
4 | // Copyright (c) 2000-2005 Per Liden | |
6fe55c25 | 5 | // Copyright (c) 2006-2007 by CRUX team (http://crux.nu) |
9ac667e6 SR |
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 "pkgutil.h" | |
24 | #include <iostream> | |
25 | #include <fstream> | |
26 | #include <iterator> | |
27 | #include <algorithm> | |
28 | #include <cstdio> | |
29 | #include <cstring> | |
30 | #include <cerrno> | |
31 | #include <csignal> | |
32 | #include <ext/stdio_filebuf.h> | |
33 | #include <pwd.h> | |
34 | #include <grp.h> | |
35 | #include <sys/types.h> | |
36 | #include <sys/stat.h> | |
37 | #include <sys/wait.h> | |
38 | #include <sys/file.h> | |
39 | #include <sys/param.h> | |
40 | #include <unistd.h> | |
41 | #include <fcntl.h> | |
9ac667e6 | 42 | #include <libgen.h> |
c3434674 MCO |
43 | #include <archive.h> |
44 | #include <archive_entry.h> | |
9ac667e6 | 45 | |
c9984aa8 | 46 | #define INIT_ARCHIVE(ar) \ |
0b4af85e | 47 | archive_read_support_compression_gzip((ar)); \ |
a09b1434 TS |
48 | archive_read_support_compression_bzip2((ar)); \ |
49 | archive_read_support_compression_xz((ar)); \ | |
0b4af85e | 50 | archive_read_support_format_tar((ar)) |
c9984aa8 | 51 | |
9ac667e6 SR |
52 | using __gnu_cxx::stdio_filebuf; |
53 | ||
9ac667e6 SR |
54 | pkgutil::pkgutil(const string& name) |
55 | : utilname(name) | |
56 | { | |
57 | // Ignore signals | |
58 | struct sigaction sa; | |
59 | memset(&sa, 0, sizeof(sa)); | |
60 | sa.sa_handler = SIG_IGN; | |
61 | sigaction(SIGHUP, &sa, 0); | |
62 | sigaction(SIGINT, &sa, 0); | |
63 | sigaction(SIGQUIT, &sa, 0); | |
64 | sigaction(SIGTERM, &sa, 0); | |
65 | } | |
66 | ||
67 | void pkgutil::db_open(const string& path) | |
68 | { | |
69 | // Read database | |
70 | root = trim_filename(path + "/"); | |
71 | const string filename = root + PKG_DB; | |
72 | ||
73 | int fd = open(filename.c_str(), O_RDONLY); | |
74 | if (fd == -1) | |
75 | throw runtime_error_with_errno("could not open " + filename); | |
76 | ||
77 | stdio_filebuf<char> filebuf(fd, ios::in, getpagesize()); | |
78 | istream in(&filebuf); | |
79 | if (!in) | |
80 | throw runtime_error_with_errno("could not read " + filename); | |
81 | ||
82 | while (!in.eof()) { | |
83 | // Read record | |
84 | string name; | |
85 | pkginfo_t info; | |
86 | getline(in, name); | |
87 | getline(in, info.version); | |
88 | for (;;) { | |
89 | string file; | |
90 | getline(in, file); | |
91 | ||
92 | if (file.empty()) | |
93 | break; // End of record | |
94 | ||
95 | info.files.insert(info.files.end(), file); | |
96 | } | |
97 | if (!info.files.empty()) | |
98 | packages[name] = info; | |
99 | } | |
100 | ||
101 | #ifndef NDEBUG | |
102 | cerr << packages.size() << " packages found in database" << endl; | |
103 | #endif | |
104 | } | |
105 | ||
106 | void pkgutil::db_commit() | |
107 | { | |
108 | const string dbfilename = root + PKG_DB; | |
109 | const string dbfilename_new = dbfilename + ".incomplete_transaction"; | |
110 | const string dbfilename_bak = dbfilename + ".backup"; | |
111 | ||
112 | // Remove failed transaction (if it exists) | |
113 | if (unlink(dbfilename_new.c_str()) == -1 && errno != ENOENT) | |
114 | throw runtime_error_with_errno("could not remove " + dbfilename_new); | |
115 | ||
116 | // Write new database | |
117 | int fd_new = creat(dbfilename_new.c_str(), 0444); | |
118 | if (fd_new == -1) | |
119 | throw runtime_error_with_errno("could not create " + dbfilename_new); | |
120 | ||
121 | stdio_filebuf<char> filebuf_new(fd_new, ios::out, getpagesize()); | |
122 | ostream db_new(&filebuf_new); | |
123 | for (packages_t::const_iterator i = packages.begin(); i != packages.end(); ++i) { | |
124 | if (!i->second.files.empty()) { | |
125 | db_new << i->first << "\n"; | |
126 | db_new << i->second.version << "\n"; | |
127 | copy(i->second.files.begin(), i->second.files.end(), ostream_iterator<string>(db_new, "\n")); | |
128 | db_new << "\n"; | |
129 | } | |
130 | } | |
131 | ||
132 | db_new.flush(); | |
133 | ||
134 | // Make sure the new database was successfully written | |
135 | if (!db_new) | |
136 | throw runtime_error("could not write " + dbfilename_new); | |
137 | ||
138 | // Synchronize file to disk | |
139 | if (fsync(fd_new) == -1) | |
140 | throw runtime_error_with_errno("could not synchronize " + dbfilename_new); | |
141 | ||
142 | // Relink database backup | |
143 | if (unlink(dbfilename_bak.c_str()) == -1 && errno != ENOENT) | |
144 | throw runtime_error_with_errno("could not remove " + dbfilename_bak); | |
145 | if (link(dbfilename.c_str(), dbfilename_bak.c_str()) == -1) | |
146 | throw runtime_error_with_errno("could not create " + dbfilename_bak); | |
147 | ||
148 | // Move new database into place | |
149 | if (rename(dbfilename_new.c_str(), dbfilename.c_str()) == -1) | |
150 | throw runtime_error_with_errno("could not rename " + dbfilename_new + " to " + dbfilename); | |
151 | ||
152 | #ifndef NDEBUG | |
153 | cerr << packages.size() << " packages written to database" << endl; | |
154 | #endif | |
155 | } | |
156 | ||
157 | void pkgutil::db_add_pkg(const string& name, const pkginfo_t& info) | |
158 | { | |
159 | packages[name] = info; | |
160 | } | |
161 | ||
162 | bool pkgutil::db_find_pkg(const string& name) | |
163 | { | |
164 | return (packages.find(name) != packages.end()); | |
165 | } | |
166 | ||
167 | void pkgutil::db_rm_pkg(const string& name) | |
168 | { | |
169 | set<string> files = packages[name].files; | |
170 | packages.erase(name); | |
171 | ||
172 | #ifndef NDEBUG | |
173 | cerr << "Removing package phase 1 (all files in package):" << endl; | |
174 | copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n")); | |
175 | cerr << endl; | |
176 | #endif | |
177 | ||
178 | // Don't delete files that still have references | |
179 | for (packages_t::const_iterator i = packages.begin(); i != packages.end(); ++i) | |
180 | for (set<string>::const_iterator j = i->second.files.begin(); j != i->second.files.end(); ++j) | |
181 | files.erase(*j); | |
182 | ||
183 | #ifndef NDEBUG | |
184 | cerr << "Removing package phase 2 (files that still have references excluded):" << endl; | |
185 | copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n")); | |
186 | cerr << endl; | |
187 | #endif | |
188 | ||
189 | // Delete the files | |
190 | for (set<string>::const_reverse_iterator i = files.rbegin(); i != files.rend(); ++i) { | |
191 | const string filename = root + *i; | |
192 | if (file_exists(filename) && remove(filename.c_str()) == -1) { | |
193 | const char* msg = strerror(errno); | |
194 | cerr << utilname << ": could not remove " << filename << ": " << msg << endl; | |
195 | } | |
196 | } | |
197 | } | |
198 | ||
199 | void pkgutil::db_rm_pkg(const string& name, const set<string>& keep_list) | |
200 | { | |
201 | set<string> files = packages[name].files; | |
202 | packages.erase(name); | |
203 | ||
204 | #ifndef NDEBUG | |
205 | cerr << "Removing package phase 1 (all files in package):" << endl; | |
206 | copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n")); | |
207 | cerr << endl; | |
208 | #endif | |
209 | ||
210 | // Don't delete files found in the keep list | |
211 | for (set<string>::const_iterator i = keep_list.begin(); i != keep_list.end(); ++i) | |
212 | files.erase(*i); | |
213 | ||
214 | #ifndef NDEBUG | |
215 | cerr << "Removing package phase 2 (files that is in the keep list excluded):" << endl; | |
216 | copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n")); | |
217 | cerr << endl; | |
218 | #endif | |
219 | ||
220 | // Don't delete files that still have references | |
221 | for (packages_t::const_iterator i = packages.begin(); i != packages.end(); ++i) | |
222 | for (set<string>::const_iterator j = i->second.files.begin(); j != i->second.files.end(); ++j) | |
223 | files.erase(*j); | |
224 | ||
225 | #ifndef NDEBUG | |
226 | cerr << "Removing package phase 3 (files that still have references excluded):" << endl; | |
227 | copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n")); | |
228 | cerr << endl; | |
229 | #endif | |
230 | ||
231 | // Delete the files | |
232 | for (set<string>::const_reverse_iterator i = files.rbegin(); i != files.rend(); ++i) { | |
233 | const string filename = root + *i; | |
234 | if (file_exists(filename) && remove(filename.c_str()) == -1) { | |
235 | if (errno == ENOTEMPTY) | |
236 | continue; | |
237 | const char* msg = strerror(errno); | |
238 | cerr << utilname << ": could not remove " << filename << ": " << msg << endl; | |
239 | } | |
240 | } | |
241 | } | |
242 | ||
243 | void pkgutil::db_rm_files(set<string> files, const set<string>& keep_list) | |
244 | { | |
245 | // Remove all references | |
246 | for (packages_t::iterator i = packages.begin(); i != packages.end(); ++i) | |
247 | for (set<string>::const_iterator j = files.begin(); j != files.end(); ++j) | |
248 | i->second.files.erase(*j); | |
249 | ||
250 | #ifndef NDEBUG | |
251 | cerr << "Removing files:" << endl; | |
252 | copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n")); | |
253 | cerr << endl; | |
254 | #endif | |
255 | ||
256 | // Don't delete files found in the keep list | |
257 | for (set<string>::const_iterator i = keep_list.begin(); i != keep_list.end(); ++i) | |
258 | files.erase(*i); | |
259 | ||
260 | // Delete the files | |
261 | for (set<string>::const_reverse_iterator i = files.rbegin(); i != files.rend(); ++i) { | |
262 | const string filename = root + *i; | |
263 | if (file_exists(filename) && remove(filename.c_str()) == -1) { | |
264 | if (errno == ENOTEMPTY) | |
265 | continue; | |
266 | const char* msg = strerror(errno); | |
267 | cerr << utilname << ": could not remove " << filename << ": " << msg << endl; | |
268 | } | |
269 | } | |
270 | } | |
271 | ||
272 | set<string> pkgutil::db_find_conflicts(const string& name, const pkginfo_t& info) | |
273 | { | |
274 | set<string> files; | |
275 | ||
276 | // Find conflicting files in database | |
277 | for (packages_t::const_iterator i = packages.begin(); i != packages.end(); ++i) { | |
278 | if (i->first != name) { | |
279 | set_intersection(info.files.begin(), info.files.end(), | |
280 | i->second.files.begin(), i->second.files.end(), | |
281 | inserter(files, files.end())); | |
282 | } | |
283 | } | |
284 | ||
285 | #ifndef NDEBUG | |
286 | cerr << "Conflicts phase 1 (conflicts in database):" << endl; | |
287 | copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n")); | |
288 | cerr << endl; | |
289 | #endif | |
290 | ||
291 | // Find conflicting files in filesystem | |
292 | for (set<string>::iterator i = info.files.begin(); i != info.files.end(); ++i) { | |
293 | const string filename = root + *i; | |
294 | if (file_exists(filename) && files.find(*i) == files.end()) | |
295 | files.insert(files.end(), *i); | |
296 | } | |
297 | ||
298 | #ifndef NDEBUG | |
299 | cerr << "Conflicts phase 2 (conflicts in filesystem added):" << endl; | |
300 | copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n")); | |
301 | cerr << endl; | |
302 | #endif | |
303 | ||
304 | // Exclude directories | |
305 | set<string> tmp = files; | |
306 | for (set<string>::const_iterator i = tmp.begin(); i != tmp.end(); ++i) { | |
307 | if ((*i)[i->length() - 1] == '/') | |
308 | files.erase(*i); | |
309 | } | |
310 | ||
311 | #ifndef NDEBUG | |
312 | cerr << "Conflicts phase 3 (directories excluded):" << endl; | |
313 | copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n")); | |
314 | cerr << endl; | |
315 | #endif | |
316 | ||
317 | // If this is an upgrade, remove files already owned by this package | |
318 | if (packages.find(name) != packages.end()) { | |
319 | for (set<string>::const_iterator i = packages[name].files.begin(); i != packages[name].files.end(); ++i) | |
320 | files.erase(*i); | |
321 | ||
322 | #ifndef NDEBUG | |
323 | cerr << "Conflicts phase 4 (files already owned by this package excluded):" << endl; | |
324 | copy(files.begin(), files.end(), ostream_iterator<string>(cerr, "\n")); | |
325 | cerr << endl; | |
326 | #endif | |
327 | } | |
328 | ||
329 | return files; | |
330 | } | |
331 | ||
332 | pair<string, pkgutil::pkginfo_t> pkgutil::pkg_open(const string& filename) const | |
333 | { | |
334 | pair<string, pkginfo_t> result; | |
335 | unsigned int i; | |
c3434674 MCO |
336 | struct archive* archive; |
337 | struct archive_entry* entry; | |
9ac667e6 SR |
338 | |
339 | // Extract name and version from filename | |
340 | string basename(filename, filename.rfind('/') + 1); | |
341 | string name(basename, 0, basename.find(VERSION_DELIM)); | |
342 | string version(basename, 0, basename.rfind(PKG_EXT)); | |
343 | version.erase(0, version.find(VERSION_DELIM) == string::npos ? string::npos : version.find(VERSION_DELIM) + 1); | |
344 | ||
345 | if (name.empty() || version.empty()) | |
346 | throw runtime_error("could not determine name and/or version of " + basename + ": Invalid package name"); | |
347 | ||
348 | result.first = name; | |
349 | result.second.version = version; | |
350 | ||
c3434674 | 351 | archive = archive_read_new(); |
c9984aa8 | 352 | INIT_ARCHIVE(archive); |
c3434674 MCO |
353 | |
354 | if (archive_read_open_filename(archive, | |
355 | const_cast<char*>(filename.c_str()), | |
356 | ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) | |
25f9975c | 357 | throw runtime_error_with_errno("could not open " + filename, archive_errno(archive)); |
9ac667e6 | 358 | |
c3434674 MCO |
359 | for (i = 0; archive_read_next_header(archive, &entry) == |
360 | ARCHIVE_OK; ++i) { | |
c3434674 MCO |
361 | |
362 | result.second.files.insert(result.second.files.end(), | |
363 | archive_entry_pathname(entry)); | |
364 | ||
7f84e1cc | 365 | mode_t mode = archive_entry_mode(entry); |
c3434674 | 366 | |
7f84e1cc | 367 | if (S_ISREG(mode) && |
c3434674 | 368 | archive_read_data_skip(archive) != ARCHIVE_OK) |
25f9975c | 369 | throw runtime_error_with_errno("could not read " + filename, archive_errno(archive)); |
9ac667e6 SR |
370 | } |
371 | ||
372 | if (i == 0) { | |
c3434674 | 373 | if (archive_errno(archive) == 0) |
9ac667e6 SR |
374 | throw runtime_error("empty package"); |
375 | else | |
376 | throw runtime_error("could not read " + filename); | |
377 | } | |
378 | ||
c3434674 | 379 | archive_read_finish(archive); |
9ac667e6 SR |
380 | |
381 | return result; | |
382 | } | |
383 | ||
3e5b7ed9 | 384 | void pkgutil::pkg_install(const string& filename, const set<string>& keep_list, const set<string>& non_install_list) const |
9ac667e6 | 385 | { |
c3434674 MCO |
386 | struct archive* archive; |
387 | struct archive_entry* entry; | |
9ac667e6 | 388 | unsigned int i; |
6ae354d7 LH |
389 | char buf[PATH_MAX]; |
390 | string absroot; | |
9ac667e6 | 391 | |
c3434674 | 392 | archive = archive_read_new(); |
c9984aa8 | 393 | INIT_ARCHIVE(archive); |
c3434674 MCO |
394 | |
395 | if (archive_read_open_filename(archive, | |
396 | const_cast<char*>(filename.c_str()), | |
397 | ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) | |
25f9975c | 398 | throw runtime_error_with_errno("could not open " + filename, archive_errno(archive)); |
9ac667e6 | 399 | |
c3434674 | 400 | chdir(root.c_str()); |
6ae354d7 | 401 | absroot = getcwd(buf, sizeof(buf)); |
c3434674 MCO |
402 | |
403 | for (i = 0; archive_read_next_header(archive, &entry) == | |
404 | ARCHIVE_OK; ++i) { | |
405 | string archive_filename = archive_entry_pathname(entry); | |
6ae354d7 LH |
406 | string reject_dir = trim_filename(absroot + string("/") + string(PKG_REJECTED)); |
407 | string original_filename = trim_filename(absroot + string("/") + archive_filename); | |
9ac667e6 SR |
408 | string real_filename = original_filename; |
409 | ||
3e5b7ed9 TS |
410 | // Check if file is filtered out via INSTALL |
411 | if (non_install_list.find(archive_filename) != non_install_list.end()) { | |
7f84e1cc | 412 | mode_t mode; |
c3434674 | 413 | |
22131995 TS |
414 | cout << utilname << ": ignoring " << archive_filename << endl; |
415 | ||
7f84e1cc | 416 | mode = archive_entry_mode(entry); |
c3434674 | 417 | |
7f84e1cc | 418 | if (S_ISREG(mode)) |
c3434674 | 419 | archive_read_data_skip(archive); |
3e5b7ed9 TS |
420 | |
421 | continue; | |
422 | } | |
423 | ||
9ac667e6 SR |
424 | // Check if file should be rejected |
425 | if (file_exists(real_filename) && keep_list.find(archive_filename) != keep_list.end()) | |
426 | real_filename = trim_filename(reject_dir + string("/") + archive_filename); | |
427 | ||
c3434674 MCO |
428 | archive_entry_set_pathname(entry, const_cast<char*> |
429 | (real_filename.c_str())); | |
430 | ||
9ac667e6 | 431 | // Extract file |
d324dd08 | 432 | unsigned int flags = ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_UNLINK; |
2a62ed9a TS |
433 | |
434 | if (archive_read_extract(archive, entry, flags) != ARCHIVE_OK) { | |
9ac667e6 SR |
435 | // If a file fails to install we just print an error message and |
436 | // continue trying to install the rest of the package. | |
c3434674 | 437 | const char* msg = archive_error_string(archive); |
9ac667e6 SR |
438 | cerr << utilname << ": could not install " + archive_filename << ": " << msg << endl; |
439 | continue; | |
440 | } | |
441 | ||
442 | // Check rejected file | |
443 | if (real_filename != original_filename) { | |
444 | bool remove_file = false; | |
7f84e1cc | 445 | mode_t mode = archive_entry_mode(entry); |
9ac667e6 SR |
446 | |
447 | // Directory | |
7f84e1cc | 448 | if (S_ISDIR(mode)) |
9ac667e6 SR |
449 | remove_file = permissions_equal(real_filename, original_filename); |
450 | // Other files | |
451 | else | |
452 | remove_file = permissions_equal(real_filename, original_filename) && | |
453 | (file_empty(real_filename) || file_equal(real_filename, original_filename)); | |
454 | ||
455 | // Remove rejected file or signal about its existence | |
456 | if (remove_file) | |
457 | file_remove(reject_dir, real_filename); | |
458 | else | |
459 | cout << utilname << ": rejecting " << archive_filename << ", keeping existing version" << endl; | |
460 | } | |
461 | } | |
462 | ||
463 | if (i == 0) { | |
c3434674 | 464 | if (archive_errno(archive) == 0) |
9ac667e6 SR |
465 | throw runtime_error("empty package"); |
466 | else | |
467 | throw runtime_error("could not read " + filename); | |
468 | } | |
469 | ||
c3434674 | 470 | archive_read_finish(archive); |
9ac667e6 SR |
471 | } |
472 | ||
473 | void pkgutil::ldconfig() const | |
474 | { | |
475 | // Only execute ldconfig if /etc/ld.so.conf exists | |
476 | if (file_exists(root + LDCONFIG_CONF)) { | |
477 | pid_t pid = fork(); | |
478 | ||
479 | if (pid == -1) | |
480 | throw runtime_error_with_errno("fork() failed"); | |
481 | ||
482 | if (pid == 0) { | |
5401bd45 | 483 | execl(LDCONFIG, LDCONFIG, "-r", root.c_str(), (char *) 0); |
9ac667e6 SR |
484 | const char* msg = strerror(errno); |
485 | cerr << utilname << ": could not execute " << LDCONFIG << ": " << msg << endl; | |
486 | exit(EXIT_FAILURE); | |
487 | } else { | |
488 | if (waitpid(pid, 0, 0) == -1) | |
489 | throw runtime_error_with_errno("waitpid() failed"); | |
490 | } | |
491 | } | |
492 | } | |
493 | ||
494 | void pkgutil::pkg_footprint(string& filename) const | |
495 | { | |
fa6b0879 | 496 | unsigned int i; |
c3434674 MCO |
497 | struct archive* archive; |
498 | struct archive_entry* entry; | |
9ac667e6 | 499 | |
80db05ef TS |
500 | map<string, mode_t> hardlink_target_modes; |
501 | ||
502 | // We first do a run over the archive and remember the modes | |
503 | // of regular files. | |
504 | // In the second run, we print the footprint - using the stored | |
505 | // modes for hardlinks. | |
506 | // | |
507 | // FIXME the code duplication here is butt ugly | |
508 | archive = archive_read_new(); | |
c9984aa8 | 509 | INIT_ARCHIVE(archive); |
80db05ef TS |
510 | |
511 | if (archive_read_open_filename(archive, | |
512 | const_cast<char*>(filename.c_str()), | |
513 | ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) | |
514 | throw runtime_error_with_errno("could not open " + filename, archive_errno(archive)); | |
515 | ||
516 | for (i = 0; archive_read_next_header(archive, &entry) == | |
517 | ARCHIVE_OK; ++i) { | |
518 | ||
519 | mode_t mode = archive_entry_mode(entry); | |
520 | ||
521 | if (!archive_entry_hardlink(entry)) { | |
522 | const char *s = archive_entry_pathname(entry); | |
523 | ||
524 | hardlink_target_modes[s] = mode; | |
525 | } | |
526 | ||
527 | if (S_ISREG(mode) && archive_read_data_skip(archive)) | |
528 | throw runtime_error_with_errno("could not read " + filename, archive_errno(archive)); | |
529 | } | |
530 | ||
531 | archive_read_finish(archive); | |
532 | ||
533 | // Too bad, there doesn't seem to be a way to reuse our archive | |
534 | // instance | |
c3434674 | 535 | archive = archive_read_new(); |
c9984aa8 | 536 | INIT_ARCHIVE(archive); |
c3434674 MCO |
537 | |
538 | if (archive_read_open_filename(archive, | |
539 | const_cast<char*>(filename.c_str()), | |
540 | ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK) | |
25f9975c | 541 | throw runtime_error_with_errno("could not open " + filename, archive_errno(archive)); |
9ac667e6 | 542 | |
c3434674 MCO |
543 | for (i = 0; archive_read_next_header(archive, &entry) == |
544 | ARCHIVE_OK; ++i) { | |
7f84e1cc | 545 | mode_t mode = archive_entry_mode(entry); |
c3434674 | 546 | |
9ac667e6 | 547 | // Access permissions |
7f84e1cc | 548 | if (S_ISLNK(mode)) { |
9ac667e6 SR |
549 | // Access permissions on symlinks differ among filesystems, e.g. XFS and ext2 have different. |
550 | // To avoid getting different footprints we always use "lrwxrwxrwx". | |
551 | cout << "lrwxrwxrwx"; | |
552 | } else { | |
80db05ef TS |
553 | const char *h = archive_entry_hardlink(entry); |
554 | ||
555 | if (h) | |
556 | cout << mtos(hardlink_target_modes[h]); | |
557 | else | |
558 | cout << mtos(mode); | |
9ac667e6 SR |
559 | } |
560 | ||
561 | cout << '\t'; | |
562 | ||
563 | // User | |
c3434674 | 564 | uid_t uid = archive_entry_uid(entry); |
9ac667e6 SR |
565 | struct passwd* pw = getpwuid(uid); |
566 | if (pw) | |
567 | cout << pw->pw_name; | |
568 | else | |
569 | cout << uid; | |
570 | ||
571 | cout << '/'; | |
572 | ||
573 | // Group | |
c3434674 | 574 | gid_t gid = archive_entry_gid(entry); |
9ac667e6 SR |
575 | struct group* gr = getgrgid(gid); |
576 | if (gr) | |
577 | cout << gr->gr_name; | |
578 | else | |
579 | cout << gid; | |
580 | ||
581 | // Filename | |
c3434674 | 582 | cout << '\t' << archive_entry_pathname(entry); |
9ac667e6 SR |
583 | |
584 | // Special cases | |
7f84e1cc | 585 | if (S_ISLNK(mode)) { |
9ac667e6 | 586 | // Symlink |
c3434674 | 587 | cout << " -> " << archive_entry_symlink(entry); |
7f84e1cc TS |
588 | } else if (S_ISCHR(mode) || |
589 | S_ISBLK(mode)) { | |
9ac667e6 | 590 | // Device |
c3434674 MCO |
591 | cout << " (" << archive_entry_rdevmajor(entry) |
592 | << ", " << archive_entry_rdevminor(entry) | |
593 | << ")"; | |
7f84e1cc | 594 | } else if (S_ISREG(mode) && |
c3434674 | 595 | archive_entry_size(entry) == 0) { |
9ac667e6 SR |
596 | // Empty regular file |
597 | cout << " (EMPTY)"; | |
598 | } | |
599 | ||
600 | cout << '\n'; | |
601 | ||
7f84e1cc | 602 | if (S_ISREG(mode) && archive_read_data_skip(archive)) |
fa6b0879 TS |
603 | throw runtime_error_with_errno("could not read " + filename, archive_errno(archive)); |
604 | } | |
9ac667e6 | 605 | |
fa6b0879 | 606 | if (i == 0) { |
c3434674 | 607 | if (archive_errno(archive) == 0) |
fa6b0879 TS |
608 | throw runtime_error("empty package"); |
609 | else | |
610 | throw runtime_error("could not read " + filename); | |
611 | } | |
9ac667e6 | 612 | |
c3434674 | 613 | archive_read_finish(archive); |
9ac667e6 SR |
614 | } |
615 | ||
616 | void pkgutil::print_version() const | |
617 | { | |
618 | cout << utilname << " (pkgutils) " << VERSION << endl; | |
619 | } | |
620 | ||
621 | db_lock::db_lock(const string& root, bool exclusive) | |
622 | : dir(0) | |
623 | { | |
624 | const string dirname = trim_filename(root + string("/") + PKG_DIR); | |
625 | ||
626 | if (!(dir = opendir(dirname.c_str()))) | |
627 | throw runtime_error_with_errno("could not read directory " + dirname); | |
628 | ||
629 | if (flock(dirfd(dir), (exclusive ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) { | |
630 | if (errno == EWOULDBLOCK) | |
631 | throw runtime_error("package database is currently locked by another process"); | |
632 | else | |
633 | throw runtime_error_with_errno("could not lock directory " + dirname); | |
634 | } | |
635 | } | |
636 | ||
637 | db_lock::~db_lock() | |
638 | { | |
639 | if (dir) { | |
640 | flock(dirfd(dir), LOCK_UN); | |
641 | closedir(dir); | |
642 | } | |
643 | } | |
644 | ||
645 | void assert_argument(char** argv, int argc, int index) | |
646 | { | |
647 | if (argc - 1 < index + 1) | |
648 | throw runtime_error("option " + string(argv[index]) + " requires an argument"); | |
649 | } | |
650 | ||
651 | string itos(unsigned int value) | |
652 | { | |
653 | static char buf[20]; | |
654 | sprintf(buf, "%u", value); | |
655 | return buf; | |
656 | } | |
657 | ||
658 | string mtos(mode_t mode) | |
659 | { | |
660 | string s; | |
661 | ||
662 | // File type | |
663 | switch (mode & S_IFMT) { | |
664 | case S_IFREG: s += '-'; break; // Regular | |
665 | case S_IFDIR: s += 'd'; break; // Directory | |
666 | case S_IFLNK: s += 'l'; break; // Symbolic link | |
667 | case S_IFCHR: s += 'c'; break; // Character special | |
668 | case S_IFBLK: s += 'b'; break; // Block special | |
669 | case S_IFSOCK: s += 's'; break; // Socket | |
670 | case S_IFIFO: s += 'p'; break; // Fifo | |
671 | default: s += '?'; break; // Unknown | |
672 | } | |
673 | ||
674 | // User permissions | |
675 | s += (mode & S_IRUSR) ? 'r' : '-'; | |
676 | s += (mode & S_IWUSR) ? 'w' : '-'; | |
677 | switch (mode & (S_IXUSR | S_ISUID)) { | |
678 | case S_IXUSR: s += 'x'; break; | |
679 | case S_ISUID: s += 'S'; break; | |
680 | case S_IXUSR | S_ISUID: s += 's'; break; | |
681 | default: s += '-'; break; | |
682 | } | |
683 | ||
684 | // Group permissions | |
685 | s += (mode & S_IRGRP) ? 'r' : '-'; | |
686 | s += (mode & S_IWGRP) ? 'w' : '-'; | |
687 | switch (mode & (S_IXGRP | S_ISGID)) { | |
688 | case S_IXGRP: s += 'x'; break; | |
689 | case S_ISGID: s += 'S'; break; | |
690 | case S_IXGRP | S_ISGID: s += 's'; break; | |
691 | default: s += '-'; break; | |
692 | } | |
693 | ||
694 | // Other permissions | |
695 | s += (mode & S_IROTH) ? 'r' : '-'; | |
696 | s += (mode & S_IWOTH) ? 'w' : '-'; | |
697 | switch (mode & (S_IXOTH | S_ISVTX)) { | |
698 | case S_IXOTH: s += 'x'; break; | |
699 | case S_ISVTX: s += 'T'; break; | |
700 | case S_IXOTH | S_ISVTX: s += 't'; break; | |
701 | default: s += '-'; break; | |
702 | } | |
703 | ||
704 | return s; | |
705 | } | |
706 | ||
9ac667e6 SR |
707 | string trim_filename(const string& filename) |
708 | { | |
709 | string search("//"); | |
710 | string result = filename; | |
711 | ||
712 | for (string::size_type pos = result.find(search); pos != string::npos; pos = result.find(search)) | |
713 | result.replace(pos, search.size(), "/"); | |
714 | ||
715 | return result; | |
716 | } | |
717 | ||
718 | bool file_exists(const string& filename) | |
719 | { | |
720 | struct stat buf; | |
721 | return !lstat(filename.c_str(), &buf); | |
722 | } | |
723 | ||
724 | bool file_empty(const string& filename) | |
725 | { | |
726 | struct stat buf; | |
727 | ||
728 | if (lstat(filename.c_str(), &buf) == -1) | |
729 | return false; | |
730 | ||
731 | return (S_ISREG(buf.st_mode) && buf.st_size == 0); | |
732 | } | |
733 | ||
734 | bool file_equal(const string& file1, const string& file2) | |
735 | { | |
736 | struct stat buf1, buf2; | |
737 | ||
738 | if (lstat(file1.c_str(), &buf1) == -1) | |
739 | return false; | |
740 | ||
741 | if (lstat(file2.c_str(), &buf2) == -1) | |
742 | return false; | |
743 | ||
744 | // Regular files | |
745 | if (S_ISREG(buf1.st_mode) && S_ISREG(buf2.st_mode)) { | |
746 | ifstream f1(file1.c_str()); | |
747 | ifstream f2(file2.c_str()); | |
748 | ||
749 | if (!f1 || !f2) | |
750 | return false; | |
751 | ||
752 | while (!f1.eof()) { | |
753 | char buffer1[4096]; | |
754 | char buffer2[4096]; | |
755 | f1.read(buffer1, 4096); | |
756 | f2.read(buffer2, 4096); | |
757 | if (f1.gcount() != f2.gcount() || | |
758 | memcmp(buffer1, buffer2, f1.gcount()) || | |
759 | f1.eof() != f2.eof()) | |
760 | return false; | |
761 | } | |
762 | ||
763 | return true; | |
764 | } | |
765 | // Symlinks | |
766 | else if (S_ISLNK(buf1.st_mode) && S_ISLNK(buf2.st_mode)) { | |
767 | char symlink1[MAXPATHLEN]; | |
768 | char symlink2[MAXPATHLEN]; | |
769 | ||
770 | memset(symlink1, 0, MAXPATHLEN); | |
771 | memset(symlink2, 0, MAXPATHLEN); | |
772 | ||
773 | if (readlink(file1.c_str(), symlink1, MAXPATHLEN - 1) == -1) | |
774 | return false; | |
775 | ||
776 | if (readlink(file2.c_str(), symlink2, MAXPATHLEN - 1) == -1) | |
777 | return false; | |
778 | ||
779 | return !strncmp(symlink1, symlink2, MAXPATHLEN); | |
780 | } | |
781 | // Character devices | |
782 | else if (S_ISCHR(buf1.st_mode) && S_ISCHR(buf2.st_mode)) { | |
783 | return buf1.st_dev == buf2.st_dev; | |
784 | } | |
785 | // Block devices | |
786 | else if (S_ISBLK(buf1.st_mode) && S_ISBLK(buf2.st_mode)) { | |
787 | return buf1.st_dev == buf2.st_dev; | |
788 | } | |
789 | ||
790 | return false; | |
791 | } | |
792 | ||
793 | bool permissions_equal(const string& file1, const string& file2) | |
794 | { | |
795 | struct stat buf1; | |
796 | struct stat buf2; | |
797 | ||
798 | if (lstat(file1.c_str(), &buf1) == -1) | |
799 | return false; | |
800 | ||
801 | if (lstat(file2.c_str(), &buf2) == -1) | |
802 | return false; | |
803 | ||
804 | return(buf1.st_mode == buf2.st_mode) && | |
805 | (buf1.st_uid == buf2.st_uid) && | |
806 | (buf1.st_gid == buf2.st_gid); | |
807 | } | |
808 | ||
809 | void file_remove(const string& basedir, const string& filename) | |
810 | { | |
811 | if (filename != basedir && !remove(filename.c_str())) { | |
812 | char* path = strdup(filename.c_str()); | |
813 | file_remove(basedir, dirname(path)); | |
814 | free(path); | |
815 | } | |
816 | } |