4 // Copyright (c) 2000-2005 Per Liden
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
31 #include <ext/stdio_filebuf.h>
34 #include <sys/types.h>
38 #include <sys/param.h>
45 using __gnu_cxx::stdio_filebuf
;
47 static tartype_t gztype
= {
48 (openfunc_t
)unistd_gzopen
,
54 pkgutil::pkgutil(const string
& name
)
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);
67 void pkgutil::db_open(const string
& path
)
70 root
= trim_filename(path
+ "/");
71 const string filename
= root
+ PKG_DB
;
73 int fd
= open(filename
.c_str(), O_RDONLY
);
75 throw runtime_error_with_errno("could not open " + filename
);
77 stdio_filebuf
<char> filebuf(fd
, ios::in
, getpagesize());
80 throw runtime_error_with_errno("could not read " + filename
);
87 getline(in
, info
.version
);
93 break; // End of record
95 info
.files
.insert(info
.files
.end(), file
);
97 if (!info
.files
.empty())
98 packages
[name
] = info
;
102 cerr
<< packages
.size() << " packages found in database" << endl
;
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);
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"));
134 // Make sure the new database was successfully written
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
);
153 cerr
<< packages
.size() << " packages written to database" << endl
;
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
);
173 cerr
<< "Removing package phase 1 (all files in package):" << endl
;
174 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
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
)
184 cerr
<< "Removing package phase 2 (files that still have references excluded):" << endl
;
185 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
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
;
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
);
205 cerr
<< "Removing package phase 1 (all files in package):" << endl
;
206 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
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
)
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"));
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
)
226 cerr
<< "Removing package phase 3 (files that still have references excluded):" << endl
;
227 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
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
)
237 const char* msg
= strerror(errno
);
238 cerr
<< utilname
<< ": could not remove " << filename
<< ": " << msg
<< endl
;
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
);
251 cerr
<< "Removing files:" << endl
;
252 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
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
)
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
)
266 const char* msg
= strerror(errno
);
267 cerr
<< utilname
<< ": could not remove " << filename
<< ": " << msg
<< endl
;
272 set
<string
> pkgutil::db_find_conflicts(const string
& name
, const pkginfo_t
& info
)
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()));
286 cerr
<< "Conflicts phase 1 (conflicts in database):" << endl
;
287 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
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
);
299 cerr
<< "Conflicts phase 2 (conflicts in filesystem added):" << endl
;
300 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
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] == '/')
312 cerr
<< "Conflicts phase 3 (directories excluded):" << endl
;
313 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
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
)
323 cerr
<< "Conflicts phase 4 (files already owned by this package excluded):" << endl
;
324 copy(files
.begin(), files
.end(), ostream_iterator
<string
>(cerr
, "\n"));
332 pair
<string
, pkgutil::pkginfo_t
> pkgutil::pkg_open(const string
& filename
) const
334 pair
<string
, pkginfo_t
> result
;
338 // Extract name and version from filename
339 string
basename(filename
, filename
.rfind('/') + 1);
340 string
name(basename
, 0, basename
.find(VERSION_DELIM
));
341 string
version(basename
, 0, basename
.rfind(PKG_EXT
));
342 version
.erase(0, version
.find(VERSION_DELIM
) == string::npos
? string::npos
: version
.find(VERSION_DELIM
) + 1);
344 if (name
.empty() || version
.empty())
345 throw runtime_error("could not determine name and/or version of " + basename
+ ": Invalid package name");
348 result
.second
.version
= version
;
350 if (tar_open(&t
, const_cast<char*>(filename
.c_str()), &gztype
, O_RDONLY
, 0, TAR_GNU
) == -1)
351 throw runtime_error_with_errno("could not open " + filename
);
353 for (i
= 0; !th_read(t
); ++i
) {
354 result
.second
.files
.insert(result
.second
.files
.end(), th_get_pathname(t
));
355 if (TH_ISREG(t
) && tar_skip_regfile(t
))
356 throw runtime_error_with_errno("could not read " + filename
);
361 throw runtime_error("empty package");
363 throw runtime_error("could not read " + filename
);
371 void pkgutil::pkg_install(const string
& filename
, const set
<string
>& keep_list
) const
376 if (tar_open(&t
, const_cast<char*>(filename
.c_str()), &gztype
, O_RDONLY
, 0, TAR_GNU
) == -1)
377 throw runtime_error_with_errno("could not open " + filename
);
379 for (i
= 0; !th_read(t
); ++i
) {
380 string archive_filename
= th_get_pathname(t
);
381 string reject_dir
= trim_filename(root
+ string("/") + string(PKG_REJECTED
));
382 string original_filename
= trim_filename(root
+ string("/") + archive_filename
);
383 string real_filename
= original_filename
;
385 // Check if file should be rejected
386 if (file_exists(real_filename
) && keep_list
.find(archive_filename
) != keep_list
.end())
387 real_filename
= trim_filename(reject_dir
+ string("/") + archive_filename
);
390 if (tar_extract_file(t
, const_cast<char*>(real_filename
.c_str()))) {
391 // If a file fails to install we just print an error message and
392 // continue trying to install the rest of the package.
393 const char* msg
= strerror(errno
);
394 cerr
<< utilname
<< ": could not install " + archive_filename
<< ": " << msg
<< endl
;
398 // Check rejected file
399 if (real_filename
!= original_filename
) {
400 bool remove_file
= false;
404 remove_file
= permissions_equal(real_filename
, original_filename
);
407 remove_file
= permissions_equal(real_filename
, original_filename
) &&
408 (file_empty(real_filename
) || file_equal(real_filename
, original_filename
));
410 // Remove rejected file or signal about its existence
412 file_remove(reject_dir
, real_filename
);
414 cout
<< utilname
<< ": rejecting " << archive_filename
<< ", keeping existing version" << endl
;
420 throw runtime_error("empty package");
422 throw runtime_error("could not read " + filename
);
428 void pkgutil::ldconfig() const
430 // Only execute ldconfig if /etc/ld.so.conf exists
431 if (file_exists(root
+ LDCONFIG_CONF
)) {
435 throw runtime_error_with_errno("fork() failed");
438 execl(LDCONFIG
, LDCONFIG
, "-r", root
.c_str(), 0);
439 const char* msg
= strerror(errno
);
440 cerr
<< utilname
<< ": could not execute " << LDCONFIG
<< ": " << msg
<< endl
;
443 if (waitpid(pid
, 0, 0) == -1)
444 throw runtime_error_with_errno("waitpid() failed");
449 void pkgutil::pkg_footprint(string
& filename
) const
454 if (tar_open(&t
, const_cast<char*>(filename
.c_str()), &gztype
, O_RDONLY
, 0, TAR_GNU
) == -1)
455 throw runtime_error_with_errno("could not open " + filename
);
457 for (i
= 0; !th_read(t
); ++i
) {
458 // Access permissions
460 // Access permissions on symlinks differ among filesystems, e.g. XFS and ext2 have different.
461 // To avoid getting different footprints we always use "lrwxrwxrwx".
462 cout
<< "lrwxrwxrwx";
464 cout
<< mtos(th_get_mode(t
));
470 uid_t uid
= th_get_uid(t
);
471 struct passwd
* pw
= getpwuid(uid
);
480 gid_t gid
= th_get_gid(t
);
481 struct group
* gr
= getgrgid(gid
);
488 cout
<< '\t' << th_get_pathname(t
);
493 cout
<< " -> " << th_get_linkname(t
);
494 } else if (TH_ISCHR(t
) || TH_ISBLK(t
)) {
496 cout
<< " (" << th_get_devmajor(t
) << ", " << th_get_devminor(t
) << ")";
497 } else if (TH_ISREG(t
) && !th_get_size(t
)) {
498 // Empty regular file
504 if (TH_ISREG(t
) && tar_skip_regfile(t
))
505 throw runtime_error_with_errno("could not read " + filename
);
510 throw runtime_error("empty package");
512 throw runtime_error("could not read " + filename
);
518 void pkgutil::print_version() const
520 cout
<< utilname
<< " (pkgutils) " << VERSION
<< endl
;
523 db_lock::db_lock(const string
& root
, bool exclusive
)
526 const string dirname
= trim_filename(root
+ string("/") + PKG_DIR
);
528 if (!(dir
= opendir(dirname
.c_str())))
529 throw runtime_error_with_errno("could not read directory " + dirname
);
531 if (flock(dirfd(dir
), (exclusive
? LOCK_EX
: LOCK_SH
) | LOCK_NB
) == -1) {
532 if (errno
== EWOULDBLOCK
)
533 throw runtime_error("package database is currently locked by another process");
535 throw runtime_error_with_errno("could not lock directory " + dirname
);
542 flock(dirfd(dir
), LOCK_UN
);
547 void assert_argument(char** argv
, int argc
, int index
)
549 if (argc
- 1 < index
+ 1)
550 throw runtime_error("option " + string(argv
[index
]) + " requires an argument");
553 string
itos(unsigned int value
)
556 sprintf(buf
, "%u", value
);
560 string
mtos(mode_t mode
)
565 switch (mode
& S_IFMT
) {
566 case S_IFREG
: s
+= '-'; break; // Regular
567 case S_IFDIR
: s
+= 'd'; break; // Directory
568 case S_IFLNK
: s
+= 'l'; break; // Symbolic link
569 case S_IFCHR
: s
+= 'c'; break; // Character special
570 case S_IFBLK
: s
+= 'b'; break; // Block special
571 case S_IFSOCK
: s
+= 's'; break; // Socket
572 case S_IFIFO
: s
+= 'p'; break; // Fifo
573 default: s
+= '?'; break; // Unknown
577 s
+= (mode
& S_IRUSR
) ? 'r' : '-';
578 s
+= (mode
& S_IWUSR
) ? 'w' : '-';
579 switch (mode
& (S_IXUSR
| S_ISUID
)) {
580 case S_IXUSR
: s
+= 'x'; break;
581 case S_ISUID
: s
+= 'S'; break;
582 case S_IXUSR
| S_ISUID
: s
+= 's'; break;
583 default: s
+= '-'; break;
587 s
+= (mode
& S_IRGRP
) ? 'r' : '-';
588 s
+= (mode
& S_IWGRP
) ? 'w' : '-';
589 switch (mode
& (S_IXGRP
| S_ISGID
)) {
590 case S_IXGRP
: s
+= 'x'; break;
591 case S_ISGID
: s
+= 'S'; break;
592 case S_IXGRP
| S_ISGID
: s
+= 's'; break;
593 default: s
+= '-'; break;
597 s
+= (mode
& S_IROTH
) ? 'r' : '-';
598 s
+= (mode
& S_IWOTH
) ? 'w' : '-';
599 switch (mode
& (S_IXOTH
| S_ISVTX
)) {
600 case S_IXOTH
: s
+= 'x'; break;
601 case S_ISVTX
: s
+= 'T'; break;
602 case S_IXOTH
| S_ISVTX
: s
+= 't'; break;
603 default: s
+= '-'; break;
609 int unistd_gzopen(char* pathname
, int flags
, mode_t mode
)
613 switch (flags
& O_ACCMODE
) {
631 if ((fd
= open(pathname
, flags
, mode
)) == -1)
634 if ((flags
& O_CREAT
) && fchmod(fd
, mode
))
637 if (!(gz_file
= gzdopen(fd
, gz_mode
))) {
645 string
trim_filename(const string
& filename
)
648 string result
= filename
;
650 for (string::size_type pos
= result
.find(search
); pos
!= string::npos
; pos
= result
.find(search
))
651 result
.replace(pos
, search
.size(), "/");
656 bool file_exists(const string
& filename
)
659 return !lstat(filename
.c_str(), &buf
);
662 bool file_empty(const string
& filename
)
666 if (lstat(filename
.c_str(), &buf
) == -1)
669 return (S_ISREG(buf
.st_mode
) && buf
.st_size
== 0);
672 bool file_equal(const string
& file1
, const string
& file2
)
674 struct stat buf1
, buf2
;
676 if (lstat(file1
.c_str(), &buf1
) == -1)
679 if (lstat(file2
.c_str(), &buf2
) == -1)
683 if (S_ISREG(buf1
.st_mode
) && S_ISREG(buf2
.st_mode
)) {
684 ifstream
f1(file1
.c_str());
685 ifstream
f2(file2
.c_str());
693 f1
.read(buffer1
, 4096);
694 f2
.read(buffer2
, 4096);
695 if (f1
.gcount() != f2
.gcount() ||
696 memcmp(buffer1
, buffer2
, f1
.gcount()) ||
697 f1
.eof() != f2
.eof())
704 else if (S_ISLNK(buf1
.st_mode
) && S_ISLNK(buf2
.st_mode
)) {
705 char symlink1
[MAXPATHLEN
];
706 char symlink2
[MAXPATHLEN
];
708 memset(symlink1
, 0, MAXPATHLEN
);
709 memset(symlink2
, 0, MAXPATHLEN
);
711 if (readlink(file1
.c_str(), symlink1
, MAXPATHLEN
- 1) == -1)
714 if (readlink(file2
.c_str(), symlink2
, MAXPATHLEN
- 1) == -1)
717 return !strncmp(symlink1
, symlink2
, MAXPATHLEN
);
720 else if (S_ISCHR(buf1
.st_mode
) && S_ISCHR(buf2
.st_mode
)) {
721 return buf1
.st_dev
== buf2
.st_dev
;
724 else if (S_ISBLK(buf1
.st_mode
) && S_ISBLK(buf2
.st_mode
)) {
725 return buf1
.st_dev
== buf2
.st_dev
;
731 bool permissions_equal(const string
& file1
, const string
& file2
)
736 if (lstat(file1
.c_str(), &buf1
) == -1)
739 if (lstat(file2
.c_str(), &buf2
) == -1)
742 return(buf1
.st_mode
== buf2
.st_mode
) &&
743 (buf1
.st_uid
== buf2
.st_uid
) &&
744 (buf1
.st_gid
== buf2
.st_gid
);
747 void file_remove(const string
& basedir
, const string
& filename
)
749 if (filename
!= basedir
&& !remove(filename
.c_str())) {
750 char* path
= strdup(filename
.c_str());
751 file_remove(basedir
, dirname(path
));