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