xref: /netbsd/external/gpl2/xcvs/dist/src/logmsg.c (revision 6550d01e)
1 /*
2  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3  *
4  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5  *                                  and others.
6  *
7  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8  * Portions Copyright (C) 1989-1992, Brian Berliner
9  *
10  * You may distribute under the terms of the GNU General Public License as
11  * specified in the README file that comes with the CVS source distribution.
12  */
13 
14 
15 #include "cvs.h"
16 #include "getline.h"
17 
18 static int find_type (Node * p, void *closure);
19 static int fmt_proc (Node * p, void *closure);
20 static int logfile_write (const char *repository, const char *filter,
21 			  const char *message, FILE * logfp, List * changes);
22 static int logmsg_list_to_args_proc (Node *p, void *closure);
23 static int rcsinfo_proc (const char *repository, const char *template,
24                          void *closure );
25 static int update_logfile_proc (const char *repository, const char *filter,
26                                 void *closure);
27 static void setup_tmpfile (FILE * xfp, char *xprefix, List * changes);
28 static int verifymsg_proc (const char *repository, const char *script,
29                            void *closure );
30 
31 static FILE *fp;
32 static Ctype type;
33 
34 struct verifymsg_proc_data
35 {
36     /* The name of the temp file storing the log message to be verified.  This
37      * is initially NULL and verifymsg_proc() writes message into it so that it
38      * can be shared when multiple verifymsg scripts exist.  do_verify() is
39      * responsible for rereading the message from the file when
40      * RereadLogAfterVerify is in effect and the file has changed.
41      */
42     char *fname;
43     /* The initial message text to be verified.
44      */
45     char *message;
46     /* The initial stats of the temp file so we can tell that the temp file has
47      * been changed when RereadLogAfterVerify is STAT.
48      */
49     struct stat pre_stbuf;
50    /* The list of files being changed, with new and old version numbers.
51     */
52    List *changes;
53 };
54 
55 /*
56  * Puts a standard header on the output which is either being prepared for an
57  * editor session, or being sent to a logfile program.  The modified, added,
58  * and removed files are included (if any) and formatted to look pretty. */
59 static char *prefix;
60 static int col;
61 static char *tag;
62 static void
63 setup_tmpfile (FILE *xfp, char *xprefix, List *changes)
64 {
65     /* set up statics */
66     fp = xfp;
67     prefix = xprefix;
68 
69     type = T_MODIFIED;
70     if (walklist (changes, find_type, NULL) != 0)
71     {
72 	(void) fprintf (fp, "%sModified Files:\n", prefix);
73 	col = 0;
74 	(void) walklist (changes, fmt_proc, NULL);
75 	(void) fprintf (fp, "\n");
76 	if (tag != NULL)
77 	{
78 	    free (tag);
79 	    tag = NULL;
80 	}
81     }
82     type = T_ADDED;
83     if (walklist (changes, find_type, NULL) != 0)
84     {
85 	(void) fprintf (fp, "%sAdded Files:\n", prefix);
86 	col = 0;
87 	(void) walklist (changes, fmt_proc, NULL);
88 	(void) fprintf (fp, "\n");
89 	if (tag != NULL)
90 	{
91 	    free (tag);
92 	    tag = NULL;
93 	}
94     }
95     type = T_REMOVED;
96     if (walklist (changes, find_type, NULL) != 0)
97     {
98 	(void) fprintf (fp, "%sRemoved Files:\n", prefix);
99 	col = 0;
100 	(void) walklist (changes, fmt_proc, NULL);
101 	(void) fprintf (fp, "\n");
102 	if (tag != NULL)
103 	{
104 	    free (tag);
105 	    tag = NULL;
106 	}
107     }
108 }
109 
110 /*
111  * Looks for nodes of a specified type and returns 1 if found
112  */
113 static int
114 find_type (Node *p, void *closure)
115 {
116     struct logfile_info *li = p->data;
117 
118     if (li->type == type)
119 	return (1);
120     else
121 	return (0);
122 }
123 
124 /*
125  * Breaks the files list into reasonable sized lines to avoid line wrap...
126  * all in the name of pretty output.  It only works on nodes whose types
127  * match the one we're looking for
128  */
129 static int
130 fmt_proc (Node *p, void *closure)
131 {
132     struct logfile_info *li;
133 
134     li = p->data;
135     if (li->type == type)
136     {
137         if (li->tag == NULL
138 	    ? tag != NULL
139 	    : tag == NULL || strcmp (tag, li->tag) != 0)
140 	{
141 	    if (col > 0)
142 	        (void) fprintf (fp, "\n");
143 	    (void) fputs (prefix, fp);
144 	    col = strlen (prefix);
145 	    while (col < 6)
146 	    {
147 	        (void) fprintf (fp, " ");
148 		++col;
149 	    }
150 
151 	    if (li->tag == NULL)
152 	        (void) fprintf (fp, "No tag");
153 	    else
154 	        (void) fprintf (fp, "Tag: %s", li->tag);
155 
156 	    if (tag != NULL)
157 	        free (tag);
158 	    tag = xstrdup (li->tag);
159 
160 	    /* Force a new line.  */
161 	    col = 70;
162 	}
163 
164 	if (col == 0)
165 	{
166 	    (void) fprintf (fp, "%s\t", prefix);
167 	    col = 8;
168 	}
169 	else if (col > 8 && (col + (int) strlen (p->key)) > 70)
170 	{
171 	    (void) fprintf (fp, "\n%s\t", prefix);
172 	    col = 8;
173 	}
174 	(void) fprintf (fp, "%s ", p->key);
175 	col += strlen (p->key) + 1;
176     }
177     return (0);
178 }
179 
180 /*
181  * Builds a temporary file using setup_tmpfile() and invokes the user's
182  * editor on the file.  The header garbage in the resultant file is then
183  * stripped and the log message is stored in the "message" argument.
184  *
185  * If REPOSITORY is non-NULL, process rcsinfo for that repository; if it
186  * is NULL, use the CVSADM_TEMPLATE file instead.  REPOSITORY should be
187  * NULL when running in client mode.
188  *
189  * GLOBALS
190  *   Editor     Set to a default value by configure and overridable using the
191  *              -e option to the CVS executable.
192  */
193 void
194 do_editor (const char *dir, char **messagep, const char *repository,
195            List *changes)
196 {
197     static int reuse_log_message = 0;
198     char *line;
199     int line_length;
200     size_t line_chars_allocated;
201     char *fname;
202     struct stat pre_stbuf, post_stbuf;
203     int retcode = 0;
204 
205     assert (!current_parsed_root->isremote != !repository);
206 
207     if (noexec || reuse_log_message)
208 	return;
209 
210     /* Abort before creation of the temp file if no editor is defined. */
211     if (strcmp (Editor, "") == 0)
212         error(1, 0, "no editor defined, must use -e or -m");
213 
214   again:
215     /* Create a temporary file.  */
216     if( ( fp = cvs_temp_file( &fname ) ) == NULL )
217 	error( 1, errno, "cannot create temporary file" );
218 
219     if (*messagep)
220     {
221 	(void) fputs (*messagep, fp);
222 
223 	if ((*messagep)[0] == '\0' ||
224 	    (*messagep)[strlen (*messagep) - 1] != '\n')
225 	    (void) fprintf (fp, "\n");
226     }
227     else
228 	(void) fprintf (fp, "\n");
229 
230     if (repository != NULL)
231 	/* tack templates on if necessary */
232 	(void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc,
233 		PIOPT_ALL, NULL);
234     else
235     {
236 	FILE *tfp;
237 	char buf[1024];
238 	size_t n;
239 	size_t nwrite;
240 
241 	/* Why "b"?  */
242 	tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
243 	if (tfp == NULL)
244 	{
245 	    if (!existence_error (errno))
246 		error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
247 	}
248 	else
249 	{
250 	    while (!feof (tfp))
251 	    {
252 		char *p = buf;
253 		n = fread (buf, 1, sizeof buf, tfp);
254 		nwrite = n;
255 		while (nwrite > 0)
256 		{
257 		    n = fwrite (p, 1, nwrite, fp);
258 		    nwrite -= n;
259 		    p += n;
260 		}
261 		if (ferror (tfp))
262 		    error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
263 	    }
264 	    if (fclose (tfp) < 0)
265 		error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
266 	}
267     }
268 
269     (void) fprintf (fp,
270   "%s----------------------------------------------------------------------\n",
271 		    CVSEDITPREFIX);
272     (void) fprintf (fp,
273   "%sEnter Log.  Lines beginning with `%.*s' are removed automatically\n%s\n",
274 		    CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
275 		    CVSEDITPREFIX);
276     if (dir != NULL && *dir)
277 	(void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
278 			dir, CVSEDITPREFIX);
279     if (changes != NULL)
280 	setup_tmpfile (fp, CVSEDITPREFIX, changes);
281     (void) fprintf (fp,
282   "%s----------------------------------------------------------------------\n",
283 		    CVSEDITPREFIX);
284 
285     /* finish off the temp file */
286     if (fclose (fp) == EOF)
287         error (1, errno, "%s", fname);
288     if (stat (fname, &pre_stbuf) == -1)
289 	pre_stbuf.st_mtime = 0;
290 
291     /* run the editor */
292     run_setup (Editor);
293     run_add_arg (fname);
294     if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
295 			     RUN_NORMAL | RUN_SIGIGNORE | RUN_UNSETXID)) != 0)
296 	error (0, retcode == -1 ? errno : 0, "warning: editor session failed");
297 
298     /* put the entire message back into the *messagep variable */
299 
300     fp = xfopen (fname, "r");
301 
302     if (*messagep)
303 	free (*messagep);
304 
305     if (stat (fname, &post_stbuf) != 0)
306 	    error (1, errno, "cannot find size of temp file %s", fname);
307 
308     if (post_stbuf.st_size == 0)
309 	*messagep = NULL;
310     else
311     {
312 	/* On NT, we might read less than st_size bytes, but we won't
313 	   read more.  So this works.  */
314 	*messagep = (char *) xmalloc (post_stbuf.st_size + 1);
315  	(*messagep)[0] = '\0';
316     }
317 
318     line = NULL;
319     line_chars_allocated = 0;
320 
321     if (*messagep)
322     {
323 	size_t message_len = post_stbuf.st_size + 1;
324 	size_t offset = 0;
325 	while (1)
326 	{
327 	    line_length = getline (&line, &line_chars_allocated, fp);
328 	    if (line_length == -1)
329 	    {
330 		if (ferror (fp))
331 		    error (0, errno, "warning: cannot read %s", fname);
332 		break;
333 	    }
334 	    if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
335 		continue;
336 	    if (offset + line_length >= message_len)
337 		expand_string (messagep, &message_len,
338 				offset + line_length + 1);
339 	    (void) strcpy (*messagep + offset, line);
340 	    offset += line_length;
341 	}
342     }
343     if (fclose (fp) < 0)
344 	error (0, errno, "warning: cannot close %s", fname);
345 
346     /* canonicalize emply messages */
347     if (*messagep != NULL &&
348         (**messagep == '\0' || strcmp (*messagep, "\n") == 0))
349     {
350 	free (*messagep);
351 	*messagep = NULL;
352     }
353 
354     if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL)
355     {
356 	for (;;)
357 	{
358 	    (void) printf ("\nLog message unchanged or not specified\n");
359 	    (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
360 	    (void) printf ("Action: (abort) ");
361 	    (void) fflush (stdout);
362 	    line_length = getline (&line, &line_chars_allocated, stdin);
363 	    if (line_length < 0)
364 	    {
365 		error (0, errno, "cannot read from stdin");
366 		if (unlink_file (fname) < 0)
367 		    error (0, errno,
368 			   "warning: cannot remove temp file %s", fname);
369 		error (1, 0, "aborting");
370 	    }
371 	    else if (line_length == 0
372 		     || *line == '\n' || *line == 'a' || *line == 'A')
373 		{
374 		    if (unlink_file (fname) < 0)
375 			error (0, errno, "warning: cannot remove temp file %s", fname);
376 		    error (1, 0, "aborted by user");
377 		}
378 	    if (*line == 'c' || *line == 'C')
379 		break;
380 	    if (*line == 'e' || *line == 'E')
381 		goto again;
382 	    if (*line == '!')
383 	    {
384 		reuse_log_message = 1;
385 		break;
386 	    }
387 	    (void) printf ("Unknown input\n");
388 	}
389     }
390     if (line)
391 	free (line);
392     if (unlink_file (fname) < 0)
393 	error (0, errno, "warning: cannot remove temp file %s", fname);
394     free (fname);
395 }
396 
397 /* Runs the user-defined verification script as part of the commit or import
398    process.  This verification is meant to be run whether or not the user
399    included the -m attribute.  unlike the do_editor function, this is
400    independant of the running of an editor for getting a message.
401  */
402 void
403 do_verify (char **messagep, const char *repository, List *changes)
404 {
405     int err;
406     struct verifymsg_proc_data data;
407     struct stat post_stbuf;
408 
409     if (current_parsed_root->isremote)
410 	/* The verification will happen on the server.  */
411 	return;
412 
413     /* FIXME? Do we really want to skip this on noexec?  What do we do
414        for the other administrative files?  */
415     /* EXPLAIN: Why do we check for repository == NULL here? */
416     if (noexec || repository == NULL)
417 	return;
418 
419     /* Get the name of the verification script to run  */
420 
421     data.message = *messagep;
422     data.fname = NULL;
423     data.changes = changes;
424     if ((err = Parse_Info (CVSROOTADM_VERIFYMSG, repository,
425 	                  verifymsg_proc, 0, &data)) != 0)
426     {
427 	int saved_errno = errno;
428 	/* Since following error() exits, delete the temp file now.  */
429 	if (data.fname != NULL && unlink_file( data.fname ) < 0)
430 	    error (0, errno, "cannot remove %s", data.fname);
431 	free (data.fname);
432 
433 	errno = saved_errno;
434 	error (1, err == -1 ? errno : 0, "Message verification failed");
435     }
436 
437     /* Return if no temp file was created.  That means that we didn't call any
438      * verifymsg scripts.
439      */
440     if (data.fname == NULL)
441 	return;
442 
443     /* Get the mod time and size of the possibly new log message
444      * in always and stat modes.
445      */
446     if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
447 	config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
448     {
449 	if(stat (data.fname, &post_stbuf) != 0)
450 	    error (1, errno, "cannot find size of temp file %s", data.fname);
451     }
452 
453     /* And reread the log message in `always' mode or in `stat' mode when it's
454      * changed.
455      */
456     if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
457 	(config->RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
458 	  (data.pre_stbuf.st_mtime != post_stbuf.st_mtime ||
459 	    data.pre_stbuf.st_size != post_stbuf.st_size)))
460     {
461 	/* put the entire message back into the *messagep variable */
462 
463 	if (*messagep) free (*messagep);
464 
465 	if (post_stbuf.st_size == 0)
466 	    *messagep = NULL;
467 	else
468 	{
469 	    char *line = NULL;
470 	    int line_length;
471 	    size_t line_chars_allocated = 0;
472 	    char *p;
473 	    FILE *fp;
474 
475 	    fp = xfopen (data.fname, "r");
476 
477 	    /* On NT, we might read less than st_size bytes,
478 	       but we won't read more.  So this works.  */
479 	    p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
480 	    *messagep[0] = '\0';
481 
482 	    for (;;)
483 	    {
484 		line_length = getline( &line,
485 				       &line_chars_allocated,
486 				       fp);
487 		if (line_length == -1)
488 		{
489 		    if (ferror (fp))
490 			/* Fail in this case because otherwise we will have no
491 			 * log message
492 			 */
493 			error (1, errno, "cannot read %s", data.fname);
494 		    break;
495 		}
496 		if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
497 		    continue;
498 		(void) strcpy (p, line);
499 		p += line_length;
500 	    }
501 	    if (line) free (line);
502 	    if (fclose (fp) < 0)
503 	        error (0, errno, "warning: cannot close %s", data.fname);
504 	}
505     }
506     /* Delete the temp file  */
507     if (unlink_file (data.fname) < 0)
508 	error (0, errno, "cannot remove `%s'", data.fname);
509     free (data.fname);
510 }
511 
512 
513 
514 /*
515  * callback proc for Parse_Info for rcsinfo templates this routine basically
516  * copies the matching template onto the end of the tempfile we are setting
517  * up
518  */
519 /* ARGSUSED */
520 static int
521 rcsinfo_proc (const char *repository, const char *template, void *closure)
522 {
523     static char *last_template;
524     FILE *tfp;
525 
526     /* nothing to do if the last one included is the same as this one */
527     if (last_template && strcmp (last_template, template) == 0)
528 	return (0);
529     if (last_template)
530 	free (last_template);
531     last_template = xstrdup (template);
532 
533     if ((tfp = CVS_FOPEN (template, "r")) != NULL)
534     {
535 	char *line = NULL;
536 	size_t line_chars_allocated = 0;
537 
538 	while (getline (&line, &line_chars_allocated, tfp) >= 0)
539 	    (void) fputs (line, fp);
540 	if (ferror (tfp))
541 	    error (0, errno, "warning: cannot read %s", template);
542 	if (fclose (tfp) < 0)
543 	    error (0, errno, "warning: cannot close %s", template);
544 	if (line)
545 	    free (line);
546 	return (0);
547     }
548     else
549     {
550 	error (0, errno, "Couldn't open rcsinfo template file %s", template);
551 	return (1);
552     }
553 }
554 
555 /*
556  * Uses setup_tmpfile() to pass the updated message on directly to any
557  * logfile programs that have a regular expression match for the checked in
558  * directory in the source repository.  The log information is fed into the
559  * specified program as standard input.
560  */
561 struct ulp_data {
562     FILE *logfp;
563     const char *message;
564     List *changes;
565 };
566 
567 
568 
569 void
570 Update_Logfile (const char *repository, const char *xmessage, FILE *xlogfp,
571                 List *xchanges)
572 {
573     struct ulp_data ud;
574 
575     /* nothing to do if the list is empty */
576     if (xchanges == NULL || xchanges->list->next == xchanges->list)
577 	return;
578 
579     /* set up vars for update_logfile_proc */
580     ud.message = xmessage;
581     ud.logfp = xlogfp;
582     ud.changes = xchanges;
583 
584     /* call Parse_Info to do the actual logfile updates */
585     (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc,
586 		       PIOPT_ALL, &ud);
587 }
588 
589 
590 
591 /*
592  * callback proc to actually do the logfile write from Update_Logfile
593  */
594 static int
595 update_logfile_proc (const char *repository, const char *filter, void *closure)
596 {
597     struct ulp_data *udp = closure;
598     TRACE (TRACE_FUNCTION, "update_logfile_proc(%s,%s)", repository, filter);
599     return logfile_write (repository, filter, udp->message, udp->logfp,
600                           udp->changes);
601 }
602 
603 
604 
605 /* static int
606  * logmsg_list_to_args_proc( Node *p, void *closure )
607  * This function is intended to be passed into walklist() with a list of tags
608  * (nodes in the same format as pretag_list_proc() accepts - p->key = tagname
609  * and p->data = a revision.
610  *
611  * closure will be a struct format_cmdline_walklist_closure
612  * where closure is undefined.
613  */
614 static int
615 logmsg_list_to_args_proc (Node *p, void *closure)
616 {
617     struct format_cmdline_walklist_closure *c = closure;
618     struct logfile_info *li;
619     char *arg = NULL;
620     const char *f;
621     char *d;
622     size_t doff;
623 
624     if (p->data == NULL) return 1;
625 
626     f = c->format;
627     d = *c->d;
628     /* foreach requested attribute */
629     while (*f)
630     {
631 	switch (*f++)
632 	{
633 	    case 's':
634 		arg = p->key;
635 		break;
636 	    case 'T':
637 	    case 't':
638 		li = p->data;
639 		arg = li->tag ? li->tag : "";
640 		break;
641 	    case 'V':
642 		li = p->data;
643 		arg = li->rev_old ? li->rev_old : "NONE";
644 		break;
645 	    case 'v':
646 		li = p->data;
647 		arg = li->rev_new ? li->rev_new : "NONE";
648 		break;
649 	    default:
650 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
651 		if (c->onearg)
652 		{
653 		    /* The old deafult was to print the empty string for
654 		     * unknown args.
655 		     */
656 		    arg = "\0";
657 		}
658 		else
659 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
660 		    error (1, 0,
661 		           "Unknown format character or not a list attribute: %c", f[-1]);
662 		/* NOTREACHED */
663 		break;
664 	}
665 	/* copy the attribute into an argument */
666 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
667 	if (c->onearg)
668 	{
669 	    if (c->firstpass)
670 	    {
671 		c->firstpass = 0;
672 		doff = d - *c->buf;
673 		expand_string (c->buf, c->length,
674 		               doff + strlen (c->srepos) + 1);
675 		d = *c->buf + doff;
676 		strncpy (d, c->srepos, strlen (c->srepos));
677 		d += strlen (c->srepos);
678 	    	*d++ = ' ';
679 	    }
680 	}
681 	else /* c->onearg */
682 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
683 	{
684 	    if (c->quotes)
685 	    {
686 		arg = cmdlineescape (c->quotes, arg);
687 	    }
688 	    else
689 	    {
690 		arg = cmdlinequote ('"', arg);
691 	    }
692 	} /* !c->onearg */
693 	doff = d - *c->buf;
694 	expand_string (c->buf, c->length, doff + strlen (arg));
695 	d = *c->buf + doff;
696 	strncpy (d, arg, strlen (arg));
697 	d += strlen (arg);
698 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
699 	if (!c->onearg)
700 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
701 	    free (arg);
702 
703 	/* Always put the extra space on.  we'll have to back up a char
704 	 * when we're done, but that seems most efficient.
705 	 */
706 	doff = d - *c->buf;
707 	expand_string (c->buf, c->length, doff + 1);
708 	d = *c->buf + doff;
709 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
710 	if (c->onearg && *f) *d++ = ',';
711 	else
712 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
713 	    *d++ = ' ';
714     }
715     /* correct our original pointer into the buff */
716     *c->d = d;
717     return 0;
718 }
719 
720 
721 
722 /*
723  * Writes some stuff to the logfile "filter" and returns the status of the
724  * filter program.
725  */
726 static int
727 logfile_write (const char *repository, const char *filter, const char *message,
728                FILE *logfp, List *changes)
729 {
730     char *cmdline;
731     FILE *pipefp;
732     char *cp;
733     int c;
734     int pipestatus;
735     const char *srepos = Short_Repository (repository);
736 
737     assert (repository);
738 
739     /* The user may specify a format string as part of the filter.
740        Originally, `%s' was the only valid string.  The string that
741        was substituted for it was:
742 
743          <repository-name> <file1> <file2> <file3> ...
744 
745        Each file was either a new directory/import (T_TITLE), or a
746        added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
747        file.
748 
749        It is desirable to preserve that behavior so lots of commitlog
750        scripts won't die when they get this new code.  At the same
751        time, we'd like to pass other information about the files (like
752        version numbers, statuses, or checkin times).
753 
754        The solution is to allow a format string that allows us to
755        specify those other pieces of information.  The format string
756        will be composed of `%' followed by a single format character,
757        or followed by a set of format characters surrounded by `{' and
758        `}' as separators.  The format characters are:
759 
760          s = file name
761 	 V = old version number (pre-checkin)
762 	 v = new version number (post-checkin)
763 
764        For example, valid format strings are:
765 
766          %{}
767 	 %s
768 	 %{s}
769 	 %{sVv}
770 
771        There's no reason that more items couldn't be added (like
772        modification date or file status [added, modified, updated,
773        etc.]) -- the code modifications would be minimal (logmsg.c
774        (title_proc) and commit.c (check_fileproc)).
775 
776        The output will be a string of tokens separated by spaces.  For
777        backwards compatibility, the the first token will be the
778        repository name.  The rest of the tokens will be
779        comma-delimited lists of the information requested in the
780        format string.  For example, if `/u/src/master' is the
781        repository, `%{sVv}' is the format string, and three files
782        (ChangeLog, Makefile, foo.c) were modified, the output might
783        be:
784 
785          /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
786 
787        Why this duplicates the old behavior when the format string is
788        `%s' is left as an exercise for the reader. */
789 
790     /* %c = cvs_cmd_name
791      * %p = shortrepos
792      * %r = repository
793      * %{sVv} = file name, old revision (precommit), new revision (postcommit)
794      */
795     /*
796      * Cast any NULL arguments as appropriate pointers as this is an
797      * stdarg function and we need to be certain the caller gets what
798      * is expected.
799      */
800     cmdline = format_cmdline (
801 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
802 	                      !config->UseNewInfoFmtStrings, srepos,
803 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
804 	                      filter,
805 	                      "c", "s", cvs_cmd_name,
806 #ifdef SERVER_SUPPORT
807 	                      "R", "s", referrer ? referrer->original : "NONE",
808 #endif /* SERVER_SUPPORT */
809 	                      "p", "s", srepos,
810 	                      "r", "s", current_parsed_root->directory,
811 	                      "sVv", ",", changes,
812 			      logmsg_list_to_args_proc, (void *) NULL,
813 	                      (char *) NULL);
814     if (!cmdline || !strlen (cmdline))
815     {
816 	if (cmdline) free (cmdline);
817 	error (0, 0, "logmsg proc resolved to the empty string!");
818 	return 1;
819     }
820 
821     if ((pipefp = run_popen (cmdline, "w")) == NULL)
822     {
823 	if (!noexec)
824 	    error (0, 0, "cannot write entry to log filter: %s", cmdline);
825 	free (cmdline);
826 	return 1;
827     }
828     (void) fprintf (pipefp, "Update of %s\n", repository);
829     (void) fprintf (pipefp, "In directory %s:", hostname);
830     cp = xgetcwd ();
831     if (cp == NULL)
832 	fprintf (pipefp, "<cannot get working directory: %s>\n\n",
833 		 strerror (errno));
834     else
835     {
836 	fprintf (pipefp, "%s\n\n", cp);
837 	free (cp);
838     }
839 
840     setup_tmpfile (pipefp, "", changes);
841     (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
842     if (logfp)
843     {
844 	(void) fprintf (pipefp, "Status:\n");
845 	rewind (logfp);
846 	while ((c = getc (logfp)) != EOF)
847 	    (void) putc (c, pipefp);
848     }
849     free (cmdline);
850     pipestatus = pclose (pipefp);
851     return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
852 }
853 
854 
855 
856 /*  This routine is called by Parse_Info.  It runs the
857  *  message verification script.
858  */
859 static int
860 verifymsg_proc (const char *repository, const char *script, void *closure)
861 {
862     char *verifymsg_script;
863 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
864     char *newscript = NULL;
865 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
866     struct verifymsg_proc_data *vpd = closure;
867     const char *srepos = Short_Repository (repository);
868 
869 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
870     if (!strchr (script, '%'))
871     {
872 	error (0, 0,
873 	       "warning: verifymsg line doesn't contain any format strings:\n"
874                "    \"%s\"\n"
875                "Appending default format string (\" %%l\"), but be aware that this usage is\n"
876                "deprecated.", script);
877 	script = newscript = Xasprintf ("%s %%l", script);
878     }
879 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
880 
881     /* If we don't already have one, open a temporary file, write the message
882      * to the temp file, and close the file.
883      *
884      * We do this here so that we only create the file when there is a
885      * verifymsg script specified and we only create it once when there is
886      * more than one verifymsg script specified.
887      */
888     if (vpd->fname == NULL)
889     {
890 	FILE *fp;
891 	if ((fp = cvs_temp_file (&(vpd->fname))) == NULL)
892 	    error (1, errno, "cannot create temporary file %s", vpd->fname);
893 
894 	if (vpd->message != NULL)
895 	    fputs (vpd->message, fp);
896 	if (vpd->message == NULL ||
897 	    (vpd->message)[0] == '\0' ||
898 	    (vpd->message)[strlen (vpd->message) - 1] != '\n')
899 	    putc ('\n', fp);
900 	if (fclose (fp) == EOF)
901 	    error (1, errno, "%s", vpd->fname);
902 
903 	if (config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
904 	{
905 	    /* Remember the status of the temp file for later */
906 	    if (stat (vpd->fname, &(vpd->pre_stbuf)) != 0)
907 		error (1, errno, "cannot stat temp file %s", vpd->fname);
908 
909 	    /*
910 	     * See if we need to sleep before running the verification
911 	     * script to avoid time-stamp races.
912 	     */
913 	    sleep_past (vpd->pre_stbuf.st_mtime);
914 	}
915     } /* if (vpd->fname == NULL) */
916 
917     /*
918      * Cast any NULL arguments as appropriate pointers as this is an
919      * stdarg function and we need to be certain the caller gets what
920      * is expected.
921      */
922     verifymsg_script = format_cmdline (
923 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
924                                        false, srepos,
925 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
926                                        script,
927 				       "c", "s", cvs_cmd_name,
928 #ifdef SERVER_SUPPORT
929 				       "R", "s", referrer
930 				       ? referrer->original : "NONE",
931 #endif /* SERVER_SUPPORT */
932                                        "p", "s", srepos,
933                                        "r", "s",
934                                        current_parsed_root->directory,
935                                        "l", "s", vpd->fname,
936 				       "sV", ",", vpd->changes,
937 				       logmsg_list_to_args_proc, (void *) NULL,
938 				       (char *) NULL);
939 
940 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
941     if (newscript) free (newscript);
942 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
943 
944     if (!verifymsg_script || !strlen (verifymsg_script))
945     {
946 	if (verifymsg_script) free (verifymsg_script);
947 	verifymsg_script = NULL;
948 	error (0, 0, "verifymsg proc resolved to the empty string!");
949 	return 1;
950     }
951 
952     run_setup (verifymsg_script);
953 
954     free (verifymsg_script);
955 
956     /* FIXME - because run_exec can return negative values and Parse_Info adds
957      * the values of each call to this function to get a total error, we are
958      * calling abs on the value of run_exec to ensure two errors do not sum to
959      * zero.
960      *
961      * The only REALLY obnoxious thing about this, I guess, is that a -1 return
962      * code from run_exec can mean we failed to call the process for some
963      * reason and should care about errno or that the process we called
964      * returned -1 and the value of errno is undefined.  In other words,
965      * run_exec should probably be rewritten to have two return codes.  one
966      * which is its own exit status and one which is the child process's.  So
967      * there.  :P
968      *
969      * Once run_exec is returning two error codes, we should probably be
970      * failing here with an error message including errno when we get the
971      * return code which means we care about errno, in case you missed that
972      * little tidbit.
973      *
974      * I do happen to know we just fail for a non-zero value anyway and I
975      * believe the docs actually state that if the verifymsg_proc returns a
976      * "non-zero" value we will fail.
977      */
978     return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
979 			  RUN_NORMAL | RUN_SIGIGNORE));
980 }
981