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 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 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 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 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 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 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 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 294 wrap_free_entry(WrapperEntry *e) 295 { 296 wrap_free_entry_internal(e); 297 free(e); 298 } 299 300 void 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 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 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 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 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 * 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 * 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 * 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 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 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