xref: /dragonfly/contrib/cvs-1.12/src/watch.c (revision 5fb3968e)
1 /* Implementation for "cvs watch add", "cvs watchers", and related commands
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.  */
12 
13 #include "cvs.h"
14 #include "edit.h"
15 #include "fileattr.h"
16 #include "watch.h"
17 
18 const char *const watch_usage[] =
19 {
20     "Usage: %s %s {on|off|add|remove} [-lR] [-a <action>]... [<path>]...\n",
21     "on/off: Turn on/off read-only checkouts of files.\n",
22     "add/remove: Add or remove notification on actions.\n",
23     "-l (on/off/add/remove): Local directory only, not recursive.\n",
24     "-R (on/off/add/remove): Process directories recursively (default).\n",
25     "-a (add/remove): Specify what actions, one of: `edit', `unedit',\n",
26     "                 `commit', `all', or `none' (defaults to `all').\n",
27     "(Specify the --help global option for a list of other help options.)\n",
28     NULL
29 };
30 
31 static struct addremove_args the_args;
32 
33 void
34 watch_modify_watchers (const char *file, struct addremove_args *what)
35 {
36     char *curattr = fileattr_get0 (file, "_watchers");
37     char *p;
38     char *pend;
39     char *nextp;
40     char *who;
41     int who_len;
42     char *mycurattr;
43     char *mynewattr;
44     size_t mynewattr_size;
45 
46     int add_edit_pending;
47     int add_unedit_pending;
48     int add_commit_pending;
49     int remove_edit_pending;
50     int remove_unedit_pending;
51     int remove_commit_pending;
52     int add_tedit_pending;
53     int add_tunedit_pending;
54     int add_tcommit_pending;
55 
56     TRACE( TRACE_FUNCTION, "modify_watchers ( %s )", file );
57 
58     who = getcaller ();
59     who_len = strlen (who);
60 
61     /* Look for current watcher types for this user.  */
62     mycurattr = NULL;
63     if (curattr != NULL)
64     {
65 	p = curattr;
66 	while (1) {
67 	    if (strncmp (who, p, who_len) == 0
68 		&& p[who_len] == '>')
69 	    {
70 		/* Found this user.  */
71 		mycurattr = p + who_len + 1;
72 	    }
73 	    p = strchr (p, ',');
74 	    if (p == NULL)
75 		break;
76 	    ++p;
77 	}
78     }
79     if (mycurattr != NULL)
80     {
81 	mycurattr = xstrdup (mycurattr);
82 	p = strchr (mycurattr, ',');
83 	if (p != NULL)
84 	    *p = '\0';
85     }
86 
87     /* Now copy mycurattr to mynewattr, making the requisite modifications.
88        Note that we add a dummy '+' to the start of mynewattr, to reduce
89        special cases (but then we strip it off when we are done).  */
90 
91     mynewattr_size = sizeof "+edit+unedit+commit+tedit+tunedit+tcommit";
92     if (mycurattr != NULL)
93 	mynewattr_size += strlen (mycurattr);
94     mynewattr = xmalloc (mynewattr_size);
95     mynewattr[0] = '\0';
96 
97     add_edit_pending = what->adding && what->edit;
98     add_unedit_pending = what->adding && what->unedit;
99     add_commit_pending = what->adding && what->commit;
100     remove_edit_pending = !what->adding && what->edit;
101     remove_unedit_pending = !what->adding && what->unedit;
102     remove_commit_pending = !what->adding && what->commit;
103     add_tedit_pending = what->add_tedit;
104     add_tunedit_pending = what->add_tunedit;
105     add_tcommit_pending = what->add_tcommit;
106 
107     /* Copy over existing watch types, except those to be removed.  */
108     p = mycurattr;
109     while (p != NULL)
110     {
111 	pend = strchr (p, '+');
112 	if (pend == NULL)
113 	{
114 	    pend = p + strlen (p);
115 	    nextp = NULL;
116 	}
117 	else
118 	    nextp = pend + 1;
119 
120 	/* Process this item.  */
121 	if (pend - p == 4 && strncmp ("edit", p, 4) == 0)
122 	{
123 	    if (!remove_edit_pending)
124 		strcat (mynewattr, "+edit");
125 	    add_edit_pending = 0;
126 	}
127 	else if (pend - p == 6 && strncmp ("unedit", p, 6) == 0)
128 	{
129 	    if (!remove_unedit_pending)
130 		strcat (mynewattr, "+unedit");
131 	    add_unedit_pending = 0;
132 	}
133 	else if (pend - p == 6 && strncmp ("commit", p, 6) == 0)
134 	{
135 	    if (!remove_commit_pending)
136 		strcat (mynewattr, "+commit");
137 	    add_commit_pending = 0;
138 	}
139 	else if (pend - p == 5 && strncmp ("tedit", p, 5) == 0)
140 	{
141 	    if (!what->remove_temp)
142 		strcat (mynewattr, "+tedit");
143 	    add_tedit_pending = 0;
144 	}
145 	else if (pend - p == 7 && strncmp ("tunedit", p, 7) == 0)
146 	{
147 	    if (!what->remove_temp)
148 		strcat (mynewattr, "+tunedit");
149 	    add_tunedit_pending = 0;
150 	}
151 	else if (pend - p == 7 && strncmp ("tcommit", p, 7) == 0)
152 	{
153 	    if (!what->remove_temp)
154 		strcat (mynewattr, "+tcommit");
155 	    add_tcommit_pending = 0;
156 	}
157 	else
158 	{
159 	    char *mp;
160 
161 	    /* Copy over any unrecognized watch types, for future
162 	       expansion.  */
163 	    mp = mynewattr + strlen (mynewattr);
164 	    *mp++ = '+';
165 	    strncpy (mp, p, pend - p);
166 	    *(mp + (pend - p)) = '\0';
167 	}
168 
169 	/* Set up for next item.  */
170 	p = nextp;
171     }
172 
173     /* Add in new watch types.  */
174     if (add_edit_pending)
175 	strcat (mynewattr, "+edit");
176     if (add_unedit_pending)
177 	strcat (mynewattr, "+unedit");
178     if (add_commit_pending)
179 	strcat (mynewattr, "+commit");
180     if (add_tedit_pending)
181 	strcat (mynewattr, "+tedit");
182     if (add_tunedit_pending)
183 	strcat (mynewattr, "+tunedit");
184     if (add_tcommit_pending)
185 	strcat (mynewattr, "+tcommit");
186 
187     {
188 	char *curattr_new;
189 
190 	curattr_new =
191 	  fileattr_modify (curattr,
192 			   who,
193 			   mynewattr[0] == '\0' ? NULL : mynewattr + 1,
194 			   '>',
195 			   ',');
196 	/* If the attribute is unchanged, don't rewrite the attribute file.  */
197 	if (!((curattr_new == NULL && curattr == NULL)
198 	      || (curattr_new != NULL
199 		  && curattr != NULL
200 		  && strcmp (curattr_new, curattr) == 0)))
201 	    fileattr_set (file,
202 			  "_watchers",
203 			  curattr_new);
204 	if (curattr_new != NULL)
205 	    free (curattr_new);
206     }
207 
208     if (curattr != NULL)
209 	free (curattr);
210     if (mycurattr != NULL)
211 	free (mycurattr);
212     if (mynewattr != NULL)
213 	free (mynewattr);
214 }
215 
216 static int addremove_fileproc (void *callerdat,
217 				      struct file_info *finfo);
218 
219 static int
220 addremove_fileproc (void *callerdat, struct file_info *finfo)
221 {
222     watch_modify_watchers (finfo->file, &the_args);
223     return 0;
224 }
225 
226 static int addremove_filesdoneproc (void * callerdat, int err, const char * repository,
227                                            const char *update_dir, List * entries)
228 {
229     int set_default = the_args.setting_default;
230     int dir_check = 0;
231 
232     while ( !set_default && dir_check < the_args.num_dirs )
233     {
234 	/* If we are recursing, then just see if the first part of update_dir
235 	   matches any of the specified directories. Otherwise, it must be an exact
236 	   match. */
237 	if ( the_args.local )
238 	    set_default = strcmp( update_dir, the_args.dirs[ dir_check ] )==0;
239 	else
240 	    set_default = strncmp( update_dir, the_args.dirs[ dir_check ], strlen( the_args.dirs[ dir_check ] ) ) == 0;
241 	dir_check++;
242     }
243 
244     if (set_default)
245 	watch_modify_watchers (NULL, &the_args);
246     return err;
247 }
248 
249 
250 static int
251 watch_addremove (int argc, char **argv)
252 {
253     int c;
254     int err;
255     int a_omitted;
256     int arg_index;
257     int max_dirs;
258 
259     a_omitted = 1;
260     the_args.commit = 0;
261     the_args.edit = 0;
262     the_args.unedit = 0;
263     the_args.num_dirs = 0;
264     the_args.dirs = NULL;
265     the_args.local = 0;
266 
267     optind = 0;
268     while ((c = getopt (argc, argv, "+lRa:")) != -1)
269     {
270 	switch (c)
271 	{
272 	    case 'l':
273 		the_args.local = 1;
274 		break;
275 	    case 'R':
276 		the_args.local = 0;
277 		break;
278 	    case 'a':
279 		a_omitted = 0;
280 		if (strcmp (optarg, "edit") == 0)
281 		    the_args.edit = 1;
282 		else if (strcmp (optarg, "unedit") == 0)
283 		    the_args.unedit = 1;
284 		else if (strcmp (optarg, "commit") == 0)
285 		    the_args.commit = 1;
286 		else if (strcmp (optarg, "all") == 0)
287 		{
288 		    the_args.edit = 1;
289 		    the_args.unedit = 1;
290 		    the_args.commit = 1;
291 		}
292 		else if (strcmp (optarg, "none") == 0)
293 		{
294 		    the_args.edit = 0;
295 		    the_args.unedit = 0;
296 		    the_args.commit = 0;
297 		}
298 		else
299 		    usage (watch_usage);
300 		break;
301 	    case '?':
302 	    default:
303 		usage (watch_usage);
304 		break;
305 	}
306     }
307     argc -= optind;
308     argv += optind;
309 
310     the_args.num_dirs = 0;
311     max_dirs = 4; /* Arbitrary choice. */
312     the_args.dirs = xmalloc( sizeof( const char * ) * max_dirs );
313 
314     TRACE (TRACE_FUNCTION, "watch_addremove (%d)", argc);
315     for ( arg_index=0; arg_index<argc; ++arg_index )
316     {
317 	TRACE( TRACE_FUNCTION, "\t%s", argv[ arg_index ]);
318 	if ( isdir( argv[ arg_index ] ) )
319 	{
320 	    if ( the_args.num_dirs >= max_dirs )
321 	    {
322 		max_dirs *= 2;
323 		the_args.dirs = (const char ** )xrealloc( (void *)the_args.dirs, max_dirs );
324 	    }
325 	    the_args.dirs[ the_args.num_dirs++ ] = argv[ arg_index ];
326 	}
327     }
328 
329     if (a_omitted)
330     {
331 	the_args.edit = 1;
332 	the_args.unedit = 1;
333 	the_args.commit = 1;
334     }
335 
336 #ifdef CLIENT_SUPPORT
337     if (current_parsed_root->isremote)
338     {
339 	start_server ();
340 	ign_setup ();
341 
342 	if (the_args.local)
343 	    send_arg ("-l");
344 	/* FIXME: copes poorly with "all" if server is extended to have
345 	   new watch types and client is still running an old version.  */
346 	if (the_args.edit)
347 	    option_with_arg ("-a", "edit");
348 	if (the_args.unedit)
349 	    option_with_arg ("-a", "unedit");
350 	if (the_args.commit)
351 	    option_with_arg ("-a", "commit");
352 	if (!the_args.edit && !the_args.unedit && !the_args.commit)
353 	    option_with_arg ("-a", "none");
354 	send_arg ("--");
355 	send_files (argc, argv, the_args.local, 0, SEND_NO_CONTENTS);
356 	send_file_names (argc, argv, SEND_EXPAND_WILD);
357 	send_to_server (the_args.adding ?
358                         "watch-add\012" : "watch-remove\012",
359                         0);
360 	return get_responses_and_close ();
361     }
362 #endif /* CLIENT_SUPPORT */
363 
364     the_args.setting_default = (argc <= 0);
365 
366     lock_tree_promotably (argc, argv, the_args.local, W_LOCAL, 0);
367 
368     err = start_recursion
369 	(addremove_fileproc, addremove_filesdoneproc, NULL, NULL, NULL,
370 	 argc, argv, the_args.local, W_LOCAL, 0, CVS_LOCK_WRITE,
371 	 NULL, 1, NULL);
372 
373     Lock_Cleanup ();
374     free( (void *)the_args.dirs );
375     the_args.dirs = NULL;
376 
377     return err;
378 }
379 
380 
381 
382 int
383 watch_add (int argc, char **argv)
384 {
385     the_args.adding = 1;
386     return watch_addremove (argc, argv);
387 }
388 
389 int
390 watch_remove (int argc, char **argv)
391 {
392     the_args.adding = 0;
393     return watch_addremove (argc, argv);
394 }
395 
396 int
397 watch (int argc, char **argv)
398 {
399     if (argc <= 1)
400 	usage (watch_usage);
401     if (strcmp (argv[1], "on") == 0)
402     {
403 	--argc;
404 	++argv;
405 	return watch_on (argc, argv);
406     }
407     else if (strcmp (argv[1], "off") == 0)
408     {
409 	--argc;
410 	++argv;
411 	return watch_off (argc, argv);
412     }
413     else if (strcmp (argv[1], "add") == 0)
414     {
415 	--argc;
416 	++argv;
417 	return watch_add (argc, argv);
418     }
419     else if (strcmp (argv[1], "remove") == 0)
420     {
421 	--argc;
422 	++argv;
423 	return watch_remove (argc, argv);
424     }
425     else
426 	usage (watch_usage);
427     return 0;
428 }
429 
430 static const char *const watchers_usage[] =
431 {
432     "Usage: %s %s [-lR] [<file>]...\n",
433     "-l\tProcess this directory only (not recursive).\n",
434     "-R\tProcess directories recursively (default).\n",
435     "(Specify the --help global option for a list of other help options.)\n",
436     NULL
437 };
438 
439 static int watchers_fileproc (void *callerdat,
440 				     struct file_info *finfo);
441 
442 static int
443 watchers_fileproc (void *callerdat, struct file_info *finfo)
444 {
445     char *them;
446     char *p;
447 
448     them = fileattr_get0 (finfo->file, "_watchers");
449     if (them == NULL)
450 	return 0;
451 
452     cvs_output (finfo->fullname, 0);
453 
454     p = them;
455     while (1)
456     {
457 	cvs_output ("\t", 1);
458 	while (*p != '>' && *p != '\0')
459 	    cvs_output (p++, 1);
460 	if (*p == '\0')
461 	{
462 	    /* Only happens if attribute is misformed.  */
463 	    cvs_output ("\n", 1);
464 	    break;
465 	}
466 	++p;
467 	cvs_output ("\t", 1);
468 	while (1)
469 	{
470 	    while (*p != '+' && *p != ',' && *p != '\0')
471 		cvs_output (p++, 1);
472 	    if (*p == '\0')
473 	    {
474 		cvs_output ("\n", 1);
475 		goto out;
476 	    }
477 	    if (*p == ',')
478 	    {
479 		++p;
480 		break;
481 	    }
482 	    ++p;
483 	    cvs_output ("\t", 1);
484 	}
485 	cvs_output ("\n", 1);
486     }
487   out:;
488     free (them);
489     return 0;
490 }
491 
492 int
493 watchers (int argc, char **argv)
494 {
495     int local = 0;
496     int c;
497 
498     if (argc == -1)
499 	usage (watchers_usage);
500 
501     optind = 0;
502     while ((c = getopt (argc, argv, "+lR")) != -1)
503     {
504 	switch (c)
505 	{
506 	    case 'l':
507 		local = 1;
508 		break;
509 	    case 'R':
510 		local = 0;
511 		break;
512 	    case '?':
513 	    default:
514 		usage (watchers_usage);
515 		break;
516 	}
517     }
518     argc -= optind;
519     argv += optind;
520 
521 #ifdef CLIENT_SUPPORT
522     if (current_parsed_root->isremote)
523     {
524 	start_server ();
525 	ign_setup ();
526 
527 	if (local)
528 	    send_arg ("-l");
529 	send_arg ("--");
530 	send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
531 	send_file_names (argc, argv, SEND_EXPAND_WILD);
532 	send_to_server ("watchers\012", 0);
533 	return get_responses_and_close ();
534     }
535 #endif /* CLIENT_SUPPORT */
536 
537     return start_recursion (watchers_fileproc, NULL, NULL,
538 			    NULL, NULL, argc, argv, local, W_LOCAL, 0,
539 			    CVS_LOCK_READ, NULL, 1, NULL);
540 }
541