CRUX-ARM : Home

Home :: Documentation :: Download :: Development :: Community :: Ports :: Packages :: Bugs :: Links :: About :: Donors
Bug #204: Restore mtime for extracted files.
[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_all((ar)); \
48 archive_read_support_format_all((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
388 archive = archive_read_new();
389 INIT_ARCHIVE(archive);
390
391 if (archive_read_open_filename(archive,
392 const_cast<char*>(filename.c_str()),
393 ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK)
394 throw runtime_error_with_errno("could not open " + filename, archive_errno(archive));
395
396 chdir(root.c_str());
397
398 for (i = 0; archive_read_next_header(archive, &entry) ==
399 ARCHIVE_OK; ++i) {
400 string archive_filename = archive_entry_pathname(entry);
401 string reject_dir = trim_filename(root + string("/") + string(PKG_REJECTED));
402 string original_filename = trim_filename(root + string("/") + archive_filename);
403 string real_filename = original_filename;
404
405 // Check if file is filtered out via INSTALL
406 if (non_install_list.find(archive_filename) != non_install_list.end()) {
407 mode_t mode;
408
409 cout << utilname << ": ignoring " << archive_filename << endl;
410
411 mode = archive_entry_mode(entry);
412
413 if (S_ISREG(mode))
414 archive_read_data_skip(archive);
415
416 continue;
417 }
418
419 // Check if file should be rejected
420 if (file_exists(real_filename) && keep_list.find(archive_filename) != keep_list.end())
421 real_filename = trim_filename(reject_dir + string("/") + archive_filename);
422
423 archive_entry_set_pathname(entry, const_cast<char*>
424 (real_filename.c_str()));
425
426 // Extract file
427 unsigned int flags = ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_UNLINK;
428
429 if (archive_read_extract(archive, entry, flags) != ARCHIVE_OK) {
430 // If a file fails to install we just print an error message and
431 // continue trying to install the rest of the package.
432 const char* msg = archive_error_string(archive);
433 cerr << utilname << ": could not install " + archive_filename << ": " << msg << endl;
434 continue;
435 }
436
437 // Check rejected file
438 if (real_filename != original_filename) {
439 bool remove_file = false;
440 mode_t mode = archive_entry_mode(entry);
441
442 // Directory
443 if (S_ISDIR(mode))
444 remove_file = permissions_equal(real_filename, original_filename);
445 // Other files
446 else
447 remove_file = permissions_equal(real_filename, original_filename) &&
448 (file_empty(real_filename) || file_equal(real_filename, original_filename));
449
450 // Remove rejected file or signal about its existence
451 if (remove_file)
452 file_remove(reject_dir, real_filename);
453 else
454 cout << utilname << ": rejecting " << archive_filename << ", keeping existing version" << endl;
455 }
456 }
457
458 if (i == 0) {
459 if (archive_errno(archive) == 0)
460 throw runtime_error("empty package");
461 else
462 throw runtime_error("could not read " + filename);
463 }
464
465 archive_read_finish(archive);
466 }
467
468 void pkgutil::ldconfig() const
469 {
470 // Only execute ldconfig if /etc/ld.so.conf exists
471 if (file_exists(root + LDCONFIG_CONF)) {
472 pid_t pid = fork();
473
474 if (pid == -1)
475 throw runtime_error_with_errno("fork() failed");
476
477 if (pid == 0) {
478 execl(LDCONFIG, LDCONFIG, "-r", root.c_str(), (char *) 0);
479 const char* msg = strerror(errno);
480 cerr << utilname << ": could not execute " << LDCONFIG << ": " << msg << endl;
481 exit(EXIT_FAILURE);
482 } else {
483 if (waitpid(pid, 0, 0) == -1)
484 throw runtime_error_with_errno("waitpid() failed");
485 }
486 }
487 }
488
489 void pkgutil::pkg_footprint(string& filename) const
490 {
491 unsigned int i;
492 struct archive* archive;
493 struct archive_entry* entry;
494
495 map<string, mode_t> hardlink_target_modes;
496
497 // We first do a run over the archive and remember the modes
498 // of regular files.
499 // In the second run, we print the footprint - using the stored
500 // modes for hardlinks.
501 //
502 // FIXME the code duplication here is butt ugly
503 archive = archive_read_new();
504 INIT_ARCHIVE(archive);
505
506 if (archive_read_open_filename(archive,
507 const_cast<char*>(filename.c_str()),
508 ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK)
509 throw runtime_error_with_errno("could not open " + filename, archive_errno(archive));
510
511 for (i = 0; archive_read_next_header(archive, &entry) ==
512 ARCHIVE_OK; ++i) {
513
514 mode_t mode = archive_entry_mode(entry);
515
516 if (!archive_entry_hardlink(entry)) {
517 const char *s = archive_entry_pathname(entry);
518
519 hardlink_target_modes[s] = mode;
520 }
521
522 if (S_ISREG(mode) && archive_read_data_skip(archive))
523 throw runtime_error_with_errno("could not read " + filename, archive_errno(archive));
524 }
525
526 archive_read_finish(archive);
527
528 // Too bad, there doesn't seem to be a way to reuse our archive
529 // instance
530 archive = archive_read_new();
531 INIT_ARCHIVE(archive);
532
533 if (archive_read_open_filename(archive,
534 const_cast<char*>(filename.c_str()),
535 ARCHIVE_DEFAULT_BYTES_PER_BLOCK) != ARCHIVE_OK)
536 throw runtime_error_with_errno("could not open " + filename, archive_errno(archive));
537
538 for (i = 0; archive_read_next_header(archive, &entry) ==
539 ARCHIVE_OK; ++i) {
540 mode_t mode = archive_entry_mode(entry);
541
542 // Access permissions
543 if (S_ISLNK(mode)) {
544 // Access permissions on symlinks differ among filesystems, e.g. XFS and ext2 have different.
545 // To avoid getting different footprints we always use "lrwxrwxrwx".
546 cout << "lrwxrwxrwx";
547 } else {
548 const char *h = archive_entry_hardlink(entry);
549
550 if (h)
551 cout << mtos(hardlink_target_modes[h]);
552 else
553 cout << mtos(mode);
554 }
555
556 cout << '\t';
557
558 // User
559 uid_t uid = archive_entry_uid(entry);
560 struct passwd* pw = getpwuid(uid);
561 if (pw)
562 cout << pw->pw_name;
563 else
564 cout << uid;
565
566 cout << '/';
567
568 // Group
569 gid_t gid = archive_entry_gid(entry);
570 struct group* gr = getgrgid(gid);
571 if (gr)
572 cout << gr->gr_name;
573 else
574 cout << gid;
575
576 // Filename
577 cout << '\t' << archive_entry_pathname(entry);
578
579 // Special cases
580 if (S_ISLNK(mode)) {
581 // Symlink
582 cout << " -> " << archive_entry_symlink(entry);
583 } else if (S_ISCHR(mode) ||
584 S_ISBLK(mode)) {
585 // Device
586 cout << " (" << archive_entry_rdevmajor(entry)
587 << ", " << archive_entry_rdevminor(entry)
588 << ")";
589 } else if (S_ISREG(mode) &&
590 archive_entry_size(entry) == 0) {
591 // Empty regular file
592 cout << " (EMPTY)";
593 }
594
595 cout << '\n';
596
597 if (S_ISREG(mode) && archive_read_data_skip(archive))
598 throw runtime_error_with_errno("could not read " + filename, archive_errno(archive));
599 }
600
601 if (i == 0) {
602 if (archive_errno(archive) == 0)
603 throw runtime_error("empty package");
604 else
605 throw runtime_error("could not read " + filename);
606 }
607
608 archive_read_finish(archive);
609 }
610
611 void pkgutil::print_version() const
612 {
613 cout << utilname << " (pkgutils) " << VERSION << endl;
614 }
615
616 db_lock::db_lock(const string& root, bool exclusive)
617 : dir(0)
618 {
619 const string dirname = trim_filename(root + string("/") + PKG_DIR);
620
621 if (!(dir = opendir(dirname.c_str())))
622 throw runtime_error_with_errno("could not read directory " + dirname);
623
624 if (flock(dirfd(dir), (exclusive ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) {
625 if (errno == EWOULDBLOCK)
626 throw runtime_error("package database is currently locked by another process");
627 else
628 throw runtime_error_with_errno("could not lock directory " + dirname);
629 }
630 }
631
632 db_lock::~db_lock()
633 {
634 if (dir) {
635 flock(dirfd(dir), LOCK_UN);
636 closedir(dir);
637 }
638 }
639
640 void assert_argument(char** argv, int argc, int index)
641 {
642 if (argc - 1 < index + 1)
643 throw runtime_error("option " + string(argv[index]) + " requires an argument");
644 }
645
646 string itos(unsigned int value)
647 {
648 static char buf[20];
649 sprintf(buf, "%u", value);
650 return buf;
651 }
652
653 string mtos(mode_t mode)
654 {
655 string s;
656
657 // File type
658 switch (mode & S_IFMT) {
659 case S_IFREG: s += '-'; break; // Regular
660 case S_IFDIR: s += 'd'; break; // Directory
661 case S_IFLNK: s += 'l'; break; // Symbolic link
662 case S_IFCHR: s += 'c'; break; // Character special
663 case S_IFBLK: s += 'b'; break; // Block special
664 case S_IFSOCK: s += 's'; break; // Socket
665 case S_IFIFO: s += 'p'; break; // Fifo
666 default: s += '?'; break; // Unknown
667 }
668
669 // User permissions
670 s += (mode & S_IRUSR) ? 'r' : '-';
671 s += (mode & S_IWUSR) ? 'w' : '-';
672 switch (mode & (S_IXUSR | S_ISUID)) {
673 case S_IXUSR: s += 'x'; break;
674 case S_ISUID: s += 'S'; break;
675 case S_IXUSR | S_ISUID: s += 's'; break;
676 default: s += '-'; break;
677 }
678
679 // Group permissions
680 s += (mode & S_IRGRP) ? 'r' : '-';
681 s += (mode & S_IWGRP) ? 'w' : '-';
682 switch (mode & (S_IXGRP | S_ISGID)) {
683 case S_IXGRP: s += 'x'; break;
684 case S_ISGID: s += 'S'; break;
685 case S_IXGRP | S_ISGID: s += 's'; break;
686 default: s += '-'; break;
687 }
688
689 // Other permissions
690 s += (mode & S_IROTH) ? 'r' : '-';
691 s += (mode & S_IWOTH) ? 'w' : '-';
692 switch (mode & (S_IXOTH | S_ISVTX)) {
693 case S_IXOTH: s += 'x'; break;
694 case S_ISVTX: s += 'T'; break;
695 case S_IXOTH | S_ISVTX: s += 't'; break;
696 default: s += '-'; break;
697 }
698
699 return s;
700 }
701
702 string trim_filename(const string& filename)
703 {
704 string search("//");
705 string result = filename;
706
707 for (string::size_type pos = result.find(search); pos != string::npos; pos = result.find(search))
708 result.replace(pos, search.size(), "/");
709
710 return result;
711 }
712
713 bool file_exists(const string& filename)
714 {
715 struct stat buf;
716 return !lstat(filename.c_str(), &buf);
717 }
718
719 bool file_empty(const string& filename)
720 {
721 struct stat buf;
722
723 if (lstat(filename.c_str(), &buf) == -1)
724 return false;
725
726 return (S_ISREG(buf.st_mode) && buf.st_size == 0);
727 }
728
729 bool file_equal(const string& file1, const string& file2)
730 {
731 struct stat buf1, buf2;
732
733 if (lstat(file1.c_str(), &buf1) == -1)
734 return false;
735
736 if (lstat(file2.c_str(), &buf2) == -1)
737 return false;
738
739 // Regular files
740 if (S_ISREG(buf1.st_mode) && S_ISREG(buf2.st_mode)) {
741 ifstream f1(file1.c_str());
742 ifstream f2(file2.c_str());
743
744 if (!f1 || !f2)
745 return false;
746
747 while (!f1.eof()) {
748 char buffer1[4096];
749 char buffer2[4096];
750 f1.read(buffer1, 4096);
751 f2.read(buffer2, 4096);
752 if (f1.gcount() != f2.gcount() ||
753 memcmp(buffer1, buffer2, f1.gcount()) ||
754 f1.eof() != f2.eof())
755 return false;
756 }
757
758 return true;
759 }
760 // Symlinks
761 else if (S_ISLNK(buf1.st_mode) && S_ISLNK(buf2.st_mode)) {
762 char symlink1[MAXPATHLEN];
763 char symlink2[MAXPATHLEN];
764
765 memset(symlink1, 0, MAXPATHLEN);
766 memset(symlink2, 0, MAXPATHLEN);
767
768 if (readlink(file1.c_str(), symlink1, MAXPATHLEN - 1) == -1)
769 return false;
770
771 if (readlink(file2.c_str(), symlink2, MAXPATHLEN - 1) == -1)
772 return false;
773
774 return !strncmp(symlink1, symlink2, MAXPATHLEN);
775 }
776 // Character devices
777 else if (S_ISCHR(buf1.st_mode) && S_ISCHR(buf2.st_mode)) {
778 return buf1.st_dev == buf2.st_dev;
779 }
780 // Block devices
781 else if (S_ISBLK(buf1.st_mode) && S_ISBLK(buf2.st_mode)) {
782 return buf1.st_dev == buf2.st_dev;
783 }
784
785 return false;
786 }
787
788 bool permissions_equal(const string& file1, const string& file2)
789 {
790 struct stat buf1;
791 struct stat buf2;
792
793 if (lstat(file1.c_str(), &buf1) == -1)
794 return false;
795
796 if (lstat(file2.c_str(), &buf2) == -1)
797 return false;
798
799 return(buf1.st_mode == buf2.st_mode) &&
800 (buf1.st_uid == buf2.st_uid) &&
801 (buf1.st_gid == buf2.st_gid);
802 }
803
804 void file_remove(const string& basedir, const string& filename)
805 {
806 if (filename != basedir && !remove(filename.c_str())) {
807 char* path = strdup(filename.c_str());
808 file_remove(basedir, dirname(path));
809 free(path);
810 }
811 }