// Copyright (c) 2000-2002 by Per Liden #include "pkgutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace __gnu_cxx; static int gzopen_frontend(char *pathname, int oflags, int mode) { char* gzoflags; switch (oflags & O_ACCMODE) { case O_WRONLY: gzoflags = "w"; break; case O_RDONLY: gzoflags = "r"; break; case O_RDWR: default: errno = EINVAL; return -1; } int fd; gzFile gzf; if ((fd = open(pathname, oflags, mode)) == -1) return -1; if ((oflags & O_CREAT) && fchmod(fd, mode)) return -1; if (!(gzf = gzdopen(fd, gzoflags))) { errno = ENOMEM; return -1; } return (int)gzf; } static tartype_t gztype = { (openfunc_t)gzopen_frontend, (closefunc_t)gzclose, (readfunc_t)gzread, (writefunc_t)gzwrite }; pkgutil::pkgutil() { // Ignore signals struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_IGN; sigaction(SIGHUP, &sa, 0); sigaction(SIGINT, &sa, 0); sigaction(SIGQUIT, &sa, 0); sigaction(SIGTERM, &sa, 0); } void pkgutil::db_open(const string& path) { root = trim_filename(path + "/"); const string filename = root + PKG_DB; ifstream in(filename.c_str()); if (in) { while (!in.eof()) { // Read record string name; pkginfo_t info; getline(in, name); getline(in, info.version); for (;;) { string file; getline(in, file); if (file.empty()) break; // End of record info.files.insert(info.files.end(), file); } if (!info.files.empty()) packages[name] = info; } in.close(); } else { const char* msg = strerror(errno); print_error() << "unable to read " << filename << ": " << msg << endl; exit(EXIT_ERROR); } #ifndef NDEBUG cerr << packages.size() << " packages found in database" << endl; #endif } void pkgutil::db_commit() { const string dbfilename = root + PKG_DB; const string dbfilename_new = dbfilename + ".incomplete_transaction"; const string dbfilename_bak = dbfilename + ".backup"; // Remove failed transaction (if it exists) if (unlink(dbfilename_new.c_str()) == -1 && errno != ENOENT) { const char* msg = strerror(errno); print_error() << "unable to remove " << dbfilename_new << ": " << msg << endl; exit(EXIT_ERROR); } // Write new database int fd_new = creat(dbfilename_new.c_str(), 0444); if (fd_new != -1) { stdio_filebuf filebuf_new(fd_new, ios::out, true, getpagesize()); ostream db_new(&filebuf_new); for (packages_t::const_iterator i = packages.begin(); i != packages.end(); i++) { if (!(*i).second.files.empty()) { db_new << (*i).first << "\n"; db_new << (*i).second.version << "\n"; copy((*i).second.files.begin(), (*i).second.files.end(), ostream_iterator(db_new, "\n")); db_new << "\n"; } } db_new.flush(); fsync(fd_new); // Make backup of previous database int fd_bak = creat(dbfilename_bak.c_str(), 0444); if (fd_bak != -1) { stdio_filebuf filebuf_bak(fd_bak, ios::out, true, getpagesize()); ostream db_bak(&filebuf_bak); ifstream db(dbfilename.c_str()); if (db) { db_bak << db.rdbuf(); db.close(); } else { const char* msg = strerror(errno); print_error() << "unable read " << dbfilename << ": " << msg << endl; exit(EXIT_ERROR); } db_bak.flush(); fsync(fd_bak); } else { const char* msg = strerror(errno); print_error() << "unable write " << dbfilename_bak << ": " << msg << endl; exit(EXIT_ERROR); } // Rename new database if (rename(dbfilename_new.c_str(), dbfilename.c_str()) == -1) { const char* msg = strerror(errno); print_error() << "unable rename " << dbfilename_new << " to " << dbfilename << ": " << msg << endl; exit(EXIT_ERROR); } } else { const char* msg = strerror(errno); print_error() << "unable to write " << dbfilename << ": " << msg << endl; exit(EXIT_ERROR); } #ifndef NDEBUG cerr << packages.size() << " packages written to database" << endl; #endif } void pkgutil::db_add_pkg(const string& name, const pkginfo_t& info) { packages[name] = info; } bool pkgutil::db_find_pkg(const string& name) { return (packages.find(name) != packages.end()); } void pkgutil::db_rm_pkg(const string& name) { set files = packages[name].files; packages.erase(name); #ifndef NDEBUG cerr << "Removing package phase 1 (all files in package):" << endl; copy(files.begin(), files.end(), ostream_iterator(cerr, "\n")); cerr << endl; #endif // Don't delete files that still have references for (packages_t::const_iterator i = packages.begin(); i != packages.end(); i++) for (set::const_iterator j = (*i).second.files.begin(); j != (*i).second.files.end(); j++) files.erase((*j)); #ifndef NDEBUG cerr << "Removing package phase 2 (files that still have references excluded):" << endl; copy(files.begin(), files.end(), ostream_iterator(cerr, "\n")); cerr << endl; #endif // Delete the files for (set::const_reverse_iterator i = files.rbegin(); i != files.rend(); i++) { const string filename = root + (*i); if (file_exists(filename) && remove(filename.c_str()) == -1) { const char* msg = strerror(errno); print_error() << "unable to remove " << filename << ": " << msg << endl; } } } void pkgutil::db_rm_pkg(const string& name, const set& keep_list) { set files = packages[name].files; packages.erase(name); #ifndef NDEBUG cerr << "Removing package phase 1 (all files in package):" << endl; copy(files.begin(), files.end(), ostream_iterator(cerr, "\n")); cerr << endl; #endif // Don't delete files found in the keep list for (set::const_iterator i = keep_list.begin(); i != keep_list.end(); i++) files.erase(*i); #ifndef NDEBUG cerr << "Removing package phase 2 (files that is in the keep list excluded):" << endl; copy(files.begin(), files.end(), ostream_iterator(cerr, "\n")); cerr << endl; #endif // Don't delete files that still have references for (packages_t::const_iterator i = packages.begin(); i != packages.end(); i++) for (set::const_iterator j = (*i).second.files.begin(); j != (*i).second.files.end(); j++) files.erase((*j)); #ifndef NDEBUG cerr << "Removing package phase 3 (files that still have references excluded):" << endl; copy(files.begin(), files.end(), ostream_iterator(cerr, "\n")); cerr << endl; #endif // Delete the files for (set::const_reverse_iterator i = files.rbegin(); i != files.rend(); i++) { const string filename = root + (*i); if (file_exists(filename) && remove(filename.c_str()) == -1) { if (errno == ENOTEMPTY) continue; const char* msg = strerror(errno); print_error() << "unable to remove " << filename << ": " << msg << endl; } } } void pkgutil::db_rm_files(set files) { // Remove all references for (packages_t::iterator i = packages.begin(); i != packages.end(); i++) for (set::const_iterator j = files.begin(); j != files.end(); j++) (*i).second.files.erase((*j)); #ifndef NDEBUG cerr << "Removing files:" << endl; copy(files.begin(), files.end(), ostream_iterator(cerr, "\n")); cerr << endl; #endif // Delete the files for (set::const_reverse_iterator i = files.rbegin(); i != files.rend(); i++) { const string filename = root + (*i); if (file_exists(filename) && remove(filename.c_str()) == -1) { if (errno == ENOTEMPTY) continue; const char* msg = strerror(errno); print_error() << "unable to remove " << filename << ": " << msg << endl; } } } set pkgutil::db_find_conflicts(const string& name, const pkginfo_t& info) { set files; // Find conflicting files in database for (packages_t::const_iterator i = packages.begin(); i != packages.end(); i++) { if ((*i).first != name) { set_intersection(info.files.begin(), info.files.end(), (*i).second.files.begin(), (*i).second.files.end(), inserter(files, files.end())); } } #ifndef NDEBUG cerr << "Conflicts phase 1 (conflicts in database):" << endl; copy(files.begin(), files.end(), ostream_iterator(cerr, "\n")); cerr << endl; #endif // Find conflicting files in filesystem for (set::iterator i = info.files.begin(); i != info.files.end(); i++) { const string filename = root + (*i); if (file_exists(filename) && files.find(*i) == files.end()) files.insert(files.end(), *i); } #ifndef NDEBUG cerr << "Conflicts phase 2 (conflicts in filesystem added):" << endl; copy(files.begin(), files.end(), ostream_iterator(cerr, "\n")); cerr << endl; #endif // Exclude directories set tmp = files; for (set::const_iterator i = tmp.begin(); i != tmp.end(); i++) { if ((*i)[(*i).length() - 1] == '/') files.erase(*i); } #ifndef NDEBUG cerr << "Conflicts phase 3 (directories excluded):" << endl; copy(files.begin(), files.end(), ostream_iterator(cerr, "\n")); cerr << endl; #endif // If this is an upgrade, remove files already owned by this package if (packages.find(name) != packages.end()) { for (set::const_iterator i = packages[name].files.begin(); i != packages[name].files.end(); i++) files.erase(*i); #ifndef NDEBUG cerr << "Conflicts phase 4 (files already owned by this package excluded):" << endl; copy(files.begin(), files.end(), ostream_iterator(cerr, "\n")); cerr << endl; #endif } return files; } pair pkgutil::pkg_open(const string& filename) const { pair result; unsigned int i; TAR* t; // Extract name and version from filename string basename(filename, filename.rfind('/') + 1); string name(basename, 0, basename.find(VERSION_DELIM)); string version(basename, 0, basename.rfind(PKG_EXT)); version.erase(0, version.find(VERSION_DELIM)==string::npos?string::npos:version.find(VERSION_DELIM) + 1); if (name.empty() || version.empty()) { print_error() << "unable to determine name and/or version of " << basename << ": Invalid package name" << endl; exit(EXIT_ERROR); } result.first = name; result.second.version = version; if (tar_open(&t, const_cast(filename.c_str()), &gztype, O_RDONLY, 0, TAR_GNU) == -1) { const char* msg = strerror(errno); print_error() << "unable to open " << filename << ": " << msg << endl; exit(EXIT_ERROR); } for (i = 0; !th_read(t); i++) { result.second.files.insert(result.second.files.end(), th_get_pathname(t)); if (TH_ISREG(t) && tar_skip_regfile(t)) { const char* msg = strerror(errno); print_error() << "unable to read " << filename << ": " << msg << endl; exit(EXIT_ERROR); } } if (i == 0) { const char* msg = strerror(errno); print_error() << "unable to read " << filename << ": " << msg << endl; exit(EXIT_ERROR); } tar_close(t); return result; } void pkgutil::pkg_install(const string& filename) const { TAR* t; if (tar_open(&t, const_cast(filename.c_str()), &gztype, O_RDONLY, 0, TAR_GNU) == -1) { const char* msg = strerror(errno); print_error() << "unable to open " << filename << ": " << msg << endl; exit(EXIT_ERROR); } while (!th_read(t)) { const string archive_filename = th_get_pathname(t); string real_filename = trim_filename(root + string("/") + archive_filename); if (file_exists(real_filename) && real_filename[real_filename.length() - 1] != '/') { real_filename = trim_filename(root + string("/") + string(PKG_REJECTED) + archive_filename); print_info() << "rejecting " << archive_filename << ", keeping existing version" << endl; } if (tar_extract_file(t, const_cast(real_filename.c_str())) != 0) { const char* msg = strerror(errno); print_error() << "unable to read " << filename << ": " << msg << endl; exit(EXIT_ERROR); } } tar_close(t); } string pkgutil::trim_filename(const string& filename) const { string search("//"); string result = filename; for (string::size_type pos = result.find(search); pos != string::npos; pos = result.find(search)) { result.replace(pos, search.size(), "/"); } return result; } bool pkgutil::file_exists(const string& filename) const { struct stat buf; if (!lstat(filename.c_str(), &buf)) return true; else return false; } void pkgutil::print_version() const { cout << name() << " (pkgutils) " << VERSION << endl; exit(EXIT_OK); } void pkgutil::print_try_help() const { cout << "Try '" << name() << " --help' for more information." << endl; exit(EXIT_ERROR); } void pkgutil::print_invalid_option(const string& option) const { print_error() << "invalid option " << option << endl; print_try_help(); } void pkgutil::print_option_missing() const { print_error() << "option missing" << endl; print_try_help(); } void pkgutil::check_argument(char** argv, int argc, int index) const { if (argc - 1 < index + 1) { print_error() << "option " << argv[index] << " requires an argument" << endl; print_try_help(); } }