1 /* Implementation for "cvs edit", "cvs watch on", and related commands 2 3 This program is free software; you can redistribute it and/or modify 4 it under the terms of the GNU General Public License as published by 5 the Free Software Foundation; either version 2, or (at your option) 6 any later version. 7 8 This program is distributed in the hope that it will be useful, 9 but WITHOUT ANY WARRANTY; without even the implied warranty of 10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 GNU General Public License for more details. */ 12 13 #include "cvs.h" 14 #include "getline.h" 15 #include "watch.h" 16 #include "edit.h" 17 #include "fileattr.h" 18 19 static int watch_onoff PROTO ((int, char **)); 20 21 static int setting_default; 22 static int turning_on; 23 24 static int setting_tedit; 25 static int setting_tunedit; 26 static int setting_tcommit; 27 28 static int onoff_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 29 30 static int 31 onoff_fileproc (callerdat, finfo) 32 void *callerdat; 33 struct file_info *finfo; 34 { 35 fileattr_set (finfo->file, "_watched", turning_on ? "" : NULL); 36 return 0; 37 } 38 39 static int onoff_filesdoneproc PROTO ((void *, int, char *, char *, List *)); 40 41 static int 42 onoff_filesdoneproc (callerdat, err, repository, update_dir, entries) 43 void *callerdat; 44 int err; 45 char *repository; 46 char *update_dir; 47 List *entries; 48 { 49 if (setting_default) 50 fileattr_set (NULL, "_watched", turning_on ? "" : NULL); 51 return err; 52 } 53 54 static int 55 watch_onoff (argc, argv) 56 int argc; 57 char **argv; 58 { 59 int c; 60 int local = 0; 61 int err; 62 63 optind = 0; 64 while ((c = getopt (argc, argv, "+lR")) != -1) 65 { 66 switch (c) 67 { 68 case 'l': 69 local = 1; 70 break; 71 case 'R': 72 local = 0; 73 break; 74 case '?': 75 default: 76 usage (watch_usage); 77 break; 78 } 79 } 80 argc -= optind; 81 argv += optind; 82 83 #ifdef CLIENT_SUPPORT 84 if (client_active) 85 { 86 start_server (); 87 88 ign_setup (); 89 90 if (local) 91 send_arg ("-l"); 92 send_files (argc, argv, local, 0, SEND_NO_CONTENTS); 93 send_file_names (argc, argv, SEND_EXPAND_WILD); 94 send_to_server (turning_on ? "watch-on\012" : "watch-off\012", 0); 95 return get_responses_and_close (); 96 } 97 #endif /* CLIENT_SUPPORT */ 98 99 setting_default = (argc <= 0); 100 101 lock_tree_for_write (argc, argv, local, 0); 102 103 err = start_recursion (onoff_fileproc, onoff_filesdoneproc, 104 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 105 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, 106 0); 107 108 Lock_Cleanup (); 109 return err; 110 } 111 112 int 113 watch_on (argc, argv) 114 int argc; 115 char **argv; 116 { 117 turning_on = 1; 118 return watch_onoff (argc, argv); 119 } 120 121 int 122 watch_off (argc, argv) 123 int argc; 124 char **argv; 125 { 126 turning_on = 0; 127 return watch_onoff (argc, argv); 128 } 129 130 static int dummy_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 131 132 static int 133 dummy_fileproc (callerdat, finfo) 134 void *callerdat; 135 struct file_info *finfo; 136 { 137 /* This is a pretty hideous hack, but the gist of it is that recurse.c 138 won't call notify_check unless there is a fileproc, so we can't just 139 pass NULL for fileproc. */ 140 return 0; 141 } 142 143 static int ncheck_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 144 145 /* Check for and process notifications. Local only. I think that doing 146 this as a fileproc is the only way to catch all the 147 cases (e.g. foo/bar.c), even though that means checking over and over 148 for the same CVSADM_NOTIFY file which we removed the first time we 149 processed the directory. */ 150 151 static int 152 ncheck_fileproc (callerdat, finfo) 153 void *callerdat; 154 struct file_info *finfo; 155 { 156 int notif_type; 157 char *filename; 158 char *val; 159 char *cp; 160 char *watches; 161 162 FILE *fp; 163 char *line = NULL; 164 size_t line_len = 0; 165 166 /* We send notifications even if noexec. I'm not sure which behavior 167 is most sensible. */ 168 169 fp = CVS_FOPEN (CVSADM_NOTIFY, "r"); 170 if (fp == NULL) 171 { 172 if (!existence_error (errno)) 173 error (0, errno, "cannot open %s", CVSADM_NOTIFY); 174 return 0; 175 } 176 177 while (getline (&line, &line_len, fp) > 0) 178 { 179 notif_type = line[0]; 180 if (notif_type == '\0') 181 continue; 182 filename = line + 1; 183 cp = strchr (filename, '\t'); 184 if (cp == NULL) 185 continue; 186 *cp++ = '\0'; 187 val = cp; 188 cp = strchr (val, '\t'); 189 if (cp == NULL) 190 continue; 191 *cp++ = '+'; 192 cp = strchr (cp, '\t'); 193 if (cp == NULL) 194 continue; 195 *cp++ = '+'; 196 cp = strchr (cp, '\t'); 197 if (cp == NULL) 198 continue; 199 *cp++ = '\0'; 200 watches = cp; 201 cp = strchr (cp, '\n'); 202 if (cp == NULL) 203 continue; 204 *cp = '\0'; 205 206 notify_do (notif_type, filename, getcaller (), val, watches, 207 finfo->repository); 208 } 209 free (line); 210 211 if (ferror (fp)) 212 error (0, errno, "cannot read %s", CVSADM_NOTIFY); 213 if (fclose (fp) < 0) 214 error (0, errno, "cannot close %s", CVSADM_NOTIFY); 215 216 if ( CVS_UNLINK (CVSADM_NOTIFY) < 0) 217 error (0, errno, "cannot remove %s", CVSADM_NOTIFY); 218 219 return 0; 220 } 221 222 static int send_notifications PROTO ((int, char **, int)); 223 224 /* Look through the CVSADM_NOTIFY file and process each item there 225 accordingly. */ 226 static int 227 send_notifications (argc, argv, local) 228 int argc; 229 char **argv; 230 int local; 231 { 232 int err = 0; 233 234 #ifdef CLIENT_SUPPORT 235 /* OK, we've done everything which needs to happen on the client side. 236 Now we can try to contact the server; if we fail, then the 237 notifications stay in CVSADM_NOTIFY to be sent next time. */ 238 if (client_active) 239 { 240 if (strcmp (command_name, "release") != 0) 241 { 242 start_server (); 243 ign_setup (); 244 } 245 246 err += start_recursion (dummy_fileproc, (FILESDONEPROC) NULL, 247 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 248 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, 249 0); 250 251 send_to_server ("noop\012", 0); 252 if (strcmp (command_name, "release") == 0) 253 err += get_server_responses (); 254 else 255 err += get_responses_and_close (); 256 } 257 else 258 #endif 259 { 260 /* Local. */ 261 262 lock_tree_for_write (argc, argv, local, 0); 263 err += start_recursion (ncheck_fileproc, (FILESDONEPROC) NULL, 264 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 265 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, 266 0); 267 Lock_Cleanup (); 268 } 269 return err; 270 } 271 272 static int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 273 274 static int 275 edit_fileproc (callerdat, finfo) 276 void *callerdat; 277 struct file_info *finfo; 278 { 279 FILE *fp; 280 time_t now; 281 char *ascnow; 282 char *basefilename; 283 284 if (noexec) 285 return 0; 286 287 /* This is a somewhat screwy way to check for this, because it 288 doesn't help errors other than the nonexistence of the file 289 (e.g. permissions problems). It might be better to rearrange 290 the code so that CVSADM_NOTIFY gets written only after the 291 various actions succeed (but what if only some of them 292 succeed). */ 293 if (!isfile (finfo->file)) 294 { 295 error (0, 0, "no such file %s; ignored", finfo->fullname); 296 return 0; 297 } 298 299 fp = open_file (CVSADM_NOTIFY, "a"); 300 301 (void) time (&now); 302 ascnow = asctime (gmtime (&now)); 303 ascnow[24] = '\0'; 304 fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file, 305 ascnow, hostname, CurDir); 306 if (setting_tedit) 307 fprintf (fp, "E"); 308 if (setting_tunedit) 309 fprintf (fp, "U"); 310 if (setting_tcommit) 311 fprintf (fp, "C"); 312 fprintf (fp, "\n"); 313 314 if (fclose (fp) < 0) 315 { 316 if (finfo->update_dir[0] == '\0') 317 error (0, errno, "cannot close %s", CVSADM_NOTIFY); 318 else 319 error (0, errno, "cannot close %s/%s", finfo->update_dir, 320 CVSADM_NOTIFY); 321 } 322 323 xchmod (finfo->file, 1); 324 325 /* Now stash the file away in CVSADM so that unedit can revert even if 326 it can't communicate with the server. We stash away a writable 327 copy so that if the user removes the working file, then restores it 328 with "cvs update" (which clears _editors but does not update 329 CVSADM_BASE), then a future "cvs edit" can still win. */ 330 /* Could save a system call by only calling mkdir_if_needed if 331 trying to create the output file fails. But copy_file isn't 332 set up to facilitate that. */ 333 mkdir_if_needed (CVSADM_BASE); 334 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file)); 335 strcpy (basefilename, CVSADM_BASE); 336 strcat (basefilename, "/"); 337 strcat (basefilename, finfo->file); 338 copy_file (finfo->file, basefilename); 339 free (basefilename); 340 341 { 342 Node *node; 343 344 node = findnode_fn (finfo->entries, finfo->file); 345 if (node != NULL) 346 base_register (finfo, ((Entnode *) node->data)->version); 347 } 348 349 return 0; 350 } 351 352 static const char *const edit_usage[] = 353 { 354 "Usage: %s %s [-lR] [files...]\n", 355 "-l: Local directory only, not recursive\n", 356 "-R: Process directories recursively\n", 357 "-a: Specify what actions for temporary watch, one of\n", 358 " edit,unedit,commit,all,none\n", 359 "(Specify the --help global option for a list of other help options)\n", 360 NULL 361 }; 362 363 int 364 edit (argc, argv) 365 int argc; 366 char **argv; 367 { 368 int local = 0; 369 int c; 370 int err; 371 int a_omitted; 372 373 if (argc == -1) 374 usage (edit_usage); 375 376 a_omitted = 1; 377 setting_tedit = 0; 378 setting_tunedit = 0; 379 setting_tcommit = 0; 380 optind = 0; 381 while ((c = getopt (argc, argv, "+lRa:")) != -1) 382 { 383 switch (c) 384 { 385 case 'l': 386 local = 1; 387 break; 388 case 'R': 389 local = 0; 390 break; 391 case 'a': 392 a_omitted = 0; 393 if (strcmp (optarg, "edit") == 0) 394 setting_tedit = 1; 395 else if (strcmp (optarg, "unedit") == 0) 396 setting_tunedit = 1; 397 else if (strcmp (optarg, "commit") == 0) 398 setting_tcommit = 1; 399 else if (strcmp (optarg, "all") == 0) 400 { 401 setting_tedit = 1; 402 setting_tunedit = 1; 403 setting_tcommit = 1; 404 } 405 else if (strcmp (optarg, "none") == 0) 406 { 407 setting_tedit = 0; 408 setting_tunedit = 0; 409 setting_tcommit = 0; 410 } 411 else 412 usage (edit_usage); 413 break; 414 case '?': 415 default: 416 usage (edit_usage); 417 break; 418 } 419 } 420 argc -= optind; 421 argv += optind; 422 423 if (a_omitted) 424 { 425 setting_tedit = 1; 426 setting_tunedit = 1; 427 setting_tcommit = 1; 428 } 429 430 if (strpbrk (hostname, "+,>;=\t\n") != NULL) 431 error (1, 0, 432 "host name (%s) contains an invalid character (+,>;=\\t\\n)", 433 hostname); 434 if (strpbrk (CurDir, "+,>;=\t\n") != NULL) 435 error (1, 0, 436 "current directory (%s) contains an invalid character (+,>;=\\t\\n)", 437 CurDir); 438 439 /* No need to readlock since we aren't doing anything to the 440 repository. */ 441 err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL, 442 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 443 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, 444 0); 445 446 err += send_notifications (argc, argv, local); 447 448 return err; 449 } 450 451 static int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 452 453 static int 454 unedit_fileproc (callerdat, finfo) 455 void *callerdat; 456 struct file_info *finfo; 457 { 458 FILE *fp; 459 time_t now; 460 char *ascnow; 461 char *basefilename; 462 463 if (noexec) 464 return 0; 465 466 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file)); 467 strcpy (basefilename, CVSADM_BASE); 468 strcat (basefilename, "/"); 469 strcat (basefilename, finfo->file); 470 if (!isfile (basefilename)) 471 { 472 /* This file apparently was never cvs edit'd (e.g. we are uneditting 473 a directory where only some of the files were cvs edit'd. */ 474 free (basefilename); 475 return 0; 476 } 477 478 if (xcmp (finfo->file, basefilename) != 0) 479 { 480 printf ("%s has been modified; revert changes? ", finfo->fullname); 481 if (!yesno ()) 482 { 483 /* "no". */ 484 free (basefilename); 485 return 0; 486 } 487 } 488 rename_file (basefilename, finfo->file); 489 free (basefilename); 490 491 fp = open_file (CVSADM_NOTIFY, "a"); 492 493 (void) time (&now); 494 ascnow = asctime (gmtime (&now)); 495 ascnow[24] = '\0'; 496 fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file, 497 ascnow, hostname, CurDir); 498 499 if (fclose (fp) < 0) 500 { 501 if (finfo->update_dir[0] == '\0') 502 error (0, errno, "cannot close %s", CVSADM_NOTIFY); 503 else 504 error (0, errno, "cannot close %s/%s", finfo->update_dir, 505 CVSADM_NOTIFY); 506 } 507 508 /* Now update the revision number in CVS/Entries from CVS/Baserev. 509 The basic idea here is that we are reverting to the revision 510 that the user edited. If we wanted "cvs update" to update 511 CVS/Base as we go along (so that an unedit could revert to the 512 current repository revision), we would need: 513 514 update (or all send_files?) (client) needs to send revision in 515 new Entry-base request. update (server/local) needs to check 516 revision against repository and send new Update-base response 517 (like Update-existing in that the file already exists. While 518 we are at it, might try to clean up the syntax by having the 519 mode only in a "Mode" response, not in the Update-base itself). */ 520 { 521 char *baserev; 522 Node *node; 523 Entnode *entdata; 524 525 baserev = base_get (finfo); 526 node = findnode_fn (finfo->entries, finfo->file); 527 /* The case where node is NULL probably should be an error or 528 something, but I don't want to think about it too hard right 529 now. */ 530 if (node != NULL) 531 { 532 entdata = (Entnode *) node->data; 533 if (baserev == NULL) 534 { 535 /* This can only happen if the CVS/Baserev file got 536 corrupted. We suspect it might be possible if the 537 user interrupts CVS, although I haven't verified 538 that. */ 539 error (0, 0, "%s not mentioned in %s", finfo->fullname, 540 CVSADM_BASEREV); 541 542 /* Since we don't know what revision the file derives from, 543 keeping it around would be asking for trouble. */ 544 if (unlink_file (finfo->file) < 0) 545 error (0, errno, "cannot remove %s", finfo->fullname); 546 547 /* This is cheesy, in a sense; why shouldn't we do the 548 update for the user? However, doing that would require 549 contacting the server, so maybe this is OK. */ 550 error (0, 0, "run update to complete the unedit"); 551 return 0; 552 } 553 Register (finfo->entries, finfo->file, baserev, entdata->timestamp, 554 entdata->options, entdata->tag, entdata->date, 555 entdata->conflict); 556 } 557 free (baserev); 558 base_deregister (finfo); 559 } 560 561 xchmod (finfo->file, 0); 562 return 0; 563 } 564 565 static const char *const unedit_usage[] = 566 { 567 "Usage: %s %s [-lR] [files...]\n", 568 "-l: Local directory only, not recursive\n", 569 "-R: Process directories recursively\n", 570 "(Specify the --help global option for a list of other help options)\n", 571 NULL 572 }; 573 574 int 575 unedit (argc, argv) 576 int argc; 577 char **argv; 578 { 579 int local = 0; 580 int c; 581 int err; 582 583 if (argc == -1) 584 usage (unedit_usage); 585 586 optind = 0; 587 while ((c = getopt (argc, argv, "+lR")) != -1) 588 { 589 switch (c) 590 { 591 case 'l': 592 local = 1; 593 break; 594 case 'R': 595 local = 0; 596 break; 597 case '?': 598 default: 599 usage (unedit_usage); 600 break; 601 } 602 } 603 argc -= optind; 604 argv += optind; 605 606 /* No need to readlock since we aren't doing anything to the 607 repository. */ 608 err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL, 609 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 610 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, 611 0); 612 613 err += send_notifications (argc, argv, local); 614 615 return err; 616 } 617 618 void 619 mark_up_to_date (file) 620 char *file; 621 { 622 char *base; 623 624 /* The file is up to date, so we better get rid of an out of 625 date file in CVSADM_BASE. */ 626 base = xmalloc (strlen (file) + 80); 627 strcpy (base, CVSADM_BASE); 628 strcat (base, "/"); 629 strcat (base, file); 630 if (unlink_file (base) < 0 && ! existence_error (errno)) 631 error (0, errno, "cannot remove %s", file); 632 free (base); 633 } 634 635 636 void 637 editor_set (filename, editor, val) 638 char *filename; 639 char *editor; 640 char *val; 641 { 642 char *edlist; 643 char *newlist; 644 645 edlist = fileattr_get0 (filename, "_editors"); 646 newlist = fileattr_modify (edlist, editor, val, '>', ','); 647 /* If the attributes is unchanged, don't rewrite the attribute file. */ 648 if (!((edlist == NULL && newlist == NULL) 649 || (edlist != NULL 650 && newlist != NULL 651 && strcmp (edlist, newlist) == 0))) 652 fileattr_set (filename, "_editors", newlist); 653 if (edlist != NULL) 654 free (edlist); 655 if (newlist != NULL) 656 free (newlist); 657 } 658 659 struct notify_proc_args { 660 /* What kind of notification, "edit", "tedit", etc. */ 661 char *type; 662 /* User who is running the command which causes notification. */ 663 char *who; 664 /* User to be notified. */ 665 char *notifyee; 666 /* File. */ 667 char *file; 668 }; 669 670 /* Pass as a static until we get around to fixing Parse_Info to pass along 671 a void * where we can stash it. */ 672 static struct notify_proc_args *notify_args; 673 674 static int notify_proc PROTO ((char *repository, char *filter)); 675 676 static int 677 notify_proc (repository, filter) 678 char *repository; 679 char *filter; 680 { 681 FILE *pipefp; 682 char *prog; 683 char *expanded_prog; 684 char *p; 685 char *q; 686 char *srepos; 687 struct notify_proc_args *args = notify_args; 688 689 srepos = Short_Repository (repository); 690 prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1); 691 /* Copy FILTER to PROG, replacing the first occurrence of %s with 692 the notifyee. We only allocated enough memory for one %s, and I doubt 693 there is a need for more. */ 694 for (p = filter, q = prog; *p != '\0'; ++p) 695 { 696 if (p[0] == '%') 697 { 698 if (p[1] == 's') 699 { 700 strcpy (q, args->notifyee); 701 q += strlen (q); 702 strcpy (q, p + 2); 703 q += strlen (q); 704 break; 705 } 706 else 707 continue; 708 } 709 *q++ = *p; 710 } 711 *q = '\0'; 712 713 /* FIXME: why are we calling expand_proc? Didn't we already 714 expand it in Parse_Info, before passing it to notify_proc? */ 715 expanded_prog = expand_path (prog, "notify", 0); 716 if (!expanded_prog) 717 { 718 free (prog); 719 return 1; 720 } 721 722 pipefp = run_popen (expanded_prog, "w"); 723 if (pipefp == NULL) 724 { 725 error (0, errno, "cannot write entry to notify filter: %s", prog); 726 free (prog); 727 free (expanded_prog); 728 return 1; 729 } 730 731 fprintf (pipefp, "%s %s\n---\n", srepos, args->file); 732 fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository); 733 fprintf (pipefp, "By %s\n", args->who); 734 735 /* Lots more potentially useful information we could add here; see 736 logfile_write for inspiration. */ 737 738 free (prog); 739 free (expanded_prog); 740 return (pclose (pipefp)); 741 } 742 743 /* FIXME: this function should have a way to report whether there was 744 an error so that server.c can know whether to report Notified back 745 to the client. */ 746 void 747 notify_do (type, filename, who, val, watches, repository) 748 int type; 749 char *filename; 750 char *who; 751 char *val; 752 char *watches; 753 char *repository; 754 { 755 static struct addremove_args blank; 756 struct addremove_args args; 757 char *watchers; 758 char *p; 759 char *endp; 760 char *nextp; 761 762 /* Initialize fields to 0, NULL, or 0.0. */ 763 args = blank; 764 switch (type) 765 { 766 case 'E': 767 if (strpbrk (val, ",>;=\n") != NULL) 768 { 769 error (0, 0, "invalid character in editor value"); 770 return; 771 } 772 editor_set (filename, who, val); 773 break; 774 case 'U': 775 case 'C': 776 editor_set (filename, who, NULL); 777 break; 778 default: 779 return; 780 } 781 782 watchers = fileattr_get0 (filename, "_watchers"); 783 p = watchers; 784 while (p != NULL) 785 { 786 char *q; 787 char *endq; 788 char *nextq; 789 char *notif; 790 791 endp = strchr (p, '>'); 792 if (endp == NULL) 793 break; 794 nextp = strchr (p, ','); 795 796 if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0) 797 { 798 /* Don't notify user of their own changes. Would perhaps 799 be better to check whether it is the same working 800 directory, not the same user, but that is hairy. */ 801 p = nextp == NULL ? nextp : nextp + 1; 802 continue; 803 } 804 805 /* Now we point q at a string which looks like 806 "edit+unedit+commit,"... and walk down it. */ 807 q = endp + 1; 808 notif = NULL; 809 while (q != NULL) 810 { 811 endq = strchr (q, '+'); 812 if (endq == NULL || (nextp != NULL && endq > nextp)) 813 { 814 if (nextp == NULL) 815 endq = q + strlen (q); 816 else 817 endq = nextp; 818 nextq = NULL; 819 } 820 else 821 nextq = endq + 1; 822 823 /* If there is a temporary and a regular watch, send a single 824 notification, for the regular watch. */ 825 if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0) 826 { 827 notif = "edit"; 828 } 829 else if (type == 'U' 830 && endq - q == 6 && strncmp ("unedit", q, 6) == 0) 831 { 832 notif = "unedit"; 833 } 834 else if (type == 'C' 835 && endq - q == 6 && strncmp ("commit", q, 6) == 0) 836 { 837 notif = "commit"; 838 } 839 else if (type == 'E' 840 && endq - q == 5 && strncmp ("tedit", q, 5) == 0) 841 { 842 if (notif == NULL) 843 notif = "temporary edit"; 844 } 845 else if (type == 'U' 846 && endq - q == 7 && strncmp ("tunedit", q, 7) == 0) 847 { 848 if (notif == NULL) 849 notif = "temporary unedit"; 850 } 851 else if (type == 'C' 852 && endq - q == 7 && strncmp ("tcommit", q, 7) == 0) 853 { 854 if (notif == NULL) 855 notif = "temporary commit"; 856 } 857 q = nextq; 858 } 859 if (nextp != NULL) 860 ++nextp; 861 862 if (notif != NULL) 863 { 864 struct notify_proc_args args; 865 size_t len = endp - p; 866 FILE *fp; 867 char *usersname; 868 char *line = NULL; 869 size_t line_len = 0; 870 871 args.notifyee = NULL; 872 usersname = xmalloc (strlen (CVSroot_directory) 873 + sizeof CVSROOTADM 874 + sizeof CVSROOTADM_USERS 875 + 20); 876 strcpy (usersname, CVSroot_directory); 877 strcat (usersname, "/"); 878 strcat (usersname, CVSROOTADM); 879 strcat (usersname, "/"); 880 strcat (usersname, CVSROOTADM_USERS); 881 fp = CVS_FOPEN (usersname, "r"); 882 if (fp == NULL && !existence_error (errno)) 883 error (0, errno, "cannot read %s", usersname); 884 if (fp != NULL) 885 { 886 while (getline (&line, &line_len, fp) >= 0) 887 { 888 if (strncmp (line, p, len) == 0 889 && line[len] == ':') 890 { 891 char *cp; 892 args.notifyee = xstrdup (line + len + 1); 893 894 /* There may or may not be more 895 colon-separated fields added to this in the 896 future; in any case, we ignore them right 897 now, and if there are none we make sure to 898 chop off the final newline, if any. */ 899 cp = strpbrk (args.notifyee, ":\n"); 900 901 if (cp != NULL) 902 *cp = '\0'; 903 break; 904 } 905 } 906 if (ferror (fp)) 907 error (0, errno, "cannot read %s", usersname); 908 if (fclose (fp) < 0) 909 error (0, errno, "cannot close %s", usersname); 910 } 911 free (usersname); 912 if (line != NULL) 913 free (line); 914 915 if (args.notifyee == NULL) 916 { 917 args.notifyee = xmalloc (endp - p + 1); 918 strncpy (args.notifyee, p, endp - p); 919 args.notifyee[endp - p] = '\0'; 920 } 921 922 notify_args = &args; 923 args.type = notif; 924 args.who = who; 925 args.file = filename; 926 927 (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1); 928 free (args.notifyee); 929 } 930 931 p = nextp; 932 } 933 if (watchers != NULL) 934 free (watchers); 935 936 switch (type) 937 { 938 case 'E': 939 if (*watches == 'E') 940 { 941 args.add_tedit = 1; 942 ++watches; 943 } 944 if (*watches == 'U') 945 { 946 args.add_tunedit = 1; 947 ++watches; 948 } 949 if (*watches == 'C') 950 { 951 args.add_tcommit = 1; 952 } 953 watch_modify_watchers (filename, &args); 954 break; 955 case 'U': 956 case 'C': 957 args.remove_temp = 1; 958 watch_modify_watchers (filename, &args); 959 break; 960 } 961 } 962 963 #ifdef CLIENT_SUPPORT 964 /* Check and send notifications. This is only for the client. */ 965 void 966 notify_check (repository, update_dir) 967 char *repository; 968 char *update_dir; 969 { 970 FILE *fp; 971 char *line = NULL; 972 size_t line_len = 0; 973 974 if (! server_started) 975 /* We are in the midst of a command which is not to talk to 976 the server (e.g. the first phase of a cvs edit). Just chill 977 out, we'll catch the notifications on the flip side. */ 978 return; 979 980 /* We send notifications even if noexec. I'm not sure which behavior 981 is most sensible. */ 982 983 fp = CVS_FOPEN (CVSADM_NOTIFY, "r"); 984 if (fp == NULL) 985 { 986 if (!existence_error (errno)) 987 error (0, errno, "cannot open %s", CVSADM_NOTIFY); 988 return; 989 } 990 while (getline (&line, &line_len, fp) > 0) 991 { 992 int notif_type; 993 char *filename; 994 char *val; 995 char *cp; 996 997 notif_type = line[0]; 998 if (notif_type == '\0') 999 continue; 1000 filename = line + 1; 1001 cp = strchr (filename, '\t'); 1002 if (cp == NULL) 1003 continue; 1004 *cp++ = '\0'; 1005 val = cp; 1006 1007 client_notify (repository, update_dir, filename, notif_type, val); 1008 } 1009 if (line) 1010 free (line); 1011 if (ferror (fp)) 1012 error (0, errno, "cannot read %s", CVSADM_NOTIFY); 1013 if (fclose (fp) < 0) 1014 error (0, errno, "cannot close %s", CVSADM_NOTIFY); 1015 1016 /* Leave the CVSADM_NOTIFY file there, until the server tells us it 1017 has dealt with it. */ 1018 } 1019 #endif /* CLIENT_SUPPORT */ 1020 1021 1022 static const char *const editors_usage[] = 1023 { 1024 "Usage: %s %s [-lR] [files...]\n", 1025 "\t-l\tProcess this directory only (not recursive).\n", 1026 "\t-R\tProcess directories recursively.\n", 1027 "(Specify the --help global option for a list of other help options)\n", 1028 NULL 1029 }; 1030 1031 static int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 1032 1033 static int 1034 editors_fileproc (callerdat, finfo) 1035 void *callerdat; 1036 struct file_info *finfo; 1037 { 1038 char *them; 1039 char *p; 1040 1041 them = fileattr_get0 (finfo->file, "_editors"); 1042 if (them == NULL) 1043 return 0; 1044 1045 cvs_output (finfo->fullname, 0); 1046 1047 p = them; 1048 while (1) 1049 { 1050 cvs_output ("\t", 1); 1051 while (*p != '>' && *p != '\0') 1052 cvs_output (p++, 1); 1053 if (*p == '\0') 1054 { 1055 /* Only happens if attribute is misformed. */ 1056 cvs_output ("\n", 1); 1057 break; 1058 } 1059 ++p; 1060 cvs_output ("\t", 1); 1061 while (1) 1062 { 1063 while (*p != '+' && *p != ',' && *p != '\0') 1064 cvs_output (p++, 1); 1065 if (*p == '\0') 1066 { 1067 cvs_output ("\n", 1); 1068 goto out; 1069 } 1070 if (*p == ',') 1071 { 1072 ++p; 1073 break; 1074 } 1075 ++p; 1076 cvs_output ("\t", 1); 1077 } 1078 cvs_output ("\n", 1); 1079 } 1080 out:; 1081 free (them); 1082 return 0; 1083 } 1084 1085 int 1086 editors (argc, argv) 1087 int argc; 1088 char **argv; 1089 { 1090 int local = 0; 1091 int c; 1092 1093 if (argc == -1) 1094 usage (editors_usage); 1095 1096 optind = 0; 1097 while ((c = getopt (argc, argv, "+lR")) != -1) 1098 { 1099 switch (c) 1100 { 1101 case 'l': 1102 local = 1; 1103 break; 1104 case 'R': 1105 local = 0; 1106 break; 1107 case '?': 1108 default: 1109 usage (editors_usage); 1110 break; 1111 } 1112 } 1113 argc -= optind; 1114 argv += optind; 1115 1116 #ifdef CLIENT_SUPPORT 1117 if (client_active) 1118 { 1119 start_server (); 1120 ign_setup (); 1121 1122 if (local) 1123 send_arg ("-l"); 1124 send_files (argc, argv, local, 0, SEND_NO_CONTENTS); 1125 send_file_names (argc, argv, SEND_EXPAND_WILD); 1126 send_to_server ("editors\012", 0); 1127 return get_responses_and_close (); 1128 } 1129 #endif /* CLIENT_SUPPORT */ 1130 1131 return start_recursion (editors_fileproc, (FILESDONEPROC) NULL, 1132 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 1133 argc, argv, local, W_LOCAL, 0, 1, (char *)NULL, 1134 0); 1135 } 1136