117721Speter/* Implementation for "cvs edit", "cvs watch on", and related commands
217721Speter
317721Speter   This program is free software; you can redistribute it and/or modify
417721Speter   it under the terms of the GNU General Public License as published by
517721Speter   the Free Software Foundation; either version 2, or (at your option)
617721Speter   any later version.
717721Speter
817721Speter   This program is distributed in the hope that it will be useful,
917721Speter   but WITHOUT ANY WARRANTY; without even the implied warranty of
1017721Speter   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1125839Speter   GNU General Public License for more details.  */
1217721Speter
1317721Speter#include "cvs.h"
1417721Speter#include "getline.h"
1517721Speter#include "watch.h"
1617721Speter#include "edit.h"
1717721Speter#include "fileattr.h"
1817721Speter
1917721Speterstatic int watch_onoff PROTO ((int, char **));
2017721Speter
2117721Speterstatic int setting_default;
2217721Speterstatic int turning_on;
2317721Speter
2417721Speterstatic int setting_tedit;
2517721Speterstatic int setting_tunedit;
2617721Speterstatic int setting_tcommit;
2717721Speter
2825839Speterstatic int onoff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
2917721Speter
3017721Speterstatic int
3125839Speteronoff_fileproc (callerdat, finfo)
3225839Speter    void *callerdat;
3317721Speter    struct file_info *finfo;
3417721Speter{
35175261Sobrien    char *watched = fileattr_get0 (finfo->file, "_watched");
3617721Speter    fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL);
37175261Sobrien    if (watched != NULL)
38175261Sobrien	free (watched);
3917721Speter    return 0;
4017721Speter}
4117721Speter
4217721Speter
43128266Speter
44128266Speterstatic int onoff_filesdoneproc PROTO ((void *, int, const char *, const char *,
45128266Speter                                       List *));
46128266Speter
4717721Speterstatic int
4825839Speteronoff_filesdoneproc (callerdat, err, repository, update_dir, entries)
4925839Speter    void *callerdat;
5017721Speter    int err;
51128266Speter    const char *repository;
52128266Speter    const char *update_dir;
5325839Speter    List *entries;
5417721Speter{
5517721Speter    if (setting_default)
56175261Sobrien    {
57175261Sobrien	char *watched = fileattr_get0 (NULL, "_watched");
5817721Speter	fileattr_set (NULL, "_watched", turning_on ? "" : NULL);
59175261Sobrien	if (watched != NULL)
60175261Sobrien	    free (watched);
61175261Sobrien    }
6217721Speter    return err;
6317721Speter}
6417721Speter
6517721Speterstatic int
6617721Speterwatch_onoff (argc, argv)
6717721Speter    int argc;
6817721Speter    char **argv;
6917721Speter{
7017721Speter    int c;
7117721Speter    int local = 0;
7217721Speter    int err;
7317721Speter
7426065Speter    optind = 0;
7525839Speter    while ((c = getopt (argc, argv, "+lR")) != -1)
7617721Speter    {
7717721Speter	switch (c)
7817721Speter	{
7917721Speter	    case 'l':
8017721Speter		local = 1;
8117721Speter		break;
8225839Speter	    case 'R':
8325839Speter		local = 0;
8425839Speter		break;
8517721Speter	    case '?':
8617721Speter	    default:
8717721Speter		usage (watch_usage);
8817721Speter		break;
8917721Speter	}
9017721Speter    }
9117721Speter    argc -= optind;
9217721Speter    argv += optind;
9317721Speter
9417721Speter#ifdef CLIENT_SUPPORT
9581404Speter    if (current_parsed_root->isremote)
9617721Speter    {
9717721Speter	start_server ();
9817721Speter
9917721Speter	ign_setup ();
10017721Speter
10117721Speter	if (local)
10217721Speter	    send_arg ("-l");
103107484Speter	send_arg ("--");
10454427Speter	send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
10517721Speter	send_file_names (argc, argv, SEND_EXPAND_WILD);
10617721Speter	send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0);
10717721Speter	return get_responses_and_close ();
10817721Speter    }
10917721Speter#endif /* CLIENT_SUPPORT */
11017721Speter
11117721Speter    setting_default = (argc <= 0);
11217721Speter
11381404Speter    lock_tree_for_write (argc, argv, local, W_LOCAL, 0);
11417721Speter
11517721Speter    err = start_recursion (onoff_fileproc, onoff_filesdoneproc,
11625839Speter			   (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
117109655Speter			   argc, argv, local, W_LOCAL, 0, CVS_LOCK_NONE,
118128266Speter			   (char *) NULL, 0, (char *) NULL);
11917721Speter
12025839Speter    Lock_Cleanup ();
12117721Speter    return err;
12217721Speter}
12317721Speter
12417721Speterint
12517721Speterwatch_on (argc, argv)
12617721Speter    int argc;
12717721Speter    char **argv;
12817721Speter{
12917721Speter    turning_on = 1;
13017721Speter    return watch_onoff (argc, argv);
13117721Speter}
13217721Speter
13317721Speterint
13417721Speterwatch_off (argc, argv)
13517721Speter    int argc;
13617721Speter    char **argv;
13717721Speter{
13817721Speter    turning_on = 0;
13917721Speter    return watch_onoff (argc, argv);
14017721Speter}
14117721Speter
14225839Speterstatic int dummy_fileproc PROTO ((void *callerdat, struct file_info *finfo));
14317721Speter
14417721Speterstatic int
14525839Speterdummy_fileproc (callerdat, finfo)
14625839Speter    void *callerdat;
14717721Speter    struct file_info *finfo;
14817721Speter{
14917721Speter    /* This is a pretty hideous hack, but the gist of it is that recurse.c
150177391Sobrien       won't call cvs_notify_check unless there is a fileproc, so we
151177391Sobrien       can't just pass NULL for fileproc.  */
15217721Speter    return 0;
15317721Speter}
15417721Speter
15525839Speterstatic int ncheck_fileproc PROTO ((void *callerdat, struct file_info *finfo));
15617721Speter
15717721Speter/* Check for and process notifications.  Local only.  I think that doing
15817721Speter   this as a fileproc is the only way to catch all the
15917721Speter   cases (e.g. foo/bar.c), even though that means checking over and over
16017721Speter   for the same CVSADM_NOTIFY file which we removed the first time we
16117721Speter   processed the directory.  */
16217721Speter
16317721Speterstatic int
16425839Speterncheck_fileproc (callerdat, finfo)
16525839Speter    void *callerdat;
16617721Speter    struct file_info *finfo;
16717721Speter{
16817721Speter    int notif_type;
16917721Speter    char *filename;
17017721Speter    char *val;
17117721Speter    char *cp;
17217721Speter    char *watches;
17317721Speter
17417721Speter    FILE *fp;
17517721Speter    char *line = NULL;
17617721Speter    size_t line_len = 0;
17717721Speter
17817721Speter    /* We send notifications even if noexec.  I'm not sure which behavior
17917721Speter       is most sensible.  */
18017721Speter
18125839Speter    fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
18217721Speter    if (fp == NULL)
18317721Speter    {
18417721Speter	if (!existence_error (errno))
18517721Speter	    error (0, errno, "cannot open %s", CVSADM_NOTIFY);
18617721Speter	return 0;
18717721Speter    }
18817721Speter
18917721Speter    while (getline (&line, &line_len, fp) > 0)
19017721Speter    {
19117721Speter	notif_type = line[0];
19217721Speter	if (notif_type == '\0')
19317721Speter	    continue;
19417721Speter	filename = line + 1;
19517721Speter	cp = strchr (filename, '\t');
19617721Speter	if (cp == NULL)
19717721Speter	    continue;
19817721Speter	*cp++ = '\0';
19917721Speter	val = cp;
20017721Speter	cp = strchr (val, '\t');
20117721Speter	if (cp == NULL)
20217721Speter	    continue;
20317721Speter	*cp++ = '+';
20417721Speter	cp = strchr (cp, '\t');
20517721Speter	if (cp == NULL)
20617721Speter	    continue;
20717721Speter	*cp++ = '+';
20817721Speter	cp = strchr (cp, '\t');
20917721Speter	if (cp == NULL)
21017721Speter	    continue;
21117721Speter	*cp++ = '\0';
21217721Speter	watches = cp;
21317721Speter	cp = strchr (cp, '\n');
21417721Speter	if (cp == NULL)
21517721Speter	    continue;
21617721Speter	*cp = '\0';
21717721Speter
21817721Speter	notify_do (notif_type, filename, getcaller (), val, watches,
21917721Speter		   finfo->repository);
22017721Speter    }
22117721Speter    free (line);
22217721Speter
22317721Speter    if (ferror (fp))
22417721Speter	error (0, errno, "cannot read %s", CVSADM_NOTIFY);
22517721Speter    if (fclose (fp) < 0)
22617721Speter	error (0, errno, "cannot close %s", CVSADM_NOTIFY);
22717721Speter
22825839Speter    if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
22917721Speter	error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
23017721Speter
23117721Speter    return 0;
23217721Speter}
23317721Speter
23417721Speterstatic int send_notifications PROTO ((int, char **, int));
23517721Speter
23617721Speter/* Look through the CVSADM_NOTIFY file and process each item there
23717721Speter   accordingly.  */
23817721Speterstatic int
23917721Spetersend_notifications (argc, argv, local)
24017721Speter    int argc;
24117721Speter    char **argv;
24217721Speter    int local;
24317721Speter{
24417721Speter    int err = 0;
24517721Speter
24617721Speter#ifdef CLIENT_SUPPORT
24717721Speter    /* OK, we've done everything which needs to happen on the client side.
24817721Speter       Now we can try to contact the server; if we fail, then the
24917721Speter       notifications stay in CVSADM_NOTIFY to be sent next time.  */
25081404Speter    if (current_parsed_root->isremote)
25117721Speter    {
252128266Speter	if (strcmp (cvs_cmd_name, "release") != 0)
25317721Speter	{
25417721Speter	    start_server ();
25517721Speter	    ign_setup ();
25617721Speter	}
25717721Speter
25817721Speter	err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL,
25925839Speter				(DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
26017721Speter				argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
261128266Speter				0, (char *) NULL);
26217721Speter
26317721Speter	send_to_server ("noop\012", 0);
264128266Speter	if (strcmp (cvs_cmd_name, "release") == 0)
26517721Speter	    err += get_server_responses ();
26617721Speter	else
26717721Speter	    err += get_responses_and_close ();
26817721Speter    }
26917721Speter    else
27017721Speter#endif
27117721Speter    {
27217721Speter	/* Local.  */
27317721Speter
27481404Speter	lock_tree_for_write (argc, argv, local, W_LOCAL, 0);
27517721Speter	err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL,
27625839Speter				(DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
27717721Speter				argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
278128266Speter				0, (char *) NULL);
27925839Speter	Lock_Cleanup ();
28017721Speter    }
28117721Speter    return err;
28217721Speter}
28317721Speter
28425839Speterstatic int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
28517721Speter
28617721Speterstatic int
28725839Speteredit_fileproc (callerdat, finfo)
28825839Speter    void *callerdat;
28917721Speter    struct file_info *finfo;
29017721Speter{
29117721Speter    FILE *fp;
29217721Speter    time_t now;
29317721Speter    char *ascnow;
29417721Speter    char *basefilename;
29517721Speter
29617721Speter    if (noexec)
29717721Speter	return 0;
29817721Speter
29932785Speter    /* This is a somewhat screwy way to check for this, because it
30032785Speter       doesn't help errors other than the nonexistence of the file
30132785Speter       (e.g. permissions problems).  It might be better to rearrange
30232785Speter       the code so that CVSADM_NOTIFY gets written only after the
30332785Speter       various actions succeed (but what if only some of them
30432785Speter       succeed).  */
30532785Speter    if (!isfile (finfo->file))
30632785Speter    {
30732785Speter	error (0, 0, "no such file %s; ignored", finfo->fullname);
30832785Speter	return 0;
30932785Speter    }
31032785Speter
31117721Speter    fp = open_file (CVSADM_NOTIFY, "a");
31217721Speter
31317721Speter    (void) time (&now);
31417721Speter    ascnow = asctime (gmtime (&now));
31517721Speter    ascnow[24] = '\0';
31681404Speter    /* Fix non-standard format.  */
31781404Speter    if (ascnow[8] == '0') ascnow[8] = ' ';
31817721Speter    fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file,
31917721Speter	     ascnow, hostname, CurDir);
32017721Speter    if (setting_tedit)
32117721Speter	fprintf (fp, "E");
32217721Speter    if (setting_tunedit)
32317721Speter	fprintf (fp, "U");
32417721Speter    if (setting_tcommit)
32517721Speter	fprintf (fp, "C");
32617721Speter    fprintf (fp, "\n");
32717721Speter
32817721Speter    if (fclose (fp) < 0)
32917721Speter    {
33017721Speter	if (finfo->update_dir[0] == '\0')
33117721Speter	    error (0, errno, "cannot close %s", CVSADM_NOTIFY);
33217721Speter	else
33317721Speter	    error (0, errno, "cannot close %s/%s", finfo->update_dir,
33417721Speter		   CVSADM_NOTIFY);
33517721Speter    }
33617721Speter
33717721Speter    xchmod (finfo->file, 1);
33817721Speter
33917721Speter    /* Now stash the file away in CVSADM so that unedit can revert even if
34017721Speter       it can't communicate with the server.  We stash away a writable
34117721Speter       copy so that if the user removes the working file, then restores it
34217721Speter       with "cvs update" (which clears _editors but does not update
34317721Speter       CVSADM_BASE), then a future "cvs edit" can still win.  */
34425839Speter    /* Could save a system call by only calling mkdir_if_needed if
34525839Speter       trying to create the output file fails.  But copy_file isn't
34625839Speter       set up to facilitate that.  */
34725839Speter    mkdir_if_needed (CVSADM_BASE);
34817721Speter    basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
34917721Speter    strcpy (basefilename, CVSADM_BASE);
35017721Speter    strcat (basefilename, "/");
35117721Speter    strcat (basefilename, finfo->file);
35217721Speter    copy_file (finfo->file, basefilename);
35317721Speter    free (basefilename);
35417721Speter
35532785Speter    {
35632785Speter	Node *node;
35732785Speter
35832785Speter	node = findnode_fn (finfo->entries, finfo->file);
35932785Speter	if (node != NULL)
36032785Speter	    base_register (finfo, ((Entnode *) node->data)->version);
36132785Speter    }
36232785Speter
36317721Speter    return 0;
36417721Speter}
36517721Speter
36617721Speterstatic const char *const edit_usage[] =
36717721Speter{
368175261Sobrien    "Usage: %s %s [-lR] [-a <action>]... [<file>]...\n",
369175261Sobrien    "-l\tLocal directory only, not recursive.\n",
370175261Sobrien    "-R\tProcess directories recursively (default).\n",
371175261Sobrien    "-a\tSpecify action to register for temporary watch, one of:\n",
372175261Sobrien    "  \t`edit', `unedit', `commit', `all', or `none' (defaults to `all').\n",
373175261Sobrien    "(Specify the --help global option for a list of other help options.)\n",
37417721Speter    NULL
37517721Speter};
37617721Speter
37717721Speterint
37817721Speteredit (argc, argv)
37917721Speter    int argc;
38017721Speter    char **argv;
38117721Speter{
38217721Speter    int local = 0;
38317721Speter    int c;
38417721Speter    int err;
38517721Speter    int a_omitted;
38617721Speter
38717721Speter    if (argc == -1)
38817721Speter	usage (edit_usage);
38917721Speter
39017721Speter    a_omitted = 1;
39117721Speter    setting_tedit = 0;
39217721Speter    setting_tunedit = 0;
39317721Speter    setting_tcommit = 0;
39426065Speter    optind = 0;
39525839Speter    while ((c = getopt (argc, argv, "+lRa:")) != -1)
39617721Speter    {
39717721Speter	switch (c)
39817721Speter	{
39917721Speter	    case 'l':
40017721Speter		local = 1;
40117721Speter		break;
40225839Speter	    case 'R':
40325839Speter		local = 0;
40425839Speter		break;
40517721Speter	    case 'a':
40617721Speter		a_omitted = 0;
40717721Speter		if (strcmp (optarg, "edit") == 0)
40817721Speter		    setting_tedit = 1;
40917721Speter		else if (strcmp (optarg, "unedit") == 0)
41017721Speter		    setting_tunedit = 1;
41117721Speter		else if (strcmp (optarg, "commit") == 0)
41217721Speter		    setting_tcommit = 1;
41317721Speter		else if (strcmp (optarg, "all") == 0)
41417721Speter		{
41517721Speter		    setting_tedit = 1;
41617721Speter		    setting_tunedit = 1;
41717721Speter		    setting_tcommit = 1;
41817721Speter		}
41917721Speter		else if (strcmp (optarg, "none") == 0)
42017721Speter		{
42117721Speter		    setting_tedit = 0;
42217721Speter		    setting_tunedit = 0;
42317721Speter		    setting_tcommit = 0;
42417721Speter		}
42517721Speter		else
42617721Speter		    usage (edit_usage);
42717721Speter		break;
42817721Speter	    case '?':
42917721Speter	    default:
43017721Speter		usage (edit_usage);
43117721Speter		break;
43217721Speter	}
43317721Speter    }
43417721Speter    argc -= optind;
43517721Speter    argv += optind;
43617721Speter
43717721Speter    if (a_omitted)
43817721Speter    {
43917721Speter	setting_tedit = 1;
44017721Speter	setting_tunedit = 1;
44117721Speter	setting_tcommit = 1;
44217721Speter    }
44317721Speter
44466525Speter    if (strpbrk (hostname, "+,>;=\t\n") != NULL)
44566525Speter	error (1, 0,
44666525Speter	       "host name (%s) contains an invalid character (+,>;=\\t\\n)",
44766525Speter	       hostname);
44866525Speter    if (strpbrk (CurDir, "+,>;=\t\n") != NULL)
44966525Speter	error (1, 0,
45066525Speter"current directory (%s) contains an invalid character (+,>;=\\t\\n)",
45166525Speter	       CurDir);
45266525Speter
45317721Speter    /* No need to readlock since we aren't doing anything to the
45417721Speter       repository.  */
45517721Speter    err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL,
45625839Speter			   (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
457128266Speter			   argc, argv, local, W_LOCAL, 0, 0, (char *) NULL,
458128266Speter			   0, (char *) NULL);
45917721Speter
46017721Speter    err += send_notifications (argc, argv, local);
46117721Speter
46217721Speter    return err;
46317721Speter}
46417721Speter
46525839Speterstatic int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
46617721Speter
46717721Speterstatic int
46825839Speterunedit_fileproc (callerdat, finfo)
46925839Speter    void *callerdat;
47017721Speter    struct file_info *finfo;
47117721Speter{
47217721Speter    FILE *fp;
47317721Speter    time_t now;
47417721Speter    char *ascnow;
47517721Speter    char *basefilename;
47617721Speter
47717721Speter    if (noexec)
47817721Speter	return 0;
47917721Speter
48017721Speter    basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file));
48117721Speter    strcpy (basefilename, CVSADM_BASE);
48217721Speter    strcat (basefilename, "/");
48317721Speter    strcat (basefilename, finfo->file);
48417721Speter    if (!isfile (basefilename))
48517721Speter    {
48617721Speter	/* This file apparently was never cvs edit'd (e.g. we are uneditting
48717721Speter	   a directory where only some of the files were cvs edit'd.  */
48817721Speter	free (basefilename);
48917721Speter	return 0;
49017721Speter    }
49117721Speter
49217721Speter    if (xcmp (finfo->file, basefilename) != 0)
49317721Speter    {
49417721Speter	printf ("%s has been modified; revert changes? ", finfo->fullname);
49517721Speter	if (!yesno ())
49617721Speter	{
49717721Speter	    /* "no".  */
49817721Speter	    free (basefilename);
49917721Speter	    return 0;
50017721Speter	}
50117721Speter    }
50217721Speter    rename_file (basefilename, finfo->file);
50317721Speter    free (basefilename);
50417721Speter
50517721Speter    fp = open_file (CVSADM_NOTIFY, "a");
50617721Speter
50717721Speter    (void) time (&now);
50817721Speter    ascnow = asctime (gmtime (&now));
50917721Speter    ascnow[24] = '\0';
51081404Speter    /* Fix non-standard format.  */
51181404Speter    if (ascnow[8] == '0') ascnow[8] = ' ';
51217721Speter    fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file,
51317721Speter	     ascnow, hostname, CurDir);
51417721Speter
51517721Speter    if (fclose (fp) < 0)
51617721Speter    {
51717721Speter	if (finfo->update_dir[0] == '\0')
51817721Speter	    error (0, errno, "cannot close %s", CVSADM_NOTIFY);
51917721Speter	else
52017721Speter	    error (0, errno, "cannot close %s/%s", finfo->update_dir,
52117721Speter		   CVSADM_NOTIFY);
52217721Speter    }
52317721Speter
52432785Speter    /* Now update the revision number in CVS/Entries from CVS/Baserev.
52532785Speter       The basic idea here is that we are reverting to the revision
52632785Speter       that the user edited.  If we wanted "cvs update" to update
52732785Speter       CVS/Base as we go along (so that an unedit could revert to the
52832785Speter       current repository revision), we would need:
52932785Speter
53032785Speter       update (or all send_files?) (client) needs to send revision in
53132785Speter       new Entry-base request.  update (server/local) needs to check
53232785Speter       revision against repository and send new Update-base response
53332785Speter       (like Update-existing in that the file already exists.  While
53432785Speter       we are at it, might try to clean up the syntax by having the
53532785Speter       mode only in a "Mode" response, not in the Update-base itself).  */
53632785Speter    {
53732785Speter	char *baserev;
53832785Speter	Node *node;
53932785Speter	Entnode *entdata;
54032785Speter
54132785Speter	baserev = base_get (finfo);
54232785Speter	node = findnode_fn (finfo->entries, finfo->file);
54332785Speter	/* The case where node is NULL probably should be an error or
54432785Speter	   something, but I don't want to think about it too hard right
54532785Speter	   now.  */
54632785Speter	if (node != NULL)
54732785Speter	{
548128266Speter	    entdata = node->data;
54944852Speter	    if (baserev == NULL)
55044852Speter	    {
55144852Speter		/* This can only happen if the CVS/Baserev file got
55244852Speter		   corrupted.  We suspect it might be possible if the
55344852Speter		   user interrupts CVS, although I haven't verified
55444852Speter		   that.  */
55544852Speter		error (0, 0, "%s not mentioned in %s", finfo->fullname,
55644852Speter		       CVSADM_BASEREV);
55744852Speter
55844852Speter		/* Since we don't know what revision the file derives from,
55944852Speter		   keeping it around would be asking for trouble.  */
56044852Speter		if (unlink_file (finfo->file) < 0)
56144852Speter		    error (0, errno, "cannot remove %s", finfo->fullname);
56244852Speter
56344852Speter		/* This is cheesy, in a sense; why shouldn't we do the
56444852Speter		   update for the user?  However, doing that would require
56544852Speter		   contacting the server, so maybe this is OK.  */
56644852Speter		error (0, 0, "run update to complete the unedit");
56744852Speter		return 0;
56844852Speter	    }
56932785Speter	    Register (finfo->entries, finfo->file, baserev, entdata->timestamp,
57032785Speter		      entdata->options, entdata->tag, entdata->date,
57132785Speter		      entdata->conflict);
57232785Speter	}
57332785Speter	free (baserev);
57432785Speter	base_deregister (finfo);
57532785Speter    }
57632785Speter
57717721Speter    xchmod (finfo->file, 0);
57817721Speter    return 0;
57917721Speter}
58017721Speter
58166525Speterstatic const char *const unedit_usage[] =
58266525Speter{
583175261Sobrien    "Usage: %s %s [-lR] [<file>]...\n",
584175261Sobrien    "-l\tLocal directory only, not recursive.\n",
585175261Sobrien    "-R\tProcess directories recursively (default).\n",
586175261Sobrien    "(Specify the --help global option for a list of other help options.)\n",
58766525Speter    NULL
58866525Speter};
58966525Speter
59017721Speterint
59117721Speterunedit (argc, argv)
59217721Speter    int argc;
59317721Speter    char **argv;
59417721Speter{
59517721Speter    int local = 0;
59617721Speter    int c;
59717721Speter    int err;
59817721Speter
59917721Speter    if (argc == -1)
60066525Speter	usage (unedit_usage);
60117721Speter
60226065Speter    optind = 0;
60325839Speter    while ((c = getopt (argc, argv, "+lR")) != -1)
60417721Speter    {
60517721Speter	switch (c)
60617721Speter	{
60717721Speter	    case 'l':
60817721Speter		local = 1;
60917721Speter		break;
61025839Speter	    case 'R':
61125839Speter		local = 0;
61225839Speter		break;
61317721Speter	    case '?':
61417721Speter	    default:
61566525Speter		usage (unedit_usage);
61617721Speter		break;
61717721Speter	}
61817721Speter    }
61917721Speter    argc -= optind;
62017721Speter    argv += optind;
62117721Speter
62217721Speter    /* No need to readlock since we aren't doing anything to the
62317721Speter       repository.  */
62417721Speter    err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL,
62525839Speter			   (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
62617721Speter			   argc, argv, local, W_LOCAL, 0, 0, (char *)NULL,
627128266Speter			   0,  (char *) NULL);
62817721Speter
62917721Speter    err += send_notifications (argc, argv, local);
63017721Speter
63117721Speter    return err;
63217721Speter}
63317721Speter
63417721Spetervoid
63517721Spetermark_up_to_date (file)
636128266Speter    const char *file;
63717721Speter{
63817721Speter    char *base;
63917721Speter
64017721Speter    /* The file is up to date, so we better get rid of an out of
64117721Speter       date file in CVSADM_BASE.  */
64217721Speter    base = xmalloc (strlen (file) + 80);
64317721Speter    strcpy (base, CVSADM_BASE);
64417721Speter    strcat (base, "/");
64517721Speter    strcat (base, file);
64617721Speter    if (unlink_file (base) < 0 && ! existence_error (errno))
64717721Speter	error (0, errno, "cannot remove %s", file);
64817721Speter    free (base);
64917721Speter}
65017721Speter
651128266Speter
652128266Speter
65317721Spetervoid
65417721Spetereditor_set (filename, editor, val)
655128266Speter    const char *filename;
656128266Speter    const char *editor;
657128266Speter    const char *val;
65817721Speter{
65917721Speter    char *edlist;
66017721Speter    char *newlist;
66117721Speter
66217721Speter    edlist = fileattr_get0 (filename, "_editors");
66317721Speter    newlist = fileattr_modify (edlist, editor, val, '>', ',');
66417721Speter    /* If the attributes is unchanged, don't rewrite the attribute file.  */
66517721Speter    if (!((edlist == NULL && newlist == NULL)
66617721Speter	  || (edlist != NULL
66717721Speter	      && newlist != NULL
66817721Speter	      && strcmp (edlist, newlist) == 0)))
66917721Speter	fileattr_set (filename, "_editors", newlist);
67032785Speter    if (edlist != NULL)
67132785Speter	free (edlist);
67217721Speter    if (newlist != NULL)
67317721Speter	free (newlist);
67417721Speter}
67517721Speter
67617721Speterstruct notify_proc_args {
67717721Speter    /* What kind of notification, "edit", "tedit", etc.  */
678128266Speter    const char *type;
67917721Speter    /* User who is running the command which causes notification.  */
680128266Speter    const char *who;
68117721Speter    /* User to be notified.  */
682128266Speter    const char *notifyee;
68317721Speter    /* File.  */
684128266Speter    const char *file;
68517721Speter};
68617721Speter
687128266Speter
688128266Speter
68917721Speter/* Pass as a static until we get around to fixing Parse_Info to pass along
69017721Speter   a void * where we can stash it.  */
69117721Speterstatic struct notify_proc_args *notify_args;
69217721Speter
69317721Speter
694128266Speter
695128266Speterstatic int notify_proc PROTO ((const char *repository, const char *filter));
696128266Speter
69717721Speterstatic int
69817721Speternotify_proc (repository, filter)
699128266Speter    const char *repository;
700128266Speter    const char *filter;
70117721Speter{
70217721Speter    FILE *pipefp;
70317721Speter    char *prog;
70417721Speter    char *expanded_prog;
705128266Speter    const char *p;
70617721Speter    char *q;
707128266Speter    const char *srepos;
70817721Speter    struct notify_proc_args *args = notify_args;
70917721Speter
71017721Speter    srepos = Short_Repository (repository);
71117721Speter    prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1);
71217721Speter    /* Copy FILTER to PROG, replacing the first occurrence of %s with
71317721Speter       the notifyee.  We only allocated enough memory for one %s, and I doubt
71417721Speter       there is a need for more.  */
71517721Speter    for (p = filter, q = prog; *p != '\0'; ++p)
71617721Speter    {
71717721Speter	if (p[0] == '%')
71817721Speter	{
71917721Speter	    if (p[1] == 's')
72017721Speter	    {
72117721Speter		strcpy (q, args->notifyee);
72217721Speter		q += strlen (q);
72317721Speter		strcpy (q, p + 2);
72417721Speter		q += strlen (q);
72517721Speter		break;
72617721Speter	    }
72717721Speter	    else
72817721Speter		continue;
72917721Speter	}
73017721Speter	*q++ = *p;
73117721Speter    }
73217721Speter    *q = '\0';
73317721Speter
73417721Speter    /* FIXME: why are we calling expand_proc?  Didn't we already
73517721Speter       expand it in Parse_Info, before passing it to notify_proc?  */
73617721Speter    expanded_prog = expand_path (prog, "notify", 0);
73717721Speter    if (!expanded_prog)
73817721Speter    {
73917721Speter	free (prog);
74017721Speter	return 1;
74117721Speter    }
74217721Speter
74317721Speter    pipefp = run_popen (expanded_prog, "w");
74417721Speter    if (pipefp == NULL)
74517721Speter    {
74617721Speter	error (0, errno, "cannot write entry to notify filter: %s", prog);
74717721Speter	free (prog);
74817721Speter	free (expanded_prog);
74917721Speter	return 1;
75017721Speter    }
75117721Speter
75217721Speter    fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
75317721Speter    fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
75417721Speter    fprintf (pipefp, "By %s\n", args->who);
75517721Speter
75617721Speter    /* Lots more potentially useful information we could add here; see
75717721Speter       logfile_write for inspiration.  */
75817721Speter
75917721Speter    free (prog);
76017721Speter    free (expanded_prog);
76117721Speter    return (pclose (pipefp));
76217721Speter}
76317721Speter
76454427Speter/* FIXME: this function should have a way to report whether there was
76554427Speter   an error so that server.c can know whether to report Notified back
76654427Speter   to the client.  */
76717721Spetervoid
76817721Speternotify_do (type, filename, who, val, watches, repository)
76917721Speter    int type;
770128266Speter    const char *filename;
771128266Speter    const char *who;
772128266Speter    const char *val;
773128266Speter    const char *watches;
774128266Speter    const char *repository;
77517721Speter{
77617721Speter    static struct addremove_args blank;
77717721Speter    struct addremove_args args;
77817721Speter    char *watchers;
77917721Speter    char *p;
78017721Speter    char *endp;
78117721Speter    char *nextp;
78217721Speter
78317721Speter    /* Initialize fields to 0, NULL, or 0.0.  */
78417721Speter    args = blank;
78517721Speter    switch (type)
78617721Speter    {
78717721Speter	case 'E':
78854427Speter	    if (strpbrk (val, ",>;=\n") != NULL)
78954427Speter	    {
79054427Speter		error (0, 0, "invalid character in editor value");
79154427Speter		return;
79254427Speter	    }
79317721Speter	    editor_set (filename, who, val);
79417721Speter	    break;
79517721Speter	case 'U':
79617721Speter	case 'C':
79717721Speter	    editor_set (filename, who, NULL);
79817721Speter	    break;
79917721Speter	default:
80017721Speter	    return;
80117721Speter    }
80217721Speter
80317721Speter    watchers = fileattr_get0 (filename, "_watchers");
80417721Speter    p = watchers;
80517721Speter    while (p != NULL)
80617721Speter    {
80717721Speter	char *q;
80817721Speter	char *endq;
80917721Speter	char *nextq;
81017721Speter	char *notif;
81117721Speter
81217721Speter	endp = strchr (p, '>');
81317721Speter	if (endp == NULL)
81417721Speter	    break;
81517721Speter	nextp = strchr (p, ',');
81617721Speter
81717721Speter	if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
81817721Speter	{
81917721Speter	    /* Don't notify user of their own changes.  Would perhaps
82017721Speter	       be better to check whether it is the same working
82117721Speter	       directory, not the same user, but that is hairy.  */
82217721Speter	    p = nextp == NULL ? nextp : nextp + 1;
82317721Speter	    continue;
82417721Speter	}
82517721Speter
82617721Speter	/* Now we point q at a string which looks like
82717721Speter	   "edit+unedit+commit,"... and walk down it.  */
82817721Speter	q = endp + 1;
82917721Speter	notif = NULL;
83017721Speter	while (q != NULL)
83117721Speter	{
83217721Speter	    endq = strchr (q, '+');
83317721Speter	    if (endq == NULL || (nextp != NULL && endq > nextp))
83417721Speter	    {
83517721Speter		if (nextp == NULL)
83617721Speter		    endq = q + strlen (q);
83717721Speter		else
83817721Speter		    endq = nextp;
83917721Speter		nextq = NULL;
84017721Speter	    }
84117721Speter	    else
84217721Speter		nextq = endq + 1;
84317721Speter
84417721Speter	    /* If there is a temporary and a regular watch, send a single
84517721Speter	       notification, for the regular watch.  */
84617721Speter	    if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
84717721Speter	    {
84817721Speter		notif = "edit";
84917721Speter	    }
85017721Speter	    else if (type == 'U'
85117721Speter		     && endq - q == 6 && strncmp ("unedit", q, 6) == 0)
85217721Speter	    {
85317721Speter		notif = "unedit";
85417721Speter	    }
85517721Speter	    else if (type == 'C'
85617721Speter		     && endq - q == 6 && strncmp ("commit", q, 6) == 0)
85717721Speter	    {
85817721Speter		notif = "commit";
85917721Speter	    }
86017721Speter	    else if (type == 'E'
86117721Speter		     && endq - q == 5 && strncmp ("tedit", q, 5) == 0)
86217721Speter	    {
86317721Speter		if (notif == NULL)
86417721Speter		    notif = "temporary edit";
86517721Speter	    }
86617721Speter	    else if (type == 'U'
86717721Speter		     && endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
86817721Speter	    {
86917721Speter		if (notif == NULL)
87017721Speter		    notif = "temporary unedit";
87117721Speter	    }
87217721Speter	    else if (type == 'C'
87317721Speter		     && endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
87417721Speter	    {
87517721Speter		if (notif == NULL)
87617721Speter		    notif = "temporary commit";
87717721Speter	    }
87817721Speter	    q = nextq;
87917721Speter	}
88017721Speter	if (nextp != NULL)
88117721Speter	    ++nextp;
88217721Speter
88317721Speter	if (notif != NULL)
88417721Speter	{
88517721Speter	    struct notify_proc_args args;
88617721Speter	    size_t len = endp - p;
88717721Speter	    FILE *fp;
88817721Speter	    char *usersname;
88917721Speter	    char *line = NULL;
89017721Speter	    size_t line_len = 0;
89117721Speter
89217721Speter	    args.notifyee = NULL;
89381404Speter	    usersname = xmalloc (strlen (current_parsed_root->directory)
89417721Speter				 + sizeof CVSROOTADM
89517721Speter				 + sizeof CVSROOTADM_USERS
89617721Speter				 + 20);
89781404Speter	    strcpy (usersname, current_parsed_root->directory);
89817721Speter	    strcat (usersname, "/");
89917721Speter	    strcat (usersname, CVSROOTADM);
90017721Speter	    strcat (usersname, "/");
90117721Speter	    strcat (usersname, CVSROOTADM_USERS);
90225839Speter	    fp = CVS_FOPEN (usersname, "r");
90317721Speter	    if (fp == NULL && !existence_error (errno))
90417721Speter		error (0, errno, "cannot read %s", usersname);
90517721Speter	    if (fp != NULL)
90617721Speter	    {
90717721Speter		while (getline (&line, &line_len, fp) >= 0)
90817721Speter		{
90917721Speter		    if (strncmp (line, p, len) == 0
91017721Speter			&& line[len] == ':')
91117721Speter		    {
91217721Speter			char *cp;
91317721Speter			args.notifyee = xstrdup (line + len + 1);
91454427Speter
91554427Speter                        /* There may or may not be more
91654427Speter                           colon-separated fields added to this in the
91754427Speter                           future; in any case, we ignore them right
91854427Speter                           now, and if there are none we make sure to
91954427Speter                           chop off the final newline, if any. */
92054427Speter			cp = strpbrk (args.notifyee, ":\n");
92154427Speter
92217721Speter			if (cp != NULL)
92317721Speter			    *cp = '\0';
92417721Speter			break;
92517721Speter		    }
92617721Speter		}
92717721Speter		if (ferror (fp))
92817721Speter		    error (0, errno, "cannot read %s", usersname);
92917721Speter		if (fclose (fp) < 0)
93017721Speter		    error (0, errno, "cannot close %s", usersname);
93117721Speter	    }
93217721Speter	    free (usersname);
93354427Speter	    if (line != NULL)
93454427Speter		free (line);
93517721Speter
93617721Speter	    if (args.notifyee == NULL)
93717721Speter	    {
938128266Speter		char *tmp;
939128266Speter		tmp = xmalloc (endp - p + 1);
940128266Speter		strncpy (tmp, p, endp - p);
941128266Speter		tmp[endp - p] = '\0';
942128266Speter		args.notifyee = tmp;
94317721Speter	    }
94417721Speter
94517721Speter	    notify_args = &args;
94617721Speter	    args.type = notif;
94717721Speter	    args.who = who;
94817721Speter	    args.file = filename;
94917721Speter
95017721Speter	    (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1);
951128266Speter
952128266Speter            /* It's okay to cast out the const for the free() below since we
953128266Speter             * just allocated this a few lines above.  The const was for
954128266Speter             * everybody else.
955128266Speter             */
956128266Speter            free ((char *)args.notifyee);
95717721Speter	}
95817721Speter
95917721Speter	p = nextp;
96017721Speter    }
96117721Speter    if (watchers != NULL)
96217721Speter	free (watchers);
96317721Speter
96417721Speter    switch (type)
96517721Speter    {
96617721Speter	case 'E':
96717721Speter	    if (*watches == 'E')
96817721Speter	    {
96917721Speter		args.add_tedit = 1;
97017721Speter		++watches;
97117721Speter	    }
97217721Speter	    if (*watches == 'U')
97317721Speter	    {
97417721Speter		args.add_tunedit = 1;
97517721Speter		++watches;
97617721Speter	    }
97717721Speter	    if (*watches == 'C')
97817721Speter	    {
97917721Speter		args.add_tcommit = 1;
98017721Speter	    }
98117721Speter	    watch_modify_watchers (filename, &args);
98217721Speter	    break;
98317721Speter	case 'U':
98417721Speter	case 'C':
98517721Speter	    args.remove_temp = 1;
98617721Speter	    watch_modify_watchers (filename, &args);
98717721Speter	    break;
98817721Speter    }
98917721Speter}
99017721Speter
99117721Speter#ifdef CLIENT_SUPPORT
99217721Speter/* Check and send notifications.  This is only for the client.  */
99317721Spetervoid
994177391Sobriencvs_notify_check (repository, update_dir)
995128266Speter    const char *repository;
996128266Speter    const char *update_dir;
99717721Speter{
99817721Speter    FILE *fp;
99917721Speter    char *line = NULL;
100017721Speter    size_t line_len = 0;
100117721Speter
100217721Speter    if (! server_started)
100317721Speter	/* We are in the midst of a command which is not to talk to
100417721Speter	   the server (e.g. the first phase of a cvs edit).  Just chill
100517721Speter	   out, we'll catch the notifications on the flip side.  */
100617721Speter	return;
100717721Speter
100817721Speter    /* We send notifications even if noexec.  I'm not sure which behavior
100917721Speter       is most sensible.  */
101017721Speter
101125839Speter    fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
101217721Speter    if (fp == NULL)
101317721Speter    {
101417721Speter	if (!existence_error (errno))
101517721Speter	    error (0, errno, "cannot open %s", CVSADM_NOTIFY);
101617721Speter	return;
101717721Speter    }
101817721Speter    while (getline (&line, &line_len, fp) > 0)
101917721Speter    {
102017721Speter	int notif_type;
102117721Speter	char *filename;
102217721Speter	char *val;
102317721Speter	char *cp;
102417721Speter
102517721Speter	notif_type = line[0];
102617721Speter	if (notif_type == '\0')
102717721Speter	    continue;
102817721Speter	filename = line + 1;
102917721Speter	cp = strchr (filename, '\t');
103017721Speter	if (cp == NULL)
103117721Speter	    continue;
103217721Speter	*cp++ = '\0';
103317721Speter	val = cp;
103417721Speter
103517721Speter	client_notify (repository, update_dir, filename, notif_type, val);
103617721Speter    }
103725839Speter    if (line)
103825839Speter	free (line);
103917721Speter    if (ferror (fp))
104017721Speter	error (0, errno, "cannot read %s", CVSADM_NOTIFY);
104117721Speter    if (fclose (fp) < 0)
104217721Speter	error (0, errno, "cannot close %s", CVSADM_NOTIFY);
104317721Speter
104417721Speter    /* Leave the CVSADM_NOTIFY file there, until the server tells us it
104517721Speter       has dealt with it.  */
104617721Speter}
104717721Speter#endif /* CLIENT_SUPPORT */
104817721Speter
104917721Speter
105017721Speterstatic const char *const editors_usage[] =
105117721Speter{
1052175261Sobrien    "Usage: %s %s [-lR] [<file>]...\n",
1053175261Sobrien    "-l\tProcess this directory only (not recursive).\n",
1054175261Sobrien    "-R\tProcess directories recursively (default).\n",
1055175261Sobrien    "(Specify the --help global option for a list of other help options.)\n",
105617721Speter    NULL
105717721Speter};
105817721Speter
105925839Speterstatic int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo));
106017721Speter
106117721Speterstatic int
106225839Spetereditors_fileproc (callerdat, finfo)
106325839Speter    void *callerdat;
106417721Speter    struct file_info *finfo;
106517721Speter{
106617721Speter    char *them;
106717721Speter    char *p;
106817721Speter
106917721Speter    them = fileattr_get0 (finfo->file, "_editors");
107017721Speter    if (them == NULL)
107117721Speter	return 0;
107217721Speter
107354427Speter    cvs_output (finfo->fullname, 0);
107417721Speter
107517721Speter    p = them;
107617721Speter    while (1)
107717721Speter    {
107854427Speter	cvs_output ("\t", 1);
107917721Speter	while (*p != '>' && *p != '\0')
108054427Speter	    cvs_output (p++, 1);
108117721Speter	if (*p == '\0')
108217721Speter	{
108317721Speter	    /* Only happens if attribute is misformed.  */
108454427Speter	    cvs_output ("\n", 1);
108517721Speter	    break;
108617721Speter	}
108717721Speter	++p;
108854427Speter	cvs_output ("\t", 1);
108917721Speter	while (1)
109017721Speter	{
109117721Speter	    while (*p != '+' && *p != ',' && *p != '\0')
109254427Speter		cvs_output (p++, 1);
109317721Speter	    if (*p == '\0')
109417721Speter	    {
109554427Speter		cvs_output ("\n", 1);
109617721Speter		goto out;
109717721Speter	    }
109817721Speter	    if (*p == ',')
109917721Speter	    {
110017721Speter		++p;
110117721Speter		break;
110217721Speter	    }
110317721Speter	    ++p;
110454427Speter	    cvs_output ("\t", 1);
110517721Speter	}
110654427Speter	cvs_output ("\n", 1);
110717721Speter    }
110817721Speter  out:;
110966525Speter    free (them);
111017721Speter    return 0;
111117721Speter}
111217721Speter
111317721Speterint
111417721Spetereditors (argc, argv)
111517721Speter    int argc;
111617721Speter    char **argv;
111717721Speter{
111817721Speter    int local = 0;
111917721Speter    int c;
112017721Speter
112117721Speter    if (argc == -1)
112217721Speter	usage (editors_usage);
112317721Speter
112426065Speter    optind = 0;
112525839Speter    while ((c = getopt (argc, argv, "+lR")) != -1)
112617721Speter    {
112717721Speter	switch (c)
112817721Speter	{
112917721Speter	    case 'l':
113017721Speter		local = 1;
113117721Speter		break;
113225839Speter	    case 'R':
113325839Speter		local = 0;
113425839Speter		break;
113517721Speter	    case '?':
113617721Speter	    default:
113717721Speter		usage (editors_usage);
113817721Speter		break;
113917721Speter	}
114017721Speter    }
114117721Speter    argc -= optind;
114217721Speter    argv += optind;
114317721Speter
114417721Speter#ifdef CLIENT_SUPPORT
114581404Speter    if (current_parsed_root->isremote)
114617721Speter    {
114717721Speter	start_server ();
114817721Speter	ign_setup ();
114917721Speter
115017721Speter	if (local)
115117721Speter	    send_arg ("-l");
1152107484Speter	send_arg ("--");
115354427Speter	send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
115417721Speter	send_file_names (argc, argv, SEND_EXPAND_WILD);
115517721Speter	send_to_server ("editors\012", 0);
115617721Speter	return get_responses_and_close ();
115717721Speter    }
115817721Speter#endif /* CLIENT_SUPPORT */
115917721Speter
116017721Speter    return start_recursion (editors_fileproc, (FILESDONEPROC) NULL,
116125839Speter			    (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL,
1162128266Speter			    argc, argv, local, W_LOCAL, 0, 1, (char *) NULL,
1163128266Speter			    0,  (char *) NULL);
116417721Speter}
1165