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 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 222 addremove_fileproc (void *callerdat, struct file_info *finfo) 223 { 224 watch_modify_watchers (finfo->file, &the_args); 225 return 0; 226 } 227 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 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 385 watch_add (int argc, char **argv) 386 { 387 the_args.adding = 1; 388 return watch_addremove (argc, argv); 389 } 390 391 int 392 watch_remove (int argc, char **argv) 393 { 394 the_args.adding = 0; 395 return watch_addremove (argc, argv); 396 } 397 398 int 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 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 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