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