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