/* -*-Mode: C++;-*- * PRCS - The Project Revision Control System * Copyright (C) 1997 Josh MacDonald * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id: repository.cc 1.22.1.11.1.28.1.16.1.35.2.4 Wed, 06 Feb 2002 20:57:16 -0800 jmacd $ */ extern "C" { #include #include #include #include } #if __GNUG__ < 3 #define pubsync sync #endif #include "prcs.h" #include "hash.h" #include "prcsdir.h" #include "repository.h" #include "vc.h" #include "checkout.h" #include "misc.h" #include "lock.h" #include "syscmd.h" #include "system.h" #include "convert.h" #include "memseg.h" #include "rebuild.h" #include "projdesc.h" static const char radix[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; static const char prcs_tagname[] = "prcs_tag"; static const char prcs_compression_tag[] = "prcs_compress"; const char prcs_lock_dir[] = ".locks"; static int rep_root_path_len; static Dstring *rep_root_path = NULL; static const char prcs_created_by[] = "created by PRCS version "; static const char prcs_usable_by[] = "usable down to PRCS version "; extern PrVoidError save_package(); static PrVoidError Rep_init_command(const char* path); static const char* Rep_name_in_repository(const char *s); static bool version_less_than(const int* a, const int* b); static PrVoidError repository_cleanup_handler(void* data, bool); static PrVoidError repository_alarm_handler(void* data, bool); /* The lowest version which may use a repository last touched by this * version of PRCS */ static int const prcs_usable_number[3] = { 1, 3, 0 }; /* The lowest version which this version of PRCS may use without being * converted. */ static int const prcs_may_use_number[3] = { 1, 3, 0 }; /* Rep_guess_repository_path -- * * returns the calculated repository path from environment * variables or command line options, without actually * initilaizing anything. */ NprConstCharPtrError Rep_guess_repository_path() { if(rep_root_path) { return Rep_name_in_repository(""); } const char* rp; rep_root_path = new Dstring; if(cmd_repository_path) rp = cmd_repository_path; else rp = get_environ_var ("PRCS_REPOSITORY"); if ( rp == NULL ) { rp = get_environ_var ("HOME"); if (rp == NULL) return NonFatalError; rep_root_path->assign(rp); rep_root_path->append("/PRCS"); } else { rep_root_path->assign(rp); } while(rep_root_path->index(rep_root_path->length() - 1) == '/') rep_root_path->truncate(rep_root_path->length() - 1); rep_root_path_len = rep_root_path->length(); return Rep_name_in_repository(""); } /* Rep_init_repository -- * * Must be called before any of the below commands are used. * This places a read or write lock in the repository, reads the * repository tag, insures that this version of PRCS is * compatible with the repository format being opened. It will * prompt the user to upgrade repository versions if the version * is outdated and return NoError only when the repository is * locked and ready to use by the current version of PRCS. */ PrVoidError Rep_init_repository() { static bool do_once = true; if(do_once) { do_once = false; } else { return NoError; } If_fail(Rep_guess_repository_path()) pthrow prcserror << "Please set your PRCS_REPOSITORY environment variable" << dotendl; rep_root_path->truncate(rep_root_path_len); Return_if_fail(Rep_init_command(rep_root_path->cast())); return NoError; } static PrVoidError Rep_init_command(const char* path) { if (!fs_file_exists(path) ) { if (option_report_actions) pthrow prcsinfo << "Create repository " << squote(path) << dotendl; If_fail( Err_mkdir(path, 0777 | S_ISGID | 01000)) pthrow prcserror << "Cannot create repository " << squote(path) << perror; else prcswarning << "Created repository " << squote(path) << ", you may wish to run " << squote("prcs admin access") << " to set its permissions" << dotendl; } const char* rep_lock_name = Rep_name_in_repository(prcs_lock_dir); if(!fs_is_directory(rep_lock_name)) { if (option_report_actions) pthrow prcserror << "Create repository lock directory " << squote(rep_lock_name) << perror; Umask mask (0); If_fail( Err_mkdir(rep_lock_name, 0777 | S_ISGID) ) pthrow prcserror << "Cannot create repository lock directory " << squote(rep_lock_name) << perror; } if(!fs_file_writeable(rep_lock_name)) pthrow prcserror << "You must have write permission on the lock directory " << squote(rep_lock_name) << perror; return NoError; } static const char* Rep_name_in_repository(const char *s) { rep_root_path->truncate(rep_root_path_len); rep_root_path->append('/'); rep_root_path->append(s); return rep_root_path->cast(); } PrVoidError RepEntry::Rep_lock(bool write_lock) { _repository_lock = NULL; #ifdef PRCS_DEVEL if (option_debug) return NoError; #endif Dstring lock_name; lock_name.assign(Rep_name_in_repository(prcs_lock_dir)); lock_name.append('/'); lock_name.append(_entry_name); _repository_lock = new AdvisoryLock(lock_name); Return_if_fail (obtain_lock (_repository_lock, write_lock)); return NoError; } PrVoidError obtain_lock(AdvisoryLock* lock, bool write_lock) { int lockval; Umask mask (0); if(write_lock) lockval = lock->write_lock_nowait(); else lockval = lock->read_lock_nowait(); while(true) { if(lockval == LOCK_FAIL) pthrow prcserror << "Failed obtaining a repository lock" << dotendl; if(lockval == LOCK_SUCCESS) return NoError; char buf[256]; int lock_count = 0; FILE* other_locks; other_locks = lock->who_is_locking(); if(!other_locks) pthrow prcserror << "Could not obtain a list of lockers, unknown " "locking error" << dotendl; prcsinfo << "Could not obtain a lock because of the following locks:" << prcsendl; while(fgets(buf, 256, other_locks) == buf) { if(!isspace(buf[0])) { cout << buf; lock_count += 1; } } fclose(other_locks); if(lock_count == 0) pthrow prcserror << "Inconsistant lock information, cannot continue" << dotendl; if(lockval == LOCK_TRY_STEAL) { prcsquery << "Lock age exceeds timeout age. " << force("Stealing") << report("Steal") << optfail('n') << defopt('y', "Steal the lock and continue") << query("Steal the lock"); Return_if_fail(prcsquery.result()); if(!lock->steal_lock()) return PrVoidError(FatalError); } else { prcsquery << force("Waiting for the lock") << report("Waiting for the lock") << optfail('n') << defopt('y', "Wait for the lock") << query("Wait for the lock"); Return_if_fail(prcsquery.result()); } if(write_lock) lockval = lock->write_lock_wait(); else lockval = lock->read_lock_wait(); } } void RepEntry::Rep_unlock_repository() { if (_repository_lock) _repository_lock->unlock(); } static bool version_less_than(const int* a, const int* b) { for(int i = 0; i < 3; i += 1) { if(a[i] < b[i]) return true; if(b[i] < a[i]) return false; } return false; } PrVoidError RepEntry::Rep_make_default_tag() { return Rep_make_tag(prcs_version_number, prcs_usable_number); } PrVoidError RepEntry::Rep_make_tag(const int created[3], const int usable[3]) { const char* file_name = Rep_name_in_entry(prcs_tagname); unlink(file_name); ofstream tag_file(file_name, ios::out); tag_file << prcs_created_by << created[0] << ' ' << created[1] << ' ' << created[2] << "\n" << prcs_usable_by << usable[0] << ' ' << usable[1] << ' ' << usable[2] << "\n"; tag_file.close(); if(tag_file.bad()) { pthrow prcserror << "Failed writing repository tagfile " << squote(file_name) << perror; } If_fail(Err_chmod(file_name, 0444)) { pthrow prcserror << "Chmod failed on new repository tagfile " << squote(file_name) << perror; } return NoError; } PrVoidError RepEntry::Rep_verify_tag(UpgradeRepository* upgrades) { const char* file_name = Rep_name_in_entry(prcs_tagname); char buf[256]; int created[3]; int usable[3]; if(!fs_file_exists(file_name)) { pthrow prcserror << "Repository is not tagged, the file " << squote(file_name) << " does not exist" << dotendl; } if(!fs_file_readable(file_name)) { pthrow prcserror << "Can't read repository tag " << squote(file_name) << dotendl; } ifstream tag_file(file_name); if(tag_file.bad()) { pthrow prcserror << "Can't open repository tag file " << squote(file_name) << dotendl; } tag_file.get(buf, sizeof(prcs_created_by)); tag_file >> created[0]; tag_file >> created[1]; tag_file >> created[2]; if(strncmp(buf, prcs_created_by, strlen(prcs_created_by)) != 0 || tag_file.bad()) { pthrow prcserror << "Repository tag file " << squote(file_name) << " is invalid" << dotendl; } tag_file.get(); tag_file.get(buf, sizeof(prcs_usable_by)); tag_file >> usable[0]; tag_file >> usable[1]; tag_file >> usable[2]; if(strncmp(buf, prcs_usable_by, strlen(prcs_usable_by)) != 0 || tag_file.bad()) { pthrow prcserror << "Repository tag file " << squote(file_name) << " is invalid" << dotendl; } bool confirm_upgrade = false; while(upgrades && upgrades->upgradeFunc) { if(version_less_than(created, upgrades->version)) { if(!_writeable) { pthrow prcserror << "This version of PRCS needs to modify your repository before " "continuing. You do not currently have a write lock, required to do so. " "You can run " << squote("prcs admin rebuild") << " to upgrade your repository" << dotendl; } if(!confirm_upgrade) { prcsquery << "This version of PRCS needs to modify your repository. " << force("Upgrading") << report("Upgrade") << defopt('s', "Upgrade the repository, after saving a package in case of trouble") << optfail('n') << option('y', "Upgrade the repository, don't save a package") << query("Upgrade"); char c; Return_if_fail(c << prcsquery.result()); confirm_upgrade = true; if(c == 's' && !option_report_actions) { Return_if_fail(save_package()); } } prcsinfo << "Upgrading repository to version format " << upgrades->update_version[0] << '.' << upgrades->update_version[1] << '.' << upgrades->update_version[2] << dotendl; UpgradeRepository *tmp_upgrades = upgrades; for (; tmp_upgrades->upgradeFunc; tmp_upgrades += 1) { prcsinfo << "[" << tmp_upgrades->version[0] << "." << tmp_upgrades->version[1] << "." << tmp_upgrades->version[2] << "] " << tmp_upgrades->description << dotendl; if (upgrades->update_version[0] == tmp_upgrades->version[0] && upgrades->update_version[1] == tmp_upgrades->version[1] && upgrades->update_version[2] == tmp_upgrades->version[2]) break; } created[0] = usable[0] = upgrades->update_version[0]; created[1] = usable[1] = upgrades->update_version[1]; created[2] = usable[2] = upgrades->update_version[2]; bool should_exit; Return_if_fail(should_exit << upgrades->upgradeFunc(this)); Return_if_fail(Rep_make_tag(upgrades->update_version, upgrades->update_version)); prcsinfo << "Finished upgrade to version format " << upgrades->update_version[0] << '.' << upgrades->update_version[1] << '.' << upgrades->update_version[2] << dotendl; if(should_exit) { return PrVoidError(UserAbort); } } upgrades += 1; } /* If the tag says an older version which this version is unable * to use, and the upgrade did not help, tell the user the * repository is too old to use. */ if(version_less_than(created, prcs_may_use_number)) { pthrow prcserror << "This version of PRCS outdates the version which created your repository, " << created[0] << '.' << created[1] << '.' << created[2] << ". Please convert your repository" << dotendl; } /* If the tag says a newer version which this version is unable to * use, print a message saying to upgrade the binary to the newer * PRCS. */ if(version_less_than(prcs_version_number, usable)) { pthrow prcserror << "This version of PRCS is outdated by version " << created[0] << '.' << created[1] << '.' << created[2] << ". You must upgrade before using this repository" << dotendl; } /* If the tag has a newer version number than this version which * is still usable, notify the user that a newer version has * touched the repository and continue. */ if(version_less_than(prcs_version_number, created)) prcswarning << "This version of PRCS is outdated by version " << created[0] << '.' << created[1] << '.' << created[2] << ". It is still compatible, but you should consider upgrading" << dotendl; /* If we have a write lock, modify the tag with the up to date * versions */ if(version_less_than(created, prcs_version_number) && _writeable) { Return_if_fail(Rep_make_default_tag()); } return NoError; } PrVoidError RepEntry::Rep_create_repository_entry() { const char* file_name = Rep_entry_path(); if ( option_report_actions ) { pthrow prcserror << "Create repository entry " << squote(_entry_name); } else { If_fail( Err_mkdir(file_name, 0777 | S_ISGID) ) { pthrow prcserror << "Cannot create repository entry " << squote(_entry_name) << perror; } Return_if_fail(Rep_make_default_tag()); Return_if_fail(VC_register(_project_file)); If_fail(VC_register(NULL)) { fs_nuke_file(file_name); pthrow prcserror << "Failed registering project version file" << squote(_project_file) << dotendl; } Return_if_fail(create_empty_data(_entry_name, Rep_name_in_entry("prcs_data"))); /* If the user doesn't have permission on the group under * whose ownership the new directory was just created, this * chmod fails, so ignore it. */ If_fail (Err_creat (Rep_name_in_entry("prcs_log"), 0666 ^ get_umask())) prcsinfo << "Create failed on repository log" << perror; prcsinfo << "Created repository entry " << squote(_entry_name) << dotendl; If_fail(Rep_chmod(0777 ^ get_umask())) prcsinfo << "Chmod failed on new repository entry. This has the consequence that " "permissions may be wrong, either allowing too much, too little, or incorrect access " "to the repository. After the command finishes, please run " << squote("prcs admin access") << " to attempt to set its group and access permissions correctly" << dotendl; } return NoError; } PrBoolError Rep_repository_entry_exists(const char* entry) { Return_if_fail(Rep_init_repository()); return fs_is_directory(Rep_name_in_repository(entry)); } /* The repsository entry class */ /* Rep_init_repository_entry -- * * assures that the named entry exists, is executable, and if * writeable is true, also writeable. Also checks the repository * tag created by Rep_create_repository_entry to see that this * entry has not been used by some future version of PRCS making * it unusable by the version running */ PrRepEntryPtrError Rep_init_repository_entry(const char* entry, bool writeable, bool create, bool require_db) { RepEntry* ret = new RepEntry(); Return_if_fail(ret->init(entry, writeable, create, require_db)); return ret; } PrVoidError RepEntry::init(const char* entry, bool writeable0, bool create, bool require_db) { Return_if_fail(Rep_init_repository()); _project_file.sprintf("%s/%s/%s.prj,v", Rep_name_in_repository(""), entry, entry); _entry_name.assign(entry); _writeable = writeable0; _base.assign(Rep_name_in_repository(entry)); _baselen = _base.length(); _compressed = false; _rebuild_file = NULL; _rep_comp_cache = new CharPtrArray; bool exists; Return_if_fail(exists << Rep_repository_entry_exists(entry)); if(!exists) { if(create) { Return_if_fail(Rep_create_repository_entry()); } else { pthrow prcserror << "Repository entry " << squote(entry) << " does not exist, you can create an entry with " << squote("prcs checkin") << dotendl; } } If_fail(Err_stat(Rep_entry_path(), &_rep_stat_buf)) pthrow prcserror << "Stat failed on repository entry " << squote(Rep_entry_path()) << perror; _rep_mask = (_rep_stat_buf.st_mode & 0777) ^ 0777; Return_if_fail(Rep_lock(_writeable)); install_cleanup_handler(repository_cleanup_handler, this); install_alarm_handler (repository_alarm_handler, this); if(_writeable && !(fs_file_rwx(Rep_entry_path()))) { pthrow prcserror << "You need read, write, and execute permission on the repository entry " << squote(Rep_entry_path()) << dotendl; } else if(!fs_file_executable(Rep_entry_path())) { pthrow prcserror << "You need execute permission on the repository entry " << squote(Rep_entry_path()) << dotendl; } if (require_db) { Return_if_fail(Rep_verify_tag(entry_upgrades)); if(!fs_file_exists(Rep_name_in_entry(prcs_data_file_name))) { pthrow prcserror << "Repository entry " << squote(entry) << " is missing its data file, please run " << squote("prcs admin rebuild") << " to generate this file" << dotendl; } Return_if_fail(_rebuild_file << read_repository_data(Rep_name_in_entry (prcs_data_file_name), _writeable)); _project_data_array = _rebuild_file->get_project_summary(); _rcs_file_table = _rebuild_file->get_rcs_file_summary(); /* Keith Owens 1.3.0 bug: a consistency check here for the * case where a checkin aborts after finishing the RCS checkin * but before updating the repository entry. */ int rev_count; If_fail (rev_count << VC_get_version_count (Rep_name_of_version_file ())) { pthrow prcserror << "Cannot determine the number of versions for repository entry " << squote(entry) << dotendl; } if (_project_data_array->length () != rev_count) { pthrow prcserror << "Detected an inconsistent data file in repository entry " << squote(entry) << " (possibly due to an aborted checkin), please run " << squote("prcs admin rebuild") << " to generate this file" << dotendl; } } _rfl = new RepFreeFilename(Rep_entry_path(), _rep_mask); _compressed = fs_file_exists(Rep_name_in_entry(prcs_compression_tag)); if (_writeable) { _log_stream = new filebuf; if (!_log_stream->open(Rep_name_in_entry("prcs_log"), ios::out|ios::app)) pthrow prcserror << "Failed opening repository log file" << perror; _log_pstream = new PrettyStreambuf(_log_stream, NULL); _log_pstream->set_fill_width (1<<20); _log_pstream->set_fill_prefix ("log: "); _log = new PrettyOstream (_log_pstream, NoError); } return NoError; } static PrVoidError uncompress_file(const char* name, const void* data) { static bool once = true; const char* basename = strip_leading_path(name); int len = strlen(basename); ArgList* args; if (len > 4 && strcmp(".gz", basename + len - 3) == 0) { if (option_report_actions) pthrow prcserror << "You may not perform this command on a compressed repository with -n" << dotendl; if(once) { prcsinfo << "Uncompressing repository entry " << (const char*)data << dotendl; once = false; } Return_if_fail(args << gzip_command.new_arg_list()); args->append("-d"); args->append("-f"); args->append(name); Return_if_fail_if_ne(gzip_command.open_stdout(), 0) { pthrow prcserror << "Gzip returned non-zero status on file " << squote (name) << ", aborting" << dotendl; } } return NoError; } static PrVoidError compress_file(const char* name, const void* data) { static bool once = true; const char* basename = strip_leading_path(name); int len = strlen(basename); Dstring prjname((const char*)data); ArgList* args; prjname.append(".prj,v"); if(len > 3 && strcmp(",v", basename + len - 2) == 0 && strcmp(basename, prjname) != 0) { if (option_report_actions) pthrow prcserror << "You may not perform this command on a compressed repository with -n" << dotendl; if(once) { prcsinfo << "Compressing repository entry " << (const char*)data << dotendl; once = false; } Return_if_fail(args << gzip_command.new_arg_list()); args->append("-f"); args->append(name); Return_if_fail_if_ne(gzip_command.open_stdout(), 0) { pthrow prcserror << "Gzip returned non-zero status on file " << squote (name) << ", aborting" << dotendl; } } return NoError; } PrVoidError RepEntry::Rep_uncompress_all_files() { ASSERT(_writeable, "you need a write lock"); Return_if_fail (directory_recurse(Rep_entry_path(), _entry_name.cast(), uncompress_file)); return NoError; } PrVoidError RepEntry::Rep_compress_all_files() { if(!Rep_needs_compress()) return NoError; ASSERT(_writeable, "you need a write lock"); Return_if_fail (directory_recurse(Rep_entry_path(), _entry_name.cast(), compress_file)); return NoError; } /* * Rep_prepare_file -- * * this procedure has to copy the file to a temp location in order * to avoid writing the repository if the file is compressed and * the repository is only read-only. */ PrConstCharPtrError RepEntry::Rep_prepare_file(const char* file) { static Dstring ret; Dstring fullname, zipname; ArgList *args; ASSERT(strlen(file) > 0, "no empty files"); Rep_name_in_entry(file, &fullname); Rep_name_in_entry(file, &zipname); fullname.append(",v"); zipname.append(",v.gz"); if(fs_file_exists(zipname)) { Return_if_fail(args << gzip_command.new_arg_list()); if(_writeable) { args->append("-f"); args->append("-d"); args->append(zipname); Return_if_fail_if_ne(gzip_command.open_stdout(), 0) pthrow prcserror << "Gzip returned non-zero status, aborting" << dotendl; } else { const char* temp = make_temp_file(",v"); args->append("-f"); args->append("-d"); args->append("-c"); args->append(zipname); Return_if_fail(gzip_command.open(true, false)); Return_if_fail(fs_write_filename(gzip_command.standard_out(), temp)); Return_if_fail_if_ne(gzip_command.close(), 0) pthrow prcserror << "Gzip returned non-zero status, aborting" << dotendl; _rep_comp_cache->append (temp); /* this memory is ours to * free, except that it's * part of a Dstring * segment and if you ever * clamp down on the stuff * allocated by * make_temp_file this'll * break.*/ ret.assign(temp); return ret.cast(); } } if(!fs_file_exists(fullname)) pthrow prcserror << "Repository file " << squote(fullname) << " not found--try running " << squote("prcs admin rebuild") << " for a complete diagnosis" << dotendl; ret.assign(fullname); return ret.cast(); } void RepEntry::Rep_clear_compressed_cache (void) { foreach (ch_ptr, _rep_comp_cache, CharPtrArray::ArrayIterator) { unlink (*ch_ptr); delete (*ch_ptr); } _rep_comp_cache->truncate(0); } bool RepEntry::Rep_file_exists(const char* name) { Dstring d; Rep_name_in_entry(name, &d); if(fs_file_exists(d)) return true; d.append(".gz"); if(fs_file_exists(d)) return true; return false; } const char* RepEntry::Rep_name_in_entry(const char* name) { _base.truncate(_baselen); _base.append('/'); _base.append(name); return _base; } void RepEntry::Rep_name_in_entry(const char* name, Dstring * buf) { _base.truncate(_baselen); buf->assign(_base); buf->append('/'); buf->append(name); } static PrVoidError copy_change_ownership(const char* name) { Dstring temp (name); make_temp_file_same_dir (&temp); Return_if_fail(fs_copy_filename(name, temp)); If_fail(Err_rename(temp, name)) pthrow prcserror << "Rename " << squote(temp) << " to " << squote(name) << " failed while changing group -- correct this manually!" << perror; return NoError; } static PrVoidError set_ugrid(const char* name, uid_t uid, gid_t gid, struct stat *buf) { if (buf->st_uid != uid && get_user_id() != 0) Return_if_fail(copy_change_ownership(name)); If_fail (Err_chown(name, uid, gid)) pthrow prcserror << "Chown failed on " << squote(name) << perror; return NoError; } struct RepChownCallbackData { gid_t gid; uid_t uid; }; PrVoidError Rep_chown_dir_callback (const char* name, const void* v_data) { struct stat buf; RepChownCallbackData* data = (RepChownCallbackData*) v_data; If_fail (Err_stat(name, &buf)) pthrow prcserror << "Stat failed on " << squote(name) << perror; if (buf.st_uid != data->uid || buf.st_gid != data->gid) { /* No copying here, only the owner or root can do it. */ If_fail (Err_chown(name, data->uid, data->gid)) pthrow prcserror << "Chown failed on " << squote(name) << perror; } return NoError; } PrVoidError Rep_chown_callback (const char* name, const void* v_data) { struct stat buf; RepChownCallbackData* data = (RepChownCallbackData*) v_data; If_fail (Err_stat(name, &buf)) pthrow prcserror << "Stat failed on " << squote(name) << perror; if (buf.st_uid != data->uid || buf.st_gid != data->gid) Return_if_fail(set_ugrid(name, data->uid, data->gid, &buf)); return NoError; } PrVoidError RepEntry::Rep_chown_file (const char* desc) { Dstring full_name(Rep_name_in_entry (desc)); full_name.append (",v"); If_fail(Err_chmod (full_name, (0777 ^ _rep_mask) & 0444)) pthrow prcserror << "Chown failed on " << full_name << perror; return NoError; } PrVoidError RepEntry::Rep_chown (const char* user, const char* group) { struct group* grp; struct passwd* pwd; RepChownCallbackData data; grp = getgrnam(group); if (!grp) pthrow prcserror << "Cannot lookup group " << squote(group) << perror; pwd = getpwnam(user); if (!pwd) pthrow prcserror << "Cannot lookup user " << squote(user) << perror; data.gid = grp->gr_gid; data.uid = pwd->pw_uid; const char* dir_name = Rep_name_in_repository(_entry_name); If_fail (Err_chown(dir_name, data.uid, data.gid)) pthrow prcserror << "Chown failed on " << squote(dir_name) << perror; Return_if_fail (directory_recurse_dirs (dir_name, &data, Rep_chown_dir_callback)); Return_if_fail (directory_recurse (dir_name, &data, Rep_chown_callback)); return NoError; } #define DO_CHMOD_REP(f, m2) do { \ const char* file = (f); \ const mode_t m = (m2); \ If_fail(Err_chmod(file, m)) \ pthrow prcserror << "Chmod failed on repository entry " \ << squote(file) << perror; \ } while (false) PrVoidError Rep_chmod_dir_callback (const char* name, const void* data) { DO_CHMOD_REP (name, (* (mode_t*) data) | S_ISGID); return NoError; } PrVoidError Rep_chmod_callback (const char* name, const void* data) { if (strncmp (strip_leading_path (name), "prcs_", 5) != 0) DO_CHMOD_REP (name, * (mode_t*) data & 0444); return NoError; } PrVoidError RepEntry::Rep_chmod (mode_t mode) { DO_CHMOD_REP (Rep_name_in_entry(prcs_tagname), 0444 & mode); if (_compressed) DO_CHMOD_REP (Rep_name_in_entry(prcs_compression_tag), 0444 & mode); DO_CHMOD_REP (Rep_name_in_entry("prcs_log"), 0666 & mode); DO_CHMOD_REP (Rep_name_in_entry(prcs_data_file_name), 0666 & mode); const char* dir_name = Rep_name_in_repository(_entry_name); Return_if_fail (directory_recurse_dirs (dir_name, &mode, Rep_chmod_dir_callback)); Return_if_fail (directory_recurse (dir_name, &mode, Rep_chmod_callback)); return NoError; } #undef DO_CHMOD_REP void fix_file_name (const char* filename, Dstring* safe_file_name) { safe_file_name->assign(filename); if(safe_file_name->length() > 10) safe_file_name->truncate(10); else while(safe_file_name->length() < 2) safe_file_name->append('x'); if(safe_file_name->length() > 1 && strcmp(filename + safe_file_name->length() - 2, ",v") == 0) safe_file_name->append('x'); if(safe_file_name->length() > 2 && strcmp(filename + safe_file_name->length() - 3, ".gz") == 0) safe_file_name->append('x'); } PrVoidError RepFreeFilename::find_next (const char* base, Dstring* fill) { Dstring safe_file_name; fix_file_name (base, &safe_file_name); here: for (int i = 0; i < MAX_REPOSITORY_COUNT; i += 1) { if (_available[i]) { fill->sprintf ("%s%d_%s,v", _offset, i, safe_file_name.cast()); _available[i] = false; return NoError; } } _directory += 1; int val = _directory; int degree_max = 1; int index = 2; while (MAX_REPOSITORY_COUNT * degree_max <= _directory) { degree_max *= MAX_REPOSITORY_COUNT; index += 2; } _offset[index] = 0; _offset[index-1] = '/'; index -= 2; while (degree_max >= 1) { int c = val / degree_max; _offset[index] = radix[c]; if (index > 0) _offset[index-1] = '/'; index -= 2; val -= c * degree_max; degree_max /= 52; } Return_if_fail(real_open_dir(_offset)); goto here; return NoError; } PrVoidError RepFreeFilename::real_open_dir (const char* dirname) { Dstring fullname(_root); fullname.append('/'); fullname.append(dirname); fullname.truncate(fullname.length() - 1); if (!fs_is_directory(fullname)) { Dstring fullname_x (_root); const char *p = dirname; do { fullname_x.append ('/'); fullname_x.append (*p); p += 2; if (!fs_is_directory(fullname_x)) { Umask mask (_umask); If_fail(Err_mkdir(fullname_x, 0777 | S_ISGID)) pthrow prcserror << "Cannot create repository subdirectory " << squote(fullname_x) << perror; } } while (*p); } Dir dir (fullname); for(int i = 0; i < MAX_REPOSITORY_COUNT; i += 1) _available[i] = true; foreach(ent_ptr, dir, Dir::DirIterator) { const char* ent = *ent_ptr; if (isdigit(ent[0])) { char* ent_num_end = NULL; long num = strtol(ent, &ent_num_end, 10); if (ent_num_end[0] == '_' && num >= 0 && num < MAX_REPOSITORY_COUNT) _available[num] = false; } } if (! dir.OK() ) pthrow prcserror << "Error reading repository directory " << squote(fullname) << perror; return NoError; } PrVoidError RepEntry::Rep_new_filename(const char* filename, Dstring* buf) { Dstring gzname; do { Return_if_fail(_rfl->find_next(filename, buf)); gzname.assign(*buf); gzname.append(".gz"); } while (fs_file_exists(Rep_name_in_entry(buf->cast())) || fs_file_exists(Rep_name_in_entry(gzname.cast())) || _rcs_file_table->isdefined(buf->cast())); buf->truncate (buf->length() - 2); /* Remove ,v */ return NoError; } /* Lookup project data. */ ProjectVersionData* RepEntry::lookup_version(const char* major, int minorint) const { for(int i = version_count() - 1; i >= 0; i -= 1) { if (minorint == _project_data_array->index(i)->prcs_minor_int() && strcmp(_project_data_array->index(i)->prcs_major(), major) == 0) { return _project_data_array->index(i); } } return NULL; } ProjectVersionData* RepEntry::lookup_version(const char* major, const char* minor) const { return lookup_version (major, atoi(minor)); } ProjectVersionData* RepEntry::lookup_version(ProjectDescriptor *project) const { return lookup_version (project->project_version_major()->cast(), project->project_version_minor()->cast()); } int RepEntry::highest_major_version() const /* -1 if none */ { int max = -1; for(int i = 0; i < version_count(); i += 1) { if (VC_check_token_match(_project_data_array->index(i)->prcs_major(), "number")) { int val = atoi(_project_data_array->index(i)->prcs_major()); if(val > max) max = val; } } return max; } ProjectVersionData* RepEntry::highest_minor_version_data(const char* major, bool may_be_deleted) const { int max = 0; ProjectVersionData* data = NULL; for(int i = 0; i < version_count(); i += 1) { ProjectVersionData* pd = _project_data_array->index(i); if (strcmp(pd->prcs_major(), major) == 0) { if (! may_be_deleted && pd->deleted ()) { continue; } int val = pd->prcs_minor_int(); if(val > max) { data = pd; max = val; } } } return data; } int RepEntry::highest_minor_version(const char* major, bool may_be_deleted) const /* 0 if none */ { ProjectVersionData* data = highest_minor_version_data(major, may_be_deleted); if(!data) return 0; return data->prcs_minor_int(); } bool RepEntry::major_version_exists(const char* major) const { for(int i = 0; i < version_count(); i += 1) { if (strcmp(_project_data_array->index(i)->prcs_major(), major) == 0) { return true; } } return false; } void RepEntry::common_version_dfs_1(ProjectVersionData* node) const { DEBUG ("node: " << node->version_index()); if (node->flag1(true)) return; for (int i = 0; i < node->parent_count(); i += 1) common_version_dfs_1 (project_summary()->index(node->parent_index(i))); } void RepEntry::common_version_dfs_2(ProjectVersionData* node, ProjectVersionDataPtrArray* res) const { DEBUG ("node: " << node->version_index()); if (node->flag2(true)) return; if (node->flag1()) { res->append (node); return; } for (int i = 0; i < node->parent_count(); i += 1) common_version_dfs_2 (project_summary()->index(node->parent_index(i)), res); } void RepEntry::common_version_dfs_3(ProjectVersionData* node) const { DEBUG ("node: " << node->version_index()); /* Like CVD_1 but don't set this node, only it's parents. */ for (int i = 0; i < node->parent_count(); i += 1) common_version_dfs_1 (project_summary()->index(node->parent_index(i))); } void RepEntry::clear_flags() const { foreach (pvd_ptr, project_summary(), ProjectVersionDataPtrArray::ArrayIterator) (*pvd_ptr)->clear_flags(); } ProjectVersionDataPtrArray* RepEntry::common_version(ProjectVersionData* one, ProjectVersionData* two) const { if (one == NULL || two == NULL) return NULL; ProjectVersionDataPtrArray* tmppvda = new ProjectVersionDataPtrArray; /* Reset flags */ one->clear_flags(); two->clear_flags(); clear_flags (); /* Mark all versions visible from one */ common_version_dfs_1 (one); /* Fill tmppvda with possible common versions */ common_version_dfs_2 (two, tmppvda); /* Reset flags */ clear_flags (); /* Mark all parents of all versions in tmppvda. */ foreach (pvd_ptr, tmppvda, ProjectVersionDataPtrArray::ArrayIterator) common_version_dfs_3 (*pvd_ptr); /* Create the real array. */ ProjectVersionDataPtrArray* pvda = new ProjectVersionDataPtrArray; /* Transfer all non-marked versions in tmppvda to pvda. */ foreach (pvd_ptr, tmppvda, ProjectVersionDataPtrArray::ArrayIterator) { ProjectVersionData *pvd = *pvd_ptr; if (!pvd->flag1()) pvda->append (pvd); } delete tmppvda; return pvda; } bool RepEntry::common_version_dfs_4(ProjectVersionData* from, ProjectVersionData* to, ProjectVersionDataPtrArray* res) const { bool ret = false; if (from->flag1(true)) return from == to; for (int i = 0; i < from->parent_count(); i += 1) { ProjectVersionData *child = project_summary()->index(from->parent_index(i)); if (common_version_dfs_4 (child, to, res)) { if (!child->flag2(true)) { res->append (child); } ret = true; } } return ret; } static ProjectVersionDataPtrArray* reverse_pvda (ProjectVersionDataPtrArray* a) { ProjectVersionDataPtrArray* reversed = new ProjectVersionDataPtrArray; for (int i = a->length () - 1; i >= 0; i -= 1) { reversed->append (a->index (i)); } delete a; return reversed; } ProjectVersionDataPtrArray* RepEntry::common_lineage (ProjectVersionData* one, ProjectVersionData* two) const { /* This returns all versions that are in the ancestry lineage of * both versions. The return should be ordered such that if one * or two is the ancestor of one another, then reversing them with * reverse the order. */ ASSERT (one && two, "need both"); ProjectVersionDataPtrArray* result = new ProjectVersionDataPtrArray; /* Mark all versions ancestor to one */ clear_flags (); common_version_dfs_1 (one); if (two->flag1()) { /* Two is an ancestor of one, mark all ancestors of two instead. */ clear_flags (); common_version_dfs_1 (two); /* Fill the array with versions starting at one, ending at two. */ common_version_dfs_4 (one, two, result); result->append (one); return reverse_pvda (result); } /* Mark all versions ancestor to two */ clear_flags (); common_version_dfs_1 (two); if (one->flag1()) { /* One is an ancestor of two, mark all ancestors of one instead. */ clear_flags (); common_version_dfs_1 (one); /* Fill the array with versions starting at one, ending at two. */ common_version_dfs_4 (two, one, result); result->append (two); return result; } return NULL; } PrRcsVersionDataPtrError RepEntry::lookup_rcs_file_data(const char* filename, const char* version_num) const { RcsDelta **lookup; if((lookup = _rcs_file_table->lookup(filename)) != NULL) { RcsVersionData* data = (*lookup)->lookup(version_num); if(data) return data; } pthrow prcserror << "Repository inconsistency detected in repository file " << squote(filename) << " at file version " << version_num << ", please run " << squote("prcs admin rebuild") << " to diagnose this error" << dotendl; } RepEntry::~RepEntry() { if(_log) { if (_log_pstream->pubsync()) { prcserror << "warning: Write to repository log failed" << perror; } delete _log; delete _log_pstream; delete _log_stream; } if (_rebuild_file) delete _rebuild_file; if (_repository_lock) delete _repository_lock; if (_rfl) delete _rfl; if (_rep_comp_cache) { foreach (ch_ptr, _rep_comp_cache, CharPtrArray::ArrayIterator) delete (*ch_ptr); delete _rep_comp_cache; } } void RepEntry::add_project_data(ProjectVersionData* new_data) { if(!option_report_actions) _rebuild_file->add_project_data(new_data); } void RepEntry::add_rcs_file_data(const char* filename, RcsVersionData* new_data) { if(!option_report_actions) _rebuild_file->add_rcs_file_data(filename, new_data); } PrVoidError RepEntry::close_repository() { if(!option_report_actions) { Return_if_fail(_rebuild_file->update_project_data()); delete _rebuild_file; _rebuild_file = NULL; } return NoError; } PrProjectDescriptorPtrError RepEntry::checkout_prj_file(const char* fullname, const char* version, ProjectReadData flags) { FILE* cofile; ProjectDescriptor* project; Dstring filename; filename.sprintf("%s:%s", Rep_name_of_version_file(), version); Return_if_fail(cofile << VC_checkout_stream(version, Rep_name_of_version_file())); PrVoidError it = (project << read_project_file(fullname, filename, false, cofile, flags)); Return_if_fail(VC_close_checkout_stream(cofile, version, Rep_name_of_version_file())); Return_if_fail (it); project->repository_entry(this); return project; } PrProjectDescriptorPtrError RepEntry::checkout_create_prj_file (const char *filename, const char *fullname, const char *version, ProjectReadData flags) { ProjectDescriptor *project; Return_if_fail(project << checkout_prj_file(fullname, version, flags)); Return_if_fail(project->write_project_file(filename)); return project; } PrVoidError RepEntry::Rep_alarm() const { if (_repository_lock && _repository_lock->touch_lock() != LOCK_SUCCESS) prcsinfo << "Failed refreshing repository lock, something bad " "is happening" << dotendl; return NoError; } static void print_user_access(int r, int w, int x) { if(!r && !w && !x) { prcsoutput << "no permission" << prcsendl; } else if (r && w && x) { prcsoutput << "read/write" << prcsendl; } else if (x && r) { prcsoutput << "read only" << prcsendl; } else { prcsoutput << "neither read nor write -- fix me" << prcsendl; } } static void print_access(struct stat *buf, const char *pwd_name, const char *grp_name) { prcsoutput << "Repository entry owner: " << pwd_name << prcsendl; prcsoutput << "Repository entry group: " << grp_name << prcsendl; prcsoutput << "Repository entry access: " << prcsendl; prcsoutput << "Owner: "; print_user_access(buf->st_mode & S_IRUSR, buf->st_mode & S_IWUSR, buf->st_mode & S_IXUSR); prcsoutput << "Group: "; print_user_access(buf->st_mode & S_IRGRP, buf->st_mode & S_IWGRP, buf->st_mode & S_IXGRP); prcsoutput << "Other: "; print_user_access(buf->st_mode & S_IROTH, buf->st_mode & S_IWOTH, buf->st_mode & S_IXOTH); } PrIntError RepEntry::Rep_print_access() { struct passwd *pwd; struct group *grp; pwd = getpwuid(_rep_stat_buf.st_uid); grp = getgrgid(_rep_stat_buf.st_gid); _owner_name = p_strdup((pwd && pwd->pw_name) ? pwd->pw_name : "nouser"); _group_name = p_strdup((grp && grp->gr_name) ? grp->gr_name : "nogroup"); print_access(&_rep_stat_buf, _owner_name, _group_name); return _rep_stat_buf.st_mode; } static PrVoidError can_set_group(const char* group_name, const char *current_group_name) { /* The reason for this function is just to give a nice error * message. If you wait until chmod then you get "Operation not * permitted". */ if (strcmp(group_name, current_group_name) != 0) { struct group *grp; if((grp = getgrnam(group_name)) == NULL) pthrow prcserror << "Can't lookup group " << squote(group_name) << dotendl; char** grp_members = grp->gr_mem; if (get_user_id() == 0) return NoError; while(*grp_members) { if(strcmp(get_login(), *grp_members++) == 0) { return NoError; } } /* Linux (at least) doesn't return users whose default group * is this group, check that too. */ struct passwd *pwd = getpwuid (get_user_id ()); if (pwd && pwd->pw_gid == grp->gr_gid) { return NoError; } pthrow prcserror << "You are not a member of that group" << dotendl; } return NoError; } static PrIntError query_mask(mode_t old_mode) { int mask = 0; mask |= 0077; /* clear group/other read/write */ prcsquery << "Group members' access, "; if (old_mode & 0020) prcsquery << force("read/write") << report("read/write") << option('r', "Allow group read access") << defopt('w', "Allow group read/write access") << option('n', "Disallow group access"); else if (old_mode & 0010) prcsquery << force("read only") << report("read only") << defopt('r', "Allow group read access") << option('w', "Allow group read/write access") << option('n', "Disallow group access"); else prcsquery << force("read only") << report("read only") << option('r', "Allow group read access") << option('w', "Allow group read/write access") << defopt('n', "Disallow group access"); prcsquery << query("select"); char c; Return_if_fail(c << prcsquery.result()); if (c == 'r') mask &= 0727; else if (c == 'w') mask &= 0707; prcsquery << "Other users' access, "; if (old_mode & 0002) prcsquery << force("read/write") << report("read/write") << option('r', "Allow other users read access") << option('n', "Disallow other users access") << defopt('w', "Allow other users read/write access"); else if (old_mode & 0001) prcsquery << force("read only") << report("read only") << defopt('r', "Allow other users read access") << option('n', "Disallow other users access") << option('w', "Allow other users read/write access"); else prcsquery << force("none") << report("none") << option('r', "Allow other users read access") << defopt('n', "Disallow other users access") << option('w', "Allow other users read/write access"); prcsquery << query("select"); Return_if_fail(c << prcsquery.result()); if (c == 'r') mask &= 0772; else if(c == 'w') mask &= 0770; return mask; } const char* RepEntry::Rep_entry_path() { return Rep_name_in_repository(_entry_name); } RepFreeFilename::RepFreeFilename(const char* name, int umask) { _umask = umask; _directory = 0; _root.assign (name); real_open_dir (""); memset (_offset, 0, sizeof (_offset)); } PrettyOstream& RepEntry::Rep_log() const { ASSERT(_writeable, "only a writeable rep"); (*_log) << get_login() << " " << getpid() << " " << get_host_name() << " " << get_utc_time() << " " << prcs_version_string << ": "; return *_log; } bool RepEntry::Rep_needs_compress() const { return _writeable && _compressed; } mode_t RepEntry::Rep_get_umask() const { return _rep_mask; } void RepEntry::Rep_set_compression(bool c) { _compressed = c; } const char* RepEntry::Rep_name_of_version_file() const { return _project_file.cast(); } int RepEntry::version_count() const { return _project_data_array->length(); } ProjectVersionDataPtrArray* RepEntry::project_summary() const { return _project_data_array; } void RepEntry::project_summary(ProjectVersionDataPtrArray* pda0) { _project_data_array = pda0; } RcsFileTable* RepEntry::rcs_file_summary() const { return _rcs_file_table; } void RepEntry::rcs_file_summary(RcsFileTable* rft0) { _rcs_file_table = rft0; } const char* RepEntry::Rep_owner_name() const { return _owner_name; } const char* RepEntry::Rep_group_name() const { return _group_name; } /* * Compression is implemented by lazily uncompressing repository files * when they are needed an compressing all uncompressed files upon * exiting. Therefore, the compress_command and uncompress_command * need only touch and remove the file COMPRESSION_TAG in the repository * to do this. The code that compresses the directory is called from * clean_up() in prcs.cc. */ PrPrcsExitStatusError admin_compress_command() { RepEntry* rep_entry; Return_if_fail(rep_entry << Rep_init_repository_entry(cmd_root_project_name, true, false, true)); if(!rep_entry->Rep_needs_compress()) { Umask mask (rep_entry->Rep_get_umask()); if(creat(rep_entry->Rep_name_in_entry(prcs_compression_tag), 0444) < 0) pthrow prcserror << "Failed writing repository tagfile" << perror; rep_entry->Rep_set_compression(true); } return ExitSuccess; } PrPrcsExitStatusError admin_uncompress_command() { RepEntry* rep_entry; Return_if_fail(rep_entry << Rep_init_repository_entry(cmd_root_project_name, true, false, true)); if(!rep_entry->Rep_needs_compress()) { if(option_immediate_uncompression) { Return_if_fail(rep_entry->Rep_uncompress_all_files()); } else { prcsinfo << "Repository entry " << cmd_root_project_name << "is already uncompressed. Use -i to immediately decompress " "the entire repository" << dotendl; } return ExitSuccess; } If_fail(Err_unlink(rep_entry->Rep_name_in_entry(prcs_compression_tag))) { pthrow prcserror << "Failed unlinking repository compression tag " << perror; } if(option_immediate_uncompression) { Return_if_fail(rep_entry->Rep_uncompress_all_files()); } else { prcsinfo << "Repository lazily uncompressed. Actual decompression takes " "place the next time you access each file of the project" << dotendl; } rep_entry->Rep_set_compression(false); return ExitSuccess; } PrPrcsExitStatusError admin_access_command() { int mask = 0; const char* group_name, *user_name; int uid = get_user_id(); int old_mode; if (cmd_root_project_name) { RepEntry* rep_entry; prcsinfo << "Setting access permissions for project " << squote(cmd_root_project_name) << dotendl; Return_if_fail(rep_entry << Rep_init_repository_entry(cmd_root_project_name, true, false, true)); Return_if_fail(old_mode << rep_entry->Rep_print_access()); if (option_report_actions) return ExitSuccess; if (! fs_same_file_owner(rep_entry->Rep_entry_path()) && uid != 0) { pthrow prcserror <<"Only the owner of this repository entry may modify its " "access permissions" << dotendl; } if (uid == 0) { prcsquery << "Enter a user" << definput(rep_entry->Rep_owner_name()) << string_query(""); Return_if_fail(user_name << prcsquery.string_result()); } else { user_name = get_login(); } prcsquery << "Enter a group" << definput(rep_entry->Rep_group_name()) << string_query(""); Return_if_fail (group_name << prcsquery.string_result()); Return_if_fail (can_set_group(group_name, rep_entry->Rep_group_name())); Return_if_fail (mask << query_mask(old_mode)); Return_if_fail(rep_entry->Rep_chown(user_name, group_name)); Return_if_fail(rep_entry->Rep_chmod(0777 ^ mask)); } else { /* Set permissions for repository */ Return_if_fail(Rep_init_repository()); const char* rep_name = Rep_name_in_repository(""); prcsinfo << "Setting access permissions for repository " << rep_name << dotendl; struct passwd *pwd; struct group *grp; const char* group_name, *owner_name, *new_group_name; struct stat stat_buf; If_fail(Err_stat(Rep_name_in_repository(""), &stat_buf)) pthrow prcserror << "Stat failed on " << squote(rep_name) << perror; pwd = getpwuid(stat_buf.st_uid); grp = getgrgid(stat_buf.st_gid); owner_name = p_strdup((pwd && pwd->pw_name) ? pwd->pw_name : "nouser"); group_name = p_strdup((grp && grp->gr_name) ? grp->gr_name : "nogroup"); print_access(&stat_buf, owner_name, group_name); if(option_report_actions) return ExitSuccess; if(!fs_same_file_owner(rep_name) && uid != 0) { pthrow prcserror <<"Only the owner of this repository may modify its " "access permissions" << dotendl; } if (uid == 0) { prcsquery << "Enter a user" << definput(owner_name) << string_query(""); Return_if_fail(user_name << prcsquery.string_result()); } else { user_name = get_login(); } prcsquery << "Enter a group" << definput(group_name) << string_query(""); Return_if_fail(new_group_name << prcsquery.string_result()); Return_if_fail(can_set_group(new_group_name, group_name)); Return_if_fail(mask << query_mask(stat_buf.st_mode)); If_fail(Err_chmod(rep_name, (0777 ^ mask) | S_ISGID | 01000)) pthrow prcserror << "Chmod failed on " << squote(rep_name) << perror; const char* rep_lock_name = Rep_name_in_repository(prcs_lock_dir); If_fail(Err_chmod(rep_lock_name, 0777 ^ mask)) pthrow prcserror << "Chmod failed on " << squote(rep_lock_name) << perror; } return ExitSuccess; } PrPrcsExitStatusError admin_init_command() { RepEntry* rep_entry; Return_if_fail(rep_entry << Rep_init_repository_entry(cmd_root_project_name, true, true, true)); rep_entry->Rep_print_access(); return ExitSuccess; } static PrVoidError repository_cleanup_handler(void* data, bool signaled) { RepEntry* rep_entry; rep_entry = (RepEntry*) data; if(signaled) { if(rep_entry->Rep_needs_compress()) { prcswarning << "Repository entry " << rep_entry->Rep_entry_path() << " not recompressed" << dotendl; } } else if (! option_report_actions) { rep_entry->Rep_compress_all_files(); } rep_entry->Rep_unlock_repository(); #ifdef PURIFY delete rep_entry; /* About to exit, not worth it. */ #endif return NoError; } static PrVoidError repository_alarm_handler(void* data, bool) { RepEntry* rep_entry; rep_entry = (RepEntry*) data; Return_if_fail(rep_entry->Rep_alarm()); return NoError; }