summaryrefslogtreecommitdiff
path: root/pkgutil.cc
blob: bf1a0aa6852954bfd04e219faaf61704768b3eda (plain)
    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 }

Generated by cgit