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

Generated by cgit