xref: /openbsd/gnu/usr.bin/cvs/src/wrapper.c (revision f9bbbf45)
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 
wrap_setup()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 (!current_parsed_root->isremote)
100 #endif
101     {
102 	char *file;
103 
104 	file = xmalloc (strlen (current_parsed_root->directory)
105 			+ sizeof (CVSROOTADM)
106 			+ sizeof (CVSROOTADM_WRAPPER)
107 			+ 3);
108 	/* Then add entries found in repository, if it exists.  */
109 	(void) sprintf (file, "%s/%s/%s", current_parsed_root->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
wrap_send()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
wrap_unparse_rcs_options(line,first_call_p)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  * Remove fmt str specifier other than %% or %s. And allow
250  * only max_s %s specifiers
251  */
252 void
wrap_clean_fmt_str(char * fmt,int max_s)253 wrap_clean_fmt_str(char *fmt, int max_s)
254 {
255     while (*fmt) {
256 	if (fmt[0] == '%' && fmt[1])
257 	{
258 	    if (fmt[1] == '%')
259 		fmt++;
260 	    else
261 		if (fmt[1] == 's' && max_s > 0)
262 		{
263 		    max_s--;
264 		    fmt++;
265 		} else
266 		    *fmt = ' ';
267 	}
268 	fmt++;
269     }
270 }
271 
272 /*
273  * Open a file and read lines, feeding each line to a line parser. Arrange
274  * for keeping a temporary list of wrappers at the end, if the "temp"
275  * argument is set.
276  */
277 void
wrap_add_file(file,temp)278 wrap_add_file (file, temp)
279     const char *file;
280     int temp;
281 {
282     FILE *fp;
283     char *line = NULL;
284     size_t line_allocated = 0;
285 
286     wrap_restore_saved ();
287     wrap_kill_temp ();
288 
289     /* Load the file.  */
290     fp = CVS_FOPEN (file, "r");
291     if (fp == NULL)
292     {
293 	if (!existence_error (errno))
294 	    error (0, errno, "cannot open %s", file);
295 	return;
296     }
297     while (get_line (&line, &line_allocated, fp) >= 0)
298 	wrap_add (line, temp);
299     if (line)
300         free (line);
301     if (ferror (fp))
302 	error (0, errno, "cannot read %s", file);
303     if (fclose (fp) == EOF)
304 	error (0, errno, "cannot close %s", file);
305 }
306 
307 void
wrap_kill()308 wrap_kill()
309 {
310     wrap_kill_temp();
311     while(wrap_count)
312 	wrap_free_entry(wrap_list[--wrap_count]);
313 }
314 
315 void
wrap_kill_temp()316 wrap_kill_temp()
317 {
318     WrapperEntry **temps=wrap_list+wrap_count;
319 
320     while(wrap_tempcount)
321 	wrap_free_entry(temps[--wrap_tempcount]);
322 }
323 
324 void
wrap_free_entry(e)325 wrap_free_entry(e)
326      WrapperEntry *e;
327 {
328     wrap_free_entry_internal(e);
329     free(e);
330 }
331 
332 void
wrap_free_entry_internal(e)333 wrap_free_entry_internal(e)
334     WrapperEntry *e;
335 {
336     free (e->wildCard);
337     if (e->tocvsFilter)
338 	free (e->tocvsFilter);
339     if (e->fromcvsFilter)
340 	free (e->fromcvsFilter);
341     if (e->rcsOption)
342 	free (e->rcsOption);
343 }
344 
345 void
wrap_restore_saved()346 wrap_restore_saved()
347 {
348     if(!wrap_saved_list)
349 	return;
350 
351     wrap_kill();
352 
353     free(wrap_list);
354 
355     wrap_list=wrap_saved_list;
356     wrap_count=wrap_saved_count;
357     wrap_tempcount=wrap_saved_tempcount;
358 
359     wrap_saved_list=NULL;
360     wrap_saved_count=0;
361     wrap_saved_tempcount=0;
362 }
363 
364 void
wrap_add(line,isTemp)365 wrap_add (line, isTemp)
366    char *line;
367    int         isTemp;
368 {
369     char *temp;
370     char ctemp;
371     WrapperEntry e;
372     char opt;
373 
374     if (!line || line[0] == '#')
375 	return;
376 
377     memset (&e, 0, sizeof(e));
378 
379 	/* Search for the wild card */
380     while (*line && isspace ((unsigned char) *line))
381 	++line;
382     for (temp = line;
383 	 *line && !isspace ((unsigned char) *line);
384 	 ++line)
385 	;
386     if(temp==line)
387 	return;
388 
389     ctemp=*line;
390     *line='\0';
391 
392     e.wildCard=xstrdup(temp);
393     *line=ctemp;
394 
395     while(*line){
396 	    /* Search for the option */
397 	while(*line && *line!='-')
398 	    ++line;
399 	if(!*line)
400 	    break;
401 	++line;
402 	if(!*line)
403 	    break;
404 	opt=*line;
405 
406 	    /* Search for the filter commandline */
407 	for(++line;*line && *line!='\'';++line);
408 	if(!*line)
409 	    break;
410 
411 	for(temp=++line;*line && (*line!='\'' || line[-1]=='\\');++line)
412 	    ;
413 
414 	/* This used to "break;" (ignore the option) if there was a
415 	   single character between the single quotes (I'm guessing
416 	   that was accidental).  Now it "break;"s if there are no
417 	   characters.  I'm not sure either behavior is particularly
418 	   necessary--the current options might not require ''
419 	   arguments, but surely some future option legitimately
420 	   might.  Also I'm not sure that ignoring the option is a
421 	   swift way to handle syntax errors in general.  */
422 	if (line==temp)
423 	    break;
424 
425 	ctemp=*line;
426 	*line='\0';
427 	switch(opt){
428 	case 'f':
429 	    /* Before this is reenabled, need to address the problem in
430 	       commit.c (see http://www.cyclic.com/cvs/dev-wrap.txt).  */
431 	    error (1, 0,
432 		   "-t/-f wrappers not supported by this version of CVS");
433 
434 	    if(e.fromcvsFilter)
435 		free(e.fromcvsFilter);
436 	    /* FIXME: error message should say where the bad value
437 	       came from.  */
438 	    e.fromcvsFilter=expand_path (temp, "<wrapper>", 0);
439             if (!e.fromcvsFilter)
440 		error (1, 0, "Correct above errors first");
441 	    break;
442 	case 't':
443 	    /* Before this is reenabled, need to address the problem in
444 	       commit.c (see http://www.cyclic.com/cvs/dev-wrap.txt).  */
445 	    error (1, 0,
446 		   "-t/-f wrappers not supported by this version of CVS");
447 
448 	    if(e.tocvsFilter)
449 		free(e.tocvsFilter);
450 	    /* FIXME: error message should say where the bad value
451 	       came from.  */
452 	    e.tocvsFilter=expand_path (temp, "<wrapper>", 0);
453             if (!e.tocvsFilter)
454 		error (1, 0, "Correct above errors first");
455 	    break;
456 	case 'm':
457 	    if(*temp=='C' || *temp=='c')
458 		e.mergeMethod=WRAP_COPY;
459 	    else
460 		e.mergeMethod=WRAP_MERGE;
461 	    break;
462 	case 'k':
463 	    if (e.rcsOption)
464 		free (e.rcsOption);
465 	    e.rcsOption = xstrdup (temp);
466 	    break;
467 	default:
468 	    break;
469 	}
470 	*line=ctemp;
471 	if(!*line)break;
472 	++line;
473     }
474 
475     wrap_add_entry(&e, isTemp);
476 }
477 
478 void
wrap_add_entry(e,temp)479 wrap_add_entry(e, temp)
480     WrapperEntry *e;
481     int temp;
482 {
483     int x;
484     if(wrap_count+wrap_tempcount>=wrap_size){
485 	wrap_size += WRAPPER_GROW;
486 	wrap_list = (WrapperEntry **) xrealloc ((char *) wrap_list,
487 						wrap_size *
488 						sizeof (WrapperEntry *));
489     }
490 
491     if(!temp && wrap_tempcount){
492 	for(x=wrap_count+wrap_tempcount-1;x>=wrap_count;--x)
493 	    wrap_list[x+1]=wrap_list[x];
494     }
495 
496     x=(temp ? wrap_count+(wrap_tempcount++):(wrap_count++));
497     wrap_list[x]=(WrapperEntry *)xmalloc(sizeof(WrapperEntry));
498     wrap_list[x]->wildCard=e->wildCard;
499     wrap_list[x]->fromcvsFilter=e->fromcvsFilter;
500     wrap_list[x]->tocvsFilter=e->tocvsFilter;
501     wrap_list[x]->mergeMethod=e->mergeMethod;
502     wrap_list[x]->rcsOption = e->rcsOption;
503 }
504 
505 /* Return 1 if the given filename is a wrapper filename */
506 int
wrap_name_has(name,has)507 wrap_name_has (name,has)
508     const char   *name;
509     WrapMergeHas  has;
510 {
511     int x,count=wrap_count+wrap_tempcount;
512     char *temp;
513 
514     for(x=0;x<count;++x)
515 	if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0){
516 	    switch(has){
517 	    case WRAP_TOCVS:
518 		temp=wrap_list[x]->tocvsFilter;
519 		break;
520 	    case WRAP_FROMCVS:
521 		temp=wrap_list[x]->fromcvsFilter;
522 		break;
523 	    case WRAP_RCSOPTION:
524 		temp = wrap_list[x]->rcsOption;
525 		break;
526 	    default:
527 	        abort ();
528 	    }
529 	    if(temp==NULL)
530 		return (0);
531 	    else
532 		return (1);
533 	}
534     return (0);
535 }
536 
537 static WrapperEntry *wrap_matching_entry PROTO ((const char *));
538 
539 static WrapperEntry *
wrap_matching_entry(name)540 wrap_matching_entry (name)
541     const char *name;
542 {
543     int x,count=wrap_count+wrap_tempcount;
544 
545     for(x=0;x<count;++x)
546 	if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0)
547 	    return wrap_list[x];
548     return (WrapperEntry *)NULL;
549 }
550 
551 /* Return the RCS options for FILENAME in a newly malloc'd string.  If
552    ASFLAG, then include "-k" at the beginning (e.g. "-kb"), otherwise
553    just give the option itself (e.g. "b").  */
554 char *
wrap_rcsoption(filename,asflag)555 wrap_rcsoption (filename, asflag)
556     const char *filename;
557     int asflag;
558 {
559     WrapperEntry *e = wrap_matching_entry (filename);
560     char *buf;
561 
562     if (e == NULL || e->rcsOption == NULL || (*e->rcsOption == '\0'))
563 	return NULL;
564 
565     buf = xmalloc (strlen (e->rcsOption) + 3);
566     if (asflag)
567     {
568 	strcpy (buf, "-k");
569 	strcat (buf, e->rcsOption);
570     }
571     else
572     {
573 	strcpy (buf, e->rcsOption);
574     }
575     return buf;
576 }
577 
578 char *
wrap_tocvs_process_file(fileName)579 wrap_tocvs_process_file(fileName)
580     const char *fileName;
581 {
582     WrapperEntry *e=wrap_matching_entry(fileName);
583     static char *buf = NULL;
584     char *args;
585 
586     if(e==NULL || e->tocvsFilter==NULL)
587 	return NULL;
588 
589     if (buf != NULL)
590 	free (buf);
591     buf = cvs_temp_name ();
592 
593     args = xmalloc (strlen (e->tocvsFilter)
594 		    + strlen (fileName)
595 		    + strlen (buf));
596 
597     wrap_clean_fmt_str(e->tocvsFilter, 2);
598     sprintf (args, e->tocvsFilter, fileName, buf);
599     run_setup (args);
600     run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL|RUN_REALLY );
601     free (args);
602 
603     return buf;
604 }
605 
606 int
wrap_merge_is_copy(fileName)607 wrap_merge_is_copy (fileName)
608     const char *fileName;
609 {
610     WrapperEntry *e=wrap_matching_entry(fileName);
611     if(e==NULL || e->mergeMethod==WRAP_MERGE)
612 	return 0;
613 
614     return 1;
615 }
616 
617 void
wrap_fromcvs_process_file(fileName)618 wrap_fromcvs_process_file(fileName)
619     const char *fileName;
620 {
621     char *args;
622     WrapperEntry *e=wrap_matching_entry(fileName);
623 
624     if(e==NULL || e->fromcvsFilter==NULL)
625 	return;
626 
627     args = xmalloc (strlen (e->fromcvsFilter)
628 		    + strlen (fileName));
629 
630     wrap_clean_fmt_str(e->fromcvsFilter, 1);
631     sprintf (args, e->fromcvsFilter, fileName);
632     run_setup (args);
633     run_exec(RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL );
634     free (args);
635     return;
636 }
637