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.5 2010/09/19 19:43:03 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
onoff_fileproc(void * callerdat,struct file_info * finfo)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
onoff_filesdoneproc(void * callerdat,int err,const char * repository,const char * update_dir,List * entries)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
watch_onoff(int argc,char ** argv)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
watch_on(int argc,char ** argv)115 watch_on (int argc, char **argv)
116 {
117 turning_on = 1;
118 return watch_onoff (argc, argv);
119 }
120
121 int
watch_off(int argc,char ** argv)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
dummy_fileproc(void * callerdat,struct file_info * finfo)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
ncheck_fileproc(void * callerdat,struct file_info * finfo)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
send_notifications(int argc,char ** argv,int local)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
editors_output(const char * fullname,const char * p)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 misformed. */
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
find_editors_and_output(struct file_info * finfo)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
edit_file(void * data,List * ent_list,const char * short_pathname,const char * filename)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
edit_fileproc(void * callerdat,struct file_info * finfo)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
edit(int argc,char ** argv)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
unedit_fileproc(void * callerdat,struct file_info * finfo)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
unedit(int argc,char ** argv)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
mark_up_to_date(const char * file)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
editor_set(const char * filename,const char * editor,const char * val)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
notify_proc(const char * repository,const char * filter,void * closure)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
notify_do(int type,const char * filename,const char * update_dir,const char * who,const char * val,const char * watches,const char * repository)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
notify_check(const char * repository,const char * update_dir)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
editors_fileproc(void * callerdat,struct file_info * finfo)1181 editors_fileproc (void *callerdat, struct file_info *finfo)
1182 {
1183 return find_editors_and_output (finfo);
1184 }
1185
1186
1187
1188 int
editors(int argc,char ** argv)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 }
1237