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