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 (current_parsed_root->isremote) 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, W_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 (get_line (&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 (current_parsed_root->isremote) 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, W_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 /* Fix non-standard format. */ 305 if (ascnow[8] == '0') ascnow[8] = ' '; 306 fprintf (fp, "E%s\t%s GMT\t%s\t%s\t", finfo->file, 307 ascnow, hostname, CurDir); 308 if (setting_tedit) 309 fprintf (fp, "E"); 310 if (setting_tunedit) 311 fprintf (fp, "U"); 312 if (setting_tcommit) 313 fprintf (fp, "C"); 314 fprintf (fp, "\n"); 315 316 if (fclose (fp) < 0) 317 { 318 if (finfo->update_dir[0] == '\0') 319 error (0, errno, "cannot close %s", CVSADM_NOTIFY); 320 else 321 error (0, errno, "cannot close %s/%s", finfo->update_dir, 322 CVSADM_NOTIFY); 323 } 324 325 xchmod (finfo->file, 1); 326 327 /* Now stash the file away in CVSADM so that unedit can revert even if 328 it can't communicate with the server. We stash away a writable 329 copy so that if the user removes the working file, then restores it 330 with "cvs update" (which clears _editors but does not update 331 CVSADM_BASE), then a future "cvs edit" can still win. */ 332 /* Could save a system call by only calling mkdir_if_needed if 333 trying to create the output file fails. But copy_file isn't 334 set up to facilitate that. */ 335 mkdir_if_needed (CVSADM_BASE); 336 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file)); 337 strcpy (basefilename, CVSADM_BASE); 338 strcat (basefilename, "/"); 339 strcat (basefilename, finfo->file); 340 copy_file (finfo->file, basefilename); 341 free (basefilename); 342 343 { 344 Node *node; 345 346 node = findnode_fn (finfo->entries, finfo->file); 347 if (node != NULL) 348 base_register (finfo, ((Entnode *) node->data)->version); 349 } 350 351 return 0; 352 } 353 354 static const char *const edit_usage[] = 355 { 356 "Usage: %s %s [-lR] [files...]\n", 357 "-l: Local directory only, not recursive\n", 358 "-R: Process directories recursively\n", 359 "-a: Specify what actions for temporary watch, one of\n", 360 " edit,unedit,commit,all,none\n", 361 "(Specify the --help global option for a list of other help options)\n", 362 NULL 363 }; 364 365 int 366 edit (argc, argv) 367 int argc; 368 char **argv; 369 { 370 int local = 0; 371 int c; 372 int err; 373 int a_omitted; 374 375 if (argc == -1) 376 usage (edit_usage); 377 378 a_omitted = 1; 379 setting_tedit = 0; 380 setting_tunedit = 0; 381 setting_tcommit = 0; 382 optind = 0; 383 while ((c = getopt (argc, argv, "+lRa:")) != -1) 384 { 385 switch (c) 386 { 387 case 'l': 388 local = 1; 389 break; 390 case 'R': 391 local = 0; 392 break; 393 case 'a': 394 a_omitted = 0; 395 if (strcmp (optarg, "edit") == 0) 396 setting_tedit = 1; 397 else if (strcmp (optarg, "unedit") == 0) 398 setting_tunedit = 1; 399 else if (strcmp (optarg, "commit") == 0) 400 setting_tcommit = 1; 401 else if (strcmp (optarg, "all") == 0) 402 { 403 setting_tedit = 1; 404 setting_tunedit = 1; 405 setting_tcommit = 1; 406 } 407 else if (strcmp (optarg, "none") == 0) 408 { 409 setting_tedit = 0; 410 setting_tunedit = 0; 411 setting_tcommit = 0; 412 } 413 else 414 usage (edit_usage); 415 break; 416 case '?': 417 default: 418 usage (edit_usage); 419 break; 420 } 421 } 422 argc -= optind; 423 argv += optind; 424 425 if (a_omitted) 426 { 427 setting_tedit = 1; 428 setting_tunedit = 1; 429 setting_tcommit = 1; 430 } 431 432 if (strpbrk (hostname, "+,>;=\t\n") != NULL) 433 error (1, 0, 434 "host name (%s) contains an invalid character (+,>;=\\t\\n)", 435 hostname); 436 if (strpbrk (CurDir, "+,>;=\t\n") != NULL) 437 error (1, 0, 438 "current directory (%s) contains an invalid character (+,>;=\\t\\n)", 439 CurDir); 440 441 /* No need to readlock since we aren't doing anything to the 442 repository. */ 443 err = start_recursion (edit_fileproc, (FILESDONEPROC) NULL, 444 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 445 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, 446 0); 447 448 err += send_notifications (argc, argv, local); 449 450 return err; 451 } 452 453 static int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 454 455 static int 456 unedit_fileproc (callerdat, finfo) 457 void *callerdat; 458 struct file_info *finfo; 459 { 460 FILE *fp; 461 time_t now; 462 char *ascnow; 463 char *basefilename; 464 465 if (noexec) 466 return 0; 467 468 basefilename = xmalloc (10 + sizeof CVSADM_BASE + strlen (finfo->file)); 469 strcpy (basefilename, CVSADM_BASE); 470 strcat (basefilename, "/"); 471 strcat (basefilename, finfo->file); 472 if (!isfile (basefilename)) 473 { 474 /* This file apparently was never cvs edit'd (e.g. we are uneditting 475 a directory where only some of the files were cvs edit'd. */ 476 free (basefilename); 477 return 0; 478 } 479 480 if (xcmp (finfo->file, basefilename) != 0) 481 { 482 printf ("%s has been modified; revert changes? ", finfo->fullname); 483 if (!yesno ()) 484 { 485 /* "no". */ 486 free (basefilename); 487 return 0; 488 } 489 } 490 rename_file (basefilename, finfo->file); 491 free (basefilename); 492 493 fp = open_file (CVSADM_NOTIFY, "a"); 494 495 (void) time (&now); 496 ascnow = asctime (gmtime (&now)); 497 ascnow[24] = '\0'; 498 /* Fix non-standard format. */ 499 if (ascnow[8] == '0') ascnow[8] = ' '; 500 fprintf (fp, "U%s\t%s GMT\t%s\t%s\t\n", finfo->file, 501 ascnow, hostname, CurDir); 502 503 if (fclose (fp) < 0) 504 { 505 if (finfo->update_dir[0] == '\0') 506 error (0, errno, "cannot close %s", CVSADM_NOTIFY); 507 else 508 error (0, errno, "cannot close %s/%s", finfo->update_dir, 509 CVSADM_NOTIFY); 510 } 511 512 /* Now update the revision number in CVS/Entries from CVS/Baserev. 513 The basic idea here is that we are reverting to the revision 514 that the user edited. If we wanted "cvs update" to update 515 CVS/Base as we go along (so that an unedit could revert to the 516 current repository revision), we would need: 517 518 update (or all send_files?) (client) needs to send revision in 519 new Entry-base request. update (server/local) needs to check 520 revision against repository and send new Update-base response 521 (like Update-existing in that the file already exists. While 522 we are at it, might try to clean up the syntax by having the 523 mode only in a "Mode" response, not in the Update-base itself). */ 524 { 525 char *baserev; 526 Node *node; 527 Entnode *entdata; 528 529 baserev = base_get (finfo); 530 node = findnode_fn (finfo->entries, finfo->file); 531 /* The case where node is NULL probably should be an error or 532 something, but I don't want to think about it too hard right 533 now. */ 534 if (node != NULL) 535 { 536 entdata = (Entnode *) node->data; 537 if (baserev == NULL) 538 { 539 /* This can only happen if the CVS/Baserev file got 540 corrupted. We suspect it might be possible if the 541 user interrupts CVS, although I haven't verified 542 that. */ 543 error (0, 0, "%s not mentioned in %s", finfo->fullname, 544 CVSADM_BASEREV); 545 546 /* Since we don't know what revision the file derives from, 547 keeping it around would be asking for trouble. */ 548 if (unlink_file (finfo->file) < 0) 549 error (0, errno, "cannot remove %s", finfo->fullname); 550 551 /* This is cheesy, in a sense; why shouldn't we do the 552 update for the user? However, doing that would require 553 contacting the server, so maybe this is OK. */ 554 error (0, 0, "run update to complete the unedit"); 555 return 0; 556 } 557 Register (finfo->entries, finfo->file, baserev, entdata->timestamp, 558 entdata->options, entdata->tag, entdata->date, 559 entdata->conflict); 560 } 561 free (baserev); 562 base_deregister (finfo); 563 } 564 565 xchmod (finfo->file, 0); 566 return 0; 567 } 568 569 static const char *const unedit_usage[] = 570 { 571 "Usage: %s %s [-lR] [files...]\n", 572 "-l: Local directory only, not recursive\n", 573 "-R: Process directories recursively\n", 574 "(Specify the --help global option for a list of other help options)\n", 575 NULL 576 }; 577 578 int 579 unedit (argc, argv) 580 int argc; 581 char **argv; 582 { 583 int local = 0; 584 int c; 585 int err; 586 587 if (argc == -1) 588 usage (unedit_usage); 589 590 optind = 0; 591 while ((c = getopt (argc, argv, "+lR")) != -1) 592 { 593 switch (c) 594 { 595 case 'l': 596 local = 1; 597 break; 598 case 'R': 599 local = 0; 600 break; 601 case '?': 602 default: 603 usage (unedit_usage); 604 break; 605 } 606 } 607 argc -= optind; 608 argv += optind; 609 610 /* No need to readlock since we aren't doing anything to the 611 repository. */ 612 err = start_recursion (unedit_fileproc, (FILESDONEPROC) NULL, 613 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 614 argc, argv, local, W_LOCAL, 0, 0, (char *)NULL, 615 0); 616 617 err += send_notifications (argc, argv, local); 618 619 return err; 620 } 621 622 void 623 mark_up_to_date (file) 624 char *file; 625 { 626 char *base; 627 628 /* The file is up to date, so we better get rid of an out of 629 date file in CVSADM_BASE. */ 630 base = xmalloc (strlen (file) + 80); 631 strcpy (base, CVSADM_BASE); 632 strcat (base, "/"); 633 strcat (base, file); 634 if (unlink_file (base) < 0 && ! existence_error (errno)) 635 error (0, errno, "cannot remove %s", file); 636 free (base); 637 } 638 639 640 void 641 editor_set (filename, editor, val) 642 char *filename; 643 char *editor; 644 char *val; 645 { 646 char *edlist; 647 char *newlist; 648 649 edlist = fileattr_get0 (filename, "_editors"); 650 newlist = fileattr_modify (edlist, editor, val, '>', ','); 651 /* If the attributes is unchanged, don't rewrite the attribute file. */ 652 if (!((edlist == NULL && newlist == NULL) 653 || (edlist != NULL 654 && newlist != NULL 655 && strcmp (edlist, newlist) == 0))) 656 fileattr_set (filename, "_editors", newlist); 657 if (edlist != NULL) 658 free (edlist); 659 if (newlist != NULL) 660 free (newlist); 661 } 662 663 struct notify_proc_args { 664 /* What kind of notification, "edit", "tedit", etc. */ 665 char *type; 666 /* User who is running the command which causes notification. */ 667 char *who; 668 /* User to be notified. */ 669 char *notifyee; 670 /* File. */ 671 char *file; 672 }; 673 674 /* Pass as a static until we get around to fixing Parse_Info to pass along 675 a void * where we can stash it. */ 676 static struct notify_proc_args *notify_args; 677 678 static int notify_proc PROTO ((char *repository, char *filter)); 679 680 static int 681 notify_proc (repository, filter) 682 char *repository; 683 char *filter; 684 { 685 FILE *pipefp; 686 char *prog; 687 char *expanded_prog; 688 char *p; 689 char *q; 690 char *srepos; 691 struct notify_proc_args *args = notify_args; 692 693 srepos = Short_Repository (repository); 694 prog = xmalloc (strlen (filter) + strlen (args->notifyee) + 1); 695 /* Copy FILTER to PROG, replacing the first occurrence of %s with 696 the notifyee. We only allocated enough memory for one %s, and I doubt 697 there is a need for more. */ 698 for (p = filter, q = prog; *p != '\0'; ++p) 699 { 700 if (p[0] == '%') 701 { 702 if (p[1] == 's') 703 { 704 strcpy (q, args->notifyee); 705 q += strlen (q); 706 strcpy (q, p + 2); 707 q += strlen (q); 708 break; 709 } 710 else 711 continue; 712 } 713 *q++ = *p; 714 } 715 *q = '\0'; 716 717 /* FIXME: why are we calling expand_proc? Didn't we already 718 expand it in Parse_Info, before passing it to notify_proc? */ 719 expanded_prog = expand_path (prog, "notify", 0); 720 if (!expanded_prog) 721 { 722 free (prog); 723 return 1; 724 } 725 726 pipefp = run_popen (expanded_prog, "w"); 727 if (pipefp == NULL) 728 { 729 error (0, errno, "cannot write entry to notify filter: %s", prog); 730 free (prog); 731 free (expanded_prog); 732 return 1; 733 } 734 735 fprintf (pipefp, "%s %s\n---\n", srepos, args->file); 736 fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository); 737 fprintf (pipefp, "By %s\n", args->who); 738 739 /* Lots more potentially useful information we could add here; see 740 logfile_write for inspiration. */ 741 742 free (prog); 743 free (expanded_prog); 744 return (pclose (pipefp)); 745 } 746 747 /* FIXME: this function should have a way to report whether there was 748 an error so that server.c can know whether to report Notified back 749 to the client. */ 750 void 751 notify_do (type, filename, who, val, watches, repository) 752 int type; 753 char *filename; 754 char *who; 755 char *val; 756 char *watches; 757 char *repository; 758 { 759 static struct addremove_args blank; 760 struct addremove_args args; 761 char *watchers; 762 char *p; 763 char *endp; 764 char *nextp; 765 766 /* Initialize fields to 0, NULL, or 0.0. */ 767 args = blank; 768 switch (type) 769 { 770 case 'E': 771 if (strpbrk (val, ",>;=\n") != NULL) 772 { 773 error (0, 0, "invalid character in editor value"); 774 return; 775 } 776 editor_set (filename, who, val); 777 break; 778 case 'U': 779 case 'C': 780 editor_set (filename, who, NULL); 781 break; 782 default: 783 return; 784 } 785 786 watchers = fileattr_get0 (filename, "_watchers"); 787 p = watchers; 788 while (p != NULL) 789 { 790 char *q; 791 char *endq; 792 char *nextq; 793 char *notif; 794 795 endp = strchr (p, '>'); 796 if (endp == NULL) 797 break; 798 nextp = strchr (p, ','); 799 800 if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0) 801 { 802 /* Don't notify user of their own changes. Would perhaps 803 be better to check whether it is the same working 804 directory, not the same user, but that is hairy. */ 805 p = nextp == NULL ? nextp : nextp + 1; 806 continue; 807 } 808 809 /* Now we point q at a string which looks like 810 "edit+unedit+commit,"... and walk down it. */ 811 q = endp + 1; 812 notif = NULL; 813 while (q != NULL) 814 { 815 endq = strchr (q, '+'); 816 if (endq == NULL || (nextp != NULL && endq > nextp)) 817 { 818 if (nextp == NULL) 819 endq = q + strlen (q); 820 else 821 endq = nextp; 822 nextq = NULL; 823 } 824 else 825 nextq = endq + 1; 826 827 /* If there is a temporary and a regular watch, send a single 828 notification, for the regular watch. */ 829 if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0) 830 { 831 notif = "edit"; 832 } 833 else if (type == 'U' 834 && endq - q == 6 && strncmp ("unedit", q, 6) == 0) 835 { 836 notif = "unedit"; 837 } 838 else if (type == 'C' 839 && endq - q == 6 && strncmp ("commit", q, 6) == 0) 840 { 841 notif = "commit"; 842 } 843 else if (type == 'E' 844 && endq - q == 5 && strncmp ("tedit", q, 5) == 0) 845 { 846 if (notif == NULL) 847 notif = "temporary edit"; 848 } 849 else if (type == 'U' 850 && endq - q == 7 && strncmp ("tunedit", q, 7) == 0) 851 { 852 if (notif == NULL) 853 notif = "temporary unedit"; 854 } 855 else if (type == 'C' 856 && endq - q == 7 && strncmp ("tcommit", q, 7) == 0) 857 { 858 if (notif == NULL) 859 notif = "temporary commit"; 860 } 861 q = nextq; 862 } 863 if (nextp != NULL) 864 ++nextp; 865 866 if (notif != NULL) 867 { 868 struct notify_proc_args args; 869 size_t len = endp - p; 870 FILE *fp; 871 char *usersname; 872 char *line = NULL; 873 size_t line_len = 0; 874 875 args.notifyee = NULL; 876 usersname = xmalloc (strlen (current_parsed_root->directory) 877 + sizeof CVSROOTADM 878 + sizeof CVSROOTADM_USERS 879 + 20); 880 strcpy (usersname, current_parsed_root->directory); 881 strcat (usersname, "/"); 882 strcat (usersname, CVSROOTADM); 883 strcat (usersname, "/"); 884 strcat (usersname, CVSROOTADM_USERS); 885 fp = CVS_FOPEN (usersname, "r"); 886 if (fp == NULL && !existence_error (errno)) 887 error (0, errno, "cannot read %s", usersname); 888 if (fp != NULL) 889 { 890 while (get_line (&line, &line_len, fp) >= 0) 891 { 892 if (strncmp (line, p, len) == 0 893 && line[len] == ':') 894 { 895 char *cp; 896 args.notifyee = xstrdup (line + len + 1); 897 898 /* There may or may not be more 899 colon-separated fields added to this in the 900 future; in any case, we ignore them right 901 now, and if there are none we make sure to 902 chop off the final newline, if any. */ 903 cp = strpbrk (args.notifyee, ":\n"); 904 905 if (cp != NULL) 906 *cp = '\0'; 907 break; 908 } 909 } 910 if (ferror (fp)) 911 error (0, errno, "cannot read %s", usersname); 912 if (fclose (fp) < 0) 913 error (0, errno, "cannot close %s", usersname); 914 } 915 free (usersname); 916 if (line != NULL) 917 free (line); 918 919 if (args.notifyee == NULL) 920 { 921 args.notifyee = xmalloc (endp - p + 1); 922 strncpy (args.notifyee, p, endp - p); 923 args.notifyee[endp - p] = '\0'; 924 } 925 926 notify_args = &args; 927 args.type = notif; 928 args.who = who; 929 args.file = filename; 930 931 (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc, 1); 932 free (args.notifyee); 933 } 934 935 p = nextp; 936 } 937 if (watchers != NULL) 938 free (watchers); 939 940 switch (type) 941 { 942 case 'E': 943 if (*watches == 'E') 944 { 945 args.add_tedit = 1; 946 ++watches; 947 } 948 if (*watches == 'U') 949 { 950 args.add_tunedit = 1; 951 ++watches; 952 } 953 if (*watches == 'C') 954 { 955 args.add_tcommit = 1; 956 } 957 watch_modify_watchers (filename, &args); 958 break; 959 case 'U': 960 case 'C': 961 args.remove_temp = 1; 962 watch_modify_watchers (filename, &args); 963 break; 964 } 965 } 966 967 #ifdef CLIENT_SUPPORT 968 /* Check and send notifications. This is only for the client. */ 969 void 970 notify_check (repository, update_dir) 971 char *repository; 972 char *update_dir; 973 { 974 FILE *fp; 975 char *line = NULL; 976 size_t line_len = 0; 977 978 if (! server_started) 979 /* We are in the midst of a command which is not to talk to 980 the server (e.g. the first phase of a cvs edit). Just chill 981 out, we'll catch the notifications on the flip side. */ 982 return; 983 984 /* We send notifications even if noexec. I'm not sure which behavior 985 is most sensible. */ 986 987 fp = CVS_FOPEN (CVSADM_NOTIFY, "r"); 988 if (fp == NULL) 989 { 990 if (!existence_error (errno)) 991 error (0, errno, "cannot open %s", CVSADM_NOTIFY); 992 return; 993 } 994 while (get_line (&line, &line_len, fp) > 0) 995 { 996 int notif_type; 997 char *filename; 998 char *val; 999 char *cp; 1000 1001 notif_type = line[0]; 1002 if (notif_type == '\0') 1003 continue; 1004 filename = line + 1; 1005 cp = strchr (filename, '\t'); 1006 if (cp == NULL) 1007 continue; 1008 *cp++ = '\0'; 1009 val = cp; 1010 1011 client_notify (repository, update_dir, filename, notif_type, val); 1012 } 1013 if (line) 1014 free (line); 1015 if (ferror (fp)) 1016 error (0, errno, "cannot read %s", CVSADM_NOTIFY); 1017 if (fclose (fp) < 0) 1018 error (0, errno, "cannot close %s", CVSADM_NOTIFY); 1019 1020 /* Leave the CVSADM_NOTIFY file there, until the server tells us it 1021 has dealt with it. */ 1022 } 1023 #endif /* CLIENT_SUPPORT */ 1024 1025 1026 static const char *const editors_usage[] = 1027 { 1028 "Usage: %s %s [-lR] [files...]\n", 1029 "\t-l\tProcess this directory only (not recursive).\n", 1030 "\t-R\tProcess directories recursively.\n", 1031 "(Specify the --help global option for a list of other help options)\n", 1032 NULL 1033 }; 1034 1035 static int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 1036 1037 static int 1038 editors_fileproc (callerdat, finfo) 1039 void *callerdat; 1040 struct file_info *finfo; 1041 { 1042 char *them; 1043 char *p; 1044 1045 them = fileattr_get0 (finfo->file, "_editors"); 1046 if (them == NULL) 1047 return 0; 1048 1049 cvs_output (finfo->fullname, 0); 1050 1051 p = them; 1052 while (1) 1053 { 1054 cvs_output ("\t", 1); 1055 while (*p != '>' && *p != '\0') 1056 cvs_output (p++, 1); 1057 if (*p == '\0') 1058 { 1059 /* Only happens if attribute is misformed. */ 1060 cvs_output ("\n", 1); 1061 break; 1062 } 1063 ++p; 1064 cvs_output ("\t", 1); 1065 while (1) 1066 { 1067 while (*p != '+' && *p != ',' && *p != '\0') 1068 cvs_output (p++, 1); 1069 if (*p == '\0') 1070 { 1071 cvs_output ("\n", 1); 1072 goto out; 1073 } 1074 if (*p == ',') 1075 { 1076 ++p; 1077 break; 1078 } 1079 ++p; 1080 cvs_output ("\t", 1); 1081 } 1082 cvs_output ("\n", 1); 1083 } 1084 out:; 1085 free (them); 1086 return 0; 1087 } 1088 1089 int 1090 editors (argc, argv) 1091 int argc; 1092 char **argv; 1093 { 1094 int local = 0; 1095 int c; 1096 1097 if (argc == -1) 1098 usage (editors_usage); 1099 1100 optind = 0; 1101 while ((c = getopt (argc, argv, "+lR")) != -1) 1102 { 1103 switch (c) 1104 { 1105 case 'l': 1106 local = 1; 1107 break; 1108 case 'R': 1109 local = 0; 1110 break; 1111 case '?': 1112 default: 1113 usage (editors_usage); 1114 break; 1115 } 1116 } 1117 argc -= optind; 1118 argv += optind; 1119 1120 #ifdef CLIENT_SUPPORT 1121 if (current_parsed_root->isremote) 1122 { 1123 start_server (); 1124 ign_setup (); 1125 1126 if (local) 1127 send_arg ("-l"); 1128 send_files (argc, argv, local, 0, SEND_NO_CONTENTS); 1129 send_file_names (argc, argv, SEND_EXPAND_WILD); 1130 send_to_server ("editors\012", 0); 1131 return get_responses_and_close (); 1132 } 1133 #endif /* CLIENT_SUPPORT */ 1134 1135 return start_recursion (editors_fileproc, (FILESDONEPROC) NULL, 1136 (DIRENTPROC) NULL, (DIRLEAVEPROC) NULL, NULL, 1137 argc, argv, local, W_LOCAL, 0, 1, (char *)NULL, 1138 0); 1139 } 1140