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