#include "cvs.h"
#include "getline.h"
#include "yesno.h"
#include "watch.h"
#include "edit.h"
#include "fileattr.h"
static int watch_onoff (int, char **);
static bool check_edited = false;
static int setting_default;
static int turning_on;
static bool setting_tedit;
static bool setting_tunedit;
static bool setting_tcommit;
static int
onoff_fileproc (void *callerdat, struct file_info *finfo)
{
fileattr_get0 (finfo->file, "_watched");
fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
return 0;
}
static int
onoff_filesdoneproc (void *callerdat, int err, const char *repository,
const char *update_dir, List *entries)
{
if (setting_default)
{
fileattr_get0 (NULL, "_watched");
fileattr_set (NULL, "_watched", turning_on ? "" : NULL);
}
return err;
}
static int
watch_onoff (int argc, char **argv)
{
int c;
int local = 0;
int err;
optind = 0;
while ((c = getopt (argc, argv, "+lR")) != -1)
{
switch (c)
{
case 'l':
local = 1;
break;
case 'R':
local = 0;
break;
case '?':
default:
usage (watch_usage);
break;
}
}
argc -= optind;
argv += optind;
#ifdef CLIENT_SUPPORT
if (current_parsed_root->isremote)
{
start_server ();
ign_setup ();
if (local)
send_arg ("-l");
send_arg ("--");
send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
send_file_names (argc, argv, SEND_EXPAND_WILD);
send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0);
return get_responses_and_close ();
}
#endif
setting_default = (argc <= 0);
lock_tree_promotably (argc, argv, local, W_LOCAL, 0);
err = start_recursion (onoff_fileproc, onoff_filesdoneproc, NULL, NULL,
NULL, argc, argv, local, W_LOCAL, 0, CVS_LOCK_WRITE,
NULL, 0, NULL);
Lock_Cleanup ();
return err;
}
int
watch_on (int argc, char **argv)
{
turning_on = 1;
return watch_onoff (argc, argv);
}
int
watch_off (int argc, char **argv)
{
turning_on = 0;
return watch_onoff (argc, argv);
}
static int
dummy_fileproc (void *callerdat, struct file_info *finfo)
{
return 0;
}
static int
ncheck_fileproc (void *callerdat, struct file_info *finfo)
{
int notif_type;
char *filename;
char *val;
char *cp;
char *watches;
FILE *fp;
char *line = NULL;
size_t line_len = 0;
fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
if (fp == NULL)
{
if (!existence_error (errno))
error (0, errno, "cannot open %s", CVSADM_NOTIFY);
return 0;
}
while (getline (&line, &line_len, fp) > 0)
{
notif_type = line[0];
if (notif_type == '\0')
continue;
filename = line + 1;
cp = strchr (filename, '\t');
if (cp == NULL)
continue;
*cp++ = '\0';
val = cp;
cp = strchr (val, '\t');
if (cp == NULL)
continue;
*cp++ = '+';
cp = strchr (cp, '\t');
if (cp == NULL)
continue;
*cp++ = '+';
cp = strchr (cp, '\t');
if (cp == NULL)
continue;
*cp++ = '\0';
watches = cp;
cp = strchr (cp, '\n');
if (cp == NULL)
continue;
*cp = '\0';
notify_do (notif_type, filename, finfo->update_dir, getcaller (), val,
watches, finfo->repository);
}
free (line);
if (ferror (fp))
error (0, errno, "cannot read %s", CVSADM_NOTIFY);
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", CVSADM_NOTIFY);
if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
return 0;
}
static int
send_notifications (int argc, char **argv, int local)
{
int err = 0;
#ifdef CLIENT_SUPPORT
if (current_parsed_root->isremote)
{
if (strcmp (cvs_cmd_name, "release") != 0)
{
start_server ();
ign_setup ();
}
err += start_recursion (dummy_fileproc, NULL, NULL, NULL, NULL, argc,
argv, local, W_LOCAL, 0, 0, NULL, 0, NULL);
send_to_server ("noop\012", 0);
if (strcmp (cvs_cmd_name, "release") == 0)
err += get_server_responses ();
else
err += get_responses_and_close ();
}
else
#endif
{
err += start_recursion (ncheck_fileproc, NULL, NULL, NULL, NULL, argc,
argv, local, W_LOCAL, 0, CVS_LOCK_WRITE, NULL,
0, NULL);
Lock_Cleanup ();
}
return err;
}
void editors_output (const char *fullname, const char *p)
{
cvs_output (fullname, 0);
while (1)
{
cvs_output ("\t", 1);
while (*p != '>' && *p != '\0')
cvs_output (p++, 1);
if (*p == '\0')
{
cvs_output ("\n", 1);
break;
}
++p;
cvs_output ("\t", 1);
while (1)
{
while (*p != '+' && *p != ',' && *p != '\0')
cvs_output (p++, 1);
if (*p == '\0')
{
cvs_output ("\n", 1);
return;
}
if (*p == ',')
{
++p;
break;
}
++p;
cvs_output ("\t", 1);
}
cvs_output ("\n", 1);
}
}
static int find_editors_and_output (struct file_info *finfo)
{
char *them;
them = fileattr_get0 (finfo->file, "_editors");
if (them == NULL)
return 0;
editors_output (finfo->fullname, them);
return 0;
}
void
edit_file (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
Node *node;
struct file_info finfo;
char *basefilename;
xchmod (filename, 1);
mkdir_if_needed (CVSADM_BASE);
basefilename = Xasprintf ("%s/%s", CVSADM_BASE, filename);
copy_file (filename, basefilename);
free (basefilename);
node = findnode_fn (ent_list, filename);
if (node != NULL)
{
finfo.file = filename;
finfo.fullname = short_pathname;
finfo.update_dir = dir_name (short_pathname);
base_register (&finfo, ((Entnode *) node->data)->version);
free ((char *)finfo.update_dir);
}
}
static int
edit_fileproc (void *callerdat, struct file_info *finfo)
{
FILE *fp;
time_t now;
char *ascnow;
Vers_TS *vers;
#if defined (CLIENT_SUPPORT)
assert (!(current_parsed_root->isremote && check_edited));
#else
assert (!check_edited);
#endif
if (noexec)
return 0;
vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
if (!vers->vn_user)
{
error (0, 0, "no such file %s; ignored", finfo->fullname);
return 1;
}
#ifdef CLIENT_SUPPORT
if (!current_parsed_root->isremote)
#endif
{
char *editors = fileattr_get0 (finfo->file, "_editors");
if (editors)
{
if (check_edited)
{
if (!quiet)
editors_output (finfo->fullname, editors);
if (!really_quiet)
error (0, 0, "Skipping file `%s' due to existing editors.",
finfo->fullname);
return 1;
}
free (editors);
}
}
fp = xfopen (CVSADM_NOTIFY, "a");
(void) time (&now);
ascnow = asctime (gmtime (&now));
ascnow[24] = '\0';
if (ascnow[8] == '0') ascnow[8] = ' ';
fprintf (fp, "E%s\t%s -0000\t%s\t%s\t", finfo->file,
ascnow, hostname, CurDir);
if (setting_tedit)
fprintf (fp, "E");
if (setting_tunedit)
fprintf (fp, "U");
if (setting_tcommit)
fprintf (fp, "C");
fprintf (fp, "\n");
if (fclose (fp) < 0)
{
if (finfo->update_dir[0] == '\0')
error (0, errno, "cannot close %s", CVSADM_NOTIFY);
else
error (0, errno, "cannot close %s/%s", finfo->update_dir,
CVSADM_NOTIFY);
}
#ifdef SERVER_SUPPORT
if (server_active)
server_edit_file (finfo);
else
#endif
edit_file (NULL, finfo->entries, finfo->fullname, finfo->file);
return 0;
}
static const char *const edit_usage[] =
{
"Usage: %s %s [-lRcf] [-a <action>]... [<file>]...\n",
"-l\tLocal directory only, not recursive.\n",
"-R\tProcess directories recursively (default).\n",
"-a\tSpecify action to register for temporary watch, one of:\n",
" \t`edit', `unedit', `commit', `all', `none' (defaults to `all').\n",
"-c\tCheck for <file>s edited by others and abort if found.\n",
"-f\tAllow edit if <file>s are edited by others (default).\n",
"(Specify the --help global option for a list of other help options.)\n",
NULL
};
int
edit (int argc, char **argv)
{
int local = 0;
int c;
int err = 0;
bool a_omitted, a_all, a_none;
if (argc == -1)
usage (edit_usage);
a_omitted = true;
a_all = false;
a_none = false;
setting_tedit = false;
setting_tunedit = false;
setting_tcommit = false;
optind = 0;
while ((c = getopt (argc, argv, "+cflRa:")) != -1)
{
switch (c)
{
case 'c':
check_edited = true;
break;
case 'f':
check_edited = false;
break;
case 'l':
local = 1;
break;
case 'R':
local = 0;
break;
case 'a':
a_omitted = false;
if (strcmp (optarg, "edit") == 0)
setting_tedit = true;
else if (strcmp (optarg, "unedit") == 0)
setting_tunedit = true;
else if (strcmp (optarg, "commit") == 0)
setting_tcommit = true;
else if (strcmp (optarg, "all") == 0)
{
a_all = true;
a_none = false;
setting_tedit = true;
setting_tunedit = true;
setting_tcommit = true;
}
else if (strcmp (optarg, "none") == 0)
{
a_none = true;
a_all = false;
setting_tedit = false;
setting_tunedit = false;
setting_tcommit = false;
}
else
usage (edit_usage);
break;
case '?':
default:
usage (edit_usage);
break;
}
}
argc -= optind;
argv += optind;
if (strpbrk (hostname, "+,>;=\t\n") != NULL)
error (1, 0,
"host name (%s) contains an invalid character (+,>;=\\t\\n)",
hostname);
if (strpbrk (CurDir, "+,>;=\t\n") != NULL)
error (1, 0,
"current directory (%s) contains an invalid character (+,>;=\\t\\n)",
CurDir);
#ifdef CLIENT_SUPPORT
if (check_edited && current_parsed_root->isremote)
{
start_server();
if (!supported_request ("edit"))
error (1, 0, "Server does not support enforced advisory locks.");
ign_setup();
send_to_server ("Hostname ", 0);
send_to_server (hostname, 0);
send_to_server ("\012", 1);
send_to_server ("LocalDir ", 0);
send_to_server (CurDir, 0);
send_to_server ("\012", 1);
if (local)
send_arg ("-l");
send_arg ("-c");
if (!a_omitted)
{
if (a_all)
option_with_arg ("-a", "all");
else if (a_none)
option_with_arg ("-a", "none");
else
{
if (setting_tedit)
option_with_arg ("-a", "edit");
if (setting_tunedit)
option_with_arg ("-a", "unedit");
if (setting_tcommit)
option_with_arg ("-a", "commit");
}
}
send_arg ("--");
send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
send_file_names (argc, argv, SEND_EXPAND_WILD);
send_to_server ("edit\012", 0);
return get_responses_and_close ();
}
#endif
if (a_omitted)
{
setting_tedit = true;
setting_tunedit = true;
setting_tcommit = true;
}
TRACE (TRACE_DATA, "edit(): EUC: %d%d%d edit-check: %d",
setting_tedit, setting_tunedit, setting_tcommit, check_edited);
err = start_recursion (edit_fileproc, NULL, NULL, NULL, NULL, argc, argv,
local, W_LOCAL, 0, 0, NULL, 0, NULL);
err += send_notifications (argc, argv, local);
return err;
}
static int unedit_fileproc (void *callerdat, struct file_info *finfo);
static int
unedit_fileproc (void *callerdat, struct file_info *finfo)
{
FILE *fp;
time_t now;
char *ascnow;
char *basefilename = NULL;
if (noexec)
return 0;
basefilename = Xasprintf ("%s/%s", CVSADM_BASE, finfo->file);
if (!isfile (basefilename))
{
free (basefilename);
return 0;
}
if (xcmp (finfo->file, basefilename) != 0)
{
printf ("%s has been modified; revert changes? ", finfo->fullname);
fflush (stderr);
fflush (stdout);
if (!yesno ())
{
free (basefilename);
return 0;
}
}
rename_file (basefilename, finfo->file);
free (basefilename);
fp = xfopen (CVSADM_NOTIFY, "a");
(void) time (&now);
ascnow = asctime (gmtime (&now));
ascnow[24] = '\0';
if (ascnow[8] == '0') ascnow[8] = ' ';
fprintf (fp, "U%s\t%s -0000\t%s\t%s\t\n", finfo->file,
ascnow, hostname, CurDir);
if (fclose (fp) < 0)
{
if (finfo->update_dir[0] == '\0')
error (0, errno, "cannot close %s", CVSADM_NOTIFY);
else
error (0, errno, "cannot close %s/%s", finfo->update_dir,
CVSADM_NOTIFY);
}
{
char *baserev;
Node *node;
Entnode *entdata;
baserev = base_get (finfo);
node = findnode_fn (finfo->entries, finfo->file);
if (node != NULL)
{
entdata = node->data;
if (baserev == NULL)
{
error (0, 0, "%s not mentioned in %s", finfo->fullname,
CVSADM_BASEREV);
if (unlink_file (finfo->file) < 0)
error (0, errno, "cannot remove %s", finfo->fullname);
error (0, 0, "run update to complete the unedit");
return 0;
}
Register (finfo->entries, finfo->file, baserev, entdata->timestamp,
entdata->options, entdata->tag, entdata->date,
entdata->conflict);
}
free (baserev);
base_deregister (finfo);
}
xchmod (finfo->file, 0);
return 0;
}
static const char *const unedit_usage[] =
{
"Usage: %s %s [-lR] [<file>]...\n",
"-l\tLocal directory only, not recursive.\n",
"-R\tProcess directories recursively (default).\n",
"(Specify the --help global option for a list of other help options.)\n",
NULL
};
int
unedit (int argc, char **argv)
{
int local = 0;
int c;
int err;
if (argc == -1)
usage (unedit_usage);
optind = 0;
while ((c = getopt (argc, argv, "+lR")) != -1)
{
switch (c)
{
case 'l':
local = 1;
break;
case 'R':
local = 0;
break;
case '?':
default:
usage (unedit_usage);
break;
}
}
argc -= optind;
argv += optind;
err = start_recursion (unedit_fileproc, NULL, NULL, NULL, NULL, argc, argv,
local, W_LOCAL, 0, 0, NULL, 0, NULL);
err += send_notifications (argc, argv, local);
return err;
}
void
mark_up_to_date (const char *file)
{
char *base;
base = Xasprintf ("%s/%s", CVSADM_BASE, file);
if (unlink_file (base) < 0 && ! existence_error (errno))
error (0, errno, "cannot remove %s", file);
free (base);
}
void
editor_set (const char *filename, const char *editor, const char *val)
{
char *edlist;
char *newlist;
edlist = fileattr_get0 (filename, "_editors");
newlist = fileattr_modify (edlist, editor, val, '>', ',');
if (!((edlist == NULL && newlist == NULL)
|| (edlist != NULL
&& newlist != NULL
&& strcmp (edlist, newlist) == 0)))
fileattr_set (filename, "_editors", newlist);
if (edlist != NULL)
free (edlist);
if (newlist != NULL)
free (newlist);
}
struct notify_proc_args {
const char *type;
const char *who;
const char *notifyee;
const char *file;
};
static int
notify_proc (const char *repository, const char *filter, void *closure)
{
char *cmdline;
FILE *pipefp;
const char *srepos = Short_Repository (repository);
struct notify_proc_args *args = closure;
cmdline = format_cmdline (
#ifdef SUPPORT_OLD_INFO_FMT_STRINGS
false, srepos,
#endif
filter,
"c", "s", cvs_cmd_name,
#ifdef SERVER_SUPPORT
"R", "s", referrer ? referrer->original : "NONE",
#endif
"p", "s", srepos,
"r", "s", current_parsed_root->directory,
"s", "s", args->notifyee,
(char *) NULL);
if (!cmdline || !strlen (cmdline))
{
if (cmdline) free (cmdline);
error (0, 0, "pretag proc resolved to the empty string!");
return 1;
}
pipefp = run_popen (cmdline, "w");
if (pipefp == NULL)
{
error (0, errno, "cannot write entry to notify filter: %s", cmdline);
free (cmdline);
return 1;
}
fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
fprintf (pipefp, "By %s\n", args->who);
free (cmdline);
return pclose (pipefp);
}
void
notify_do (int type, const char *filename, const char *update_dir,
const char *who, const char *val, const char *watches,
const char *repository)
{
static struct addremove_args blank;
struct addremove_args args;
char *watchers;
char *p;
char *endp;
char *nextp;
if (type == 'E' && !check_edited && !quiet)
{
char *editors = fileattr_get0 (filename, "_editors");
if (editors)
{
const char *tmp;
if (update_dir && *update_dir)
tmp = Xasprintf ("%s/%s", update_dir, filename);
else
tmp = filename;
editors_output (tmp, editors);
if (update_dir && *update_dir) free ((char *)tmp);
free (editors);
}
}
args = blank;
switch (type)
{
case 'E':
if (strpbrk (val, ",>;=\n") != NULL)
{
error (0, 0, "invalid character in editor value");
return;
}
editor_set (filename, who, val);
break;
case 'U':
case 'C':
editor_set (filename, who, NULL);
break;
default:
return;
}
watchers = fileattr_get0 (filename, "_watchers");
p = watchers;
while (p != NULL)
{
char *q;
char *endq;
char *nextq;
char *notif;
endp = strchr (p, '>');
if (endp == NULL)
break;
nextp = strchr (p, ',');
if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
{
p = nextp == NULL ? nextp : nextp + 1;
continue;
}
q = endp + 1;
notif = NULL;
while (q != NULL)
{
endq = strchr (q, '+');
if (endq == NULL || (nextp != NULL && endq > nextp))
{
if (nextp == NULL)
endq = q + strlen (q);
else
endq = nextp;
nextq = NULL;
}
else
nextq = endq + 1;
if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
{
notif = "edit";
}
else if (type == 'U'
&& endq - q == 6 && strncmp ("unedit", q, 6) == 0)
{
notif = "unedit";
}
else if (type == 'C'
&& endq - q == 6 && strncmp ("commit", q, 6) == 0)
{
notif = "commit";
}
else if (type == 'E'
&& endq - q == 5 && strncmp ("tedit", q, 5) == 0)
{
if (notif == NULL)
notif = "temporary edit";
}
else if (type == 'U'
&& endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
{
if (notif == NULL)
notif = "temporary unedit";
}
else if (type == 'C'
&& endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
{
if (notif == NULL)
notif = "temporary commit";
}
q = nextq;
}
if (nextp != NULL)
++nextp;
if (notif != NULL)
{
struct notify_proc_args args;
size_t len = endp - p;
FILE *fp;
char *usersname;
char *line = NULL;
size_t line_len = 0;
args.notifyee = NULL;
usersname = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSROOTADM_USERS);
fp = CVS_FOPEN (usersname, "r");
if (fp == NULL && !existence_error (errno))
error (0, errno, "cannot read %s", usersname);
if (fp != NULL)
{
while (getline (&line, &line_len, fp) >= 0)
{
if (strncmp (line, p, len) == 0
&& line[len] == ':')
{
char *cp;
args.notifyee = xstrdup (line + len + 1);
cp = strpbrk (args.notifyee, ":\n");
if (cp != NULL)
*cp = '\0';
break;
}
}
if (ferror (fp))
error (0, errno, "cannot read %s", usersname);
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", usersname);
}
free (usersname);
if (line != NULL)
free (line);
if (args.notifyee == NULL)
{
char *tmp;
tmp = xmalloc (endp - p + 1);
strncpy (tmp, p, endp - p);
tmp[endp - p] = '\0';
args.notifyee = tmp;
}
args.type = notif;
args.who = who;
args.file = filename;
(void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc,
PIOPT_ALL, &args);
free ((char *)args.notifyee);
}
p = nextp;
}
if (watchers != NULL)
free (watchers);
switch (type)
{
case 'E':
if (*watches == 'E')
{
args.add_tedit = 1;
++watches;
}
if (*watches == 'U')
{
args.add_tunedit = 1;
++watches;
}
if (*watches == 'C')
{
args.add_tcommit = 1;
}
watch_modify_watchers (filename, &args);
break;
case 'U':
case 'C':
args.remove_temp = 1;
watch_modify_watchers (filename, &args);
break;
}
}
#ifdef CLIENT_SUPPORT
void
notify_check (const char *repository, const char *update_dir)
{
FILE *fp;
char *line = NULL;
size_t line_len = 0;
if (!server_started)
return;
fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
if (fp == NULL)
{
if (!existence_error (errno))
error (0, errno, "cannot open %s", CVSADM_NOTIFY);
return;
}
while (getline (&line, &line_len, fp) > 0)
{
int notif_type;
char *filename;
char *val;
char *cp;
notif_type = line[0];
if (notif_type == '\0')
continue;
filename = line + 1;
cp = strchr (filename, '\t');
if (cp == NULL)
continue;
*cp++ = '\0';
val = cp;
client_notify (repository, update_dir, filename, notif_type, val);
}
if (line)
free (line);
if (ferror (fp))
error (0, errno, "cannot read %s", CVSADM_NOTIFY);
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", CVSADM_NOTIFY);
}
#endif
static const char *const editors_usage[] =
{
"Usage: %s %s [-lR] [<file>]...\n",
"-l\tProcess this directory only (not recursive).\n",
"-R\tProcess directories recursively (default).\n",
"(Specify the --help global option for a list of other help options.)\n",
NULL
};
static int
editors_fileproc (void *callerdat, struct file_info *finfo)
{
return find_editors_and_output (finfo);
}
int
editors (int argc, char **argv)
{
int local = 0;
int c;
if (argc == -1)
usage (editors_usage);
optind = 0;
while ((c = getopt (argc, argv, "+lR")) != -1)
{
switch (c)
{
case 'l':
local = 1;
break;
case 'R':
local = 0;
break;
case '?':
default:
usage (editors_usage);
break;
}
}
argc -= optind;
argv += optind;
#ifdef CLIENT_SUPPORT
if (current_parsed_root->isremote)
{
start_server ();
ign_setup ();
if (local)
send_arg ("-l");
send_arg ("--");
send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
send_file_names (argc, argv, SEND_EXPAND_WILD);
send_to_server ("editors\012", 0);
return get_responses_and_close ();
}
#endif
return start_recursion (editors_fileproc, NULL, NULL, NULL, NULL,
argc, argv, local, W_LOCAL, 0, CVS_LOCK_READ, NULL,
0, NULL);
}