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