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