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