Index: pkgadd.h =================================================================== --- pkgadd.h (revision 2) +++ pkgadd.h (revision 3) @@ -43,7 +43,8 @@ private: vector read_config() const; - set make_keep_list(const set& files, const vector& rules) const; + template + filenames_t make_keep_list(const in_t& files, const vector& rules) const; }; #endif /* PKGADD_H */ Index: pkginfo.cc =================================================================== --- pkginfo.cc (revision 2) +++ pkginfo.cc (revision 3) @@ -97,10 +97,16 @@ // List package or file contents // if (db_find_pkg(o_arg)) { - copy(packages[o_arg].files.begin(), packages[o_arg].files.end(), ostream_iterator(cout, "\n")); + transform(packages[o_arg].files.begin(), + packages[o_arg].files.end(), + ostream_iterator(cout, "\n"), + first_in_pair()); } else if (file_exists(o_arg)) { pair package = pkg_open(o_arg); - copy(package.second.files.begin(), package.second.files.end(), ostream_iterator(cout, "\n")); + transform(package.second.files.begin(), + package.second.files.end(), + ostream_iterator(cout, "\n"), + first_in_pair()); } else { throw runtime_error(o_arg + " is neither an installed package nor a package file"); } @@ -117,10 +123,10 @@ unsigned int width = result.begin()->first.length(); // Width of "Package" 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) { - const string file('/' + *j); + for (files_t::const_iterator j = i->second.files.begin(); j != i->second.files.end(); ++j) { + const string file('/' + j->first); if (!regexec(&preg, file.c_str(), 0, 0, 0)) { - result.push_back(pair(i->first, *j)); + result.push_back(pair(i->first, j->first)); if (i->first.length() > width) width = i->first.length(); } Index: pkgutil.cc =================================================================== --- pkgutil.cc (revision 2) +++ pkgutil.cc (revision 3) @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,17 @@ using __gnu_cxx::stdio_filebuf; +/* TODO: gcc-4.0.3 have bug: + * http://gcc.gnu.org/ml/gcc-bugs/2006-03/msg02653.html which prevents + * using map::const_reverse_iterator. When we'll upgrade to gcc-4.1.x, + * const_ should be added. + * + * Also, there is error: + * error: no match for 'operator!=' in 'i != std::map<_Key, + * _Tp, _Compare, _Alloc>::rend() + * when using fileconflicts_t::const_reverse_iterator. Most probably + * it's the same bug. */ + static tartype_t gztype = { (openfunc_t)unistd_gzopen, (closefunc_t)gzclose, @@ -81,21 +93,23 @@ while (!in.eof()) { // Read record - string name; - pkginfo_t info; - getline(in, name); - getline(in, info.version); + string pkgname; + pkginfo_t pkginfo; + getline(in, pkgname); + getline(in, pkginfo.version); for (;;) { - string file; - getline(in, file); + string filename; + getline(in, filename); - if (file.empty()) + if (filename.empty()) break; // End of record - - info.files.insert(info.files.end(), file); + + file_t file; + file.first = filename; + pkginfo.files.insert(pkginfo.files.end(), file); } - if (!info.files.empty()) - packages[name] = info; + if (!pkginfo.files.empty()) + packages[pkgname] = pkginfo; } dbg(t2s(packages.size()) + " packages found in database"); @@ -122,7 +136,9 @@ 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")); + transform(i->second.files.begin(), i->second.files.end(), + ostream_iterator(db_new, "\n"), + first_in_pair()); db_new << "\n"; } } @@ -162,21 +178,21 @@ void pkgutil::db_rm_pkg(const string& name) { - set files = packages[name].files; + files_t files = packages[name].files; packages.erase(name); dbg("Removing package phase 1 (all files in package):", files); // 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); + for (files_t::const_iterator j = i->second.files.begin(); j != i->second.files.end(); ++j) + files.erase(j->first); dbg("Removing package phase 2 (files that still have references excluded):", files); // Delete the files - for (set::const_reverse_iterator i = files.rbegin(); i != files.rend(); ++i) { - const string filename = root + *i; + for (files_t::reverse_iterator i = files.rbegin(); i != files.rend(); ++i) { + const string filename = root + i->first; if (file_exists(filename) && remove(filename.c_str()) == -1) { const char* msg = strerror(errno); cerr << utilname << ": could not remove " << filename << ": " << msg << endl; @@ -184,29 +200,29 @@ } } -void pkgutil::db_rm_pkg(const string& name, const set& keep_list) +void pkgutil::db_rm_pkg(const string& name, const filenames_t& keep_list) { - set files = packages[name].files; + files_t files = packages[name].files; packages.erase(name); dbg("Removing package phase 1 (all files in package):", files); // Don't delete files found in the keep list - for (set::const_iterator i = keep_list.begin(); i != keep_list.end(); ++i) + for (filenames_t::const_iterator i = keep_list.begin(); i != keep_list.end(); ++i) files.erase(*i); dbg("Removing package phase 2 (files that is in the keep list excluded):", files); // 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); + for (files_t::const_iterator j = i->second.files.begin(); j != i->second.files.end(); ++j) + files.erase(j->first); dbg("Removing package phase 3 (files that still have references excluded):", files); // Delete the files - for (set::const_reverse_iterator i = files.rbegin(); i != files.rend(); ++i) { - const string filename = root + *i; + for (files_t::reverse_iterator i = files.rbegin(); i != files.rend(); ++i) { + const string filename = root + i->first; if (file_exists(filename) && remove(filename.c_str()) == -1) { if (errno == ENOTEMPTY) continue; @@ -216,22 +232,22 @@ } } -void pkgutil::db_rm_files(set files, const set& keep_list) +void pkgutil::db_rm_fileconflicts(fileconflicts_t files, const filenames_t& keep_list) { // 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); + for (fileconflicts_t::const_iterator j = files.begin(); j != files.end(); ++j) + i->second.files.erase(j->first.first); dbg("Removing files:", files); - // 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); + // Delete the files + for (fileconflicts_t::reverse_iterator i = files.rbegin(); i != files.rend(); ++i) { + // Don't delete files found in the keep list + if (keep_list.find(i->first.first) != keep_list.end()) + continue; - // Delete the files - for (set::const_reverse_iterator i = files.rbegin(); i != files.rend(); ++i) { - const string filename = root + *i; + const string filename = root + i->first.first; if (file_exists(filename) && remove(filename.c_str()) == -1) { if (errno == ENOTEMPTY) continue; @@ -241,53 +257,108 @@ } } -set pkgutil::db_find_conflicts(const string& name, const pkginfo_t& info) +fileconflicts_t pkgutil::db_find_conflicts(const string& name, const pkginfo_t& pkginfo) { - set files; - + fileconflicts_t fileconflicts; + filenames_t filenames; + filenames_t pkgfilenames; + filenames_t dbfilenames; + + /* TODO 1: write smth like map_intersection, which intersects + * map<->map by map<>::first, to not use {db,pkg}filenames + * temporary variables. */ + transform(pkginfo.files.begin(), pkginfo.files.end(), + inserter(pkgfilenames, pkgfilenames.end()), + first_in_pair()); + // 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())); + /* TODO 2: same as TODO 1 */ + transform(i->second.files.begin(), i->second.files.end(), + inserter(dbfilenames, dbfilenames.end()), + first_in_pair()); + set_intersection(pkgfilenames.begin(), pkgfilenames.end(), + dbfilenames.begin(), dbfilenames.end(), + inserter(filenames, filenames.end())); } } - dbg("Conflicts phase 1 (conflicts in database):", files); + // Exclude directories + for (filenames_t::const_iterator i = filenames.begin(); i != filenames.end(); ++i) { + if ((*i)[i->length() - 1] == '/') { + filenames.erase(*i); + } + } + transform(filenames.begin(), filenames.end(), + inserter(fileconflicts, fileconflicts.end()), + to_fileconflict(CONFLICT_DB)); + + dbg("Conflicts phase 1 (conflicts in database w/o dirs):", fileconflicts); + + string filename; // 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); + for (files_t::const_iterator i = pkginfo.files.begin(); i != pkginfo.files.end(); ++i) { + filename = root + i->first; + if (file_exists(filename) && filenames.find(i->first) == filenames.end()) { + file_t file; + fileconflict_t fileconflict; + file.first = i->first; + file.second.mode = i->second.mode; + file.second.uid = i->second.uid; + file.second.gid = i->second.gid; + fileconflict.first = file; + fileconflict.second = CONFLICT_FS; + fileconflicts.insert(fileconflicts.end(), fileconflict); + } } + dbg("Conflicts phase 2 (conflicts in filesystem added):", fileconflicts); - dbg("Conflicts phase 2 (conflicts in filesystem added):", files); - - // Exclude directories - set tmp = files; - for (set::const_iterator i = tmp.begin(); i != tmp.end(); ++i) { - if ((*i)[i->length() - 1] == '/') - files.erase(*i); + // Exclude directories which do not conflicts by mode, + // else mark it as mode-conflicting + for (fileconflicts_t::iterator i = fileconflicts.begin(), j = i; j != fileconflicts.end(); j = i) { + i++; + filename = root + (*j).first.first; + if (S_ISDIR((*j).first.second.mode)) { + if (permissions_equal(filename, j->first)) { + fileconflicts.erase(j); + } + else j->second = CONFLICT_MODE; + } } - dbg("Conflicts phase 3 (directories excluded):", files); + dbg("Conflicts phase 3 (not conflicting directories excluded):", fileconflicts); - // If this is an upgrade, remove files already owned by this package + // If this is an upgrade, exclude files already owned by this package + fileconflicts_t::iterator tmp; if (packages.find(name) != packages.end()) { - for (set::const_iterator i = packages[name].files.begin(); i != packages[name].files.end(); ++i) - files.erase(*i); + for (files_t::const_iterator i = packages[name].files.begin(); i != packages[name].files.end(); ++i) { + tmp = find_if( + fileconflicts.begin(), fileconflicts.end(), + compose1( + bind2nd(equal_to(), i->first), + compose1( + first_in_pair(), + first_in_pair() + ) + ) + ); + // If new file have changed permissions - it's conflict, + // thus do not exclude it + if (tmp != fileconflicts.end() && permissions_equal(root + i->first, tmp->first)) + fileconflicts.erase(tmp->first); + } - dbg("Conflicts phase 4 (files already owned by this package excluded):", files); + dbg("Conflicts phase 4 (files already owned by this package excluded):", fileconflicts); } - return files; + return fileconflicts; } -pair pkgutil::pkg_open(const string& filename) const +package_t pkgutil::pkg_open(const string& filename) const { - pair result; + package_t result; unsigned int i; TAR* t; @@ -306,8 +377,13 @@ if (tar_open(&t, const_cast(filename.c_str()), &gztype, O_RDONLY, 0, TAR_GNU) == -1) throw runtime_error_with_errno("could not open " + filename); + file_t file; for (i = 0; !th_read(t); ++i) { - result.second.files.insert(result.second.files.end(), th_get_pathname(t)); + file.first = th_get_pathname(t); + file.second.mode = th_get_mode(t); + file.second.uid = th_get_uid(t); + file.second.gid = th_get_gid(t); + result.second.files.insert(result.second.files.end(), file); if (TH_ISREG(t) && tar_skip_regfile(t)) throw runtime_error_with_errno("could not read " + filename); } @@ -324,7 +400,7 @@ return result; } -void pkgutil::pkg_install(const string& filename, const set& keep_list) const +void pkgutil::pkg_install(const string& filename, const filenames_t& keep_list) const { TAR* t; unsigned int i; @@ -694,10 +770,22 @@ if (lstat(file2.c_str(), &buf2) == -1) return false; + + return (buf1.st_mode == buf2.st_mode) && + (buf1.st_uid == buf2.st_uid) && + (buf1.st_gid == buf2.st_gid); +} + +bool permissions_equal(const string& file1, const file_t &file2) +{ + struct stat st; + + if (lstat(file1.c_str(), &st) == -1) + return false; - return(buf1.st_mode == buf2.st_mode) && - (buf1.st_uid == buf2.st_uid) && - (buf1.st_gid == buf2.st_gid); + return (st.st_mode == file2.second.mode) && + (st.st_uid == file2.second.uid) && + (st.st_gid == file2.second.gid); } void file_remove(const string& basedir, const string& filename) @@ -715,11 +803,40 @@ cerr << msg << endl; } -void dbg(const string &msg, const set &filenames) +void dbg(const string &msg, const files_t &files) { cerr << msg << endl; + transform(files.begin(), files.end(), + ostream_iterator(cerr, "\n"), + first_in_pair()); + cerr << endl; +} + +void dbg(const string &msg, const filenames_t &filenames) +{ + cerr << msg << endl; copy(filenames.begin(), filenames.end(), ostream_iterator(cerr, "\n")); cerr << endl; } + +void print_fileconflict(const fileconflict_t &f) +{ + cerr << mtos(f.first.second.mode) << " " + << f.first.first << " " + << f.first.second.uid << " " + << f.first.second.gid << " " + << f.first.second.mode << " " + << f.second << endl; +} + +void dbg(const string &msg, const fileconflicts_t &fileconflicts) +{ + cerr << msg << endl; + + for_each(fileconflicts.begin(), fileconflicts.end(), + print_fileconflict); + + cerr << endl; +} #endif Index: pkgutil.h =================================================================== --- pkgutil.h (revision 2) +++ pkgutil.h (revision 3) @@ -42,15 +42,74 @@ using namespace std; +template +struct first_in_pair : std::unary_function { + out_t operator()(const in_t &in) const + { + return in.first; + } +}; + +/* General files handling types */ +struct fileinfo_t { + mode_t mode; + uid_t uid; + gid_t gid; + // Later we can add md5sum_t for security checking + bool operator<(const fileinfo_t &fileinfo) const + { + return mode < fileinfo.mode; + } +}; +typedef pair file_t; +typedef map files_t; +typedef set filenames_t; + +/* Conflicting files handling types */ +enum conflict_t { + CONFLICT_NONE = 0, // gag + CONFLICT_DB = 1, // file conflict in database + CONFLICT_FS = 2, // file conflict in filesystem + CONFLICT_MODE = 3 // directory mode conflict +}; +typedef pair fileconflict_t; +typedef map fileconflicts_t; + +struct to_fileconflict { + fileconflict_t fileconflict; + to_fileconflict(conflict_t conflict) + { + fileconflict.second = conflict; + } + + fileconflict_t operator()(const string &filename) + { + fileconflict.first.first = filename; + return fileconflict; + } +}; + +struct extract_filename { + string operator()(const fileconflict_t &in) const + { + return in.first.first; + } + string operator()(const file_t &in) const + { + return in.first; + } +}; + +/* Package handling types */ +struct pkginfo_t { + string version; + files_t files; +}; +typedef map packages_t; +typedef pair package_t; + class pkgutil { public: - struct pkginfo_t { - string version; - set files; - }; - - typedef map packages_t; - explicit pkgutil(const string& name); virtual ~pkgutil() {} virtual void run(int argc, char** argv) = 0; @@ -64,13 +123,13 @@ void db_add_pkg(const string& name, const pkginfo_t& info); bool db_find_pkg(const string& name); void db_rm_pkg(const string& name); - void db_rm_pkg(const string& name, const set& keep_list); - void db_rm_files(set files, const set& keep_list); - set db_find_conflicts(const string& name, const pkginfo_t& info); + void db_rm_pkg(const string& name, const filenames_t& keep_list); + void db_rm_fileconflicts(fileconflicts_t files, const filenames_t& keep_list); + fileconflicts_t db_find_conflicts(const string& name, const pkginfo_t& info); // Tar.gz - pair pkg_open(const string& filename) const; - void pkg_install(const string& filename, const set& keep_list) const; + package_t pkg_open(const string& filename) const; + void pkg_install(const string& filename, const filenames_t& keep_list) const; void pkg_footprint(string& filename) const; void ldconfig() const; @@ -103,6 +162,7 @@ bool file_empty(const string& filename); bool file_equal(const string& file1, const string& file2); bool permissions_equal(const string& file1, const string& file2); +bool permissions_equal(const string& file1, const file_t& file2); void file_remove(const string& basedir, const string& filename); // Debug helpers @@ -118,7 +178,9 @@ } void dbg(const string &msg); -void dbg(const string &msg, const set &filenames); +void dbg(const string &msg, const files_t &files); +void dbg(const string &msg, const filenames_t &filenames); +void dbg(const string &msg, const fileconflicts_t &fileconflicts); #else #define dbg(...) ; #endif /* NDEBUG */ Index: pkgadd.cc =================================================================== --- pkgadd.cc (revision 2) +++ pkgadd.cc (revision 3) @@ -23,9 +23,59 @@ #include #include #include +#include +#include #include #include +template +struct swap_pair : unary_function +{ + out_t operator()(const in_t &in) const { + return out_t(in.second, in.first); + } +}; + +struct print_sorted_fileconflicts : unary_function +{ + bool operator()(const fileconflict_t &in) const; +}; + +bool print_sorted_fileconflicts::operator()(const fileconflict_t &in) const +{ + static conflict_t last_conflict; + bool is_new = false; + + if (last_conflict != in.second) { + last_conflict && cout << endl; + cout << "Following files conflicts "; + last_conflict = in.second; + is_new = true; + } + + switch (last_conflict) { + case CONFLICT_DB: + is_new && cout << "with database records:" << endl; + cout << in.first.first; + break; + case CONFLICT_FS: + is_new && cout << "with filesystem files:" << endl; + cout << in.first.first; + break; + case CONFLICT_MODE: + is_new && cout << "by mode or ownership:" << endl; + cout << mtos(in.first.second.mode) << " " + << "uid: " << in.first.second.uid << " " + << "gid: " << in.first.second.gid << " " + << in.first.first << " "; + break; + default: break; + } + cout << endl; + return true; +} + + void pkgadd::run(int argc, char** argv) { // @@ -77,25 +127,41 @@ 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 conflicting_files = db_find_conflicts(package.first, package.second); - - if (!conflicting_files.empty()) { + + fileconflicts_t fileconflicts = db_find_conflicts(package.first, package.second); + + if (!fileconflicts.empty()) { if (o_force) { - set 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 + filenames_t keep_list; + // Don't remove files matching the rules in configuration + if (o_upgrade) { + keep_list = make_keep_list(fileconflicts, + config_rules); + } + // Remove unwanted conflicts + db_rm_fileconflicts(fileconflicts, keep_list); } else { - copy(conflicting_files.begin(), conflicting_files.end(), ostream_iterator(cerr, "\n")); - throw runtime_error("listed file(s) already installed (use -f to ignore and overwrite)"); + typedef pair swapped_t; + deque sorted; + transform(fileconflicts.begin(), fileconflicts.end(), + back_inserter(sorted), + swap_pair()); + sort(sorted.begin(), sorted.end()); + for_each(sorted.begin(), sorted.end(), + compose1(print_sorted_fileconflicts(), + swap_pair() + )); + cout << endl; + throw runtime_error("use -f to ignore and overwrite"); } } - - set keep_list; + filenames_t keep_list; + if (o_upgrade) { - keep_list = make_keep_list(package.second.files, config_rules); + keep_list = make_keep_list(package.second.files, config_rules); db_rm_pkg(package.first, keep_list); } @@ -172,20 +238,21 @@ return rules; } -set pkgadd::make_keep_list(const set& files, const vector& rules) const +template +filenames_t pkgadd::make_keep_list(const in_t& files, const vector& rules) const { - set keep_list; + filenames_t keep_list; - for (set::const_iterator i = files.begin(); i != files.end(); i++) { + for (typename in_t::const_iterator i = files.begin(); i != files.end(); i++) { for (vector::const_reverse_iterator j = rules.rbegin(); j != rules.rend(); j++) { if ((*j).event == rule_t::UPGRADE) { regex_t preg; if (regcomp(&preg, (*j).pattern.c_str(), REG_EXTENDED | REG_NOSUB)) throw runtime_error("error compiling regular expression '" + (*j).pattern + "', aborting"); - if (!regexec(&preg, (*i).c_str(), 0, 0, 0)) { + if (!regexec(&preg, (extract_filename()((conv_t)(*i))).c_str(), 0, 0, 0)) { if (!(*j).action) - keep_list.insert(keep_list.end(), *i); + keep_list.insert(keep_list.end(), extract_filename()((conv_t)(*i))); regfree(&preg); break; }