/* -*-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: prcs.cc 1.23.1.1.1.14.1.18.1.8.1.16.1.1.1.7.1.41.1.2 Wed, 06 Feb 2002 20:57:16 -0800 jmacd $
*/
/* $Format: "static const char prcs_version_id[] = \"$ProjectVersion$ $ProjectAuthor$ $ProjectDate$\";"$ */
static const char prcs_version_id[] = "1.3.3-relase.1 jmacd Sun, 09 May 2004 18:34:01 -0700";
#include "fnmatch.h"
#include "prcs.h"
#include "lock.h"
#include "docs.h"
#include "vc.h"
#include "misc.h"
#include "projdesc.h"
#include "fileent.h"
#include "syscmd.h"
#include "repository.h"
#include "system.h"
#if defined(__BEOS__)
#include <KernelKit.h>
#endif
extern "C" {
#include <signal.h>
#include <sys/time.h>
#include "getopt.h"
}
/* $Format: "const int prcs_version_number[3] = { $ReleaseMajorVersion$, $ReleaseMinorVersion$, $ReleaseMicroVersion$ };" $ */
const int prcs_version_number[3] = { 1, 3, 3 };
/* $Format: "const char prcs_version_string[] = \"$ReleaseVersion$\";"$ */
const char prcs_version_string[] = "1.3.3";
/* The following classes are only used inside this file, and oraganize
* several arrays of information used to select the command and
* configuration. */
enum ProjectArgType {
InsureProjectName,
NoInsureProjectName,
NoProjectRequired,
OptionalProjectName
};
enum RevisionSpecifierArgType {
NoRevisionArg,
OneRevisionArg,
TwoRevisionArgs
};
enum RevisionFlags {
NoFlags = 0,
AllowWildCards = 1 << 1,
NoDefault = 1 << 2
};
enum DiffArgType {
ReallyDiffArgs,
ReallyExecuteArgs,
NoDiffArgs
};
struct CommandOptionPair {
char opt;
const char* desc;
int *addr;
};
struct CommandNamePair {
SystemCommand* command;
const char* name;
};
struct PrcsCommand {
const char *command_name;
const char *defmaj;
const char *defmin;
const char *defmin_maj_only;
int revision_flags;
PrPrcsExitStatusError (*command_function)();
const char *help_string;
RevisionSpecifierArgType revision_arg_type;
ProjectArgType project_arg_type;
const char* optstring;
DiffArgType diff_arg_type;
int n_files_allowed;
};
struct CleanupHandler {
CleanupHandler(PrVoidError (*h)(void*, bool), void* d, struct CleanupHandler* n)
:handler(h), data(d), next_handler(n) { }
PrVoidError (*handler)(void*, bool);
void *data;
CleanupHandler* next_handler;
};
/* Static data */
static PrcsCommand *command = NULL;
static PrcsCommand *subcommand = NULL;
static bool illegal_option = false;
static struct CleanupHandler *cleanup_handler_list = NULL;
static struct CleanupHandler *alarm_handler_list = NULL;
static const char check_rcs_arg[] = "-V";
static const char check_rcs_expected[] = "*RCS version 5*";
static const CommandNamePair check_rcs = { &rcs_command, "rcs" };
static const char check_diff_arg[] = "-v";
static const char check_diff_expected[] = "*GNU diffutils*";
static const CommandNamePair check_diff = { &gdiff_command, "gdiff" };
static const char check_diff3_arg[] = "-v";
static const char check_diff3_expected[] = "*GNU diffutils*";
static const CommandNamePair check_diff3 = { &gdiff3_command, "gdiff3" };
static CommandNamePair const command_names[] = {
{ &rcs_command, "rcs" },
{ &ci_command, "ci" },
{ &co_command, "co" },
{ &rlog_command, "rlog" },
{ &gdiff_command, "diff" },
{ &gdiff3_command, "diff3" },
{ &gzip_command, "gzip" },
{ &ls_command, "ls" },
{ &tar_command, "tar" },
{ NULL, NULL }
};
#define MATCH_CHAR '\300'
#define NOTMATCH_CHAR '\301'
#define ALL_CHAR '\302'
#define PRE_CHAR '\303'
#define PIPE_CHAR '\304'
#define PLAINFORMAT_CHAR '\305'
#define SORT_CHAR '\306'
#define NOKEYW_CHAR '\307'
#define VLOG_CHAR '\310'
#define EXECUTE_STR "\300\301\302\303\304"
#define PLAINFORMAT_STR "\305"
#define SORT_STR "\306"
#define NOKEYW_STR "\307"
#define VLOG_STR "\310"
static CommandOptionPair const command_options[] = {
{ 'i', "--immediate", &option_immediate_uncompression },
{ 'k', "--keywords", &option_diff_keywords },
{ 'N', "--new", &option_diff_new_file },
{ 'd', "--delete", &option_populate_delete },
{ 'z', "--compress", &option_package_compress },
{ 'p', "--preserve", &option_preserve_permissions },
{ 'P', "--exclude-project-file", &option_exclude_project_file },
{ 'u', "--unlink", &option_unlink },
{ 's', "--skilled-merge", &option_skilled_merge },
{ NOKEYW_CHAR, "--no-keywords", &option_nokeywords },
{ VLOG_CHAR, "--version-log", &option_version_log },
{ SORT_CHAR, "--sort", &option_sort },
{ MATCH_CHAR, "--match", &option_match_file },
{ NOTMATCH_CHAR, "--not", &option_not_match_file },
{ ALL_CHAR, "--all", &option_all_files },
{ PRE_CHAR, "--pre", &option_preorder },
{ PIPE_CHAR, "--pipe", &option_pipe },
{ 0, NULL }
};
static struct option const long_options[] = {
/* standard operands */
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"long-long-format", no_argument, 0, 'L'},
{"long-format", no_argument, 0, 'l'},
{"no-action", no_argument, 0, 'n'},
{"quiet", no_argument, 0, 'q'},
{"force", no_argument, 0, 'f'},
{"repository", required_argument, 0, 'R'},
{"jobs", required_argument, 0, 'j'},
{"plain-format", no_argument, 0, PLAINFORMAT_CHAR },
#ifdef PRCS_DEVEL
{"debug", no_argument, 0, 'D'},
{"tune", required_argument, 0, 't'},
#endif
/* non-standard operands */
{"new", no_argument, 0, 'N'},
{"keywords", no_argument, 0, 'k'},
{"immediate", no_argument, 0, 'i'},
{"delete", no_argument, 0, 'd'},
{"compress", no_argument, 0, 'z'},
{"preserve-permissions", no_argument, 0, 'p'},
{"preserve", no_argument, 0, 'p'},
{"permissions", no_argument, 0, 'p'},
{"exclude-project-file", no_argument, 0, 'P'},
{"revision", required_argument, 0, 'r'},
{"unlink", no_argument, 0, 'u'},
{"skilled-merge", no_argument, 0, 's'},
{"no-keywords", no_argument, 0, NOKEYW_CHAR},
{"version-log", required_argument, 0, VLOG_CHAR},
{"sort", required_argument, 0, SORT_CHAR},
{"match", required_argument, 0, MATCH_CHAR},
{"not", required_argument, 0, NOTMATCH_CHAR},
{"all", no_argument, 0, ALL_CHAR},
{"pre", no_argument, 0, PRE_CHAR},
{"pipe", no_argument, 0, PIPE_CHAR},
{0,0,0,0}};
/* It looks like a bug in the optstring that EXECUTE_STR members don't
* have ':' characters, but GNU getopt doesn't seem to mind. */
static const char prcs_optstring[] = "+vhHLlnqfR:r:NkidzpPruj:s"
EXECUTE_STR PLAINFORMAT_STR NOKEYW_STR VLOG_STR ":" SORT_STR ":"
#ifdef PRCS_DEVEL
"Dt:"
#endif
;
static PrcsCommand commands[] = {
/* commands accepting -r */
{"checkin", "", "@", "@", NoFlags,
checkin_command, checkin_help_string,
OneRevisionArg, InsureProjectName, "uj" VLOG_STR, NoDiffArgs, -1 },
{"checkout", "@", "@", "@", NoFlags,
checkout_command, checkout_help_string,
OneRevisionArg, InsureProjectName, "upP", NoDiffArgs, -1 },
{"diff", "", "", "@", NoFlags,
diff_command, diff_help_string,
TwoRevisionArgs, InsureProjectName, "NkP", ReallyDiffArgs, -1 },
{"merge", "", "@", "@", NoFlags,
merge_command, merge_help_string,
OneRevisionArg, InsureProjectName, "us", ReallyDiffArgs, -1 },
{"delete", "", "", "@", NoDefault,
delete_command, delete_help_string,
OneRevisionArg, InsureProjectName, NULL, NoDiffArgs, 0 },
{"info", "*", "*", "*", AllowWildCards,
info_command, info_help_string,
OneRevisionArg, InsureProjectName, SORT_STR, NoDiffArgs, -1 },
{"changes", "", "", "@", NoFlags,
changes_command, changes_help_string,
TwoRevisionArgs, InsureProjectName, "", NoDiffArgs, -1 },
{"execute", "", "", "@", NoFlags,
execute_command, execute_help_string,
OneRevisionArg, InsureProjectName, "P" EXECUTE_STR, ReallyExecuteArgs, -1 },
/* commands not accepting -r */
{"populate", NULL, NULL, NULL, NoFlags,
populate_command, populate_help_string,
NoRevisionArg, InsureProjectName, "du" NOKEYW_STR, NoDiffArgs, -1 },
{"depopulate", NULL, NULL, NULL, NoFlags,
depopulate_command, depopulate_help_string,
NoRevisionArg, InsureProjectName, "u", NoDiffArgs, -1 },
{"package", NULL, NULL, NULL, NoFlags,
package_command, package_help_string,
NoRevisionArg, InsureProjectName, "z", NoDiffArgs, 1 },
{"unpackage", NULL, NULL, NULL, NoFlags,
unpackage_command, unpackage_help_string,
NoRevisionArg, NoInsureProjectName, NULL, NoDiffArgs, -2 },
{"rekey", NULL, NULL, NULL, NoFlags,
rekey_command, rekey_help_string,
NoRevisionArg, InsureProjectName, "u", NoDiffArgs, -1 },
{"config", NULL, NULL, NULL, NoFlags,
config_command, config_help_string,
NoRevisionArg, NoProjectRequired, NULL, NoDiffArgs, 0 },
/* fake command entries */
{"admin", NULL, NULL, NULL, NoFlags,
NULL, NULL, NoRevisionArg, InsureProjectName, NULL, NoDiffArgs, 0 },
{NULL, NULL, NULL, NULL, NoFlags,
NULL, NULL, NoRevisionArg, InsureProjectName, NULL, NoDiffArgs, 0 }
};
static PrcsCommand subcommands[] = {
{"compress", NULL, NULL, NULL, NoFlags,
admin_compress_command, admin_compress_help_string,
NoRevisionArg, InsureProjectName, NULL, NoDiffArgs, 0 },
{"uncompress", NULL, NULL, NULL, NoFlags,
admin_uncompress_command, admin_uncompress_help_string,
NoRevisionArg, InsureProjectName, "i", NoDiffArgs, 0 },
{"rebuild", NULL, NULL, NULL, NoFlags,
admin_rebuild_command, admin_rebuild_help_string,
NoRevisionArg, InsureProjectName, NULL, NoDiffArgs, 0 },
{"init", NULL, NULL, NULL, NoFlags,
admin_init_command, admin_init_help_string,
NoRevisionArg, InsureProjectName, NULL, NoDiffArgs, 0 },
{"access", NULL, NULL, NULL, NoFlags,
admin_access_command, admin_access_help_string,
NoRevisionArg, OptionalProjectName, NULL, NoDiffArgs, 0 },
{"pdelete", NULL, NULL, NULL, NoFlags,
admin_pdelete_command, admin_pdelete_help_string,
NoRevisionArg, InsureProjectName, NULL, NoDiffArgs, 0 },
{"pinfo", NULL, NULL, NULL, NoFlags,
admin_pinfo_command, admin_pinfo_help_string,
NoRevisionArg, NoProjectRequired, NULL, NoDiffArgs, 0 },
{"prename", NULL, NULL, NULL, NoFlags,
admin_prename_command, admin_prename_help_string,
NoRevisionArg, InsureProjectName, NULL, NoDiffArgs, 1 },
{NULL, NULL, NULL, NULL, NoFlags,
NULL, NULL, NoRevisionArg, InsureProjectName, NULL, NoDiffArgs, 0 }
};
static const int friendly_signals[] = {
SIGINT, SIGTERM, SIGPIPE, SIGHUP,
#ifdef SIGXCPU
SIGXCPU,
#endif
#ifdef SIGXFSZ
SIGXFSZ,
#endif
0
};
static const int unfriendly_signals[] = {
SIGBUS, SIGFPE, SIGILL, SIGQUIT, SIGSEGV, 0
};
#if defined(__BEOS__)
static thread_id beos_alarm_id = 0;
#endif
/* End static data */
static PrVoidError check_system_command(CommandNamePair com,
const char* arg,
const char* expected)
{
Dstring output;
ArgList *args;
Return_if_fail(args << com.command->new_arg_list());
args->append(arg);
Return_if_fail(com.command->open(true, false));
If_fail(read_string(com.command->standard_out(), &output))
pthrow prcserror << "Read failed from " << com.name << perror;
Return_if_fail_if_ne(com.command->close(), 0) {
pthrow prcserror << com.name << ' ' << arg
<< " failed, " << com.name
<< " is probably too old for PRCS to use"
<< dotendl;
}
if (fnmatch (expected, output.cast (), FNM_NOESCAPE) != 0) {
pthrow prcserror << com.name
<< " installation is too old, please install "
<< expected << dotendl;
}
return NoError;
}
static PrVoidError check_system_commands()
{
Return_if_fail(check_system_command(check_rcs,
check_rcs_arg,
check_rcs_expected));
Return_if_fail(check_system_command(check_diff,
check_diff_arg,
check_diff_expected));
Return_if_fail(check_system_command(check_diff3,
check_diff3_arg,
check_diff3_expected));
return NoError;
}
/* Config checks and reports the paths of all the programs upon which
* PRCS depends, and exits according to whether everything checked
* out. */
PrPrcsExitStatusError config_command()
{
const int width = 40;
const CommandNamePair* command = command_names;
PrPrcsExitStatusError ret(ExitSuccess);
prcsoutput << "Version " << prcs_version_string << prcsendl;
prcsoutput << prcs_version_id << prcsendl;
if(get_environ_var("RCS_PATH"))
prcsinfo << "Using $RCS_PATH to find binaries" << dotendl;
for(;command->command;command += 1) {
If_fail(command->command->init()) {
ret = PrPrcsExitStatusError (FatalError);
continue;
}
prcsoutput << "System path for " << squote(command->name)
<< " is: " << setcol(width) << command->command->path() << prcsendl;
}
const char* rep;
If_fail(rep << Rep_guess_repository_path())
rep = "-*- unavailable -*-";
prcsoutput << "Repository path is: " << setcol(width) << rep << prcsendl;
const char* log = get_environ_var ("PRCS_LOGQUERY");
prcsoutput << "Log querying is: " << setcol(width)
<< (log ? "on" : "off") << prcsendl;
const char* editor = get_environ_var ("PRCS_CONFLICT_EDITOR");
if (editor)
prcsoutput << "Conflict editor is: " << setcol(width) << editor << prcsendl;
const char* merge = get_environ_var ("PRCS_MERGE_COMMAND");
if (merge)
prcsoutput << "Merge command is: " << setcol(width) << merge << prcsendl;
const char* diff = get_environ_var ("PRCS_DIFF_COMMAND");
if (diff)
prcsoutput << "Diff command is: " << setcol(width) << diff << prcsendl;
prcsoutput << "Job number is: " << setcol(width) << option_jobs << prcsendl;
diff = get_environ_var ("PRCS_DIFF_OPTIONS");
if (diff)
prcsoutput << "Diff options are: " << setcol(width) << diff << prcsendl;
const char* tmpdir = get_environ_var ("TMPDIR");
if (!tmpdir) tmpdir = "/tmp";
prcsoutput << "Temp directory is: " << setcol(width) << tmpdir << prcsendl;
prcsoutput << "Plain formatting is: " << setcol(width)
<< (option_plain_format ? "on" : "off") << prcsendl;
//Return_if_fail(check_system_commands());
return ret;
}
static bool check_ignored_options(const char* optstring)
{
const CommandOptionPair *p = command_options;
bool warning = false;
for(; p->opt; p += 1) {
if(*p->addr && !strchr(optstring, p->opt)) {
prcsinfo << "Ignoring command line switch "
<< p->desc << dotendl;
warning = true;
*p->addr = 0;
}
}
if (option_all_files && option_pipe) {
prcsinfo << "Ignoring --pipe with --all specified" << dotendl;
option_pipe = 0;
warning = true;
}
return warning;
}
static PrVoidError check_major_version_name(const char* name)
{
if (name[0] && strcmp(name, "@") != 0 &&
VC_check_token_match(name, "label") <= 0)
pthrow prcserror << "Illegal major version name "
<< squote(name)
<< dotendl;
return NoError;
}
static PrVoidError check_minor_version_name(const char* name)
{
if (name[0] && strcmp(name, "@") != 0 &&
VC_check_token_match(name, "number") <= 0)
pthrow prcserror << "Illegal minor version name "
<< squote(name)
<< dotendl;
return NoError;
}
static PrPrcsExitStatusError invoke_command(PrcsCommand *c)
{
bool warning = false;
if (c->n_files_allowed >= 0 && cmd_filenames_count != c->n_files_allowed) {
pthrow prcserror << "This command requires " << c->n_files_allowed
<< " file-or-dir arguments" << dotendl;
}
if(c->project_arg_type == InsureProjectName ||
(c->project_arg_type == OptionalProjectName && cmd_root_project_name)) {
if(VC_check_token_match(cmd_root_project_name, "label") <= 0 ||
/* this length is arbitrary */
strlen(cmd_root_project_name) > 200) {
prcserror << "Illegal project name " << squote(cmd_root_project_name)
<< dotendl;
exit(2);
}
}
if (c->revision_arg_type != NoRevisionArg) {
if (c->revision_flags & NoDefault &&
!cmd_version_specifier_major &&
!cmd_version_specifier_minor)
pthrow prcserror << "You must specify a revision argument with "
<< squote ("-r") << " for this command" << dotendl;
if (!cmd_version_specifier_major && !cmd_version_specifier_minor) {
cmd_version_specifier_major = c->defmaj;
cmd_version_specifier_minor = c->defmin;
}
if (!cmd_version_specifier_minor)
cmd_version_specifier_minor = c->defmin_maj_only;
if (!cmd_version_specifier_minor[0] && cmd_version_specifier_major[0])
pthrow prcserror << "It is illegal to specify a null minor "
"version without a null major version" << dotendl;
if (!(c->revision_flags & AllowWildCards)) {
Return_if_fail(check_major_version_name(cmd_version_specifier_major));
Return_if_fail(check_minor_version_name(cmd_version_specifier_minor));
}
if (c->revision_arg_type == TwoRevisionArgs &&
cmd_alt_version_specifier_major) {
if (!cmd_alt_version_specifier_minor)
cmd_alt_version_specifier_minor = c->defmin_maj_only;
if (!(c->revision_flags & AllowWildCards)) {
Return_if_fail(check_major_version_name(cmd_alt_version_specifier_major));
Return_if_fail(check_minor_version_name(cmd_alt_version_specifier_minor));
}
}
}
warning |= check_ignored_options(c->optstring ? c->optstring : "");
if(warning) {
prcsquery << "Command line options are being ignored. "
<< force("Continuing")
<< report("Continue")
<< optfail('n')
<< defopt('y', "Continue, ignoring these options")
<< query("Continue");
Return_if_fail(prcsquery.result());
}
if (cmd_version_specifier_minor &&
cmd_version_specifier_minor[0] &&
strcmp(cmd_version_specifier_minor, "@") != 0) {
cmd_version_specifier_minor_int = atoi(cmd_version_specifier_minor);
}
return c->command_function();
}
static PrVoidError set_project_name(const char* specifier0)
{
/* There is ambiguity between interpreting "prcs" as a directory
* or as a project name. It will only be interpreted as a dir
* if the directory is really a directory. */
int len = strlen(specifier0);
const char* last_slash = strrchr(specifier0, '/');
Dstring* project_path = new Dstring;
Dstring* project_name = new Dstring;
if (len > 4 && strcmp (specifier0 + len - 4, ".prj") == 0) {
if (last_slash) {
project_path->assign(specifier0);
project_path->truncate(last_slash - specifier0);
project_name->assign(last_slash + 1);
} else {
/* project_path is correct */
project_name->assign(specifier0);
}
project_name->truncate(project_name->length() - 4);
} else if (fs_is_directory(specifier0)) {
Dstring* prj;
Return_if_fail(prj << guess_prj_name(specifier0));
if (prj == NULL) {
pthrow prcserror << "Cannot determine project name, there must be a single "
"project file in the directory " << squote(specifier0) << dotendl;
}
project_path->assign(specifier0);
project_name->assign(*prj);
delete prj;
} else if (specifier0[len - 1] == '/') {
pthrow prcserror << "Directory " << squote(specifier0)
<< " does not exist, cannot determine project name" << dotendl;
} else if (last_slash) {
project_path->assign(specifier0);
project_path->truncate(last_slash - specifier0);
project_name->assign(last_slash + 1);
} else {
project_name->assign(specifier0);
}
if (project_path->length() > 0 &&
project_path->index(project_path->length() - 1) != '/')
project_path->append('/');
while (strncmp(*project_path, "./", 2) == 0)
project_path->assign(project_path->cast() + 2);
Dstring* project_file = new Dstring;
Dstring* project_full = new Dstring;
project_full->assign(*project_path);
project_full->append(*project_name);
project_file->assign(*project_full);
project_file->append(".prj");
/* These all leak. */
cmd_root_project_name = project_name->cast();
cmd_root_project_file_path = project_file->cast();
cmd_root_project_full_name = project_full->cast();
cmd_root_project_path = project_path->cast();
cmd_root_project_path_len = project_path->length();
#if 0
prcsinfo << "cmd_root_project_name = " << cmd_root_project_name << prcsendl;
prcsinfo << "cmd_root_project_file_path = " << cmd_root_project_file_path << prcsendl;
prcsinfo << "cmd_root_project_full_name = " << cmd_root_project_full_name << prcsendl;
prcsinfo << "cmd_root_project_path = " << cmd_root_project_path << prcsendl;
#endif
return NoError;
}
static PrIntError determine_project(PrcsCommand* command, int argc, char** argv)
{
if(command->project_arg_type != NoProjectRequired) {
if(argc > 0 && strcmp(argv[0], "--") != 0) {
Return_if_fail(set_project_name(argv[0]));
return 1;
} else if (command->project_arg_type == InsureProjectName) {
Return_if_fail(set_project_name("./"));
} else if (command->project_arg_type != OptionalProjectName) {
pthrow prcserror << "Missing operand" << dotendl;
}
}
return 0;
}
static const char* correct_cmd_filename(char* cmd_filename)
{
Dstring correct(cmd_filename);
while (strncmp(correct.cast(), "./", 2) == 0)
correct.assign(correct.cast() + 2);
/* let "foo/" be the root project path. We want to accept anything prefixed
* with "foo/" or "foo". */
if (cmd_root_project_path_len > 0) {
if (correct.length() < cmd_root_project_path_len - 1)
/* its too short */
goto nowayman;
if (strncmp(correct, cmd_root_project_path, cmd_root_project_path_len - 1) != 0)
/* the prefix is incorrect */
goto nowayman;
if (correct.length() == cmd_root_project_path_len - 1) {
/* its "foo", cool */
correct.append('/');
} else if (strcmp(correct.cast() + cmd_root_project_path_len, ".") == 0)
/* its "foo/.", get rid of the "." */
correct.truncate(cmd_root_project_path_len);
}
while (correct.length() > 0 && correct.last_index() == '/')
correct.truncate (correct.length() - 1);
return p_strdup(correct);
nowayman:
prcswarning << "Command line file-or-dir " << squote(cmd_filename)
<< " is not prefixed with project path " << squote(cmd_root_project_path)
<< dotendl;
return NULL;
}
static PrVoidError set_cmd_filenames()
{
if(cmd_filenames_count > 0) {
cmd_filenames_found = new bool[cmd_filenames_count];
for(int i = 0; i < cmd_filenames_count; i += 1)
cmd_filenames_found[i] = false;
cmd_corrected_filenames_given = new const char*[cmd_filenames_count];
int j = 0;
bool warn = false;
for(int i = 0; i < cmd_filenames_count; i += 1) {
const char* new_name = correct_cmd_filename(cmd_filenames_given[i]);
if (!new_name) {
warn = true;
continue;
}
cmd_corrected_filenames_given[j] = new_name;
cmd_filenames_given[j++] = cmd_filenames_given[i];
}
cmd_filenames_count = j;
for (int i = 0; i < cmd_filenames_count; i += 1) {
if (strcmp(cmd_corrected_filenames_given[i], cmd_root_project_path) == 0) {
/* This is okay, there may be bogus file-or-dirs that won't be reported. */
#if 0
prcsinfo << "Command line file-or-dir " << squote(cmd_filenames_given[i])
<< " matches project root, proceeding with all files selected"
<< dotendl;
#endif
cmd_filenames_count = 0;
}
}
if (warn) {
prcsquery << "Command line file-or-dirs are being ignored. "
<< force("Continuing")
<< report("Continue")
<< optfail('n')
<< defopt('y', "Proceed, ignore these filenames")
<< query ("Proceed");
char c;
Return_if_fail(c << prcsquery.result());
}
}
return NoError;
}
static bool read_command_line(int argc, char** argv)
{
int c;
PrcsCommand *pc = commands;
int num_revs = 0, i;
int longind;
bool saw_j = false;
if(argc > 1)
for(; pc->command_name != NULL; pc += 1)
if (strcmp(pc->command_name, argv[1]) == 0) {
argv += 1;
argc -= 1;
command = pc;
break;
}
if(command && strcmp(command->command_name, "admin") == 0) {
if(argc <= 1)
return false;
for(pc = subcommands; pc->command_name != NULL; pc += 1)
if (strcmp(pc->command_name, argv[1]) == 0) {
argv += 1;
argc -= 1;
subcommand = pc;
break;
}
if(subcommand == NULL)
return false;
}
while ((c = getopt_long(argc, argv, prcs_optstring, long_options, &longind)) != EOF)
{
char *min;
switch (c) {
case 'v':
prcsoutput << "Version " << prcs_version_string << prcsendl;
exit(0);
break;
case 'h': case 'H': return false; break;
case 'f': option_force_resolution = 1; break;
case 'k': option_diff_keywords = 1; break;
case 'l': option_long_format = 1; break;
case 'i': option_immediate_uncompression = 1; break;
case 'z': option_package_compress = 1; break;
case 'd': option_populate_delete = 1; break;
case 'q': option_be_silent = 1; break;
case 'N': option_diff_new_file = 1; break;
case 'L': option_really_long_format = 1; break;
case 'n': option_report_actions = 1; break;
case 'p': option_preserve_permissions = 1; break;
case 'P': option_exclude_project_file = 1; break;
case 'u': option_unlink = 1; break;
case 'R': cmd_repository_path = optarg; break;
case 'j':
{
char* end_ptr;
saw_j = true;
option_jobs = strtol (optarg, &end_ptr, 10);
if (end_ptr[0] != 0)
option_jobs = -1;
}
break;
case MATCH_CHAR:
option_match_file = 1;
option_match_file_pattern = optarg;
break;
case NOTMATCH_CHAR:
option_not_match_file = 1;
option_not_match_file_pattern = optarg;
break;
case ALL_CHAR: option_all_files = 1; break;
case PRE_CHAR: option_preorder = 1; break;
case PIPE_CHAR: option_pipe = 1; break;
case PLAINFORMAT_CHAR: option_plain_format = 1; break;
case SORT_CHAR: option_sort = 1; option_sort_type = optarg; break;
case NOKEYW_CHAR: option_nokeywords = 1; break;
case VLOG_CHAR: option_version_log = 1; option_version_log_string = optarg; break;
#ifdef PRCS_DEVEL
case 'D': option_n_debug = 0; break;
case 't': option_tune = atoi (optarg); break;
#endif
case 's': option_skilled_merge = 1; break;
case 'r':
option_version_present = 1;
if(!command) break;
if (num_revs < 1 && !command->revision_arg_type == NoRevisionArg) {
min = strrchr(optarg, '.'); /* last occurance of a period */
if(min == NULL){
cmd_version_specifier_major = optarg;
} else {
*min = '\0';
cmd_version_specifier_minor = min + 1;
cmd_version_specifier_major = optarg;
}
num_revs += 1;
} else if(num_revs < 2 && command->revision_arg_type == TwoRevisionArgs) {
min = strrchr(optarg, '.'); /* last occurance of a period */
if(min == NULL){
cmd_alt_version_specifier_major = optarg;
} else {
*min = '\0';
cmd_alt_version_specifier_minor = min + 1;
cmd_alt_version_specifier_major = optarg;
}
num_revs += 1;
} else {
prcserror << "Too many -r options given on command line" << dotendl;
exit(2);
}
break;
case '?':
default:
illegal_option = true;
return false;
break;
}
}
if (!option_sort)
option_sort_type = "version";
if (strcmp(option_sort_type, "version") != 0 &&
strcmp(option_sort_type, "date") != 0) {
prcserror << "Illegal sort type " << squote (option_sort_type) << dotendl;
exit(2);
}
if (!saw_j) {
const char* j = get_environ_var ("PRCS_JOB_NUMBER");
char *je = NULL;
if (j) {
option_jobs = strtol (j, &je, 10);
if (je[0] != 0)
option_jobs = -1;
}
}
if (option_jobs < 1 || option_jobs > 100 /* arbitrary */) {
if (saw_j)
prcserror << "Illegal value for -j" << dotendl;
else
prcserror << "Illegal value for PRCS_JOB_NUMBER" << dotendl;
exit(2);
}
if(get_environ_var("PRCS_PLAIN_FORMAT"))
option_plain_format = 1;
if(option_report_actions)
option_long_format = 1;
if(subcommand)
command = subcommand;
if(command == NULL)
return false;
argc -= optind;
argv += optind;
int used;
If_fail(used << determine_project(command, argc, argv))
exit(2);
argv += used;
argc -= used;
cmd_filenames_given = argv;
cmd_filenames_count = argc;
i = 0;
cmd_diff_options_given = 0;
cmd_diff_options_count = -1;
while(i < cmd_filenames_count) {
if(strcmp(cmd_filenames_given[i], "--") == 0) {
cmd_filenames_count = i;
cmd_diff_options_count = argc - i - 1;
cmd_diff_options_given = argv + i + 1;
}
i += 1;
}
if (command->diff_arg_type == NoDiffArgs && cmd_diff_options_count >= 0) {
prcserror << "This command does not accept arguments following "
<< squote ("--") << dotendl;
exit(2);
}
if (cmd_diff_options_count < 0 && command->diff_arg_type == ReallyDiffArgs) {
const char* pdo = get_environ_var ("PRCS_DIFF_OPTIONS");
if (pdo) {
CharPtrArray *env_diff_options = new CharPtrArray;
char *p;
p = strtok (p_strdup (pdo), " \t\n");
while (p) {
env_diff_options->append (p);
p = strtok(NULL, " \t\n");
}
cmd_diff_options_given = (char**)env_diff_options->cast();
cmd_diff_options_count = env_diff_options->length();
/* @@@ leak */
}
}
if (command->n_files_allowed == -1) {
If_fail(set_cmd_filenames())
exit(2);
}
return true;
}
static void usage(char** argv)
{
if (illegal_option) {
cout << options_summary << "\n";
} else if(command == NULL) {
cout << "Usage: " << strip_leading_path(argv[0])
<< " command [subcommand] [option ...] "
"[project [file-or-dir]]\n";
cout << general_help_string;
} else if(strcmp(command->command_name, "admin") == 0) {
if(subcommand == NULL) {
cout << admin_help_string;
} else {
cout << subcommand->help_string;
}
} else {
cout << command->help_string;
}
exit(2);
}
/* Cleanup */
void install_cleanup_handler(PrVoidError (* handler)(void *, bool), void* data)
{
cleanup_handler_list = new CleanupHandler(handler, data, cleanup_handler_list);
}
void install_alarm_handler(PrVoidError (* handler)(void *, bool), void* data)
{
alarm_handler_list = new CleanupHandler(handler, data, alarm_handler_list);
}
static PrVoidError clean_up(bool signaled)
{
sigset_t signal_mask;
sigfillset(&signal_mask);
sigdelset(&signal_mask, SIGABRT);
sigprocmask(SIG_SETMASK, &signal_mask, NULL);
CleanupHandler* last_handler;
while(cleanup_handler_list) {
last_handler = cleanup_handler_list;
cleanup_handler_list = cleanup_handler_list->next_handler;
Return_if_fail(last_handler->handler(last_handler->data, signaled));
delete last_handler;
}
if(temp_directory
#ifdef PRCS_DEVEL
&& !option_debug
#endif
) {
fs_nuke_file(temp_directory);
temp_directory = NULL;
}
#if defined(__BEOS__)
if (beos_alarm_id > 0) {
kill_thread(beos_alarm_id);
beos_alarm_id = 0;
}
#endif
return NoError;
}
static void bad_clean_up_handler(SIGNAL_ARG_TYPE)
{
prcserror << "Internal error, attempting to drop core" << dotendl;
bug ();
clean_up(true);
abort();
}
static void clean_up_handler(SIGNAL_ARG_TYPE)
{
clean_up(true);
exit(2);
}
static void alarm_handler(SIGNAL_ARG_TYPE)
{
CleanupHandler* handler = alarm_handler_list;
for (; handler; handler = handler->next_handler)
handler->handler(handler->data, false);
}
#if defined(__BEOS__)
static int32 beos_alarm_thread (void * data)
{
while (1) {
snooze( (STALE_LOCK_TIMEOUT - 30) * 1000000 );
alarm_handler(SIGALRM);
}
}
#endif
static void chld_handler(SIGNAL_ARG_TYPE)
{
/* Silly Solaris does not interrupt select() for SIGCHLD when there
* is no handler!!! */
}
static void handle_signals()
{
struct sigaction act;
sigset_t signal_mask;
int i;
sigfillset(&signal_mask);
sigdelset(&signal_mask, SIGABRT);
act.sa_handler = clean_up_handler;
act.sa_mask = signal_mask;
#ifdef SA_RESTART
act.sa_flags = SA_RESTART;
#else
act.sa_flags = 0;
#endif
for(i = 0; friendly_signals[i]; i += 1)
sigaction(friendly_signals[i], &act, NULL);
act.sa_handler = bad_clean_up_handler;
for(i = 0; unfriendly_signals[i]; i += 1)
sigaction(unfriendly_signals[i], &act, NULL);
sigemptyset(&signal_mask);
/* SIGCONT */
act.sa_handler = continue_handler;
act.sa_mask = signal_mask;
sigaction(SIGCONT, &act, NULL);
/* SIGCHLD */
act.sa_handler = chld_handler;
act.sa_mask = signal_mask;
sigaction (SIGCHLD, &act, NULL);
#if !defined(__BEOS__)
/* SIGALRM */
act.sa_handler = alarm_handler;
act.sa_mask = signal_mask;
sigaction (SIGALRM, &act, NULL);
struct itimerval itval;
itval.it_interval.tv_sec = STALE_LOCK_TIMEOUT - 30;
itval.it_interval.tv_usec = 0;
itval.it_value.tv_sec = STALE_LOCK_TIMEOUT - 30;
itval.it_value.tv_usec = 0;
setitimer (ITIMER_REAL, &itval, NULL);
#endif
}
/* Temp dirs */
static PrVoidError make_temp_dir()
{
const char* tmpdir;
Dstring *dir;
int len, count = getpid();
struct stat sbuf;
if((tmpdir = get_environ_var ("TMPDIR")) == NULL)
tmpdir = "/tmp";
if(!fs_is_directory(tmpdir) || !fs_file_wrandex(tmpdir))
pthrow prcserror << "Can't access temp directory "
<< squote(tmpdir) << perror;
if(tmpdir[0] != '/')
pthrow prcserror << "TMPDIR must be an absolute path" << dotendl;
dir = new Dstring(tmpdir);
if (dir->index(dir->length() - 1) != '/')
dir->append ('/');
dir->append("prcs_temp.");
len = dir->length();
while(true) {
dir->append_int(count);
if(stat(*dir, &sbuf) < 0)
break;
dir->truncate(len);
count += 1;
}
temp_directory = *dir;
If_fail(Err_mkdir(temp_directory, (mode_t)0700))
pthrow prcserror << "Mkdir failed for file " << squote(temp_directory) << perror;
return NoError;
}
static PrVoidError make_temp_files()
{
/* all of this memory gets leaked */
Return_if_fail(make_temp_dir());
temp_file_1 = make_temp_file("");
temp_file_2 = make_temp_file("");
temp_file_3 = make_temp_file("");
return NoError;
}
static void check_umask()
{
mode_t user_file_creation_mask = get_umask();
if(user_file_creation_mask & 0700) {
prcswarning << "Your umask will prevent PRCS from functioning properly. "
"Proceeding with user RWX permissions" << dotendl;
umask(user_file_creation_mask & 0077);
}
}
int main(int argc, char** argv)
{
PrPrcsExitStatusError exitval(ExitSuccess);
setup_streams(argc, argv);
handle_signals();
#if defined(__BEOS__)
/* We simulate the alarm() with a dedicated thread - this makes
* executing the alarm signal handler much safer.
*/
beos_alarm_id = spawn_thread( beos_alarm_thread, "prcs alarm thread"
, B_NORMAL_PRIORITY, NULL);
if (beos_alarm_id < 0) {
prcserror << "Can't create the alarm thread: "
<< strerror(beos_alarm_id)
<< dotendl;
exit(2);
}
status_t beos_thread_state = resume_thread(beos_alarm_id);
if (B_NO_ERROR != beos_thread_state) {
prcserror << "Can't start the alarm thread: "
<< strerror(beos_thread_state)
<< dotendl;
exit(2);
}
#endif
if(VC_check_token_match(get_login(), "login") <= 0) {
prcserror << "Your login name, " << squote(get_login()) << ", may confuse the lexer" << dotendl;
bug();
exit(2);
}
check_umask();
if (read_command_line(argc, argv) == false) {
usage(argv);
exit(2);
}
adjust_streams();
If_fail(make_temp_files())
exit(2);
if(!option_force_resolution && !isatty(STDIN_FILENO) ) {
prcserror << "Please specify -f to run PRCS without a controlling terminal"
<< dotendl;
exit(2);
}
If_fail(check_system_commands()) {
exit(2);
}
if(subcommand == NULL) {
exitval = invoke_command(command);
} else {
exitval = invoke_command(subcommand);
}
bool clean_failed = false;
If_fail(clean_up(false))
clean_failed = true;
if(Failure(exitval) || clean_failed) {
if(exitval.error_val() != UserAbort || clean_failed) {
prcserror << "Command failed" << dotendl;
}
return 2;
}
switch(exitval.non_error_val()) {
case ExitSuccess:
case ExitNoDiffs:
return 0;
case ExitDiffs:
default:
return 1;
}
}
syntax highlighted by Code2HTML, v. 0.9.1