xref: /dragonfly/contrib/cvs-1.12/src/logmsg.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
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     (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
do_verify(char ** messagep,const char * repository,List * changes)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
rcsinfo_proc(const char * repository,const char * template,void * closure)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
Update_Logfile(const char * repository,const char * xmessage,FILE * xlogfp,List * xchanges)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
update_logfile_proc(const char * repository,const char * filter,void * closure)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
logmsg_list_to_args_proc(Node * p,void * closure)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
logfile_write(const char * repository,const char * filter,const char * message,FILE * logfp,List * changes)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
verifymsg_proc(const char * repository,const char * script,void * closure)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