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 __RCSID("$MirOS: src/gnu/usr.bin/cvs/src/wrapper.c,v 1.3 2010/09/19 19:43:13 tg Exp $");
15 
16 /*
17   Original Author:  athan@morgan.com <Andrew C. Athan> 2/1/94
18   Modified By:      vdemarco@bou.shl.com
19 
20   This package was written to support the NEXTSTEP concept of
21   "wrappers."  These are essentially directories that are to be
22   treated as "files."  This package allows such wrappers to be
23   "processed" on the way in and out of CVS.  The intended use is to
24   wrap up a wrapper into a single tar, such that that tar can be
25   treated as a single binary file in CVS.  To solve the problem
26   effectively, it was also necessary to be able to prevent rcsmerge
27   application at appropriate times.
28 
29   ------------------
30   Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers)
31 
32   wildcard	[option value][option value]...
33 
34   where option is one of
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 (WrapperEntry *e,int temp);
79 void wrap_kill (void);
80 void wrap_kill_temp (void);
81 void wrap_free_entry (WrapperEntry *e);
82 void wrap_free_entry_internal (WrapperEntry *e);
83 void wrap_restore_saved (void);
84 
wrap_setup(void)85 void wrap_setup(void)
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 = NULL;
92 
93     if (wrap_setup_already_done != 0)
94         return;
95     else
96         wrap_setup_already_done = 1;
97 
98     if (!current_parsed_root->isremote)
99     {
100 	char *file;
101 
102 	/* Then add entries found in repository, if it exists.  */
103 	file = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
104 			  CVSROOTADM, CVSROOTADM_WRAPPER);
105 	if (isfile (file))
106 	{
107 	    wrap_add_file(file,0);
108 	}
109 	free (file);
110     }
111 
112 #ifdef SERVER_SUPPORT
113     if (!server_active)
114 #endif
115     {
116 
117     /* Then add entries found in home dir, (if user has one) and file
118        exists.  */
119     homedir = get_homedir ();
120     /* If we can't find a home directory, ignore ~/.cvswrappers.  This may
121        make tracking down problems a bit of a pain, but on the other
122        hand it might be obnoxious to complain when CVS will function
123        just fine without .cvswrappers (and many users won't even know what
124        .cvswrappers is).  */
125     }
126 
127     if (homedir != NULL)
128     {
129 	char *file = strcat_filename_onto_homedir (homedir, CVSDOTWRAPPER);
130 	if (isfile (file))
131 	{
132 	    wrap_add_file (file, 0);
133 	}
134 	free (file);
135     }
136 
137     /* FIXME: calling wrap_add() below implies that the CVSWRAPPERS
138      * environment variable contains exactly one "wrapper" -- a line
139      * of the form
140      *
141      *    FILENAME_PATTERN	FLAG  OPTS [ FLAG OPTS ...]
142      *
143      * This may disagree with the documentation, which states:
144      *
145      *   `$CVSWRAPPERS'
146      *      A whitespace-separated list of file name patterns that CVS
147      *      should treat as wrappers. *Note Wrappers::.
148      *
149      * Does this mean the environment variable can hold multiple
150      * wrappers lines?  If so, a single call to wrap_add() is
151      * insufficient.
152      */
153 
154     /* Then add entries found in CVSWRAPPERS environment variable. */
155     wrap_add (getenv (WRAPPER_ENV), 0);
156 }
157 
158 #ifdef CLIENT_SUPPORT
159 /* Send -W arguments for the wrappers to the server.  The command must
160    be one that accepts them (e.g. update, import).  */
161 void
wrap_send(void)162 wrap_send (void)
163 {
164     int i;
165 
166     for (i = 0; i < wrap_count + wrap_tempcount; ++i)
167     {
168 	if (wrap_list[i]->tocvsFilter != NULL
169 	    || wrap_list[i]->fromcvsFilter != NULL)
170 	    /* For greater studliness we would print the offending option
171 	       and (more importantly) where we found it.  */
172 	    error (0, 0, "\
173 -t and -f wrapper options are not supported remotely; ignored");
174 	if (wrap_list[i]->mergeMethod == WRAP_COPY)
175 	    /* For greater studliness we would print the offending option
176 	       and (more importantly) where we found it.  */
177 	    error (0, 0, "\
178 -m wrapper option is not supported remotely; ignored");
179 	send_to_server ("Argument -W\012Argument ", 0);
180 	send_to_server (wrap_list[i]->wildCard, 0);
181 	send_to_server (" -k '", 0);
182 	if (wrap_list[i]->rcsOption != NULL)
183 	    send_to_server (wrap_list[i]->rcsOption, 0);
184 	else
185 	    send_to_server ("kv", 0);
186 	send_to_server ("'\012", 0);
187     }
188 }
189 #endif /* CLIENT_SUPPORT */
190 
191 #if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
192 /* Output wrapper entries in the format of cvswrappers lines.
193  *
194  * This is useful when one side of a client/server connection wants to
195  * send its wrappers to the other; since the receiving side would like
196  * to use wrap_add() to incorporate the wrapper, it's best if the
197  * entry arrives in this format.
198  *
199  * The entries are stored in `line', which is allocated here.  Caller
200  * can free() it.
201  *
202  * If first_call_p is nonzero, then start afresh.  */
203 void
wrap_unparse_rcs_options(char ** line,int first_call_p)204 wrap_unparse_rcs_options (char **line, int first_call_p)
205 {
206     /* FIXME-reentrancy: we should design a reentrant interface, like
207        a callback which gets handed each wrapper (a multithreaded
208        server being the most concrete reason for this, but the
209        non-reentrant interface is fairly unnecessary/ugly).  */
210     static int i;
211 
212     if (first_call_p)
213         i = 0;
214 
215     if (i >= wrap_count + wrap_tempcount) {
216         *line = NULL;
217         return;
218     }
219 
220     *line = Xasprintf ("%s -k '%s'",
221 		       wrap_list[i]->wildCard,
222 		       wrap_list[i]->rcsOption
223 		       ? wrap_list[i]->rcsOption : "kv");
224     ++i;
225 }
226 #endif /* SERVER_SUPPORT || CLIENT_SUPPORT */
227 
228 /*
229  * Remove fmt str specifier other than %% or %s. And allow
230  * only max_s %s specifiers
231  */
232 static void
wrap_clean_fmt_str(char * fmt,int max_s)233 wrap_clean_fmt_str(char *fmt, int max_s)
234 {
235     while (*fmt) {
236 	if (fmt[0] == '%' && fmt[1])
237 	{
238 	    if (fmt[1] == '%')
239 		fmt++;
240 	    else
241 		if (fmt[1] == 's' && max_s > 0)
242 		{
243 		    max_s--;
244 		    fmt++;
245 		} else
246 		    *fmt = ' ';
247 	}
248 	fmt++;
249     }
250 }
251 
252 /*
253  * Open a file and read lines, feeding each line to a line parser. Arrange
254  * for keeping a temporary list of wrappers at the end, if the "temp"
255  * argument is set.
256  */
257 void
wrap_add_file(const char * file,int temp)258 wrap_add_file (const char *file, int temp)
259 {
260     FILE *fp;
261     char *line = NULL;
262     size_t line_allocated = 0;
263 
264     wrap_restore_saved ();
265     wrap_kill_temp ();
266 
267     /* Load the file.  */
268     fp = CVS_FOPEN (file, "r");
269     if (fp == NULL)
270     {
271 	if (!existence_error (errno))
272 	    error (0, errno, "cannot open %s", file);
273 	return;
274     }
275     while (getline (&line, &line_allocated, fp) >= 0)
276 	wrap_add (line, temp);
277     if (line)
278         free (line);
279     if (ferror (fp))
280 	error (0, errno, "cannot read %s", file);
281     if (fclose (fp) == EOF)
282 	error (0, errno, "cannot close %s", file);
283 }
284 
285 void
wrap_kill(void)286 wrap_kill(void)
287 {
288     wrap_kill_temp();
289     while(wrap_count)
290 	wrap_free_entry(wrap_list[--wrap_count]);
291 }
292 
293 void
wrap_kill_temp(void)294 wrap_kill_temp(void)
295 {
296     WrapperEntry **temps=wrap_list+wrap_count;
297 
298     while(wrap_tempcount)
299 	wrap_free_entry(temps[--wrap_tempcount]);
300 }
301 
302 void
wrap_free_entry(WrapperEntry * e)303 wrap_free_entry(WrapperEntry *e)
304 {
305     wrap_free_entry_internal(e);
306     free(e);
307 }
308 
309 void
wrap_free_entry_internal(WrapperEntry * e)310 wrap_free_entry_internal(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
wrap_restore_saved(void)322 wrap_restore_saved(void)
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
wrap_add(char * line,int isTemp)341 wrap_add (char *line, int isTemp)
342 {
343     char *temp;
344     char ctemp;
345     WrapperEntry e;
346     char opt;
347 
348     if (!line || line[0] == '#')
349 	return;
350 
351     /* Allows user to declare all wrappers null and void */
352     if (line[0] == '!') {
353       wrap_kill ();
354       return;
355     }
356 
357     memset (&e, 0, sizeof(e));
358 
359 	/* Search for the wild card */
360     while (*line && isspace ((unsigned char) *line))
361 	++line;
362     for (temp = line;
363 	 *line && !isspace ((unsigned char) *line);
364 	 ++line)
365 	;
366     if(temp==line)
367 	return;
368 
369     ctemp=*line;
370     *line='\0';
371 
372     e.wildCard=xstrdup(temp);
373     *line=ctemp;
374 
375     while(*line){
376 	    /* Search for the option */
377 	while(*line && *line!='-')
378 	    ++line;
379 	if(!*line)
380 	    break;
381 	++line;
382 	if(!*line)
383 	    break;
384 	opt=*line;
385 
386 	    /* Search for the filter commandline */
387 	for(++line;*line && *line!='\'';++line);
388 	if(!*line)
389 	    break;
390 
391 	for(temp=++line;*line && (*line!='\'' || line[-1]=='\\');++line)
392 	    ;
393 
394 	/* This used to "break;" (ignore the option) if there was a
395 	   single character between the single quotes (I'm guessing
396 	   that was accidental).  Now it "break;"s if there are no
397 	   characters.  I'm not sure either behavior is particularly
398 	   necessary--the current options might not require ''
399 	   arguments, but surely some future option legitimately
400 	   might.  Also I'm not sure that ignoring the option is a
401 	   swift way to handle syntax errors in general.  */
402 	if (line==temp)
403 	    break;
404 
405 	ctemp=*line;
406 	*line='\0';
407 	switch(opt){
408 	case 'f':
409 	    /* Before this is reenabled, need to address the problem in
410 	       commit.c (see
411 	       <http://ximbiot.com/cvs/cvshome/docs/infowrapper.html>).  */
412 	    error (1, 0,
413 		   "-t/-f wrappers not supported by this version of CVS");
414 
415 	    if(e.fromcvsFilter)
416 		free(e.fromcvsFilter);
417 	    /* FIXME: error message should say where the bad value
418 	       came from.  */
419 	    e.fromcvsFilter =
420 	      expand_path (temp, current_parsed_root->directory, false,
421 			   "<wrapper>", 0);
422             if (!e.fromcvsFilter)
423 		error (1, 0, "Correct above errors first");
424 	    break;
425 	case 't':
426 	    /* Before this is reenabled, need to address the problem in
427 	       commit.c (see
428 	       <http://ximbiot.com/cvs/cvshome/docs/infowrapper.html>).  */
429 	    error (1, 0,
430 		   "-t/-f wrappers not supported by this version of CVS");
431 
432 	    if(e.tocvsFilter)
433 		free(e.tocvsFilter);
434 	    /* FIXME: error message should say where the bad value
435 	       came from.  */
436 	    e.tocvsFilter = expand_path (temp, current_parsed_root->directory,
437 					 false, "<wrapper>", 0);
438             if (!e.tocvsFilter)
439 		error (1, 0, "Correct above errors first");
440 	    break;
441 	case 'm':
442 	    if(*temp=='C' || *temp=='c')
443 		e.mergeMethod=WRAP_COPY;
444 	    else
445 		e.mergeMethod=WRAP_MERGE;
446 	    break;
447 	case 'k':
448 	    if (e.rcsOption)
449 		free (e.rcsOption);
450 	    e.rcsOption = strcmp (temp, "kv") ? xstrdup (temp) : NULL;
451 	    break;
452 	default:
453 	    break;
454 	}
455 	*line=ctemp;
456 	if(!*line)break;
457 	++line;
458     }
459 
460     wrap_add_entry(&e, isTemp);
461 }
462 
463 void
wrap_add_entry(WrapperEntry * e,int temp)464 wrap_add_entry (WrapperEntry *e, int temp)
465 {
466     int x;
467     if (wrap_count + wrap_tempcount >= wrap_size)
468     {
469 	wrap_size += WRAPPER_GROW;
470 	wrap_list = xnrealloc (wrap_list, wrap_size, sizeof (WrapperEntry *));
471     }
472 
473     if (!temp && wrap_tempcount)
474     {
475 	for (x = wrap_count + wrap_tempcount - 1; x >= wrap_count; --x)
476 	    wrap_list[x + 1] = wrap_list[x];
477     }
478 
479     x = (temp ? wrap_count + (wrap_tempcount++) : (wrap_count++));
480     wrap_list[x] = xmalloc (sizeof (WrapperEntry));
481     *wrap_list[x] = *e;
482 }
483 
484 /* Return 1 if the given filename is a wrapper filename */
485 int
wrap_name_has(const char * name,WrapMergeHas has)486 wrap_name_has (const char *name, WrapMergeHas has)
487 {
488     int x,count=wrap_count+wrap_tempcount;
489     char *temp;
490 
491     for(x=0;x<count;++x)
492 	if (CVS_FNMATCH (wrap_list[x]->wildCard, name, 0) == 0){
493 	    switch(has){
494 	    case WRAP_TOCVS:
495 		temp=wrap_list[x]->tocvsFilter;
496 		break;
497 	    case WRAP_FROMCVS:
498 		temp=wrap_list[x]->fromcvsFilter;
499 		break;
500 	    case WRAP_RCSOPTION:
501 		temp = wrap_list[x]->rcsOption;
502 		break;
503 	    default:
504 	        abort ();
505 	    }
506 	    if(temp==NULL)
507 		return (0);
508 	    else
509 		return (1);
510 	}
511     return (0);
512 }
513 
514 static WrapperEntry *wrap_matching_entry (const char *);
515 
516 static WrapperEntry *
wrap_matching_entry(const char * name)517 wrap_matching_entry (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 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 *
wrap_rcsoption(const char * filename,int asflag)531 wrap_rcsoption (const char *filename, int asflag)
532 {
533     WrapperEntry *e = wrap_matching_entry (filename);
534 
535     if (e == NULL || e->rcsOption == NULL || (*e->rcsOption == '\0'))
536 	return NULL;
537 
538     return Xasprintf ("%s%s", asflag ? "-k" : "", e->rcsOption);
539 }
540 
541 char *
wrap_tocvs_process_file(const char * fileName)542 wrap_tocvs_process_file(const char *fileName)
543 {
544     WrapperEntry *e=wrap_matching_entry(fileName);
545     static char *buf = NULL;
546     char *args;
547 
548     if(e==NULL || e->tocvsFilter==NULL)
549 	return NULL;
550 
551     if (buf != NULL)
552 	free (buf);
553     buf = cvs_temp_name ();
554 
555     wrap_clean_fmt_str (e->tocvsFilter, 2);
556     args = Xasprintf (e->tocvsFilter, fileName, buf);
557     run_setup (args);
558     run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL | RUN_REALLY );
559     free (args);
560 
561     return buf;
562 }
563 
564 int
wrap_merge_is_copy(const char * fileName)565 wrap_merge_is_copy (const char *fileName)
566 {
567     WrapperEntry *e=wrap_matching_entry(fileName);
568     if(e==NULL || e->mergeMethod==WRAP_MERGE)
569 	return 0;
570 
571     return 1;
572 }
573 
574 void
wrap_fromcvs_process_file(const char * fileName)575 wrap_fromcvs_process_file(const char *fileName)
576 {
577     char *args;
578     WrapperEntry *e = wrap_matching_entry(fileName);
579 
580     if (e != NULL && e->fromcvsFilter != NULL)
581     {
582 	wrap_clean_fmt_str (e->fromcvsFilter, 1);
583 	args = Xasprintf (e->fromcvsFilter, fileName);
584 	run_setup (args);
585 	run_exec (RUN_TTY, RUN_TTY, RUN_TTY, RUN_NORMAL);
586 	free (args);
587     }
588     return;
589 }
590