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