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