CRUX-ARM : Home

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