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