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