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