xref: /netbsd/external/gpl2/xcvs/dist/src/edit.c (revision 3cd63638)
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 #include <sys/cdefs.h>
13 __RCSID("$NetBSD: edit.c,v 1.3 2016/05/17 14:00:09 christos Exp $");
14 
15 #include "cvs.h"
16 #include "getline.h"
17 #include "yesno.h"
18 #include "watch.h"
19 #include "edit.h"
20 #include "fileattr.h"
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     getoptreset ();
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     getoptreset ();
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     getoptreset ();
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 #ifdef SERVER_SUPPORT
834 			      "R", "s", referrer ? referrer->original : "NONE",
835 #endif /* SERVER_SUPPORT */
836 			      "p", "s", srepos,
837 			      "r", "s", current_parsed_root->directory,
838 			      "s", "s", args->notifyee,
839 			      (char *) NULL);
840     if (!cmdline || !strlen (cmdline))
841     {
842 	if (cmdline) free (cmdline);
843 	error (0, 0, "pretag proc resolved to the empty string!");
844 	return 1;
845     }
846 
847     pipefp = run_popen (cmdline, "w");
848     if (pipefp == NULL)
849     {
850 	error (0, errno, "cannot write entry to notify filter: %s", cmdline);
851 	free (cmdline);
852 	return 1;
853     }
854 
855     fprintf (pipefp, "%s %s\n---\n", srepos, args->file);
856     fprintf (pipefp, "Triggered %s watch on %s\n", args->type, repository);
857     fprintf (pipefp, "By %s\n", args->who);
858 
859     /* Lots more potentially useful information we could add here; see
860        logfile_write for inspiration.  */
861 
862     free (cmdline);
863     return pclose (pipefp);
864 }
865 
866 
867 
868 /* FIXME: this function should have a way to report whether there was
869    an error so that server.c can know whether to report Notified back
870    to the client.  */
871 void
notify_do(int type,const char * filename,const char * update_dir,const char * who,const char * val,const char * watches,const char * repository)872 notify_do (int type, const char *filename, const char *update_dir,
873 	   const char *who, const char *val, const char *watches,
874 	   const char *repository)
875 {
876     static struct addremove_args blank;
877     struct addremove_args args;
878     char *watchers;
879     char *p;
880     char *endp;
881     char *nextp;
882 
883     /* Print out information on current editors if we were called during an
884      * edit.
885      */
886     if (type == 'E' && !check_edited && !quiet)
887     {
888 	char *editors = fileattr_get0 (filename, "_editors");
889 	if (editors)
890 	{
891 	    /* In the CHECK_EDIT case, this message is printed by
892 	     * edit_check.  It needs to be done here too since files
893 	     * which are found to be edited when CHECK_EDIT are not
894 	     * added to the notify list.
895 	     */
896 	    const char *tmp;
897 	    if (update_dir && *update_dir)
898 		tmp  = Xasprintf ("%s/%s", update_dir, filename);
899 	    else
900 		tmp = filename;
901 
902 	    editors_output (tmp, editors);
903 
904 	    if (update_dir && *update_dir) free ((char *)tmp);
905 	    free (editors);
906 	}
907     }
908 
909     /* Initialize fields to 0, NULL, or 0.0.  */
910     args = blank;
911     switch (type)
912     {
913 	case 'E':
914 	    if (strpbrk (val, ",>;=\n") != NULL)
915 	    {
916 		error (0, 0, "invalid character in editor value");
917 		return;
918 	    }
919 	    editor_set (filename, who, val);
920 	    break;
921 	case 'U':
922 	case 'C':
923 	    editor_set (filename, who, NULL);
924 	    break;
925 	default:
926 	    return;
927     }
928 
929     watchers = fileattr_get0 (filename, "_watchers");
930     p = watchers;
931     while (p != NULL)
932     {
933 	char *q;
934 	char *endq;
935 	char *nextq;
936 	char *notif;
937 
938 	endp = strchr (p, '>');
939 	if (endp == NULL)
940 	    break;
941 	nextp = strchr (p, ',');
942 
943 	if ((size_t)(endp - p) == strlen (who) && strncmp (who, p, endp - p) == 0)
944 	{
945 	    /* Don't notify user of their own changes.  Would perhaps
946 	       be better to check whether it is the same working
947 	       directory, not the same user, but that is hairy.  */
948 	    p = nextp == NULL ? nextp : nextp + 1;
949 	    continue;
950 	}
951 
952 	/* Now we point q at a string which looks like
953 	   "edit+unedit+commit,"... and walk down it.  */
954 	q = endp + 1;
955 	notif = NULL;
956 	while (q != NULL)
957 	{
958 	    endq = strchr (q, '+');
959 	    if (endq == NULL || (nextp != NULL && endq > nextp))
960 	    {
961 		if (nextp == NULL)
962 		    endq = q + strlen (q);
963 		else
964 		    endq = nextp;
965 		nextq = NULL;
966 	    }
967 	    else
968 		nextq = endq + 1;
969 
970 	    /* If there is a temporary and a regular watch, send a single
971 	       notification, for the regular watch.  */
972 	    if (type == 'E' && endq - q == 4 && strncmp ("edit", q, 4) == 0)
973 	    {
974 		notif = "edit";
975 	    }
976 	    else if (type == 'U'
977 		     && endq - q == 6 && strncmp ("unedit", q, 6) == 0)
978 	    {
979 		notif = "unedit";
980 	    }
981 	    else if (type == 'C'
982 		     && endq - q == 6 && strncmp ("commit", q, 6) == 0)
983 	    {
984 		notif = "commit";
985 	    }
986 	    else if (type == 'E'
987 		     && endq - q == 5 && strncmp ("tedit", q, 5) == 0)
988 	    {
989 		if (notif == NULL)
990 		    notif = "temporary edit";
991 	    }
992 	    else if (type == 'U'
993 		     && endq - q == 7 && strncmp ("tunedit", q, 7) == 0)
994 	    {
995 		if (notif == NULL)
996 		    notif = "temporary unedit";
997 	    }
998 	    else if (type == 'C'
999 		     && endq - q == 7 && strncmp ("tcommit", q, 7) == 0)
1000 	    {
1001 		if (notif == NULL)
1002 		    notif = "temporary commit";
1003 	    }
1004 	    q = nextq;
1005 	}
1006 	if (nextp != NULL)
1007 	    ++nextp;
1008 
1009 	if (notif != NULL)
1010 	{
1011 	    struct notify_proc_args args;
1012 	    size_t len = endp - p;
1013 	    FILE *fp;
1014 	    char *usersname;
1015 	    char *line = NULL;
1016 	    size_t line_len = 0;
1017 
1018 	    args.notifyee = NULL;
1019 	    usersname = Xasprintf ("%s/%s/%s", current_parsed_root->directory,
1020 				   CVSROOTADM, CVSROOTADM_USERS);
1021 	    fp = CVS_FOPEN (usersname, "r");
1022 	    if (fp == NULL && !existence_error (errno))
1023 		error (0, errno, "cannot read %s", usersname);
1024 	    if (fp != NULL)
1025 	    {
1026 		while (getline (&line, &line_len, fp) >= 0)
1027 		{
1028 		    if (strncmp (line, p, len) == 0
1029 			&& line[len] == ':')
1030 		    {
1031 			char *cp;
1032 			args.notifyee = xstrdup (line + len + 1);
1033 
1034                         /* There may or may not be more
1035                            colon-separated fields added to this in the
1036                            future; in any case, we ignore them right
1037                            now, and if there are none we make sure to
1038                            chop off the final newline, if any. */
1039 			cp = strpbrk (args.notifyee, ":\n");
1040 
1041 			if (cp != NULL)
1042 			    *cp = '\0';
1043 			break;
1044 		    }
1045 		}
1046 		if (ferror (fp))
1047 		    error (0, errno, "cannot read %s", usersname);
1048 		if (fclose (fp) < 0)
1049 		    error (0, errno, "cannot close %s", usersname);
1050 	    }
1051 	    free (usersname);
1052 	    if (line != NULL)
1053 		free (line);
1054 
1055 	    if (args.notifyee == NULL)
1056 	    {
1057 		char *tmp;
1058 		tmp = xmalloc (endp - p + 1);
1059 		strncpy (tmp, p, endp - p);
1060 		tmp[endp - p] = '\0';
1061 		args.notifyee = tmp;
1062 	    }
1063 
1064 	    args.type = notif;
1065 	    args.who = who;
1066 	    args.file = filename;
1067 
1068 	    (void) Parse_Info (CVSROOTADM_NOTIFY, repository, notify_proc,
1069 			       PIOPT_ALL, &args);
1070             /* It's okay to cast out the const for the free() below since we
1071              * just allocated this a few lines above.  The const was for
1072              * everybody else.
1073              */
1074 	    free ((char *)args.notifyee);
1075 	}
1076 
1077 	p = nextp;
1078     }
1079     if (watchers != NULL)
1080 	free (watchers);
1081 
1082     switch (type)
1083     {
1084 	case 'E':
1085 	    if (*watches == 'E')
1086 	    {
1087 		args.add_tedit = 1;
1088 		++watches;
1089 	    }
1090 	    if (*watches == 'U')
1091 	    {
1092 		args.add_tunedit = 1;
1093 		++watches;
1094 	    }
1095 	    if (*watches == 'C')
1096 	    {
1097 		args.add_tcommit = 1;
1098 	    }
1099 	    watch_modify_watchers (filename, &args);
1100 	    break;
1101 	case 'U':
1102 	case 'C':
1103 	    args.remove_temp = 1;
1104 	    watch_modify_watchers (filename, &args);
1105 	    break;
1106     }
1107 }
1108 
1109 
1110 
1111 #ifdef CLIENT_SUPPORT
1112 /* Check and send notifications.  This is only for the client.  */
1113 void
notify_check(const char * repository,const char * update_dir)1114 notify_check (const char *repository, const char *update_dir)
1115 {
1116     FILE *fp;
1117     char *line = NULL;
1118     size_t line_len = 0;
1119 
1120     if (!server_started)
1121 	/* We are in the midst of a command which is not to talk to
1122 	   the server (e.g. the first phase of a cvs edit).  Just chill
1123 	   out, we'll catch the notifications on the flip side.  */
1124 	return;
1125 
1126     /* We send notifications even if noexec.  I'm not sure which behavior
1127        is most sensible.  */
1128 
1129     fp = CVS_FOPEN (CVSADM_NOTIFY, "r");
1130     if (fp == NULL)
1131     {
1132 	if (!existence_error (errno))
1133 	    error (0, errno, "cannot open %s", CVSADM_NOTIFY);
1134 	return;
1135     }
1136     while (getline (&line, &line_len, fp) > 0)
1137     {
1138 	int notif_type;
1139 	char *filename;
1140 	char *val;
1141 	char *cp;
1142 
1143 	notif_type = line[0];
1144 	if (notif_type == '\0')
1145 	    continue;
1146 	filename = line + 1;
1147 	cp = strchr (filename, '\t');
1148 	if (cp == NULL)
1149 	    continue;
1150 	*cp++ = '\0';
1151 	val = cp;
1152 
1153 	client_notify (repository, update_dir, filename, notif_type, val);
1154     }
1155     if (line)
1156 	free (line);
1157     if (ferror (fp))
1158 	error (0, errno, "cannot read %s", CVSADM_NOTIFY);
1159     if (fclose (fp) < 0)
1160 	error (0, errno, "cannot close %s", CVSADM_NOTIFY);
1161 
1162     /* Leave the CVSADM_NOTIFY file there, until the server tells us it
1163        has dealt with it.  */
1164 }
1165 #endif /* CLIENT_SUPPORT */
1166 
1167 
1168 static const char *const editors_usage[] =
1169 {
1170     "Usage: %s %s [-lR] [<file>]...\n",
1171     "-l\tProcess this directory only (not recursive).\n",
1172     "-R\tProcess directories recursively (default).\n",
1173     "(Specify the --help global option for a list of other help options.)\n",
1174     NULL
1175 };
1176 
1177 
1178 
1179 static int
editors_fileproc(void * callerdat,struct file_info * finfo)1180 editors_fileproc (void *callerdat, struct file_info *finfo)
1181 {
1182     return find_editors_and_output (finfo);
1183 }
1184 
1185 
1186 
1187 int
editors(int argc,char ** argv)1188 editors (int argc, char **argv)
1189 {
1190     int local = 0;
1191     int c;
1192 
1193     if (argc == -1)
1194 	usage (editors_usage);
1195 
1196     getoptreset ();
1197     while ((c = getopt (argc, argv, "+lR")) != -1)
1198     {
1199 	switch (c)
1200 	{
1201 	    case 'l':
1202 		local = 1;
1203 		break;
1204 	    case 'R':
1205 		local = 0;
1206 		break;
1207 	    case '?':
1208 	    default:
1209 		usage (editors_usage);
1210 		break;
1211 	}
1212     }
1213     argc -= optind;
1214     argv += optind;
1215 
1216 #ifdef CLIENT_SUPPORT
1217     if (current_parsed_root->isremote)
1218     {
1219 	start_server ();
1220 	ign_setup ();
1221 
1222 	if (local)
1223 	    send_arg ("-l");
1224 	send_arg ("--");
1225 	send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
1226 	send_file_names (argc, argv, SEND_EXPAND_WILD);
1227 	send_to_server ("editors\012", 0);
1228 	return get_responses_and_close ();
1229     }
1230 #endif /* CLIENT_SUPPORT */
1231 
1232     return start_recursion (editors_fileproc, NULL, NULL, NULL, NULL,
1233 			    argc, argv, local, W_LOCAL, 0, CVS_LOCK_READ, NULL,
1234 			    0, NULL);
1235 }
1236