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