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