/* -*-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 <sys/types.h>
#include <sys/time.h>
#include <pwd.h>
#include <grp.h>
}
#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;
}
syntax highlighted by Code2HTML, v. 0.9.1