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