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