xref: /openbsd-src/gnu/usr.bin/cvs/src/wrapper.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /* This program is free software; you can redistribute it and/or modify
2    it under the terms of the GNU General Public License as published by
3    the Free Software Foundation; either version 2, or (at your option)
4    any later version.
5 
6    This program is distributed in the hope that it will be useful,
7    but WITHOUT ANY WARRANTY; without even the implied warranty of
8    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9    GNU General Public License for more details.  */
10 
11 #include "cvs.h"
12 #include "getline.h"
13 
14 /*
15   Original Author:  athan@morgan.com <Andrew C. Athan> 2/1/94
16   Modified By:      vdemarco@bou.shl.com
17 
18   This package was written to support the NEXTSTEP concept of
19   "wrappers."  These are essentially directories that are to be
20   treated as "files."  This package allows such wrappers to be
21   "processed" on the way in and out of CVS.  The intended use is to
22   wrap up a wrapper into a single tar, such that that tar can be
23   treated as a single binary file in CVS.  To solve the problem
24   effectively, it was also necessary to be able to prevent rcsmerge
25   application at appropriate times.
26 
27   ------------------
28   Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
29 
30   wildcard	[option value][option value]...
31 
32   where option is one of
33   -f		from cvs filter		value: path to filter
34   -t		to cvs filter		value: path to filter
35   -m		update methodology	value: MERGE or COPY
36   -k		default -k rcs option to use on import or add
37 
38   and value is a single-quote delimited value.
39 
40   E.g:
41   *.nib		-f 'gunzipuntar' -t 'targzip' -m 'COPY'
42 */
43 
44 
45 typedef struct {
46     char *wildCard;
47     char *tocvsFilter;
48     char *fromcvsFilter;
49     char *rcsOption;
50     WrapMergeMethod mergeMethod;
51 } WrapperEntry;
52 
53 static WrapperEntry **wrap_list=NULL;
54 static WrapperEntry **wrap_saved_list=NULL;
55 
56 static int wrap_size=0;
57 static int wrap_count=0;
58 static int wrap_tempcount=0;
59 
60 /* FIXME: the relationship between wrap_count, wrap_tempcount,
61  * wrap_saved_count, and wrap_saved_tempcount is not entirely clear;
62  * it is certainly suspicious that wrap_saved_count is never set to a
63  * value other than zero!  If the variable isn't being used, it should
64  * be removed.  And in general, we should describe how temporary
65  * vs. permanent wrappers are implemented, and then make sure the
66  * implementation is actually doing that.
67  *
68  * Right now things seem to be working, but that's no guarantee there
69  * isn't a bug lurking somewhere in the murk.
70  */
71 
72 static int wrap_saved_count=0;
73 
74 static int wrap_saved_tempcount=0;
75 
76 #define WRAPPER_GROW	8
77 
78 void wrap_add_entry PROTO((WrapperEntry *e,int temp));
79 void wrap_kill PROTO((void));
80 void wrap_kill_temp PROTO((void));
81 void wrap_free_entry PROTO((WrapperEntry *e));
82 void wrap_free_entry_internal PROTO((WrapperEntry *e));
83 void wrap_restore_saved PROTO((void));
84 
85 void wrap_setup()
86 {
87     /* FIXME-reentrancy: if we do a multithreaded server, will need to
88        move this to a per-connection data structure, or better yet
89        think about a cleaner solution.  */
90     static int wrap_setup_already_done = 0;
91     char *homedir;
92 
93     if (wrap_setup_already_done != 0)
94         return;
95     else
96         wrap_setup_already_done = 1;
97 
98 #ifdef CLIENT_SUPPORT
99     if (!client_active)
100 #endif
101     {
102 	char *file;
103 
104 	file = xmalloc (strlen (CVSroot_directory)
105 			+ sizeof (CVSROOTADM)
106 			+ sizeof (CVSROOTADM_WRAPPER)
107 			+ 10);
108 	/* Then add entries found in repository, if it exists.  */
109 	(void) sprintf (file, "%s/%s/%s", CVSroot_directory, CVSROOTADM,
110 			CVSROOTADM_WRAPPER);
111 	if (isfile (file))
112 	{
113 	    wrap_add_file(file,0);
114 	}
115 	free (file);
116     }
117 
118     /* Then add entries found in home dir, (if user has one) and file
119        exists.  */
120     homedir = get_homedir ();
121     /* If we can't find a home directory, ignore ~/.cvswrappers.  This may
122        make tracking down problems a bit of a pain, but on the other
123        hand it might be obnoxious to complain when CVS will function
124        just fine without .cvswrappers (and many users won't even know what
125        .cvswrappers is).  */
126     if (homedir != NULL)
127     {
128 	char *file;
129 
130 	file = xmalloc (strlen (homedir) + sizeof (CVSDOTWRAPPER) + 10);
131 	(void) sprintf (file, "%s/%s", homedir, CVSDOTWRAPPER);
132 	if (isfile (file))
133 	{
134 	    wrap_add_file (file, 0);
135 	}
136 	free (file);
137     }
138 
139     /* FIXME: calling wrap_add() below implies that the CVSWRAPPERS
140      * environment variable contains exactly one "wrapper" -- a line
141      * of the form
142      *
143      *    FILENAME_PATTERN	FLAG  OPTS [ FLAG OPTS ...]
144      *
145      * This may disagree with the documentation, which states:
146      *
147      *   `$CVSWRAPPERS'
148      *      A whitespace-separated list of file name patterns that CVS
149      *      should treat as wrappers. *Note Wrappers::.
150      *
151      * Does this mean the environment variable can hold multiple
152      * wrappers lines?  If so, a single call to wrap_add() is
153      * insufficient.
154      */
155 
156     /* Then add entries found in CVSWRAPPERS environment variable. */
157     wrap_add (getenv (WRAPPER_ENV), 0);
158 }
159 
160 #ifdef CLIENT_SUPPORT
161 /* Send -W arguments for the wrappers to the server.  The command must
162    be one that accepts them (e.g. update, import).  */
163 void
164 wrap_send ()
165 {
166     int i;
167 
168     for (i = 0; i < wrap_count + wrap_tempcount; ++i)
169     {
170 	if (wrap_list[i]->tocvsFilter != NULL
171 	    || wrap_list[i]->fromcvsFilter != NULL)
172 	    /* For greater studliness we would print the offending option
173 	       and (more importantly) where we found it.  */
174 	    error (0, 0, "\
175 -t and -f wrapper options are not supported remotely; ignored");
176 	if (wrap_list[i]->mergeMethod == WRAP_COPY)
177 	    /* For greater studliness we would print the offending option
178 	       and (more importantly) where we found it.  */
179 	    error (0, 0, "\
180 -m wrapper option is not supported remotely; ignored");
181 	if (wrap_list[i]->rcsOption != NULL)
182 	{
183 	    send_to_server ("Argument -W\012Argument ", 0);
184 	    send_to_server (wrap_list[i]->wildCard, 0);
185 	    send_to_server (" -k '", 0);
186 	    send_to_server (wrap_list[i]->rcsOption, 0);
187 	    send_to_server ("'\012", 0);
188 	}
189     }
190 }
191 #endif /* CLIENT_SUPPORT */
192 
193 #if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
194 /* Output wrapper entries in the format of cvswrappers lines.
195  *
196  * This is useful when one side of a client/server connection wants to
197  * send its wrappers to the other; since the receiving side would like
198  * to use wrap_add() to incorporate the wrapper, it's best if the
199  * entry arrives in this format.
200  *
201  * The entries are stored in `line', which is allocated here.  Caller
202  * can free() it.
203  *
204  * If first_call_p is nonzero, then start afresh.  */
205 void
206 wrap_unparse_rcs_options (line, first_call_p)
207     char **line;
208     int first_call_p;
209 {
210     /* FIXME-reentrancy: we should design a reentrant interface, like
211        a callback which gets handed each wrapper (a multithreaded
212        server being the most concrete reason for this, but the
213        non-reentrant interface is fairly unnecessary/ugly).  */
214     static int i;
215 
216     if (first_call_p)
217         i = 0;
218 
219     for (; i < wrap_count + wrap_tempcount; ++i)
220     {
221 	if (wrap_list[i]->rcsOption != NULL)
222 	{
223             *line = xmalloc (strlen (wrap_list[i]->wildCard)
224                              + strlen ("\t")
225                              + strlen (" -k '")
226                              + strlen (wrap_list[i]->rcsOption)
227                              + strlen ("'")
228                              + 1);  /* leave room for '\0' */
229 
230             strcpy (*line, wrap_list[i]->wildCard);
231             strcat (*line, " -k '");
232             strcat (*line, wrap_list[i]->rcsOption);
233             strcat (*line, "'");
234 
235             /* We're going to miss the increment because we return, so
236                do it by hand. */
237             ++i;
238 
239             return;
240 	}
241     }
242 
243     *line = NULL;
244     return;
245 }
246 #endif /* SERVER_SUPPORT || CLIENT_SUPPORT */
247 
248 /*
249  * Open a file and read lines, feeding each line to a line parser. Arrange
250  * for keeping a temporary list of wrappers at the end, if the "temp"
251  * argument is set.
252  */
253 void
254 wrap_add_file (file, temp)
255     const char *file;
256     int temp;
257 {
258     FILE *fp;
259     char *line = NULL;
260     size_t line_allocated = 0;
261 
262     wrap_restore_saved ();
263     wrap_kill_temp ();
264 
265     /* Load the file.  */
266     fp = CVS_FOPEN (file, "r");
267     if (fp == NULL)
268     {
269 	if (!existence_error (errno))
270 	    error (0, errno, "cannot open %s", file);
271 	return;
272     }
273     while (getline (&line, &line_allocated, fp) >= 0)
274 	wrap_add (line, temp);
275     if (line)
276         free (line);
277     if (ferror (fp))
278 	error (0, errno, "cannot read %s", file);
279     if (fclose (fp) == EOF)
280 	error (0, errno, "cannot close %s", file);
281 }
282 
283 void
284 wrap_kill()
285 {
286     wrap_kill_temp();
287     while(wrap_count)
288 	wrap_free_entry(wrap_list[--wrap_count]);
289 }
290 
291 void
292 wrap_kill_temp()
293 {
294     WrapperEntry **temps=wrap_list+wrap_count;
295 
296     while(wrap_tempcount)
297 	wrap_free_entry(temps[--wrap_tempcount]);
298 }
299 
300 void
301 wrap_free_entry(e)
302      WrapperEntry *e;
303 {
304     wrap_free_entry_internal(e);
305     free(e);
306 }
307 
308 void
309 wrap_free_entry_internal(e)
310     WrapperEntry *e;
311 {
312     free (e->wildCard);
313     if (e->tocvsFilter)
314 	free (e->tocvsFilter);
315     if (e->fromcvsFilter)
316 	free (e->fromcvsFilter);
317     if (e->rcsOption)
318 	free (e->rcsOption);
319 }
320 
321 void
322 wrap_restore_saved()
323 {
324     if(!wrap_saved_list)
325 	return;
326 
327     wrap_kill();
328 
329     free(wrap_list);
330 
331     wrap_list=wrap_saved_list;
332     wrap_count=wrap_saved_count;
333     wrap_tempcount=wrap_saved_tempcount;
334 
335     wrap_saved_list=NULL;
336     wrap_saved_count=0;
337     wrap_saved_tempcount=0;
338 }
339 
340 void
341 wrap_add (line, isTemp)
342    char *line;
343    int         isTemp;
344 {
345     char *temp;
346     char ctemp;
347     WrapperEntry e;
348     char opt;
349 
350     if (!line || line[0] == '#')
351 	return;
352 
353     memset (&e, 0, sizeof(e));
354 
355 	/* Search for the wild card */
356     while (*line && isspace ((unsigned char) *line))
357 	++line;
358     for (temp = line;
359 	 *line && !isspace ((unsigned char) *line);
360 	 ++line)
361 	;
362     if(temp==line)
363 	return;
364 
365     ctemp=*line;
366     *line='\0';
367 
368     e.wildCard=xstrdup(temp);
369     *line=ctemp;
370 
371     while(*line){
372 	    /* Search for the option */
373 	while(*line && *line!='-')
374 	    ++line;
375 	if(!*line)
376 	    break;
377 	++line;
378 	if(!*line)
379 	    break;
380 	opt=*line;
381 
382 	    /* Search for the filter commandline */
383 	for(++line;*line && *line!='\'';++line);
384 	if(!*line)
385 	    break;
386 
387 	for(temp=++line;*line && (*line!='\'' || line[-1]=='\\');++line)
388 	    ;
389 
390 	/* This used to "break;" (ignore the option) if there was a
391 	   single character between the single quotes (I'm guessing
392 	   that was accidental).  Now it "break;"s if there are no
393 	   characters.  I'm not sure either behavior is particularly
394 	   necessary--the current options might not require ''
395 	   arguments, but surely some future option legitimately
396 	   might.  Also I'm not sure that ignoring the option is a
397 	   swift way to handle syntax errors in general.  */
398 	if (line==temp)
399 	    break;
400 
401 	ctemp=*line;
402 	*line='\0';
403 	switch(opt){
404 	case 'f':
405 	    /* Before this is reenabled, need to address the problem in
406 	       commit.c (see http://www.cyclic.com/cvs/dev-wrap.txt).  */
407 	    error (1, 0,
408 		   "-t/-f wrappers not supported by this version of CVS");
409 
410 	    if(e.fromcvsFilter)
411 		free(e.fromcvsFilter);
412 	    /* FIXME: error message should say where the bad value
413 	       came from.  */
414 	    e.fromcvsFilter=expand_path (temp, "<wrapper>", 0);
415             if (!e.fromcvsFilter)
416 		error (1, 0, "Correct above errors first");
417 	    break;
418 	case 't':
419 	    /* Before this is reenabled, need to address the problem in
420 	       commit.c (see http://www.cyclic.com/cvs/dev-wrap.txt).  */
421 	    error (1, 0,
422 		   "-t/-f wrappers not supported by this version of CVS");
423 
424 	    if(e.tocvsFilter)
425 		free(e.tocvsFilter);
426 	    /* FIXME: error message should say where the bad value
427 	       came from.  */
428 	    e.tocvsFilter=expand_path (temp, "<wrapper>", 0);
429             if (!e.tocvsFilter)
430 		error (1, 0, "Correct above errors first");
431 	    break;
432 	case 'm':
433 	    if(*temp=='C' || *temp=='c')
434 		e.mergeMethod=WRAP_COPY;
435 	    else
436 		e.mergeMethod=WRAP_MERGE;
437 	    break;
438 	case 'k':
439 	    if (e.rcsOption)
440 		free (e.rcsOption);
441 	    e.rcsOption = xstrdup (temp);
442 	    break;
443 	default:
444 	    break;
445 	}
446 	*line=ctemp;
447 	if(!*line)break;
448 	++line;
449     }
450 
451     wrap_add_entry(&e, isTemp);
452 }
453 
454 void
455 wrap_add_entry(e, temp)
456     WrapperEntry *e;
457     int temp;
458 {
459     int x;
460     if(wrap_count+wrap_tempcount>=wrap_size){
461 	wrap_size += WRAPPER_GROW;
462 	wrap_list = (WrapperEntry **) xrealloc ((char *) wrap_list,
463 						wrap_size *
464 						sizeof (WrapperEntry *));
465     }
466 
467     if(!temp && wrap_tempcount){
468 	for(x=wrap_count+wrap_tempcount-1;x>=wrap_count;--x)
469 	    wrap_list[x+1]=wrap_list[x];
470     }
471 
472     x=(temp ? wrap_count+(wrap_tempcount++):(wrap_count++));
473     wrap_list[x]=(WrapperEntry *)xmalloc(sizeof(WrapperEntry));
474     wrap_list[x]->wildCard=e->wildCard;
475     wrap_list[x]->fromcvsFilter=e->fromcvsFilter;
476     wrap_list[x]->tocvsFilter=e->tocvsFilter;
477     wrap_list[x]->mergeMethod=e->mergeMethod;
478     wrap_list[x]->rcsOption = e->rcsOption;
479 }
480 
481 /* Return 1 if the given filename is a wrapper filename */
482 int
483 wrap_name_has (name,has)
484     const char   *name;
485     WrapMergeHas  has;
486 {
487     int x,count=wrap_count+wrap_tempcount;
488     char *temp;
489 
490     for(x=0;x<count;++x)
491 	if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0){
492 	    switch(has){
493 	    case WRAP_TOCVS:
494 		temp=wrap_list[x]->tocvsFilter;
495 		break;
496 	    case WRAP_FROMCVS:
497 		temp=wrap_list[x]->fromcvsFilter;
498 		break;
499 	    case WRAP_RCSOPTION:
500 		temp = wrap_list[x]->rcsOption;
501 		break;
502 	    default:
503 	        abort ();
504 	    }
505 	    if(temp==NULL)
506 		return (0);
507 	    else
508 		return (1);
509 	}
510     return (0);
511 }
512 
513 static WrapperEntry *wrap_matching_entry PROTO ((const char *));
514 
515 static WrapperEntry *
516 wrap_matching_entry (name)
517     const char *name;
518 {
519     int x,count=wrap_count+wrap_tempcount;
520 
521     for(x=0;x<count;++x)
522 	if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0)
523 	    return wrap_list[x];
524     return (WrapperEntry *)NULL;
525 }
526 
527 /* Return the RCS options for FILENAME in a newly malloc'd string.  If
528    ASFLAG, then include "-k" at the beginning (e.g. "-kb"), otherwise
529    just give the option itself (e.g. "b").  */
530 char *
531 wrap_rcsoption (filename, asflag)
532     const char *filename;
533     int asflag;
534 {
535     WrapperEntry *e = wrap_matching_entry (filename);
536     char *buf;
537 
538     if (e == NULL || e->rcsOption == NULL || (*e->rcsOption == '\0'))
539 	return NULL;
540 
541     buf = xmalloc (strlen (e->rcsOption) + 3);
542     if (asflag)
543     {
544 	strcpy (buf, "-k");
545 	strcat (buf, e->rcsOption);
546     }
547     else
548     {
549 	strcpy (buf, e->rcsOption);
550     }
551     return buf;
552 }
553 
554 char *
555 wrap_tocvs_process_file(fileName)
556     const char *fileName;
557 {
558     WrapperEntry *e=wrap_matching_entry(fileName);
559     static char *buf = NULL;
560     char *args;
561 
562     if(e==NULL || e->tocvsFilter==NULL)
563 	return NULL;
564 
565     if (buf != NULL)
566 	free (buf);
567     buf = cvs_temp_name ();
568 
569     args = xmalloc (strlen (e->tocvsFilter)
570 		    + strlen (fileName)
571 		    + strlen (buf));
572     /* FIXME: sprintf will blow up if the format string contains items other
573        than %s, or contains too many %s's.  We should instead be parsing
574        e->tocvsFilter ourselves and giving a real error.  */
575     sprintf (args, e->tocvsFilter, fileName, buf);
576     run_setup (args);
577     run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY );
578     free (args);
579 
580     return buf;
581 }
582 
583 int
584 wrap_merge_is_copy (fileName)
585     const char *fileName;
586 {
587     WrapperEntry *e=wrap_matching_entry(fileName);
588     if(e==NULL || e->mergeMethod==WRAP_MERGE)
589 	return 0;
590 
591     return 1;
592 }
593 
594 void
595 wrap_fromcvs_process_file(fileName)
596     const char *fileName;
597 {
598     char *args;
599     WrapperEntry *e=wrap_matching_entry(fileName);
600 
601     if(e==NULL || e->fromcvsFilter==NULL)
602 	return;
603 
604     args = xmalloc (strlen (e->fromcvsFilter)
605 		    + strlen (fileName));
606     /* FIXME: sprintf will blow up if the format string contains items other
607        than %s, or contains too many %s's.  We should instead be parsing
608        e->fromcvsFilter ourselves and giving a real error.  */
609     sprintf (args, e->fromcvsFilter, fileName);
610     run_setup (args);
611     run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL );
612     free (args);
613     return;
614 }
615