xref: /dflybsd-src/contrib/cvs-1.12/src/logmsg.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
186d7f5d3SJohn Marino /*
286d7f5d3SJohn Marino  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
386d7f5d3SJohn Marino  *
486d7f5d3SJohn Marino  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
586d7f5d3SJohn Marino  *                                  and others.
686d7f5d3SJohn Marino  *
786d7f5d3SJohn Marino  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
886d7f5d3SJohn Marino  * Portions Copyright (C) 1989-1992, Brian Berliner
986d7f5d3SJohn Marino  *
1086d7f5d3SJohn Marino  * You may distribute under the terms of the GNU General Public License as
1186d7f5d3SJohn Marino  * specified in the README file that comes with the CVS source distribution.
1286d7f5d3SJohn Marino  */
1386d7f5d3SJohn Marino 
1486d7f5d3SJohn Marino 
1586d7f5d3SJohn Marino #include "cvs.h"
1686d7f5d3SJohn Marino #include "getline.h"
1786d7f5d3SJohn Marino 
1886d7f5d3SJohn Marino static int find_type (Node * p, void *closure);
1986d7f5d3SJohn Marino static int fmt_proc (Node * p, void *closure);
2086d7f5d3SJohn Marino static int logfile_write (const char *repository, const char *filter,
2186d7f5d3SJohn Marino 			  const char *message, FILE * logfp, List * changes);
2286d7f5d3SJohn Marino static int logmsg_list_to_args_proc (Node *p, void *closure);
2386d7f5d3SJohn Marino static int rcsinfo_proc (const char *repository, const char *template,
2486d7f5d3SJohn Marino                          void *closure );
2586d7f5d3SJohn Marino static int update_logfile_proc (const char *repository, const char *filter,
2686d7f5d3SJohn Marino                                 void *closure);
2786d7f5d3SJohn Marino static void setup_tmpfile (FILE * xfp, char *xprefix, List * changes);
2886d7f5d3SJohn Marino static int verifymsg_proc (const char *repository, const char *script,
2986d7f5d3SJohn Marino                            void *closure );
3086d7f5d3SJohn Marino 
3186d7f5d3SJohn Marino static FILE *fp;
3286d7f5d3SJohn Marino static Ctype type;
3386d7f5d3SJohn Marino 
3486d7f5d3SJohn Marino struct verifymsg_proc_data
3586d7f5d3SJohn Marino {
3686d7f5d3SJohn Marino     /* The name of the temp file storing the log message to be verified.  This
3786d7f5d3SJohn Marino      * is initially NULL and verifymsg_proc() writes message into it so that it
3886d7f5d3SJohn Marino      * can be shared when multiple verifymsg scripts exist.  do_verify() is
3986d7f5d3SJohn Marino      * responsible for rereading the message from the file when
4086d7f5d3SJohn Marino      * RereadLogAfterVerify is in effect and the file has changed.
4186d7f5d3SJohn Marino      */
4286d7f5d3SJohn Marino     char *fname;
4386d7f5d3SJohn Marino     /* The initial message text to be verified.
4486d7f5d3SJohn Marino      */
4586d7f5d3SJohn Marino     char *message;
4686d7f5d3SJohn Marino     /* The initial stats of the temp file so we can tell that the temp file has
4786d7f5d3SJohn Marino      * been changed when RereadLogAfterVerify is STAT.
4886d7f5d3SJohn Marino      */
4986d7f5d3SJohn Marino     struct stat pre_stbuf;
5086d7f5d3SJohn Marino    /* The list of files being changed, with new and old version numbers.
5186d7f5d3SJohn Marino     */
5286d7f5d3SJohn Marino    List *changes;
5386d7f5d3SJohn Marino };
5486d7f5d3SJohn Marino 
5586d7f5d3SJohn Marino /*
5686d7f5d3SJohn Marino  * Puts a standard header on the output which is either being prepared for an
5786d7f5d3SJohn Marino  * editor session, or being sent to a logfile program.  The modified, added,
5886d7f5d3SJohn Marino  * and removed files are included (if any) and formatted to look pretty. */
5986d7f5d3SJohn Marino static char *prefix;
6086d7f5d3SJohn Marino static int col;
6186d7f5d3SJohn Marino static char *tag;
6286d7f5d3SJohn Marino static void
setup_tmpfile(FILE * xfp,char * xprefix,List * changes)6386d7f5d3SJohn Marino setup_tmpfile (FILE *xfp, char *xprefix, List *changes)
6486d7f5d3SJohn Marino {
6586d7f5d3SJohn Marino     /* set up statics */
6686d7f5d3SJohn Marino     fp = xfp;
6786d7f5d3SJohn Marino     prefix = xprefix;
6886d7f5d3SJohn Marino 
6986d7f5d3SJohn Marino     type = T_MODIFIED;
7086d7f5d3SJohn Marino     if (walklist (changes, find_type, NULL) != 0)
7186d7f5d3SJohn Marino     {
7286d7f5d3SJohn Marino 	(void) fprintf (fp, "%sModified Files:\n", prefix);
7386d7f5d3SJohn Marino 	col = 0;
7486d7f5d3SJohn Marino 	(void) walklist (changes, fmt_proc, NULL);
7586d7f5d3SJohn Marino 	(void) fprintf (fp, "\n");
7686d7f5d3SJohn Marino 	if (tag != NULL)
7786d7f5d3SJohn Marino 	{
7886d7f5d3SJohn Marino 	    free (tag);
7986d7f5d3SJohn Marino 	    tag = NULL;
8086d7f5d3SJohn Marino 	}
8186d7f5d3SJohn Marino     }
8286d7f5d3SJohn Marino     type = T_ADDED;
8386d7f5d3SJohn Marino     if (walklist (changes, find_type, NULL) != 0)
8486d7f5d3SJohn Marino     {
8586d7f5d3SJohn Marino 	(void) fprintf (fp, "%sAdded Files:\n", prefix);
8686d7f5d3SJohn Marino 	col = 0;
8786d7f5d3SJohn Marino 	(void) walklist (changes, fmt_proc, NULL);
8886d7f5d3SJohn Marino 	(void) fprintf (fp, "\n");
8986d7f5d3SJohn Marino 	if (tag != NULL)
9086d7f5d3SJohn Marino 	{
9186d7f5d3SJohn Marino 	    free (tag);
9286d7f5d3SJohn Marino 	    tag = NULL;
9386d7f5d3SJohn Marino 	}
9486d7f5d3SJohn Marino     }
9586d7f5d3SJohn Marino     type = T_REMOVED;
9686d7f5d3SJohn Marino     if (walklist (changes, find_type, NULL) != 0)
9786d7f5d3SJohn Marino     {
9886d7f5d3SJohn Marino 	(void) fprintf (fp, "%sRemoved Files:\n", prefix);
9986d7f5d3SJohn Marino 	col = 0;
10086d7f5d3SJohn Marino 	(void) walklist (changes, fmt_proc, NULL);
10186d7f5d3SJohn Marino 	(void) fprintf (fp, "\n");
10286d7f5d3SJohn Marino 	if (tag != NULL)
10386d7f5d3SJohn Marino 	{
10486d7f5d3SJohn Marino 	    free (tag);
10586d7f5d3SJohn Marino 	    tag = NULL;
10686d7f5d3SJohn Marino 	}
10786d7f5d3SJohn Marino     }
10886d7f5d3SJohn Marino }
10986d7f5d3SJohn Marino 
11086d7f5d3SJohn Marino /*
11186d7f5d3SJohn Marino  * Looks for nodes of a specified type and returns 1 if found
11286d7f5d3SJohn Marino  */
11386d7f5d3SJohn Marino static int
find_type(Node * p,void * closure)11486d7f5d3SJohn Marino find_type (Node *p, void *closure)
11586d7f5d3SJohn Marino {
11686d7f5d3SJohn Marino     struct logfile_info *li = p->data;
11786d7f5d3SJohn Marino 
11886d7f5d3SJohn Marino     if (li->type == type)
11986d7f5d3SJohn Marino 	return (1);
12086d7f5d3SJohn Marino     else
12186d7f5d3SJohn Marino 	return (0);
12286d7f5d3SJohn Marino }
12386d7f5d3SJohn Marino 
12486d7f5d3SJohn Marino /*
12586d7f5d3SJohn Marino  * Breaks the files list into reasonable sized lines to avoid line wrap...
12686d7f5d3SJohn Marino  * all in the name of pretty output.  It only works on nodes whose types
12786d7f5d3SJohn Marino  * match the one we're looking for
12886d7f5d3SJohn Marino  */
12986d7f5d3SJohn Marino static int
fmt_proc(Node * p,void * closure)13086d7f5d3SJohn Marino fmt_proc (Node *p, void *closure)
13186d7f5d3SJohn Marino {
13286d7f5d3SJohn Marino     struct logfile_info *li;
13386d7f5d3SJohn Marino 
13486d7f5d3SJohn Marino     li = p->data;
13586d7f5d3SJohn Marino     if (li->type == type)
13686d7f5d3SJohn Marino     {
13786d7f5d3SJohn Marino         if (li->tag == NULL
13886d7f5d3SJohn Marino 	    ? tag != NULL
13986d7f5d3SJohn Marino 	    : tag == NULL || strcmp (tag, li->tag) != 0)
14086d7f5d3SJohn Marino 	{
14186d7f5d3SJohn Marino 	    if (col > 0)
14286d7f5d3SJohn Marino 	        (void) fprintf (fp, "\n");
14386d7f5d3SJohn Marino 	    (void) fputs (prefix, fp);
14486d7f5d3SJohn Marino 	    col = strlen (prefix);
14586d7f5d3SJohn Marino 	    while (col < 6)
14686d7f5d3SJohn Marino 	    {
14786d7f5d3SJohn Marino 	        (void) fprintf (fp, " ");
14886d7f5d3SJohn Marino 		++col;
14986d7f5d3SJohn Marino 	    }
15086d7f5d3SJohn Marino 
15186d7f5d3SJohn Marino 	    if (li->tag == NULL)
15286d7f5d3SJohn Marino 	        (void) fprintf (fp, "No tag");
15386d7f5d3SJohn Marino 	    else
15486d7f5d3SJohn Marino 	        (void) fprintf (fp, "Tag: %s", li->tag);
15586d7f5d3SJohn Marino 
15686d7f5d3SJohn Marino 	    if (tag != NULL)
15786d7f5d3SJohn Marino 	        free (tag);
15886d7f5d3SJohn Marino 	    tag = xstrdup (li->tag);
15986d7f5d3SJohn Marino 
16086d7f5d3SJohn Marino 	    /* Force a new line.  */
16186d7f5d3SJohn Marino 	    col = 70;
16286d7f5d3SJohn Marino 	}
16386d7f5d3SJohn Marino 
16486d7f5d3SJohn Marino 	if (col == 0)
16586d7f5d3SJohn Marino 	{
16686d7f5d3SJohn Marino 	    (void) fprintf (fp, "%s\t", prefix);
16786d7f5d3SJohn Marino 	    col = 8;
16886d7f5d3SJohn Marino 	}
16986d7f5d3SJohn Marino 	else if (col > 8 && (col + (int) strlen (p->key)) > 70)
17086d7f5d3SJohn Marino 	{
17186d7f5d3SJohn Marino 	    (void) fprintf (fp, "\n%s\t", prefix);
17286d7f5d3SJohn Marino 	    col = 8;
17386d7f5d3SJohn Marino 	}
17486d7f5d3SJohn Marino 	(void) fprintf (fp, "%s ", p->key);
17586d7f5d3SJohn Marino 	col += strlen (p->key) + 1;
17686d7f5d3SJohn Marino     }
17786d7f5d3SJohn Marino     return (0);
17886d7f5d3SJohn Marino }
17986d7f5d3SJohn Marino 
18086d7f5d3SJohn Marino /*
18186d7f5d3SJohn Marino  * Builds a temporary file using setup_tmpfile() and invokes the user's
18286d7f5d3SJohn Marino  * editor on the file.  The header garbage in the resultant file is then
18386d7f5d3SJohn Marino  * stripped and the log message is stored in the "message" argument.
18486d7f5d3SJohn Marino  *
18586d7f5d3SJohn Marino  * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
18686d7f5d3SJohn Marino  * is NULL, use the CVSADM_TEMPLATE file instead.  REPOSITORY should be
18786d7f5d3SJohn Marino  * NULL when running in client mode.
18886d7f5d3SJohn Marino  *
18986d7f5d3SJohn Marino  * GLOBALS
19086d7f5d3SJohn Marino  *   Editor     Set to a default value by configure and overridable using the
19186d7f5d3SJohn Marino  *              -e option to the CVS executable.
19286d7f5d3SJohn Marino  */
19386d7f5d3SJohn Marino void
do_editor(const char * dir,char ** messagep,const char * repository,List * changes)19486d7f5d3SJohn Marino do_editor (const char *dir, char **messagep, const char *repository,
19586d7f5d3SJohn Marino            List *changes)
19686d7f5d3SJohn Marino {
19786d7f5d3SJohn Marino     static int reuse_log_message = 0;
19886d7f5d3SJohn Marino     char *line;
19986d7f5d3SJohn Marino     int line_length;
20086d7f5d3SJohn Marino     size_t line_chars_allocated;
20186d7f5d3SJohn Marino     char *fname;
20286d7f5d3SJohn Marino     struct stat pre_stbuf, post_stbuf;
20386d7f5d3SJohn Marino     int retcode = 0;
20486d7f5d3SJohn Marino 
20586d7f5d3SJohn Marino     assert (!current_parsed_root->isremote != !repository);
20686d7f5d3SJohn Marino 
20786d7f5d3SJohn Marino     if (noexec || reuse_log_message)
20886d7f5d3SJohn Marino 	return;
20986d7f5d3SJohn Marino 
21086d7f5d3SJohn Marino     /* Abort before creation of the temp file if no editor is defined. */
21186d7f5d3SJohn Marino     if (strcmp (Editor, "") == 0)
21286d7f5d3SJohn Marino         error(1, 0, "no editor defined, must use -e or -m");
21386d7f5d3SJohn Marino 
21486d7f5d3SJohn Marino   again:
21586d7f5d3SJohn Marino     /* Create a temporary file.  */
21686d7f5d3SJohn Marino     if( ( fp = cvs_temp_file( &fname ) ) == NULL )
21786d7f5d3SJohn Marino 	error( 1, errno, "cannot create temporary file" );
21886d7f5d3SJohn Marino 
21986d7f5d3SJohn Marino     if (*messagep)
22086d7f5d3SJohn Marino     {
22186d7f5d3SJohn Marino 	(void) fputs (*messagep, fp);
22286d7f5d3SJohn Marino 
22386d7f5d3SJohn Marino 	if ((*messagep)[0] == '\0' ||
22486d7f5d3SJohn Marino 	    (*messagep)[strlen (*messagep) - 1] != '\n')
22586d7f5d3SJohn Marino 	    (void) fprintf (fp, "\n");
22686d7f5d3SJohn Marino     }
22786d7f5d3SJohn Marino 
22886d7f5d3SJohn Marino     if (repository != NULL)
22986d7f5d3SJohn Marino 	/* tack templates on if necessary */
23086d7f5d3SJohn Marino 	(void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc,
23186d7f5d3SJohn Marino 		PIOPT_ALL, NULL);
23286d7f5d3SJohn Marino     else
23386d7f5d3SJohn Marino     {
23486d7f5d3SJohn Marino 	FILE *tfp;
23586d7f5d3SJohn Marino 	char buf[1024];
23686d7f5d3SJohn Marino 	size_t n;
23786d7f5d3SJohn Marino 	size_t nwrite;
23886d7f5d3SJohn Marino 
23986d7f5d3SJohn Marino 	/* Why "b"?  */
24086d7f5d3SJohn Marino 	tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
24186d7f5d3SJohn Marino 	if (tfp == NULL)
24286d7f5d3SJohn Marino 	{
24386d7f5d3SJohn Marino 	    if (!existence_error (errno))
24486d7f5d3SJohn Marino 		error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
24586d7f5d3SJohn Marino 	}
24686d7f5d3SJohn Marino 	else
24786d7f5d3SJohn Marino 	{
24886d7f5d3SJohn Marino 	    while (!feof (tfp))
24986d7f5d3SJohn Marino 	    {
25086d7f5d3SJohn Marino 		char *p = buf;
25186d7f5d3SJohn Marino 		n = fread (buf, 1, sizeof buf, tfp);
25286d7f5d3SJohn Marino 		nwrite = n;
25386d7f5d3SJohn Marino 		while (nwrite > 0)
25486d7f5d3SJohn Marino 		{
25586d7f5d3SJohn Marino 		    n = fwrite (p, 1, nwrite, fp);
25686d7f5d3SJohn Marino 		    nwrite -= n;
25786d7f5d3SJohn Marino 		    p += n;
25886d7f5d3SJohn Marino 		}
25986d7f5d3SJohn Marino 		if (ferror (tfp))
26086d7f5d3SJohn Marino 		    error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
26186d7f5d3SJohn Marino 	    }
26286d7f5d3SJohn Marino 	    if (fclose (tfp) < 0)
26386d7f5d3SJohn Marino 		error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
26486d7f5d3SJohn Marino 	}
26586d7f5d3SJohn Marino     }
26686d7f5d3SJohn Marino 
26786d7f5d3SJohn Marino     (void) fprintf (fp,
26886d7f5d3SJohn Marino   "%s----------------------------------------------------------------------\n",
26986d7f5d3SJohn Marino 		    CVSEDITPREFIX);
27086d7f5d3SJohn Marino     (void) fprintf (fp,
27186d7f5d3SJohn Marino   "%sEnter Log.  Lines beginning with `%.*s' are removed automatically\n%s\n",
27286d7f5d3SJohn Marino 		    CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
27386d7f5d3SJohn Marino 		    CVSEDITPREFIX);
27486d7f5d3SJohn Marino     if (dir != NULL && *dir)
27586d7f5d3SJohn Marino 	(void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
27686d7f5d3SJohn Marino 			dir, CVSEDITPREFIX);
27786d7f5d3SJohn Marino     if (changes != NULL)
27886d7f5d3SJohn Marino 	setup_tmpfile (fp, CVSEDITPREFIX, changes);
27986d7f5d3SJohn Marino     (void) fprintf (fp,
28086d7f5d3SJohn Marino   "%s----------------------------------------------------------------------\n",
28186d7f5d3SJohn Marino 		    CVSEDITPREFIX);
28286d7f5d3SJohn Marino 
28386d7f5d3SJohn Marino     /* finish off the temp file */
28486d7f5d3SJohn Marino     if (fclose (fp) == EOF)
28586d7f5d3SJohn Marino         error (1, errno, "%s", fname);
28686d7f5d3SJohn Marino     if (stat (fname, &pre_stbuf) == -1)
28786d7f5d3SJohn Marino 	pre_stbuf.st_mtime = 0;
28886d7f5d3SJohn Marino 
28986d7f5d3SJohn Marino     /* run the editor */
29086d7f5d3SJohn Marino     run_setup (Editor);
29186d7f5d3SJohn Marino     run_add_arg (fname);
29286d7f5d3SJohn Marino     if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
29386d7f5d3SJohn Marino 			     RUN_NORMAL | RUN_SIGIGNORE)) != 0)
29486d7f5d3SJohn Marino 	error (0, retcode == -1 ? errno : 0, "warning: editor session failed");
29586d7f5d3SJohn Marino 
29686d7f5d3SJohn Marino     /* put the entire message back into the *messagep variable */
29786d7f5d3SJohn Marino 
29886d7f5d3SJohn Marino     fp = xfopen (fname, "r");
29986d7f5d3SJohn Marino 
30086d7f5d3SJohn Marino     if (*messagep)
30186d7f5d3SJohn Marino 	free (*messagep);
30286d7f5d3SJohn Marino 
30386d7f5d3SJohn Marino     if (stat (fname, &post_stbuf) != 0)
30486d7f5d3SJohn Marino 	    error (1, errno, "cannot find size of temp file %s", fname);
30586d7f5d3SJohn Marino 
30686d7f5d3SJohn Marino     if (post_stbuf.st_size == 0)
30786d7f5d3SJohn Marino 	*messagep = NULL;
30886d7f5d3SJohn Marino     else
30986d7f5d3SJohn Marino     {
31086d7f5d3SJohn Marino 	/* On NT, we might read less than st_size bytes, but we won't
31186d7f5d3SJohn Marino 	   read more.  So this works.  */
31286d7f5d3SJohn Marino 	*messagep = (char *) xmalloc (post_stbuf.st_size + 1);
31386d7f5d3SJohn Marino  	(*messagep)[0] = '\0';
31486d7f5d3SJohn Marino     }
31586d7f5d3SJohn Marino 
31686d7f5d3SJohn Marino     line = NULL;
31786d7f5d3SJohn Marino     line_chars_allocated = 0;
31886d7f5d3SJohn Marino 
31986d7f5d3SJohn Marino     if (*messagep)
32086d7f5d3SJohn Marino     {
32186d7f5d3SJohn Marino 	size_t message_len = post_stbuf.st_size + 1;
32286d7f5d3SJohn Marino 	size_t offset = 0;
32386d7f5d3SJohn Marino 	while (1)
32486d7f5d3SJohn Marino 	{
32586d7f5d3SJohn Marino 	    line_length = getline (&line, &line_chars_allocated, fp);
32686d7f5d3SJohn Marino 	    if (line_length == -1)
32786d7f5d3SJohn Marino 	    {
32886d7f5d3SJohn Marino 		if (ferror (fp))
32986d7f5d3SJohn Marino 		    error (0, errno, "warning: cannot read %s", fname);
33086d7f5d3SJohn Marino 		break;
33186d7f5d3SJohn Marino 	    }
33286d7f5d3SJohn Marino 	    if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
33386d7f5d3SJohn Marino 		continue;
33486d7f5d3SJohn Marino 	    if (offset + line_length >= message_len)
33586d7f5d3SJohn Marino 		expand_string (messagep, &message_len,
33686d7f5d3SJohn Marino 				offset + line_length + 1);
33786d7f5d3SJohn Marino 	    (void) strcpy (*messagep + offset, line);
33886d7f5d3SJohn Marino 	    offset += line_length;
33986d7f5d3SJohn Marino 	}
34086d7f5d3SJohn Marino     }
34186d7f5d3SJohn Marino     if (fclose (fp) < 0)
34286d7f5d3SJohn Marino 	error (0, errno, "warning: cannot close %s", fname);
34386d7f5d3SJohn Marino 
34486d7f5d3SJohn Marino     /* canonicalize emply messages */
34586d7f5d3SJohn Marino     if (*messagep != NULL &&
34686d7f5d3SJohn Marino         (**messagep == '\0' || strcmp (*messagep, "\n") == 0))
34786d7f5d3SJohn Marino     {
34886d7f5d3SJohn Marino 	free (*messagep);
34986d7f5d3SJohn Marino 	*messagep = NULL;
35086d7f5d3SJohn Marino     }
35186d7f5d3SJohn Marino 
35286d7f5d3SJohn Marino     if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL)
35386d7f5d3SJohn Marino     {
35486d7f5d3SJohn Marino 	for (;;)
35586d7f5d3SJohn Marino 	{
35686d7f5d3SJohn Marino 	    (void) printf ("\nLog message unchanged or not specified\n");
35786d7f5d3SJohn Marino 	    (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
35886d7f5d3SJohn Marino 	    (void) printf ("Action: (continue) ");
35986d7f5d3SJohn Marino 	    (void) fflush (stdout);
36086d7f5d3SJohn Marino 	    line_length = getline (&line, &line_chars_allocated, stdin);
36186d7f5d3SJohn Marino 	    if (line_length < 0)
36286d7f5d3SJohn Marino 	    {
36386d7f5d3SJohn Marino 		error (0, errno, "cannot read from stdin");
36486d7f5d3SJohn Marino 		if (unlink_file (fname) < 0)
36586d7f5d3SJohn Marino 		    error (0, errno,
36686d7f5d3SJohn Marino 			   "warning: cannot remove temp file %s", fname);
36786d7f5d3SJohn Marino 		error (1, 0, "aborting");
36886d7f5d3SJohn Marino 	    }
36986d7f5d3SJohn Marino 	    else if (line_length == 0
37086d7f5d3SJohn Marino 		     || *line == '\n' || *line == 'c' || *line == 'C')
37186d7f5d3SJohn Marino 		break;
37286d7f5d3SJohn Marino 	    if (*line == 'a' || *line == 'A')
37386d7f5d3SJohn Marino 		{
37486d7f5d3SJohn Marino 		    if (unlink_file (fname) < 0)
37586d7f5d3SJohn Marino 			error (0, errno, "warning: cannot remove temp file %s", fname);
37686d7f5d3SJohn Marino 		    error (1, 0, "aborted by user");
37786d7f5d3SJohn Marino 		}
37886d7f5d3SJohn Marino 	    if (*line == 'e' || *line == 'E')
37986d7f5d3SJohn Marino 		goto again;
38086d7f5d3SJohn Marino 	    if (*line == '!')
38186d7f5d3SJohn Marino 	    {
38286d7f5d3SJohn Marino 		reuse_log_message = 1;
38386d7f5d3SJohn Marino 		break;
38486d7f5d3SJohn Marino 	    }
38586d7f5d3SJohn Marino 	    (void) printf ("Unknown input\n");
38686d7f5d3SJohn Marino 	}
38786d7f5d3SJohn Marino     }
38886d7f5d3SJohn Marino     if (line)
38986d7f5d3SJohn Marino 	free (line);
39086d7f5d3SJohn Marino     if (unlink_file (fname) < 0)
39186d7f5d3SJohn Marino 	error (0, errno, "warning: cannot remove temp file %s", fname);
39286d7f5d3SJohn Marino     free (fname);
39386d7f5d3SJohn Marino }
39486d7f5d3SJohn Marino 
39586d7f5d3SJohn Marino /* Runs the user-defined verification script as part of the commit or import
39686d7f5d3SJohn Marino    process.  This verification is meant to be run whether or not the user
39786d7f5d3SJohn Marino    included the -m attribute.  unlike the do_editor function, this is
39886d7f5d3SJohn Marino    independant of the running of an editor for getting a message.
39986d7f5d3SJohn Marino  */
40086d7f5d3SJohn Marino void
do_verify(char ** messagep,const char * repository,List * changes)40186d7f5d3SJohn Marino do_verify (char **messagep, const char *repository, List *changes)
40286d7f5d3SJohn Marino {
40386d7f5d3SJohn Marino     int err;
40486d7f5d3SJohn Marino     struct verifymsg_proc_data data;
40586d7f5d3SJohn Marino     struct stat post_stbuf;
40686d7f5d3SJohn Marino 
40786d7f5d3SJohn Marino     if (current_parsed_root->isremote)
40886d7f5d3SJohn Marino 	/* The verification will happen on the server.  */
40986d7f5d3SJohn Marino 	return;
41086d7f5d3SJohn Marino 
41186d7f5d3SJohn Marino     /* FIXME? Do we really want to skip this on noexec?  What do we do
41286d7f5d3SJohn Marino        for the other administrative files?  */
41386d7f5d3SJohn Marino     /* EXPLAIN: Why do we check for repository == NULL here? */
41486d7f5d3SJohn Marino     if (noexec || repository == NULL)
41586d7f5d3SJohn Marino 	return;
41686d7f5d3SJohn Marino 
41786d7f5d3SJohn Marino     /* Get the name of the verification script to run  */
41886d7f5d3SJohn Marino 
41986d7f5d3SJohn Marino     data.message = *messagep;
42086d7f5d3SJohn Marino     data.fname = NULL;
42186d7f5d3SJohn Marino     data.changes = changes;
42286d7f5d3SJohn Marino     if ((err = Parse_Info (CVSROOTADM_VERIFYMSG, repository,
42386d7f5d3SJohn Marino 	                  verifymsg_proc, 0, &data)) != 0)
42486d7f5d3SJohn Marino     {
42586d7f5d3SJohn Marino 	int saved_errno = errno;
42686d7f5d3SJohn Marino 	/* Since following error() exits, delete the temp file now.  */
42786d7f5d3SJohn Marino 	if (data.fname != NULL && unlink_file( data.fname ) < 0)
42886d7f5d3SJohn Marino 	    error (0, errno, "cannot remove %s", data.fname);
42986d7f5d3SJohn Marino 	free (data.fname);
43086d7f5d3SJohn Marino 
43186d7f5d3SJohn Marino 	errno = saved_errno;
43286d7f5d3SJohn Marino 	error (1, err == -1 ? errno : 0, "Message verification failed");
43386d7f5d3SJohn Marino     }
43486d7f5d3SJohn Marino 
43586d7f5d3SJohn Marino     /* Return if no temp file was created.  That means that we didn't call any
43686d7f5d3SJohn Marino      * verifymsg scripts.
43786d7f5d3SJohn Marino      */
43886d7f5d3SJohn Marino     if (data.fname == NULL)
43986d7f5d3SJohn Marino 	return;
44086d7f5d3SJohn Marino 
44186d7f5d3SJohn Marino     /* Get the mod time and size of the possibly new log message
44286d7f5d3SJohn Marino      * in always and stat modes.
44386d7f5d3SJohn Marino      */
44486d7f5d3SJohn Marino     if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
44586d7f5d3SJohn Marino 	config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
44686d7f5d3SJohn Marino     {
44786d7f5d3SJohn Marino 	if(stat (data.fname, &post_stbuf) != 0)
44886d7f5d3SJohn Marino 	    error (1, errno, "cannot find size of temp file %s", data.fname);
44986d7f5d3SJohn Marino     }
45086d7f5d3SJohn Marino 
45186d7f5d3SJohn Marino     /* And reread the log message in `always' mode or in `stat' mode when it's
45286d7f5d3SJohn Marino      * changed.
45386d7f5d3SJohn Marino      */
45486d7f5d3SJohn Marino     if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
45586d7f5d3SJohn Marino 	(config->RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
45686d7f5d3SJohn Marino 	  (data.pre_stbuf.st_mtime != post_stbuf.st_mtime ||
45786d7f5d3SJohn Marino 	    data.pre_stbuf.st_size != post_stbuf.st_size)))
45886d7f5d3SJohn Marino     {
45986d7f5d3SJohn Marino 	/* put the entire message back into the *messagep variable */
46086d7f5d3SJohn Marino 
46186d7f5d3SJohn Marino 	if (*messagep) free (*messagep);
46286d7f5d3SJohn Marino 
46386d7f5d3SJohn Marino 	if (post_stbuf.st_size == 0)
46486d7f5d3SJohn Marino 	    *messagep = NULL;
46586d7f5d3SJohn Marino 	else
46686d7f5d3SJohn Marino 	{
46786d7f5d3SJohn Marino 	    char *line = NULL;
46886d7f5d3SJohn Marino 	    int line_length;
46986d7f5d3SJohn Marino 	    size_t line_chars_allocated = 0;
47086d7f5d3SJohn Marino 	    char *p;
47186d7f5d3SJohn Marino 	    FILE *fp;
47286d7f5d3SJohn Marino 
47386d7f5d3SJohn Marino 	    fp = xfopen (data.fname, "r");
47486d7f5d3SJohn Marino 
47586d7f5d3SJohn Marino 	    /* On NT, we might read less than st_size bytes,
47686d7f5d3SJohn Marino 	       but we won't read more.  So this works.  */
47786d7f5d3SJohn Marino 	    p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
47886d7f5d3SJohn Marino 	    *messagep[0] = '\0';
47986d7f5d3SJohn Marino 
48086d7f5d3SJohn Marino 	    for (;;)
48186d7f5d3SJohn Marino 	    {
48286d7f5d3SJohn Marino 		line_length = getline( &line,
48386d7f5d3SJohn Marino 				       &line_chars_allocated,
48486d7f5d3SJohn Marino 				       fp);
48586d7f5d3SJohn Marino 		if (line_length == -1)
48686d7f5d3SJohn Marino 		{
48786d7f5d3SJohn Marino 		    if (ferror (fp))
48886d7f5d3SJohn Marino 			/* Fail in this case because otherwise we will have no
48986d7f5d3SJohn Marino 			 * log message
49086d7f5d3SJohn Marino 			 */
49186d7f5d3SJohn Marino 			error (1, errno, "cannot read %s", data.fname);
49286d7f5d3SJohn Marino 		    break;
49386d7f5d3SJohn Marino 		}
49486d7f5d3SJohn Marino 		if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
49586d7f5d3SJohn Marino 		    continue;
49686d7f5d3SJohn Marino 		(void) strcpy (p, line);
49786d7f5d3SJohn Marino 		p += line_length;
49886d7f5d3SJohn Marino 	    }
49986d7f5d3SJohn Marino 	    if (line) free (line);
50086d7f5d3SJohn Marino 	    if (fclose (fp) < 0)
50186d7f5d3SJohn Marino 	        error (0, errno, "warning: cannot close %s", data.fname);
50286d7f5d3SJohn Marino 	}
50386d7f5d3SJohn Marino     }
50486d7f5d3SJohn Marino     /* Delete the temp file  */
50586d7f5d3SJohn Marino     if (unlink_file (data.fname) < 0)
50686d7f5d3SJohn Marino 	error (0, errno, "cannot remove `%s'", data.fname);
50786d7f5d3SJohn Marino     free (data.fname);
50886d7f5d3SJohn Marino }
50986d7f5d3SJohn Marino 
51086d7f5d3SJohn Marino 
51186d7f5d3SJohn Marino 
51286d7f5d3SJohn Marino /*
51386d7f5d3SJohn Marino  * callback proc for Parse_Info for rcsinfo templates this routine basically
51486d7f5d3SJohn Marino  * copies the matching template onto the end of the tempfile we are setting
51586d7f5d3SJohn Marino  * up
51686d7f5d3SJohn Marino  */
51786d7f5d3SJohn Marino /* ARGSUSED */
51886d7f5d3SJohn Marino static int
rcsinfo_proc(const char * repository,const char * template,void * closure)51986d7f5d3SJohn Marino rcsinfo_proc (const char *repository, const char *template, void *closure)
52086d7f5d3SJohn Marino {
52186d7f5d3SJohn Marino     static char *last_template;
52286d7f5d3SJohn Marino     FILE *tfp;
52386d7f5d3SJohn Marino 
52486d7f5d3SJohn Marino     /* nothing to do if the last one included is the same as this one */
52586d7f5d3SJohn Marino     if (last_template && strcmp (last_template, template) == 0)
52686d7f5d3SJohn Marino 	return (0);
52786d7f5d3SJohn Marino     if (last_template)
52886d7f5d3SJohn Marino 	free (last_template);
52986d7f5d3SJohn Marino     last_template = xstrdup (template);
53086d7f5d3SJohn Marino 
53186d7f5d3SJohn Marino     if ((tfp = CVS_FOPEN (template, "r")) != NULL)
53286d7f5d3SJohn Marino     {
53386d7f5d3SJohn Marino 	char *line = NULL;
53486d7f5d3SJohn Marino 	size_t line_chars_allocated = 0;
53586d7f5d3SJohn Marino 
53686d7f5d3SJohn Marino 	while (getline (&line, &line_chars_allocated, tfp) >= 0)
53786d7f5d3SJohn Marino 	    (void) fputs (line, fp);
53886d7f5d3SJohn Marino 	if (ferror (tfp))
53986d7f5d3SJohn Marino 	    error (0, errno, "warning: cannot read %s", template);
54086d7f5d3SJohn Marino 	if (fclose (tfp) < 0)
54186d7f5d3SJohn Marino 	    error (0, errno, "warning: cannot close %s", template);
54286d7f5d3SJohn Marino 	if (line)
54386d7f5d3SJohn Marino 	    free (line);
54486d7f5d3SJohn Marino 	return (0);
54586d7f5d3SJohn Marino     }
54686d7f5d3SJohn Marino     else
54786d7f5d3SJohn Marino     {
54886d7f5d3SJohn Marino 	error (0, errno, "Couldn't open rcsinfo template file %s", template);
54986d7f5d3SJohn Marino 	return (1);
55086d7f5d3SJohn Marino     }
55186d7f5d3SJohn Marino }
55286d7f5d3SJohn Marino 
55386d7f5d3SJohn Marino /*
55486d7f5d3SJohn Marino  * Uses setup_tmpfile() to pass the updated message on directly to any
55586d7f5d3SJohn Marino  * logfile programs that have a regular expression match for the checked in
55686d7f5d3SJohn Marino  * directory in the source repository.  The log information is fed into the
55786d7f5d3SJohn Marino  * specified program as standard input.
55886d7f5d3SJohn Marino  */
55986d7f5d3SJohn Marino struct ulp_data {
56086d7f5d3SJohn Marino     FILE *logfp;
56186d7f5d3SJohn Marino     const char *message;
56286d7f5d3SJohn Marino     List *changes;
56386d7f5d3SJohn Marino };
56486d7f5d3SJohn Marino 
56586d7f5d3SJohn Marino 
56686d7f5d3SJohn Marino 
56786d7f5d3SJohn Marino void
Update_Logfile(const char * repository,const char * xmessage,FILE * xlogfp,List * xchanges)56886d7f5d3SJohn Marino Update_Logfile (const char *repository, const char *xmessage, FILE *xlogfp,
56986d7f5d3SJohn Marino                 List *xchanges)
57086d7f5d3SJohn Marino {
57186d7f5d3SJohn Marino     struct ulp_data ud;
57286d7f5d3SJohn Marino 
57386d7f5d3SJohn Marino     /* nothing to do if the list is empty */
57486d7f5d3SJohn Marino     if (xchanges == NULL || xchanges->list->next == xchanges->list)
57586d7f5d3SJohn Marino 	return;
57686d7f5d3SJohn Marino 
57786d7f5d3SJohn Marino     /* set up vars for update_logfile_proc */
57886d7f5d3SJohn Marino     ud.message = xmessage;
57986d7f5d3SJohn Marino     ud.logfp = xlogfp;
58086d7f5d3SJohn Marino     ud.changes = xchanges;
58186d7f5d3SJohn Marino 
58286d7f5d3SJohn Marino     /* call Parse_Info to do the actual logfile updates */
58386d7f5d3SJohn Marino     (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc,
58486d7f5d3SJohn Marino 		       PIOPT_ALL, &ud);
58586d7f5d3SJohn Marino }
58686d7f5d3SJohn Marino 
58786d7f5d3SJohn Marino 
58886d7f5d3SJohn Marino 
58986d7f5d3SJohn Marino /*
59086d7f5d3SJohn Marino  * callback proc to actually do the logfile write from Update_Logfile
59186d7f5d3SJohn Marino  */
59286d7f5d3SJohn Marino static int
update_logfile_proc(const char * repository,const char * filter,void * closure)59386d7f5d3SJohn Marino update_logfile_proc (const char *repository, const char *filter, void *closure)
59486d7f5d3SJohn Marino {
59586d7f5d3SJohn Marino     struct ulp_data *udp = closure;
59686d7f5d3SJohn Marino     TRACE (TRACE_FUNCTION, "update_logfile_proc(%s,%s)", repository, filter);
59786d7f5d3SJohn Marino     return logfile_write (repository, filter, udp->message, udp->logfp,
59886d7f5d3SJohn Marino                           udp->changes);
59986d7f5d3SJohn Marino }
60086d7f5d3SJohn Marino 
60186d7f5d3SJohn Marino 
60286d7f5d3SJohn Marino 
60386d7f5d3SJohn Marino /* static int
60486d7f5d3SJohn Marino  * logmsg_list_to_args_proc( Node *p, void *closure )
60586d7f5d3SJohn Marino  * This function is intended to be passed into walklist() with a list of tags
60686d7f5d3SJohn Marino  * (nodes in the same format as pretag_list_proc() accepts - p->key = tagname
60786d7f5d3SJohn Marino  * and p->data = a revision.
60886d7f5d3SJohn Marino  *
60986d7f5d3SJohn Marino  * closure will be a struct format_cmdline_walklist_closure
61086d7f5d3SJohn Marino  * where closure is undefined.
61186d7f5d3SJohn Marino  */
61286d7f5d3SJohn Marino static int
logmsg_list_to_args_proc(Node * p,void * closure)61386d7f5d3SJohn Marino logmsg_list_to_args_proc (Node *p, void *closure)
61486d7f5d3SJohn Marino {
61586d7f5d3SJohn Marino     struct format_cmdline_walklist_closure *c = closure;
61686d7f5d3SJohn Marino     struct logfile_info *li;
61786d7f5d3SJohn Marino     char *arg = NULL;
61886d7f5d3SJohn Marino     const char *f;
61986d7f5d3SJohn Marino     char *d;
62086d7f5d3SJohn Marino     size_t doff;
62186d7f5d3SJohn Marino 
62286d7f5d3SJohn Marino     if (p->data == NULL) return 1;
62386d7f5d3SJohn Marino 
62486d7f5d3SJohn Marino     f = c->format;
62586d7f5d3SJohn Marino     d = *c->d;
62686d7f5d3SJohn Marino     /* foreach requested attribute */
62786d7f5d3SJohn Marino     while (*f)
62886d7f5d3SJohn Marino     {
62986d7f5d3SJohn Marino 	switch (*f++)
63086d7f5d3SJohn Marino 	{
63186d7f5d3SJohn Marino 	    case 's':
63286d7f5d3SJohn Marino 		arg = p->key;
63386d7f5d3SJohn Marino 		break;
63486d7f5d3SJohn Marino 	    case 'T':
63586d7f5d3SJohn Marino 		li = p->data;
63686d7f5d3SJohn Marino 		arg = li->tag ? li->tag : "";
63786d7f5d3SJohn Marino 		break;
63886d7f5d3SJohn Marino 	    case 'V':
63986d7f5d3SJohn Marino 		li = p->data;
64086d7f5d3SJohn Marino 		arg = li->rev_old ? li->rev_old : "NONE";
64186d7f5d3SJohn Marino 		break;
64286d7f5d3SJohn Marino 	    case 'v':
64386d7f5d3SJohn Marino 		li = p->data;
64486d7f5d3SJohn Marino 		arg = li->rev_new ? li->rev_new : "NONE";
64586d7f5d3SJohn Marino 		break;
64686d7f5d3SJohn Marino 	    default:
64786d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
64886d7f5d3SJohn Marino 		if (c->onearg)
64986d7f5d3SJohn Marino 		{
65086d7f5d3SJohn Marino 		    /* The old deafult was to print the empty string for
65186d7f5d3SJohn Marino 		     * unknown args.
65286d7f5d3SJohn Marino 		     */
65386d7f5d3SJohn Marino 		    arg = "\0";
65486d7f5d3SJohn Marino 		}
65586d7f5d3SJohn Marino 		else
65686d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
65786d7f5d3SJohn Marino 		    error (1, 0,
65886d7f5d3SJohn Marino 		           "Unknown format character or not a list attribute: %c", f[-1]);
65986d7f5d3SJohn Marino 		/* NOTREACHED */
66086d7f5d3SJohn Marino 		break;
66186d7f5d3SJohn Marino 	}
66286d7f5d3SJohn Marino 	/* copy the attribute into an argument */
66386d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
66486d7f5d3SJohn Marino 	if (c->onearg)
66586d7f5d3SJohn Marino 	{
66686d7f5d3SJohn Marino 	    if (c->firstpass)
66786d7f5d3SJohn Marino 	    {
66886d7f5d3SJohn Marino 		c->firstpass = 0;
66986d7f5d3SJohn Marino 		doff = d - *c->buf;
67086d7f5d3SJohn Marino 		expand_string (c->buf, c->length,
67186d7f5d3SJohn Marino 		               doff + strlen (c->srepos) + 1);
67286d7f5d3SJohn Marino 		d = *c->buf + doff;
67386d7f5d3SJohn Marino 		strncpy (d, c->srepos, strlen (c->srepos));
67486d7f5d3SJohn Marino 		d += strlen (c->srepos);
67586d7f5d3SJohn Marino 	    	*d++ = ' ';
67686d7f5d3SJohn Marino 	    }
67786d7f5d3SJohn Marino 	}
67886d7f5d3SJohn Marino 	else /* c->onearg */
67986d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
68086d7f5d3SJohn Marino 	{
68186d7f5d3SJohn Marino 	    if (c->quotes)
68286d7f5d3SJohn Marino 	    {
68386d7f5d3SJohn Marino 		arg = cmdlineescape (c->quotes, arg);
68486d7f5d3SJohn Marino 	    }
68586d7f5d3SJohn Marino 	    else
68686d7f5d3SJohn Marino 	    {
68786d7f5d3SJohn Marino 		arg = cmdlinequote ('"', arg);
68886d7f5d3SJohn Marino 	    }
68986d7f5d3SJohn Marino 	} /* !c->onearg */
69086d7f5d3SJohn Marino 	doff = d - *c->buf;
69186d7f5d3SJohn Marino 	expand_string (c->buf, c->length, doff + strlen (arg));
69286d7f5d3SJohn Marino 	d = *c->buf + doff;
69386d7f5d3SJohn Marino 	strncpy (d, arg, strlen (arg));
69486d7f5d3SJohn Marino 	d += strlen (arg);
69586d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
69686d7f5d3SJohn Marino 	if (!c->onearg)
69786d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
69886d7f5d3SJohn Marino 	    free (arg);
69986d7f5d3SJohn Marino 
70086d7f5d3SJohn Marino 	/* Always put the extra space on.  we'll have to back up a char
70186d7f5d3SJohn Marino 	 * when we're done, but that seems most efficient.
70286d7f5d3SJohn Marino 	 */
70386d7f5d3SJohn Marino 	doff = d - *c->buf;
70486d7f5d3SJohn Marino 	expand_string (c->buf, c->length, doff + 1);
70586d7f5d3SJohn Marino 	d = *c->buf + doff;
70686d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
70786d7f5d3SJohn Marino 	if (c->onearg && *f) *d++ = ',';
70886d7f5d3SJohn Marino 	else
70986d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
71086d7f5d3SJohn Marino 	    *d++ = ' ';
71186d7f5d3SJohn Marino     }
71286d7f5d3SJohn Marino     /* correct our original pointer into the buff */
71386d7f5d3SJohn Marino     *c->d = d;
71486d7f5d3SJohn Marino     return 0;
71586d7f5d3SJohn Marino }
71686d7f5d3SJohn Marino 
71786d7f5d3SJohn Marino 
71886d7f5d3SJohn Marino 
71986d7f5d3SJohn Marino /*
72086d7f5d3SJohn Marino  * Writes some stuff to the logfile "filter" and returns the status of the
72186d7f5d3SJohn Marino  * filter program.
72286d7f5d3SJohn Marino  */
72386d7f5d3SJohn Marino static int
logfile_write(const char * repository,const char * filter,const char * message,FILE * logfp,List * changes)72486d7f5d3SJohn Marino logfile_write (const char *repository, const char *filter, const char *message,
72586d7f5d3SJohn Marino                FILE *logfp, List *changes)
72686d7f5d3SJohn Marino {
72786d7f5d3SJohn Marino     char *cmdline;
72886d7f5d3SJohn Marino     FILE *pipefp;
72986d7f5d3SJohn Marino     char *cp;
73086d7f5d3SJohn Marino     int c;
73186d7f5d3SJohn Marino     int pipestatus;
73286d7f5d3SJohn Marino     const char *srepos = Short_Repository (repository);
73386d7f5d3SJohn Marino 
73486d7f5d3SJohn Marino     assert (repository);
73586d7f5d3SJohn Marino 
73686d7f5d3SJohn Marino     /* The user may specify a format string as part of the filter.
73786d7f5d3SJohn Marino        Originally, `%s' was the only valid string.  The string that
73886d7f5d3SJohn Marino        was substituted for it was:
73986d7f5d3SJohn Marino 
74086d7f5d3SJohn Marino          <repository-name> <file1> <file2> <file3> ...
74186d7f5d3SJohn Marino 
74286d7f5d3SJohn Marino        Each file was either a new directory/import (T_TITLE), or a
74386d7f5d3SJohn Marino        added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
74486d7f5d3SJohn Marino        file.
74586d7f5d3SJohn Marino 
74686d7f5d3SJohn Marino        It is desirable to preserve that behavior so lots of commitlog
74786d7f5d3SJohn Marino        scripts won't die when they get this new code.  At the same
74886d7f5d3SJohn Marino        time, we'd like to pass other information about the files (like
74986d7f5d3SJohn Marino        version numbers, statuses, or checkin times).
75086d7f5d3SJohn Marino 
75186d7f5d3SJohn Marino        The solution is to allow a format string that allows us to
75286d7f5d3SJohn Marino        specify those other pieces of information.  The format string
75386d7f5d3SJohn Marino        will be composed of `%' followed by a single format character,
75486d7f5d3SJohn Marino        or followed by a set of format characters surrounded by `{' and
75586d7f5d3SJohn Marino        `}' as separators.  The format characters are:
75686d7f5d3SJohn Marino 
75786d7f5d3SJohn Marino          s = file name
75886d7f5d3SJohn Marino 	 V = old version number (pre-checkin)
75986d7f5d3SJohn Marino 	 v = new version number (post-checkin)
76086d7f5d3SJohn Marino 
76186d7f5d3SJohn Marino        For example, valid format strings are:
76286d7f5d3SJohn Marino 
76386d7f5d3SJohn Marino          %{}
76486d7f5d3SJohn Marino 	 %s
76586d7f5d3SJohn Marino 	 %{s}
76686d7f5d3SJohn Marino 	 %{sVv}
76786d7f5d3SJohn Marino 
76886d7f5d3SJohn Marino        There's no reason that more items couldn't be added (like
76986d7f5d3SJohn Marino        modification date or file status [added, modified, updated,
77086d7f5d3SJohn Marino        etc.]) -- the code modifications would be minimal (logmsg.c
77186d7f5d3SJohn Marino        (title_proc) and commit.c (check_fileproc)).
77286d7f5d3SJohn Marino 
77386d7f5d3SJohn Marino        The output will be a string of tokens separated by spaces.  For
77486d7f5d3SJohn Marino        backwards compatibility, the the first token will be the
77586d7f5d3SJohn Marino        repository name.  The rest of the tokens will be
77686d7f5d3SJohn Marino        comma-delimited lists of the information requested in the
77786d7f5d3SJohn Marino        format string.  For example, if `/u/src/master' is the
77886d7f5d3SJohn Marino        repository, `%{sVv}' is the format string, and three files
77986d7f5d3SJohn Marino        (ChangeLog, Makefile, foo.c) were modified, the output might
78086d7f5d3SJohn Marino        be:
78186d7f5d3SJohn Marino 
78286d7f5d3SJohn Marino          /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
78386d7f5d3SJohn Marino 
78486d7f5d3SJohn Marino        Why this duplicates the old behavior when the format string is
78586d7f5d3SJohn Marino        `%s' is left as an exercise for the reader. */
78686d7f5d3SJohn Marino 
78786d7f5d3SJohn Marino     /* %c = cvs_cmd_name
78886d7f5d3SJohn Marino      * %p = shortrepos
78986d7f5d3SJohn Marino      * %r = repository
79086d7f5d3SJohn Marino      * %{sVv} = file name, old revision (precommit), new revision (postcommit)
79186d7f5d3SJohn Marino      */
79286d7f5d3SJohn Marino     /*
79386d7f5d3SJohn Marino      * Cast any NULL arguments as appropriate pointers as this is an
79486d7f5d3SJohn Marino      * stdarg function and we need to be certain the caller gets what
79586d7f5d3SJohn Marino      * is expected.
79686d7f5d3SJohn Marino      */
79786d7f5d3SJohn Marino     cmdline = format_cmdline (
79886d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
79986d7f5d3SJohn Marino 	                      !config->UseNewInfoFmtStrings, srepos,
80086d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
80186d7f5d3SJohn Marino 	                      filter,
80286d7f5d3SJohn Marino 	                      "c", "s", cvs_cmd_name,
80386d7f5d3SJohn Marino #ifdef SERVER_SUPPORT
80486d7f5d3SJohn Marino 	                      "R", "s", referrer ? referrer->original : "NONE",
80586d7f5d3SJohn Marino #endif /* SERVER_SUPPORT */
80686d7f5d3SJohn Marino 	                      "p", "s", srepos,
80786d7f5d3SJohn Marino 	                      "r", "s", current_parsed_root->directory,
80886d7f5d3SJohn Marino 	                      "sVv", ",", changes,
80986d7f5d3SJohn Marino 			      logmsg_list_to_args_proc, (void *) NULL,
81086d7f5d3SJohn Marino 	                      (char *) NULL);
81186d7f5d3SJohn Marino     if (!cmdline || !strlen (cmdline))
81286d7f5d3SJohn Marino     {
81386d7f5d3SJohn Marino 	if (cmdline) free (cmdline);
81486d7f5d3SJohn Marino 	error (0, 0, "logmsg proc resolved to the empty string!");
81586d7f5d3SJohn Marino 	return 1;
81686d7f5d3SJohn Marino     }
81786d7f5d3SJohn Marino 
81886d7f5d3SJohn Marino     if ((pipefp = run_popen (cmdline, "w")) == NULL)
81986d7f5d3SJohn Marino     {
82086d7f5d3SJohn Marino 	if (!noexec)
82186d7f5d3SJohn Marino 	    error (0, 0, "cannot write entry to log filter: %s", cmdline);
82286d7f5d3SJohn Marino 	free (cmdline);
82386d7f5d3SJohn Marino 	return 1;
82486d7f5d3SJohn Marino     }
82586d7f5d3SJohn Marino     (void) fprintf (pipefp, "Update of %s\n", repository);
82686d7f5d3SJohn Marino     (void) fprintf (pipefp, "In directory %s:", hostname);
82786d7f5d3SJohn Marino     cp = xgetcwd ();
82886d7f5d3SJohn Marino     if (cp == NULL)
82986d7f5d3SJohn Marino 	fprintf (pipefp, "<cannot get working directory: %s>\n\n",
83086d7f5d3SJohn Marino 		 strerror (errno));
83186d7f5d3SJohn Marino     else
83286d7f5d3SJohn Marino     {
83386d7f5d3SJohn Marino 	fprintf (pipefp, "%s\n\n", cp);
83486d7f5d3SJohn Marino 	free (cp);
83586d7f5d3SJohn Marino     }
83686d7f5d3SJohn Marino 
83786d7f5d3SJohn Marino     setup_tmpfile (pipefp, "", changes);
83886d7f5d3SJohn Marino     (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
83986d7f5d3SJohn Marino     if (logfp)
84086d7f5d3SJohn Marino     {
84186d7f5d3SJohn Marino 	(void) fprintf (pipefp, "Status:\n");
84286d7f5d3SJohn Marino 	rewind (logfp);
84386d7f5d3SJohn Marino 	while ((c = getc (logfp)) != EOF)
84486d7f5d3SJohn Marino 	    (void) putc (c, pipefp);
84586d7f5d3SJohn Marino     }
84686d7f5d3SJohn Marino     free (cmdline);
84786d7f5d3SJohn Marino     pipestatus = pclose (pipefp);
84886d7f5d3SJohn Marino     return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
84986d7f5d3SJohn Marino }
85086d7f5d3SJohn Marino 
85186d7f5d3SJohn Marino 
85286d7f5d3SJohn Marino 
85386d7f5d3SJohn Marino /*  This routine is called by Parse_Info.  It runs the
85486d7f5d3SJohn Marino  *  message verification script.
85586d7f5d3SJohn Marino  */
85686d7f5d3SJohn Marino static int
verifymsg_proc(const char * repository,const char * script,void * closure)85786d7f5d3SJohn Marino verifymsg_proc (const char *repository, const char *script, void *closure)
85886d7f5d3SJohn Marino {
85986d7f5d3SJohn Marino     char *verifymsg_script;
86086d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
86186d7f5d3SJohn Marino     char *newscript = NULL;
86286d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
86386d7f5d3SJohn Marino     struct verifymsg_proc_data *vpd = closure;
86486d7f5d3SJohn Marino     const char *srepos = Short_Repository (repository);
86586d7f5d3SJohn Marino 
86686d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
86786d7f5d3SJohn Marino     if (!strchr (script, '%'))
86886d7f5d3SJohn Marino     {
86986d7f5d3SJohn Marino 	error (0, 0,
87086d7f5d3SJohn Marino 	       "warning: verifymsg line doesn't contain any format strings:\n"
87186d7f5d3SJohn Marino                "    \"%s\"\n"
87286d7f5d3SJohn Marino                "Appending default format string (\" %%l\"), but be aware that this usage is\n"
87386d7f5d3SJohn Marino                "deprecated.", script);
87486d7f5d3SJohn Marino 	script = newscript = Xasprintf ("%s %%l", script);
87586d7f5d3SJohn Marino     }
87686d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
87786d7f5d3SJohn Marino 
87886d7f5d3SJohn Marino     /* If we don't already have one, open a temporary file, write the message
87986d7f5d3SJohn Marino      * to the temp file, and close the file.
88086d7f5d3SJohn Marino      *
88186d7f5d3SJohn Marino      * We do this here so that we only create the file when there is a
88286d7f5d3SJohn Marino      * verifymsg script specified and we only create it once when there is
88386d7f5d3SJohn Marino      * more than one verifymsg script specified.
88486d7f5d3SJohn Marino      */
88586d7f5d3SJohn Marino     if (vpd->fname == NULL)
88686d7f5d3SJohn Marino     {
88786d7f5d3SJohn Marino 	FILE *fp;
88886d7f5d3SJohn Marino 	if ((fp = cvs_temp_file (&(vpd->fname))) == NULL)
88986d7f5d3SJohn Marino 	    error (1, errno, "cannot create temporary file %s", vpd->fname);
89086d7f5d3SJohn Marino 
89186d7f5d3SJohn Marino 	if (vpd->message != NULL)
89286d7f5d3SJohn Marino 	    fputs (vpd->message, fp);
89386d7f5d3SJohn Marino 	if (vpd->message == NULL ||
89486d7f5d3SJohn Marino 	    (vpd->message)[0] == '\0' ||
89586d7f5d3SJohn Marino 	    (vpd->message)[strlen (vpd->message) - 1] != '\n')
89686d7f5d3SJohn Marino 	    putc ('\n', fp);
89786d7f5d3SJohn Marino 	if (fclose (fp) == EOF)
89886d7f5d3SJohn Marino 	    error (1, errno, "%s", vpd->fname);
89986d7f5d3SJohn Marino 
90086d7f5d3SJohn Marino 	if (config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
90186d7f5d3SJohn Marino 	{
90286d7f5d3SJohn Marino 	    /* Remember the status of the temp file for later */
90386d7f5d3SJohn Marino 	    if (stat (vpd->fname, &(vpd->pre_stbuf)) != 0)
90486d7f5d3SJohn Marino 		error (1, errno, "cannot stat temp file %s", vpd->fname);
90586d7f5d3SJohn Marino 
90686d7f5d3SJohn Marino 	    /*
90786d7f5d3SJohn Marino 	     * See if we need to sleep before running the verification
90886d7f5d3SJohn Marino 	     * script to avoid time-stamp races.
90986d7f5d3SJohn Marino 	     */
91086d7f5d3SJohn Marino 	    sleep_past (vpd->pre_stbuf.st_mtime);
91186d7f5d3SJohn Marino 	}
91286d7f5d3SJohn Marino     } /* if (vpd->fname == NULL) */
91386d7f5d3SJohn Marino 
91486d7f5d3SJohn Marino     /*
91586d7f5d3SJohn Marino      * Cast any NULL arguments as appropriate pointers as this is an
91686d7f5d3SJohn Marino      * stdarg function and we need to be certain the caller gets what
91786d7f5d3SJohn Marino      * is expected.
91886d7f5d3SJohn Marino      */
91986d7f5d3SJohn Marino     verifymsg_script = format_cmdline (
92086d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
92186d7f5d3SJohn Marino                                        false, srepos,
92286d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
92386d7f5d3SJohn Marino                                        script,
92486d7f5d3SJohn Marino 				       "c", "s", cvs_cmd_name,
92586d7f5d3SJohn Marino #ifdef SERVER_SUPPORT
92686d7f5d3SJohn Marino 				       "R", "s", referrer
92786d7f5d3SJohn Marino 				       ? referrer->original : "NONE",
92886d7f5d3SJohn Marino #endif /* SERVER_SUPPORT */
92986d7f5d3SJohn Marino                                        "p", "s", srepos,
93086d7f5d3SJohn Marino                                        "r", "s",
93186d7f5d3SJohn Marino                                        current_parsed_root->directory,
93286d7f5d3SJohn Marino                                        "l", "s", vpd->fname,
93386d7f5d3SJohn Marino 				       "sV", ",", vpd->changes,
93486d7f5d3SJohn Marino 				       logmsg_list_to_args_proc, (void *) NULL,
93586d7f5d3SJohn Marino 				       (char *) NULL);
93686d7f5d3SJohn Marino 
93786d7f5d3SJohn Marino #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
93886d7f5d3SJohn Marino     if (newscript) free (newscript);
93986d7f5d3SJohn Marino #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
94086d7f5d3SJohn Marino 
94186d7f5d3SJohn Marino     if (!verifymsg_script || !strlen (verifymsg_script))
94286d7f5d3SJohn Marino     {
94386d7f5d3SJohn Marino 	if (verifymsg_script) free (verifymsg_script);
94486d7f5d3SJohn Marino 	verifymsg_script = NULL;
94586d7f5d3SJohn Marino 	error (0, 0, "verifymsg proc resolved to the empty string!");
94686d7f5d3SJohn Marino 	return 1;
94786d7f5d3SJohn Marino     }
94886d7f5d3SJohn Marino 
94986d7f5d3SJohn Marino     run_setup (verifymsg_script);
95086d7f5d3SJohn Marino 
95186d7f5d3SJohn Marino     free (verifymsg_script);
95286d7f5d3SJohn Marino 
95386d7f5d3SJohn Marino     /* FIXME - because run_exec can return negative values and Parse_Info adds
95486d7f5d3SJohn Marino      * the values of each call to this function to get a total error, we are
95586d7f5d3SJohn Marino      * calling abs on the value of run_exec to ensure two errors do not sum to
95686d7f5d3SJohn Marino      * zero.
95786d7f5d3SJohn Marino      *
95886d7f5d3SJohn Marino      * The only REALLY obnoxious thing about this, I guess, is that a -1 return
95986d7f5d3SJohn Marino      * code from run_exec can mean we failed to call the process for some
96086d7f5d3SJohn Marino      * reason and should care about errno or that the process we called
96186d7f5d3SJohn Marino      * returned -1 and the value of errno is undefined.  In other words,
96286d7f5d3SJohn Marino      * run_exec should probably be rewritten to have two return codes.  one
96386d7f5d3SJohn Marino      * which is its own exit status and one which is the child process's.  So
96486d7f5d3SJohn Marino      * there.  :P
96586d7f5d3SJohn Marino      *
96686d7f5d3SJohn Marino      * Once run_exec is returning two error codes, we should probably be
96786d7f5d3SJohn Marino      * failing here with an error message including errno when we get the
96886d7f5d3SJohn Marino      * return code which means we care about errno, in case you missed that
96986d7f5d3SJohn Marino      * little tidbit.
97086d7f5d3SJohn Marino      *
97186d7f5d3SJohn Marino      * I do happen to know we just fail for a non-zero value anyway and I
97286d7f5d3SJohn Marino      * believe the docs actually state that if the verifymsg_proc returns a
97386d7f5d3SJohn Marino      * "non-zero" value we will fail.
97486d7f5d3SJohn Marino      */
97586d7f5d3SJohn Marino     return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
97686d7f5d3SJohn Marino 			  RUN_NORMAL | RUN_SIGIGNORE));
97786d7f5d3SJohn Marino }
978