forked from tools/pkgutils
289 lines
8.2 KiB
C++
289 lines
8.2 KiB
C++
//
|
|
// pkgutils
|
|
//
|
|
// Copyright (c) 2000-2005 Per Liden
|
|
// Copyright (c) 2006-2021 by CRUX team (http://crux.nu)
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
// USA.
|
|
//
|
|
|
|
#include "pkgadd.h"
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <cstdio>
|
|
#include <regex.h>
|
|
#include <unistd.h>
|
|
|
|
void pkgadd::run(int argc, char** argv)
|
|
{
|
|
//
|
|
// Check command line options
|
|
//
|
|
string o_root;
|
|
string o_config;
|
|
string o_package;
|
|
bool o_upgrade = false;
|
|
bool o_force = false;
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
string option(argv[i]);
|
|
if (option == "-r" || option == "--root") {
|
|
assert_argument(argv, argc, i);
|
|
o_root = argv[i + 1];
|
|
i++;
|
|
} else if (option == "-c" || option == "--config") {
|
|
assert_argument(argv, argc, i);
|
|
o_config = argv[i + 1];
|
|
i++;
|
|
} else if (option == "-u" || option == "--upgrade") {
|
|
o_upgrade = true;
|
|
} else if (option == "-f" || option == "--force") {
|
|
o_force = true;
|
|
} else if (option[0] == '-' || !o_package.empty()) {
|
|
throw runtime_error("invalid option " + option);
|
|
} else {
|
|
o_package = option;
|
|
}
|
|
}
|
|
|
|
if (o_package.empty())
|
|
throw runtime_error("option missing");
|
|
|
|
//
|
|
// Check UID
|
|
//
|
|
if (getuid())
|
|
throw runtime_error("only root can install/upgrade packages");
|
|
|
|
//
|
|
// Install/upgrade package
|
|
//
|
|
{
|
|
db_lock lock(o_root, true);
|
|
db_open(o_root);
|
|
|
|
pair<string, pkginfo_t> package = pkg_open(o_package);
|
|
vector<rule_t> config_rules = read_config(o_config);
|
|
|
|
bool installed = db_find_pkg(package.first);
|
|
if (installed && !o_upgrade)
|
|
throw runtime_error("package " + package.first + " already installed (use -u to upgrade)");
|
|
else if (!installed && o_upgrade)
|
|
throw runtime_error("package " + package.first + " not previously installed (skip -u to install)");
|
|
|
|
set<string> non_install_files = apply_install_rules(package.first, package.second, config_rules);
|
|
set<string> conflicting_files = db_find_conflicts(package.first, package.second);
|
|
|
|
if (!conflicting_files.empty()) {
|
|
if (o_force) {
|
|
set<string> keep_list;
|
|
if (o_upgrade) // Don't remove files matching the rules in configuration
|
|
keep_list = make_keep_list(conflicting_files, config_rules);
|
|
db_rm_files(conflicting_files, keep_list); // Remove unwanted conflicts
|
|
} else {
|
|
copy(conflicting_files.begin(), conflicting_files.end(), ostream_iterator<string>(cerr, "\n"));
|
|
throw runtime_error("listed file(s) already installed (use -f to ignore and overwrite)");
|
|
}
|
|
}
|
|
|
|
set<string> keep_new;
|
|
|
|
if (o_upgrade) {
|
|
keep_new = make_keep_list(package.second.files, config_rules);
|
|
set<string> files_old = packages[package.first].files;
|
|
set<string> keep_old = make_keep_list(files_old, config_rules);
|
|
db_rm_pkg(package.first, keep_old, keep_new);
|
|
}
|
|
|
|
db_add_pkg(package.first, package.second);
|
|
db_commit();
|
|
try {
|
|
pkg_install(o_package, keep_new, non_install_files, installed);
|
|
} catch (runtime_error&) {
|
|
if (!installed) {
|
|
db_rm_pkg(package.first);
|
|
db_commit();
|
|
throw runtime_error("failed");
|
|
}
|
|
}
|
|
ldconfig();
|
|
}
|
|
}
|
|
|
|
void pkgadd::print_help() const
|
|
{
|
|
cout << "usage: " << utilname << " [options] <file>" << endl
|
|
<< "options:" << endl
|
|
<< " -u, --upgrade upgrade package with the same name" << endl
|
|
<< " -f, --force force install, overwrite conflicting files" << endl
|
|
<< " -r, --root <path> specify alternative installation root" << endl
|
|
<< " -c, --config <file> use alternate configuration file" << endl
|
|
<< " -v, --version print version and exit" << endl
|
|
<< " -h, --help print help and exit" << endl;
|
|
}
|
|
|
|
vector<rule_t> pkgadd::read_config(string file) const
|
|
{
|
|
vector<rule_t> rules;
|
|
unsigned int linecount = 0;
|
|
string filename = root + PKGADD_CONF;
|
|
|
|
if (!file.empty()) filename = file;
|
|
ifstream in(filename.c_str());
|
|
|
|
if (in) {
|
|
while (!in.eof()) {
|
|
string line;
|
|
getline(in, line);
|
|
linecount++;
|
|
if (!line.empty() && line[0] != '#') {
|
|
if (line.length() >= PKGADD_CONF_MAXLINE)
|
|
throw runtime_error(filename + ":" + itos(linecount) + ": line too long, aborting");
|
|
|
|
char event[PKGADD_CONF_MAXLINE];
|
|
char pattern[PKGADD_CONF_MAXLINE];
|
|
char action[PKGADD_CONF_MAXLINE];
|
|
char dummy[PKGADD_CONF_MAXLINE];
|
|
if (sscanf(line.c_str(), "%s %s %s %s", event, pattern, action, dummy) != 3)
|
|
throw runtime_error(filename + ":" + itos(linecount) + ": wrong number of arguments, aborting");
|
|
|
|
if (!strcmp(event, "UPGRADE") || !strcmp(event, "INSTALL")) {
|
|
rule_t rule;
|
|
rule.event = strcmp(event, "UPGRADE") ? INSTALL : UPGRADE;
|
|
rule.pattern = pattern;
|
|
if (!strcmp(action, "YES")) {
|
|
rule.action = true;
|
|
} else if (!strcmp(action, "NO")) {
|
|
rule.action = false;
|
|
} else
|
|
throw runtime_error(filename + ":" + itos(linecount) + ": '" +
|
|
string(action) + "' unknown action, should be YES or NO, aborting");
|
|
|
|
rules.push_back(rule);
|
|
} else
|
|
throw runtime_error(filename + ":" + itos(linecount) + ": '" +
|
|
string(event) + "' unknown event, aborting");
|
|
}
|
|
}
|
|
in.close();
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
cerr << "Configuration:" << endl;
|
|
for (vector<rule_t>::const_iterator j = rules.begin(); j != rules.end(); j++) {
|
|
cerr << "\t" << (*j).pattern << "\t" << (*j).action << endl;
|
|
}
|
|
cerr << endl;
|
|
#endif
|
|
|
|
return rules;
|
|
}
|
|
|
|
set<string> pkgadd::make_keep_list(const set<string>& files, const vector<rule_t>& rules) const
|
|
{
|
|
set<string> keep_list;
|
|
vector<rule_t> found;
|
|
|
|
find_rules(rules, UPGRADE, found);
|
|
|
|
for (set<string>::const_iterator i = files.begin(); i != files.end(); i++) {
|
|
for (vector<rule_t>::reverse_iterator j = found.rbegin(); j != found.rend(); j++) {
|
|
if (rule_applies_to_file(*j, *i)) {
|
|
if (!(*j).action)
|
|
keep_list.insert(keep_list.end(), *i);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
cerr << "Keep list:" << endl;
|
|
for (set<string>::const_iterator j = keep_list.begin(); j != keep_list.end(); j++) {
|
|
cerr << " " << (*j) << endl;
|
|
}
|
|
cerr << endl;
|
|
#endif
|
|
|
|
return keep_list;
|
|
}
|
|
|
|
set<string> pkgadd::apply_install_rules(const string& name, pkginfo_t& info, const vector<rule_t>& rules)
|
|
{
|
|
// TODO: better algo(?)
|
|
set<string> install_set;
|
|
set<string> non_install_set;
|
|
vector<rule_t> found;
|
|
|
|
find_rules(rules, INSTALL, found);
|
|
|
|
for (set<string>::const_iterator i = info.files.begin(); i != info.files.end(); i++) {
|
|
bool install_file = true;
|
|
|
|
for (vector<rule_t>::reverse_iterator j = found.rbegin(); j != found.rend(); j++) {
|
|
if (rule_applies_to_file(*j, *i)) {
|
|
install_file = (*j).action;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (install_file)
|
|
install_set.insert(install_set.end(), *i);
|
|
else
|
|
non_install_set.insert(*i);
|
|
}
|
|
|
|
info.files.clear();
|
|
info.files = install_set;
|
|
|
|
#ifndef NDEBUG
|
|
cerr << "Install set:" << endl;
|
|
for (set<string>::iterator j = info.files.begin(); j != info.files.end(); j++) {
|
|
cerr << " " << (*j) << endl;
|
|
}
|
|
cerr << endl;
|
|
|
|
cerr << "Non-Install set:" << endl;
|
|
for (set<string>::iterator j = non_install_set.begin(); j != non_install_set.end(); j++) {
|
|
cerr << " " << (*j) << endl;
|
|
}
|
|
cerr << endl;
|
|
#endif
|
|
|
|
return non_install_set;
|
|
}
|
|
|
|
void pkgadd::find_rules(const vector<rule_t>& rules, rule_event_t event, vector<rule_t>& found) const
|
|
{
|
|
for (vector<rule_t>::const_iterator i = rules.begin(); i != rules.end(); i++)
|
|
if (i->event == event)
|
|
found.push_back(*i);
|
|
}
|
|
|
|
bool pkgadd::rule_applies_to_file(const rule_t& rule, const string& file) const
|
|
{
|
|
regex_t preg;
|
|
bool ret;
|
|
|
|
if (regcomp(&preg, rule.pattern.c_str(), REG_EXTENDED | REG_NOSUB))
|
|
throw runtime_error("error compiling regular expression '" + rule.pattern + "', aborting");
|
|
|
|
ret = !regexec(&preg, file.c_str(), 0, 0, 0);
|
|
regfree(&preg);
|
|
|
|
return ret;
|
|
}
|