CRUX-ARM : Home

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