summaryrefslogtreecommitdiff
path: root/pkgadd.cc
blob: d76a39f4980e45d178a793e01f5f4b757c605a88 (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 "pkgadd.h"
   24 #include <fstream>
   25 #include <iterator>
   26 #include <cstdio>
   27 #include <regex.h>
   28 #include <unistd.h>
   29 
   30 void pkgadd::run(int argc, char** argv)
   31 {
   32 	//
   33 	// Check command line options
   34 	//
   35 	string o_root;
   36 	string o_config;
   37 	string o_package;
   38 	bool o_upgrade = false;
   39 	bool o_force = false;
   40 
   41 	for (int i = 1; i < argc; i++) {
   42 		string option(argv[i]);
   43 		if (option == "-r" || option == "--root") {
   44 			assert_argument(argv, argc, i);
   45 			o_root = argv[i + 1];
   46 			i++;
   47 		} else if (option == "-c" || option == "--config") {
   48 			assert_argument(argv, argc, i);
   49 			o_config = argv[i + 1];
   50 			i++;
   51 		} else if (option == "-u" || option == "--upgrade") {
   52 			o_upgrade = true;
   53 		} else if (option == "-f" || option == "--force") {
   54 			o_force = true;
   55 		} else if (option[0] == '-' || !o_package.empty()) {
   56 			throw runtime_error("invalid option " + option);
   57 		} else {
   58 			o_package = option;
   59 		}
   60 	}
   61 
   62 	if (o_package.empty())
   63 		throw runtime_error("option missing");
   64 
   65 	//
   66 	// Check UID
   67 	//
   68 	if (getuid())
   69 		throw runtime_error("only root can install/upgrade packages");
   70 
   71 	//
   72 	// Install/upgrade package
   73 	//
   74 	{
   75 		db_lock lock(o_root, true);
   76 		db_open(o_root);
   77 
   78 		pair<string, pkginfo_t> package = pkg_open(o_package);
   79 		vector<rule_t> config_rules = read_config(o_config);
   80 
   81 		bool installed = db_find_pkg(package.first);
   82 		if (installed && !o_upgrade)
   83 			throw runtime_error("package " + package.first + " already installed (use -u to upgrade)");
   84 		else if (!installed && o_upgrade)
   85 			throw runtime_error("package " + package.first + " not previously installed (skip -u to install)");
   86       
   87 		set<string> non_install_files = apply_install_rules(package.first, package.second, config_rules);
   88 		set<string> conflicting_files = db_find_conflicts(package.first, package.second);
   89       
   90 		if (!conflicting_files.empty()) {
   91 			if (o_force) {
   92 				set<string> keep_list;
   93 				if (o_upgrade) // Don't remove files matching the rules in configuration
   94 					keep_list = make_keep_list(conflicting_files, config_rules);
   95 				db_rm_files(conflicting_files, keep_list); // Remove unwanted conflicts
   96 			} else {
   97 				copy(conflicting_files.begin(), conflicting_files.end(), ostream_iterator<string>(cerr, "\n"));
   98 				throw runtime_error("listed file(s) already installed (use -f to ignore and overwrite)");
   99 			}
  100 		}
  101    
  102 		set<string> keep_list;
  103 
  104 		if (o_upgrade) {
  105 			keep_list = make_keep_list(package.second.files, config_rules);
  106 			db_rm_pkg(package.first, keep_list);
  107 		}
  108    
  109 		db_add_pkg(package.first, package.second);
  110 		db_commit();
  111 		try {
  112 			pkg_install(o_package, keep_list, non_install_files, installed);
  113 		} catch (runtime_error&) {
  114 			if (!installed) {
  115 				db_rm_pkg(package.first);
  116 				db_commit();
  117 				throw runtime_error("failed");
  118 			}
  119 		}
  120 		ldconfig();
  121 	}
  122 }
  123 
  124 void pkgadd::print_help() const
  125 {
  126 	cout << "usage: " << utilname << " [options] <file>" << endl
  127 	     << "options:" << endl
  128 	     << "  -u, --upgrade        upgrade package with the same name" << endl
  129 	     << "  -f, --force          force install, overwrite conflicting files" << endl
  130 	     << "  -r, --root <path>    specify alternative installation root" << endl
  131 	     << "  -c, --config <file>  use alternate configuration file" << endl 
  132 	     << "  -v, --version        print version and exit" << endl
  133 	     << "  -h, --help           print help and exit" << endl;
  134 }
  135 
  136 vector<rule_t> pkgadd::read_config(string file) const
  137 {
  138 	vector<rule_t> rules;
  139 	unsigned int linecount = 0;
  140 	string filename = root + PKGADD_CONF;
  141 
  142 	if (!file.empty()) filename = file;
  143 	ifstream in(filename.c_str());
  144 
  145 	if (in) {
  146 		while (!in.eof()) {
  147 			string line;
  148 			getline(in, line);
  149 			linecount++;
  150 			if (!line.empty() && line[0] != '#') {
  151 				if (line.length() >= PKGADD_CONF_MAXLINE)
  152 					throw runtime_error(filename + ":" + itos(linecount) + ": line too long, aborting");
  153 
  154 				char event[PKGADD_CONF_MAXLINE];
  155 				char pattern[PKGADD_CONF_MAXLINE];
  156 				char action[PKGADD_CONF_MAXLINE];
  157 				char dummy[PKGADD_CONF_MAXLINE];
  158 				if (sscanf(line.c_str(), "%s %s %s %s", event, pattern, action, dummy) != 3)
  159 					throw runtime_error(filename + ":" + itos(linecount) + ": wrong number of arguments, aborting");
  160 
  161 				if (!strcmp(event, "UPGRADE") || !strcmp(event, "INSTALL")) {
  162 					rule_t rule;
  163 					rule.event = strcmp(event, "UPGRADE") ? INSTALL : UPGRADE;
  164 					rule.pattern = pattern;
  165 					if (!strcmp(action, "YES")) {
  166 						rule.action = true;
  167 					} else if (!strcmp(action, "NO")) {
  168 						rule.action = false;
  169 					} else
  170 						throw runtime_error(filename + ":" + itos(linecount) + ": '" +
  171 								    string(action) + "' unknown action, should be YES or NO, aborting");
  172 
  173 					rules.push_back(rule);
  174 				} else
  175 					throw runtime_error(filename + ":" + itos(linecount) + ": '" +
  176 							    string(event) + "' unknown event, aborting");
  177 			}
  178 		}
  179 		in.close();
  180 	}
  181 
  182 #ifndef NDEBUG
  183 	cerr << "Configuration:" << endl;
  184 	for (vector<rule_t>::const_iterator j = rules.begin(); j != rules.end(); j++) {
  185 		cerr << "\t" << (*j).pattern << "\t" << (*j).action << endl;
  186 	}
  187 	cerr << endl;
  188 #endif
  189 
  190 	return rules;
  191 }
  192 
  193 set<string> pkgadd::make_keep_list(const set<string>& files, const vector<rule_t>& rules) const
  194 {
  195 	set<string> keep_list;
  196 	vector<rule_t> found;
  197 
  198 	find_rules(rules, UPGRADE, found);
  199 
  200 	for (set<string>::const_iterator i = files.begin(); i != files.end(); i++) {
  201 		for (vector<rule_t>::reverse_iterator j = found.rbegin(); j != found.rend(); j++) {
  202 			if (rule_applies_to_file(*j, *i)) {
  203 				if (!(*j).action)
  204 					keep_list.insert(keep_list.end(), *i);
  205 
  206 				break;
  207 			}
  208 		}
  209 	}
  210 
  211 #ifndef NDEBUG
  212 	cerr << "Keep list:" << endl;
  213 	for (set<string>::const_iterator j = keep_list.begin(); j != keep_list.end(); j++) {
  214 		cerr << "   " << (*j) << endl;
  215 	}
  216 	cerr << endl;
  217 #endif
  218 
  219 	return keep_list;
  220 }
  221 
  222 set<string> pkgadd::apply_install_rules(const string& name, pkginfo_t& info, const vector<rule_t>& rules)
  223 {
  224 	// TODO: better algo(?)
  225 	set<string> install_set;
  226 	set<string> non_install_set;
  227 	vector<rule_t> found;
  228 
  229 	find_rules(rules, INSTALL, found);
  230 
  231 	for (set<string>::const_iterator i = info.files.begin(); i != info.files.end(); i++) {
  232 		bool install_file = true;
  233 
  234 		for (vector<rule_t>::reverse_iterator j = found.rbegin(); j != found.rend(); j++) {
  235 			if (rule_applies_to_file(*j, *i)) {
  236 				install_file = (*j).action;
  237 				break;
  238 			}
  239 		}
  240 
  241 		if (install_file)
  242 			install_set.insert(install_set.end(), *i);
  243 		else
  244 			non_install_set.insert(*i);
  245 	}
  246 
  247 	info.files.clear();
  248 	info.files = install_set;
  249 
  250 #ifndef NDEBUG
  251 	cerr << "Install set:" << endl;
  252 	for (set<string>::iterator j = info.files.begin(); j != info.files.end(); j++) {
  253 		cerr << "   " << (*j) << endl;
  254 	}
  255 	cerr << endl;
  256 
  257 	cerr << "Non-Install set:" << endl;
  258 	for (set<string>::iterator j = non_install_set.begin(); j != non_install_set.end(); j++) {
  259 		cerr << "   " << (*j) << endl;
  260 	}
  261 	cerr << endl;
  262 #endif
  263 
  264 	return non_install_set;
  265 }
  266 
  267 void pkgadd::find_rules(const vector<rule_t>& rules, rule_event_t event, vector<rule_t>& found) const
  268 {
  269 	for (vector<rule_t>::const_iterator i = rules.begin(); i != rules.end(); i++)
  270 		if (i->event == event)
  271 			found.push_back(*i);
  272 }
  273 
  274 bool pkgadd::rule_applies_to_file(const rule_t& rule, const string& file) const
  275 {
  276 	regex_t preg;
  277 	bool ret;
  278 
  279 	if (regcomp(&preg, rule.pattern.c_str(), REG_EXTENDED | REG_NOSUB))
  280 		throw runtime_error("error compiling regular expression '" + rule.pattern + "', aborting");
  281 
  282 	ret = !regexec(&preg, file.c_str(), 0, 0, 0);
  283 	regfree(&preg);
  284 
  285 	return ret;
  286 }

Generated by cgit