]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/cvs/src/edit.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / contrib / cvs / 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 PROTO ((int, char **));
20
21 static int setting_default;
22 static int turning_on;
23
24 static int setting_tedit;
25 static int setting_tunedit;
26 static int setting_tcommit;
27
28 static int onoff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
29
30 static int
31 onoff_fileproc (callerdat, finfo)
32     void *callerdat;
33     struct file_info *finfo;
34 {
35     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
44 static int onoff_filesdoneproc PROTO ((void *, int, const char *, const char *,
45                                        List *));
46
47 static int
48 onoff_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
65 static int
66 watch_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
124 int
125 watch_on (argc, argv)
126     int argc;
127     char **argv;
128 {
129     turning_on = 1;
130     return watch_onoff (argc, argv);
131 }
132
133 int
134 watch_off (argc, argv)
135     int argc;
136     char **argv;
137 {
138     turning_on = 0;
139     return watch_onoff (argc, argv);
140 }
141 \f
142 static int dummy_fileproc PROTO ((void *callerdat, struct file_info *finfo));
143
144 static int
145 dummy_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
155 static 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
163 static int
164 ncheck_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
234 static int send_notifications PROTO ((int, char **, int));
235
236 /* Look through the CVSADM_NOTIFY file and process each item there
237    accordingly.  */
238 static int
239 send_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 \f
284 static int edit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
285
286 static int
287 edit_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
366 static 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
377 int
378 edit (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
465 static int unedit_fileproc PROTO ((void *callerdat, struct file_info *finfo));
466
467 static int
468 unedit_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
581 static 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
590 int
591 unedit (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
634 void
635 mark_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
653 void
654 editor_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
676 struct 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.  */
691 static struct notify_proc_args *notify_args;
692
693
694
695 static int notify_proc PROTO ((const char *repository, const char *filter));
696
697 static int
698 notify_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.  */
767 void
768 notify_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.  */
993 void
994 cvs_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
1050 static 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
1059 static int editors_fileproc PROTO ((void *callerdat, struct file_info *finfo));
1060
1061 static int
1062 editors_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
1113 int
1114 editors (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 }