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