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