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