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