PageRenderTime 69ms CodeModel.GetById 13ms app.highlight 47ms RepoModel.GetById 1ms app.codeStats 1ms

/contrib/cvs/src/edit.c

https://bitbucket.org/freebsd/freebsd-head/
C | 1164 lines | 909 code | 136 blank | 119 comment | 253 complexity | 675fa811de507ed7af777b7e15821542 MD5 | raw file
   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}