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
1 //
2 // pkgutils
3 //
4 // Copyright (c) 2000-2005 Per Liden
5 // Copyright (c) 2006-2007 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 "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>
42 #include <libgen.h>
43 #include <archive.h>
44 #include <archive_entry.h>
45
46 #define INIT_ARCHIVE(ar) \
47 archive_read_support_compression_gzip((ar)); \
48 archive_read_support_format_tar((ar))
49
50 using __gnu_cxx::stdio_filebuf;
51
52 pkgutil::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
65 void 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
104 void 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
155 void pkgutil::db_add_pkg(const string& name, const pkginfo_t& info)
156 {
157 packages[name] = info;
158 }
159
160 bool pkgutil::db_find_pkg(const string& name)
161 {
162 return (packages.find(name) != packages.end());
163 }
164
165 void 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
197 void 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
241 void 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
270 set<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
330 pair<string, pkgutil::pkginfo_t> pkgutil::pkg_open(const string& filename) const
331 {
332 pair<string, pkginfo_t> result;
333 unsigned int i;
334 struct archive* archive;
335 struct archive_entry* entry;
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
349 archive = archive_read_new();
350 INIT_ARCHIVE(archive);
351
352 if (archive_read_open_filename(archive,
353 const_cast<char*>(filename.c_str()),
354 ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK)
355 throw runtime_error_with_errno("could not open " + filename, archive_errno(archive));
356
357 for (i = 0; archive_read_next_header(archive, &entry) ==
358 ARCHIVE_OK; ++i) {
359
360 result.second.files.insert(result.second.files.end(),
361 archive_entry_pathname(entry));
362
363 mode_t mode = archive_entry_mode(entry);
364
365 if (S_ISREG(mode) &&
366 archive_read_data_skip(archive) != ARCHIVE_OK)
367 throw runtime_error_with_errno("could not read " + filename, archive_errno(archive));
368 }
369
370 if (i == 0) {
371 if (archive_errno(archive) == 0)
372 throw runtime_error("empty package");
373 else
374 throw runtime_error("could not read " + filename);
375 }
376
377 archive_read_finish(archive);
378
379 return result;
380 }
381
382 void pkgutil::pkg_install(const string& filename, const set<string>& keep_list, const set<string>& non_install_list) const
383 {
384 struct archive* archive;
385 struct archive_entry* entry;
386 unsigned int i;
387 char buf[PATH_MAX];
388 string absroot;
389
390 archive = archive_read_new();
391 INIT_ARCHIVE(archive);
392
393 if (archive_read_open_filename(archive,
394 const_cast<char*>(filename.c_str()),
395 ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK)
396 throw runtime_error_with_errno("could not open " + filename, archive_errno(archive));
397
398 chdir(root.c_str());
399 absroot = getcwd(buf, sizeof(buf));
400
401 for (i = 0; archive_read_next_header(archive, &entry) ==
402 ARCHIVE_OK; ++i) {
403 string archive_filename = archive_entry_pathname(entry);
404 string reject_dir = trim_filename(absroot + string("/") + string(PKG_REJECTED));
405 string original_filename = trim_filename(absroot + string("/") + archive_filename);
406 string real_filename = original_filename;
407
408 // Check if file is filtered out via INSTALL
409 if (non_install_list.find(archive_filename) != non_install_list.end()) {
410 mode_t mode;
411
412 cout << utilname << ": ignoring " << archive_filename << endl;
413
414 mode = archive_entry_mode(entry);
415
416 if (S_ISREG(mode))
417 archive_read_data_skip(archive);
418
419 continue;
420 }
421
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
426 archive_entry_set_pathname(entry, const_cast<char*>
427 (real_filename.c_str()));
428
429 // Extract file
430 unsigned int flags = ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_UNLINK;
431
432 if (archive_read_extract(archive, entry, flags) != ARCHIVE_OK) {
433 // If a file fails to install we just print an error message and
434 // continue trying to install the rest of the package.
435 const char* msg = archive_error_string(archive);
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;
443 mode_t mode = archive_entry_mode(entry);
444
445 // Directory
446 if (S_ISDIR(mode))
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) {
462 if (archive_errno(archive) == 0)
463 throw runtime_error("empty package");
464 else
465 throw runtime_error("could not read " + filename);
466 }
467
468 archive_read_finish(archive);
469 }
470
471 void 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) {
481 execl(LDCONFIG, LDCONFIG, "-r", root.c_str(), (char *) 0);
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
492 void pkgutil::pkg_footprint(string& filename) const
493 {
494 unsigned int i;
495 struct archive* archive;
496 struct archive_entry* entry;
497
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();
507 INIT_ARCHIVE(archive);
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
533 archive = archive_read_new();
534 INIT_ARCHIVE(archive);
535
536 if (archive_read_open_filename(archive,
537 const_cast<char*>(filename.c_str()),
538 ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK)
539 throw runtime_error_with_errno("could not open " + filename, archive_errno(archive));
540
541 for (i = 0; archive_read_next_header(archive, &entry) ==
542 ARCHIVE_OK; ++i) {
543 mode_t mode = archive_entry_mode(entry);
544
545 // Access permissions
546 if (S_ISLNK(mode)) {
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 {
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);
557 }
558
559 cout << '\t';
560
561 // User
562 uid_t uid = archive_entry_uid(entry);
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
572 gid_t gid = archive_entry_gid(entry);
573 struct group* gr = getgrgid(gid);
574 if (gr)
575 cout << gr->gr_name;
576 else
577 cout << gid;
578
579 // Filename
580 cout << '\t' << archive_entry_pathname(entry);
581
582 // Special cases
583 if (S_ISLNK(mode)) {
584 // Symlink
585 cout << " -> " << archive_entry_symlink(entry);
586 } else if (S_ISCHR(mode) ||
587 S_ISBLK(mode)) {
588 // Device
589 cout << " (" << archive_entry_rdevmajor(entry)
590 << ", " << archive_entry_rdevminor(entry)
591 << ")";
592 } else if (S_ISREG(mode) &&
593 archive_entry_size(entry) == 0) {
594 // Empty regular file
595 cout << " (EMPTY)";
596 }
597
598 cout << '\n';
599
600 if (S_ISREG(mode) && archive_read_data_skip(archive))
601 throw runtime_error_with_errno("could not read " + filename, archive_errno(archive));
602 }
603
604 if (i == 0) {
605 if (archive_errno(archive) == 0)
606 throw runtime_error("empty package");
607 else
608 throw runtime_error("could not read " + filename);
609 }
610
611 archive_read_finish(archive);
612 }
613
614 void pkgutil::print_version() const
615 {
616 cout << utilname << " (pkgutils) " << VERSION << endl;
617 }
618
619 db_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
635 db_lock::~db_lock()
636 {
637 if (dir) {
638 flock(dirfd(dir), LOCK_UN);
639 closedir(dir);
640 }
641 }
642
643 void 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
649 string itos(unsigned int value)
650 {
651 static char buf[20];
652 sprintf(buf, "%u", value);
653 return buf;
654 }
655
656 string 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
705 string 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
716 bool file_exists(const string& filename)
717 {
718 struct stat buf;
719 return !lstat(filename.c_str(), &buf);
720 }
721
722 bool 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
732 bool 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
791 bool 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
807 void 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 }