xref: /dragonfly/contrib/cvs-1.12/src/logmsg.c (revision 9f7604d7)
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 
228     if (repository != NULL)
229 	/* tack templates on if necessary */
230 	(void) Parse_Info (CVSROOTADM_RCSINFO, repository, rcsinfo_proc,
231 		PIOPT_ALL, NULL);
232     else
233     {
234 	FILE *tfp;
235 	char buf[1024];
236 	size_t n;
237 	size_t nwrite;
238 
239 	/* Why "b"?  */
240 	tfp = CVS_FOPEN (CVSADM_TEMPLATE, "rb");
241 	if (tfp == NULL)
242 	{
243 	    if (!existence_error (errno))
244 		error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
245 	}
246 	else
247 	{
248 	    while (!feof (tfp))
249 	    {
250 		char *p = buf;
251 		n = fread (buf, 1, sizeof buf, tfp);
252 		nwrite = n;
253 		while (nwrite > 0)
254 		{
255 		    n = fwrite (p, 1, nwrite, fp);
256 		    nwrite -= n;
257 		    p += n;
258 		}
259 		if (ferror (tfp))
260 		    error (1, errno, "cannot read %s", CVSADM_TEMPLATE);
261 	    }
262 	    if (fclose (tfp) < 0)
263 		error (0, errno, "cannot close %s", CVSADM_TEMPLATE);
264 	}
265     }
266 
267     (void) fprintf (fp,
268   "%s----------------------------------------------------------------------\n",
269 		    CVSEDITPREFIX);
270     (void) fprintf (fp,
271   "%sEnter Log.  Lines beginning with `%.*s' are removed automatically\n%s\n",
272 		    CVSEDITPREFIX, CVSEDITPREFIXLEN, CVSEDITPREFIX,
273 		    CVSEDITPREFIX);
274     if (dir != NULL && *dir)
275 	(void) fprintf (fp, "%sCommitting in %s\n%s\n", CVSEDITPREFIX,
276 			dir, CVSEDITPREFIX);
277     if (changes != NULL)
278 	setup_tmpfile (fp, CVSEDITPREFIX, changes);
279     (void) fprintf (fp,
280   "%s----------------------------------------------------------------------\n",
281 		    CVSEDITPREFIX);
282 
283     /* finish off the temp file */
284     if (fclose (fp) == EOF)
285         error (1, errno, "%s", fname);
286     if (stat (fname, &pre_stbuf) == -1)
287 	pre_stbuf.st_mtime = 0;
288 
289     /* run the editor */
290     run_setup (Editor);
291     run_add_arg (fname);
292     if ((retcode = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
293 			     RUN_NORMAL | RUN_SIGIGNORE)) != 0)
294 	error (0, retcode == -1 ? errno : 0, "warning: editor session failed");
295 
296     /* put the entire message back into the *messagep variable */
297 
298     fp = xfopen (fname, "r");
299 
300     if (*messagep)
301 	free (*messagep);
302 
303     if (stat (fname, &post_stbuf) != 0)
304 	    error (1, errno, "cannot find size of temp file %s", fname);
305 
306     if (post_stbuf.st_size == 0)
307 	*messagep = NULL;
308     else
309     {
310 	/* On NT, we might read less than st_size bytes, but we won't
311 	   read more.  So this works.  */
312 	*messagep = (char *) xmalloc (post_stbuf.st_size + 1);
313  	(*messagep)[0] = '\0';
314     }
315 
316     line = NULL;
317     line_chars_allocated = 0;
318 
319     if (*messagep)
320     {
321 	size_t message_len = post_stbuf.st_size + 1;
322 	size_t offset = 0;
323 	while (1)
324 	{
325 	    line_length = getline (&line, &line_chars_allocated, fp);
326 	    if (line_length == -1)
327 	    {
328 		if (ferror (fp))
329 		    error (0, errno, "warning: cannot read %s", fname);
330 		break;
331 	    }
332 	    if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
333 		continue;
334 	    if (offset + line_length >= message_len)
335 		expand_string (messagep, &message_len,
336 				offset + line_length + 1);
337 	    (void) strcpy (*messagep + offset, line);
338 	    offset += line_length;
339 	}
340     }
341     if (fclose (fp) < 0)
342 	error (0, errno, "warning: cannot close %s", fname);
343 
344     /* canonicalize emply messages */
345     if (*messagep != NULL &&
346         (**messagep == '\0' || strcmp (*messagep, "\n") == 0))
347     {
348 	free (*messagep);
349 	*messagep = NULL;
350     }
351 
352     if (pre_stbuf.st_mtime == post_stbuf.st_mtime || *messagep == NULL)
353     {
354 	for (;;)
355 	{
356 	    (void) printf ("\nLog message unchanged or not specified\n");
357 	    (void) printf ("a)bort, c)ontinue, e)dit, !)reuse this message unchanged for remaining dirs\n");
358 	    (void) printf ("Action: (continue) ");
359 	    (void) fflush (stdout);
360 	    line_length = getline (&line, &line_chars_allocated, stdin);
361 	    if (line_length < 0)
362 	    {
363 		error (0, errno, "cannot read from stdin");
364 		if (unlink_file (fname) < 0)
365 		    error (0, errno,
366 			   "warning: cannot remove temp file %s", fname);
367 		error (1, 0, "aborting");
368 	    }
369 	    else if (line_length == 0
370 		     || *line == '\n' || *line == 'c' || *line == 'C')
371 		break;
372 	    if (*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 == 'e' || *line == 'E')
379 		goto again;
380 	    if (*line == '!')
381 	    {
382 		reuse_log_message = 1;
383 		break;
384 	    }
385 	    (void) printf ("Unknown input\n");
386 	}
387     }
388     if (line)
389 	free (line);
390     if (unlink_file (fname) < 0)
391 	error (0, errno, "warning: cannot remove temp file %s", fname);
392     free (fname);
393 }
394 
395 /* Runs the user-defined verification script as part of the commit or import
396    process.  This verification is meant to be run whether or not the user
397    included the -m attribute.  unlike the do_editor function, this is
398    independant of the running of an editor for getting a message.
399  */
400 void
401 do_verify (char **messagep, const char *repository, List *changes)
402 {
403     int err;
404     struct verifymsg_proc_data data;
405     struct stat post_stbuf;
406 
407     if (current_parsed_root->isremote)
408 	/* The verification will happen on the server.  */
409 	return;
410 
411     /* FIXME? Do we really want to skip this on noexec?  What do we do
412        for the other administrative files?  */
413     /* EXPLAIN: Why do we check for repository == NULL here? */
414     if (noexec || repository == NULL)
415 	return;
416 
417     /* Get the name of the verification script to run  */
418 
419     data.message = *messagep;
420     data.fname = NULL;
421     data.changes = changes;
422     if ((err = Parse_Info (CVSROOTADM_VERIFYMSG, repository,
423 	                  verifymsg_proc, 0, &data)) != 0)
424     {
425 	int saved_errno = errno;
426 	/* Since following error() exits, delete the temp file now.  */
427 	if (data.fname != NULL && unlink_file( data.fname ) < 0)
428 	    error (0, errno, "cannot remove %s", data.fname);
429 	free (data.fname);
430 
431 	errno = saved_errno;
432 	error (1, err == -1 ? errno : 0, "Message verification failed");
433     }
434 
435     /* Return if no temp file was created.  That means that we didn't call any
436      * verifymsg scripts.
437      */
438     if (data.fname == NULL)
439 	return;
440 
441     /* Get the mod time and size of the possibly new log message
442      * in always and stat modes.
443      */
444     if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
445 	config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
446     {
447 	if(stat (data.fname, &post_stbuf) != 0)
448 	    error (1, errno, "cannot find size of temp file %s", data.fname);
449     }
450 
451     /* And reread the log message in `always' mode or in `stat' mode when it's
452      * changed.
453      */
454     if (config->RereadLogAfterVerify == LOGMSG_REREAD_ALWAYS ||
455 	(config->RereadLogAfterVerify == LOGMSG_REREAD_STAT &&
456 	  (data.pre_stbuf.st_mtime != post_stbuf.st_mtime ||
457 	    data.pre_stbuf.st_size != post_stbuf.st_size)))
458     {
459 	/* put the entire message back into the *messagep variable */
460 
461 	if (*messagep) free (*messagep);
462 
463 	if (post_stbuf.st_size == 0)
464 	    *messagep = NULL;
465 	else
466 	{
467 	    char *line = NULL;
468 	    int line_length;
469 	    size_t line_chars_allocated = 0;
470 	    char *p;
471 	    FILE *fp;
472 
473 	    fp = xfopen (data.fname, "r");
474 
475 	    /* On NT, we might read less than st_size bytes,
476 	       but we won't read more.  So this works.  */
477 	    p = *messagep = (char *) xmalloc (post_stbuf.st_size + 1);
478 	    *messagep[0] = '\0';
479 
480 	    for (;;)
481 	    {
482 		line_length = getline( &line,
483 				       &line_chars_allocated,
484 				       fp);
485 		if (line_length == -1)
486 		{
487 		    if (ferror (fp))
488 			/* Fail in this case because otherwise we will have no
489 			 * log message
490 			 */
491 			error (1, errno, "cannot read %s", data.fname);
492 		    break;
493 		}
494 		if (strncmp (line, CVSEDITPREFIX, CVSEDITPREFIXLEN) == 0)
495 		    continue;
496 		(void) strcpy (p, line);
497 		p += line_length;
498 	    }
499 	    if (line) free (line);
500 	    if (fclose (fp) < 0)
501 	        error (0, errno, "warning: cannot close %s", data.fname);
502 	}
503     }
504     /* Delete the temp file  */
505     if (unlink_file (data.fname) < 0)
506 	error (0, errno, "cannot remove `%s'", data.fname);
507     free (data.fname);
508 }
509 
510 
511 
512 /*
513  * callback proc for Parse_Info for rcsinfo templates this routine basically
514  * copies the matching template onto the end of the tempfile we are setting
515  * up
516  */
517 /* ARGSUSED */
518 static int
519 rcsinfo_proc (const char *repository, const char *template, void *closure)
520 {
521     static char *last_template;
522     FILE *tfp;
523 
524     /* nothing to do if the last one included is the same as this one */
525     if (last_template && strcmp (last_template, template) == 0)
526 	return (0);
527     if (last_template)
528 	free (last_template);
529     last_template = xstrdup (template);
530 
531     if ((tfp = CVS_FOPEN (template, "r")) != NULL)
532     {
533 	char *line = NULL;
534 	size_t line_chars_allocated = 0;
535 
536 	while (getline (&line, &line_chars_allocated, tfp) >= 0)
537 	    (void) fputs (line, fp);
538 	if (ferror (tfp))
539 	    error (0, errno, "warning: cannot read %s", template);
540 	if (fclose (tfp) < 0)
541 	    error (0, errno, "warning: cannot close %s", template);
542 	if (line)
543 	    free (line);
544 	return (0);
545     }
546     else
547     {
548 	error (0, errno, "Couldn't open rcsinfo template file %s", template);
549 	return (1);
550     }
551 }
552 
553 /*
554  * Uses setup_tmpfile() to pass the updated message on directly to any
555  * logfile programs that have a regular expression match for the checked in
556  * directory in the source repository.  The log information is fed into the
557  * specified program as standard input.
558  */
559 struct ulp_data {
560     FILE *logfp;
561     const char *message;
562     List *changes;
563 };
564 
565 
566 
567 void
568 Update_Logfile (const char *repository, const char *xmessage, FILE *xlogfp,
569                 List *xchanges)
570 {
571     struct ulp_data ud;
572 
573     /* nothing to do if the list is empty */
574     if (xchanges == NULL || xchanges->list->next == xchanges->list)
575 	return;
576 
577     /* set up vars for update_logfile_proc */
578     ud.message = xmessage;
579     ud.logfp = xlogfp;
580     ud.changes = xchanges;
581 
582     /* call Parse_Info to do the actual logfile updates */
583     (void) Parse_Info (CVSROOTADM_LOGINFO, repository, update_logfile_proc,
584 		       PIOPT_ALL, &ud);
585 }
586 
587 
588 
589 /*
590  * callback proc to actually do the logfile write from Update_Logfile
591  */
592 static int
593 update_logfile_proc (const char *repository, const char *filter, void *closure)
594 {
595     struct ulp_data *udp = closure;
596     TRACE (TRACE_FUNCTION, "update_logfile_proc(%s,%s)", repository, filter);
597     return logfile_write (repository, filter, udp->message, udp->logfp,
598                           udp->changes);
599 }
600 
601 
602 
603 /* static int
604  * logmsg_list_to_args_proc( Node *p, void *closure )
605  * This function is intended to be passed into walklist() with a list of tags
606  * (nodes in the same format as pretag_list_proc() accepts - p->key = tagname
607  * and p->data = a revision.
608  *
609  * closure will be a struct format_cmdline_walklist_closure
610  * where closure is undefined.
611  */
612 static int
613 logmsg_list_to_args_proc (Node *p, void *closure)
614 {
615     struct format_cmdline_walklist_closure *c = closure;
616     struct logfile_info *li;
617     char *arg = NULL;
618     const char *f;
619     char *d;
620     size_t doff;
621 
622     if (p->data == NULL) return 1;
623 
624     f = c->format;
625     d = *c->d;
626     /* foreach requested attribute */
627     while (*f)
628     {
629 	switch (*f++)
630 	{
631 	    case 's':
632 		arg = p->key;
633 		break;
634 	    case 'T':
635 		li = p->data;
636 		arg = li->tag ? li->tag : "";
637 		break;
638 	    case 'V':
639 		li = p->data;
640 		arg = li->rev_old ? li->rev_old : "NONE";
641 		break;
642 	    case 'v':
643 		li = p->data;
644 		arg = li->rev_new ? li->rev_new : "NONE";
645 		break;
646 	    default:
647 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
648 		if (c->onearg)
649 		{
650 		    /* The old deafult was to print the empty string for
651 		     * unknown args.
652 		     */
653 		    arg = "\0";
654 		}
655 		else
656 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
657 		    error (1, 0,
658 		           "Unknown format character or not a list attribute: %c", f[-1]);
659 		/* NOTREACHED */
660 		break;
661 	}
662 	/* copy the attribute into an argument */
663 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
664 	if (c->onearg)
665 	{
666 	    if (c->firstpass)
667 	    {
668 		c->firstpass = 0;
669 		doff = d - *c->buf;
670 		expand_string (c->buf, c->length,
671 		               doff + strlen (c->srepos) + 1);
672 		d = *c->buf + doff;
673 		strncpy (d, c->srepos, strlen (c->srepos));
674 		d += strlen (c->srepos);
675 	    	*d++ = ' ';
676 	    }
677 	}
678 	else /* c->onearg */
679 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
680 	{
681 	    if (c->quotes)
682 	    {
683 		arg = cmdlineescape (c->quotes, arg);
684 	    }
685 	    else
686 	    {
687 		arg = cmdlinequote ('"', arg);
688 	    }
689 	} /* !c->onearg */
690 	doff = d - *c->buf;
691 	expand_string (c->buf, c->length, doff + strlen (arg));
692 	d = *c->buf + doff;
693 	strncpy (d, arg, strlen (arg));
694 	d += strlen (arg);
695 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
696 	if (!c->onearg)
697 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
698 	    free (arg);
699 
700 	/* Always put the extra space on.  we'll have to back up a char
701 	 * when we're done, but that seems most efficient.
702 	 */
703 	doff = d - *c->buf;
704 	expand_string (c->buf, c->length, doff + 1);
705 	d = *c->buf + doff;
706 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
707 	if (c->onearg && *f) *d++ = ',';
708 	else
709 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
710 	    *d++ = ' ';
711     }
712     /* correct our original pointer into the buff */
713     *c->d = d;
714     return 0;
715 }
716 
717 
718 
719 /*
720  * Writes some stuff to the logfile "filter" and returns the status of the
721  * filter program.
722  */
723 static int
724 logfile_write (const char *repository, const char *filter, const char *message,
725                FILE *logfp, List *changes)
726 {
727     char *cmdline;
728     FILE *pipefp;
729     char *cp;
730     int c;
731     int pipestatus;
732     const char *srepos = Short_Repository (repository);
733 
734     assert (repository);
735 
736     /* The user may specify a format string as part of the filter.
737        Originally, `%s' was the only valid string.  The string that
738        was substituted for it was:
739 
740          <repository-name> <file1> <file2> <file3> ...
741 
742        Each file was either a new directory/import (T_TITLE), or a
743        added (T_ADDED), modified (T_MODIFIED), or removed (T_REMOVED)
744        file.
745 
746        It is desirable to preserve that behavior so lots of commitlog
747        scripts won't die when they get this new code.  At the same
748        time, we'd like to pass other information about the files (like
749        version numbers, statuses, or checkin times).
750 
751        The solution is to allow a format string that allows us to
752        specify those other pieces of information.  The format string
753        will be composed of `%' followed by a single format character,
754        or followed by a set of format characters surrounded by `{' and
755        `}' as separators.  The format characters are:
756 
757          s = file name
758 	 V = old version number (pre-checkin)
759 	 v = new version number (post-checkin)
760 
761        For example, valid format strings are:
762 
763          %{}
764 	 %s
765 	 %{s}
766 	 %{sVv}
767 
768        There's no reason that more items couldn't be added (like
769        modification date or file status [added, modified, updated,
770        etc.]) -- the code modifications would be minimal (logmsg.c
771        (title_proc) and commit.c (check_fileproc)).
772 
773        The output will be a string of tokens separated by spaces.  For
774        backwards compatibility, the the first token will be the
775        repository name.  The rest of the tokens will be
776        comma-delimited lists of the information requested in the
777        format string.  For example, if `/u/src/master' is the
778        repository, `%{sVv}' is the format string, and three files
779        (ChangeLog, Makefile, foo.c) were modified, the output might
780        be:
781 
782          /u/src/master ChangeLog,1.1,1.2 Makefile,1.3,1.4 foo.c,1.12,1.13
783 
784        Why this duplicates the old behavior when the format string is
785        `%s' is left as an exercise for the reader. */
786 
787     /* %c = cvs_cmd_name
788      * %p = shortrepos
789      * %r = repository
790      * %{sVv} = file name, old revision (precommit), new revision (postcommit)
791      */
792     /*
793      * Cast any NULL arguments as appropriate pointers as this is an
794      * stdarg function and we need to be certain the caller gets what
795      * is expected.
796      */
797     cmdline = format_cmdline (
798 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
799 	                      !config->UseNewInfoFmtStrings, srepos,
800 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
801 	                      filter,
802 	                      "c", "s", cvs_cmd_name,
803 #ifdef SERVER_SUPPORT
804 	                      "R", "s", referrer ? referrer->original : "NONE",
805 #endif /* SERVER_SUPPORT */
806 	                      "p", "s", srepos,
807 	                      "r", "s", current_parsed_root->directory,
808 	                      "sVv", ",", changes,
809 			      logmsg_list_to_args_proc, (void *) NULL,
810 	                      (char *) NULL);
811     if (!cmdline || !strlen (cmdline))
812     {
813 	if (cmdline) free (cmdline);
814 	error (0, 0, "logmsg proc resolved to the empty string!");
815 	return 1;
816     }
817 
818     if ((pipefp = run_popen (cmdline, "w")) == NULL)
819     {
820 	if (!noexec)
821 	    error (0, 0, "cannot write entry to log filter: %s", cmdline);
822 	free (cmdline);
823 	return 1;
824     }
825     (void) fprintf (pipefp, "Update of %s\n", repository);
826     (void) fprintf (pipefp, "In directory %s:", hostname);
827     cp = xgetcwd ();
828     if (cp == NULL)
829 	fprintf (pipefp, "<cannot get working directory: %s>\n\n",
830 		 strerror (errno));
831     else
832     {
833 	fprintf (pipefp, "%s\n\n", cp);
834 	free (cp);
835     }
836 
837     setup_tmpfile (pipefp, "", changes);
838     (void) fprintf (pipefp, "Log Message:\n%s\n", (message) ? message : "");
839     if (logfp)
840     {
841 	(void) fprintf (pipefp, "Status:\n");
842 	rewind (logfp);
843 	while ((c = getc (logfp)) != EOF)
844 	    (void) putc (c, pipefp);
845     }
846     free (cmdline);
847     pipestatus = pclose (pipefp);
848     return ((pipestatus == -1) || (pipestatus == 127)) ? 1 : 0;
849 }
850 
851 
852 
853 /*  This routine is called by Parse_Info.  It runs the
854  *  message verification script.
855  */
856 static int
857 verifymsg_proc (const char *repository, const char *script, void *closure)
858 {
859     char *verifymsg_script;
860 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
861     char *newscript = NULL;
862 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
863     struct verifymsg_proc_data *vpd = closure;
864     const char *srepos = Short_Repository (repository);
865 
866 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
867     if (!strchr (script, '%'))
868     {
869 	error (0, 0,
870 	       "warning: verifymsg line doesn't contain any format strings:\n"
871                "    \"%s\"\n"
872                "Appending default format string (\" %%l\"), but be aware that this usage is\n"
873                "deprecated.", script);
874 	script = newscript = Xasprintf ("%s %%l", script);
875     }
876 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
877 
878     /* If we don't already have one, open a temporary file, write the message
879      * to the temp file, and close the file.
880      *
881      * We do this here so that we only create the file when there is a
882      * verifymsg script specified and we only create it once when there is
883      * more than one verifymsg script specified.
884      */
885     if (vpd->fname == NULL)
886     {
887 	FILE *fp;
888 	if ((fp = cvs_temp_file (&(vpd->fname))) == NULL)
889 	    error (1, errno, "cannot create temporary file %s", vpd->fname);
890 
891 	if (vpd->message != NULL)
892 	    fputs (vpd->message, fp);
893 	if (vpd->message == NULL ||
894 	    (vpd->message)[0] == '\0' ||
895 	    (vpd->message)[strlen (vpd->message) - 1] != '\n')
896 	    putc ('\n', fp);
897 	if (fclose (fp) == EOF)
898 	    error (1, errno, "%s", vpd->fname);
899 
900 	if (config->RereadLogAfterVerify == LOGMSG_REREAD_STAT)
901 	{
902 	    /* Remember the status of the temp file for later */
903 	    if (stat (vpd->fname, &(vpd->pre_stbuf)) != 0)
904 		error (1, errno, "cannot stat temp file %s", vpd->fname);
905 
906 	    /*
907 	     * See if we need to sleep before running the verification
908 	     * script to avoid time-stamp races.
909 	     */
910 	    sleep_past (vpd->pre_stbuf.st_mtime);
911 	}
912     } /* if (vpd->fname == NULL) */
913 
914     /*
915      * Cast any NULL arguments as appropriate pointers as this is an
916      * stdarg function and we need to be certain the caller gets what
917      * is expected.
918      */
919     verifymsg_script = format_cmdline (
920 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
921                                        false, srepos,
922 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
923                                        script,
924 				       "c", "s", cvs_cmd_name,
925 #ifdef SERVER_SUPPORT
926 				       "R", "s", referrer
927 				       ? referrer->original : "NONE",
928 #endif /* SERVER_SUPPORT */
929                                        "p", "s", srepos,
930                                        "r", "s",
931                                        current_parsed_root->directory,
932                                        "l", "s", vpd->fname,
933 				       "sV", ",", vpd->changes,
934 				       logmsg_list_to_args_proc, (void *) NULL,
935 				       (char *) NULL);
936 
937 #ifdef SUPPORT_OLD_INFO_FMT_STRINGS
938     if (newscript) free (newscript);
939 #endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
940 
941     if (!verifymsg_script || !strlen (verifymsg_script))
942     {
943 	if (verifymsg_script) free (verifymsg_script);
944 	verifymsg_script = NULL;
945 	error (0, 0, "verifymsg proc resolved to the empty string!");
946 	return 1;
947     }
948 
949     run_setup (verifymsg_script);
950 
951     free (verifymsg_script);
952 
953     /* FIXME - because run_exec can return negative values and Parse_Info adds
954      * the values of each call to this function to get a total error, we are
955      * calling abs on the value of run_exec to ensure two errors do not sum to
956      * zero.
957      *
958      * The only REALLY obnoxious thing about this, I guess, is that a -1 return
959      * code from run_exec can mean we failed to call the process for some
960      * reason and should care about errno or that the process we called
961      * returned -1 and the value of errno is undefined.  In other words,
962      * run_exec should probably be rewritten to have two return codes.  one
963      * which is its own exit status and one which is the child process's.  So
964      * there.  :P
965      *
966      * Once run_exec is returning two error codes, we should probably be
967      * failing here with an error message including errno when we get the
968      * return code which means we care about errno, in case you missed that
969      * little tidbit.
970      *
971      * I do happen to know we just fail for a non-zero value anyway and I
972      * believe the docs actually state that if the verifymsg_proc returns a
973      * "non-zero" value we will fail.
974      */
975     return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
976 			  RUN_NORMAL | RUN_SIGIGNORE));
977 }
978