1 /* 2 * Copyright (c) 1992, Brian Berliner and Jeff Polk 3 * Copyright (c) 1989-1992, Brian Berliner 4 * Copyright (c) 2001, Tony Hoyle 5 * Copyright (c) 2004, Derek R. Price & Ximbiot <http://ximbiot.com> 6 * 7 * You may distribute under the terms of the GNU General Public License as 8 * specified in the README file that comes with the CVS source distribution. 9 * 10 * Query CVS/Entries from server 11 */ 12 #include <sys/cdefs.h> 13 __RCSID("$NetBSD: ls.c,v 1.4 2016/05/17 14:00:09 christos Exp $"); 14 15 #include "cvs.h" 16 #include <stdbool.h> 17 18 static int ls_proc (int argc, char **argv, char *xwhere, char *mwhere, 19 char *mfile, int shorten, int local, char *mname, 20 char *msg); 21 22 static const char *const ls_usage[] = 23 { 24 "Usage: %s %s [-e | -l] [-RP] [-r rev] [-D date] [path...]\n", 25 "\t-d\tShow dead revisions (with tag when specified).\n", 26 "\t-e\tDisplay in CVS/Entries format.\n", 27 "\t-l\tDisplay all details.\n", 28 "\t-P\tPrune empty directories.\n", 29 "\t-R\tList recursively.\n", 30 "\t-r rev\tShow files with revision or tag.\n", 31 "\t-D date\tShow files from date.\n", 32 "(Specify the --help global option for a list of other help options)\n", 33 NULL 34 }; 35 36 static bool entries_format; 37 static bool long_format; 38 static char *show_tag; 39 static char *show_date; 40 static bool set_tag; 41 static char *created_dir; 42 static bool tag_validated; 43 static bool recurse; 44 static bool ls_prune_dirs; 45 static char *regexp_match; 46 static bool is_rls; 47 static bool show_dead_revs; 48 49 50 51 int 52 ls (int argc, char **argv) 53 { 54 int c; 55 int err = 0; 56 57 is_rls = strcmp (cvs_cmd_name, "rls") == 0; 58 59 if (argc == -1) 60 usage (ls_usage); 61 62 entries_format = false; 63 long_format = false; 64 show_tag = NULL; 65 show_date = NULL; 66 tag_validated = false; 67 recurse = false; 68 ls_prune_dirs = false; 69 show_dead_revs = false; 70 71 getoptreset (); 72 73 while ((c = getopt (argc, argv, 74 #ifdef SERVER_SUPPORT 75 server_active ? "qdelr:D:PR" : 76 #endif /* SERVER_SUPPORT */ 77 "delr:D:RP" 78 )) != -1) 79 { 80 switch (c) 81 { 82 #ifdef SERVER_SUPPORT 83 case 'q': 84 if (server_active) 85 { 86 error (0, 0, 87 "`%s ls -q' is deprecated. Please use the global `-q' option instead.", 88 program_name); 89 quiet = true; 90 } 91 else 92 usage (ls_usage); 93 break; 94 #endif /* SERVER_SUPPORT */ 95 case 'd': 96 show_dead_revs = true; 97 break; 98 case 'e': 99 entries_format = true; 100 break; 101 case 'l': 102 long_format = true; 103 break; 104 case 'r': 105 parse_tagdate (&show_tag, &show_date, optarg); 106 break; 107 case 'D': 108 if (show_date) free (show_date); 109 show_date = Make_Date (optarg); 110 break; 111 case 'P': 112 ls_prune_dirs = true; 113 break; 114 case 'R': 115 recurse = true; 116 break; 117 case '?': 118 default: 119 usage (ls_usage); 120 break; 121 } 122 } 123 argc -= optind; 124 argv += optind; 125 126 if (entries_format && long_format) 127 { 128 error (0, 0, "`-e' & `-l' are mutually exclusive."); 129 usage (ls_usage); 130 } 131 132 wrap_setup (); 133 134 #ifdef CLIENT_SUPPORT 135 if (current_parsed_root->isremote) 136 { 137 /* We're the local client. Fire up the remote server. */ 138 start_server (); 139 140 ign_setup (); 141 142 if (is_rls ? !(supported_request ("rlist") || supported_request ("ls")) 143 : !supported_request ("list")) 144 error (1, 0, "server does not support %s", cvs_cmd_name); 145 146 if (quiet && !supported_request ("global-list-quiet")) 147 send_arg ("-q"); 148 if (entries_format) 149 send_arg ("-e"); 150 if (long_format) 151 send_arg ("-l"); 152 if (ls_prune_dirs) 153 send_arg ("-P"); 154 if (recurse) 155 send_arg ("-R"); 156 if (show_dead_revs) 157 send_arg ("-d"); 158 if (show_tag) 159 option_with_arg ("-r", show_tag); 160 if (show_date) 161 client_senddate (show_date); 162 163 send_arg ("--"); 164 165 if (is_rls) 166 { 167 int i; 168 for (i = 0; i < argc; i++) 169 send_arg (argv[i]); 170 if (supported_request ("rlist")) 171 send_to_server ("rlist\012", 0); 172 else 173 /* For backwards compatibility with CVSNT... */ 174 send_to_server ("ls\012", 0); 175 } 176 else 177 { 178 /* Setting this means, I think, that any empty directories created 179 * by the server will be deleted by the client. Since any dirs 180 * created at all by ls should remain empty, this should cause any 181 * dirs created by the server for the ls command to be deleted. 182 */ 183 client_prune_dirs = 1; 184 185 /* I explicitly decide not to send contents here. We *could* let 186 * the user pull status information with this command, but why 187 * don't they just use update or status? 188 */ 189 send_files (argc, argv, !recurse, 0, SEND_NO_CONTENTS); 190 send_file_names (argc, argv, SEND_EXPAND_WILD); 191 send_to_server ("list\012", 0); 192 } 193 194 err = get_responses_and_close (); 195 return err; 196 } 197 #endif 198 199 if (is_rls) 200 { 201 DBM *db; 202 int i; 203 db = open_module (); 204 if (argc) 205 { 206 for (i = 0; i < argc; i++) 207 { 208 char *mod = xstrdup (argv[i]); 209 char *p; 210 211 for (p=strchr (mod,'\\'); p; p=strchr (p,'\\')) 212 *p='/'; 213 214 p = strrchr (mod,'/'); 215 if (p && (strchr (p,'?') || strchr (p,'*'))) 216 { 217 *p='\0'; 218 regexp_match = p+1; 219 } 220 else 221 regexp_match = NULL; 222 223 /* Frontends like to do 'ls -q /', so we support it explicitly. 224 */ 225 if (!strcmp (mod,"/")) 226 { 227 *mod='\0'; 228 } 229 230 err += do_module (db, mod, MISC, "Listing", 231 ls_proc, NULL, 0, 0, 0, 0, NULL); 232 233 free (mod); 234 } 235 } 236 else 237 { 238 /* should be ".", but do_recursion() 239 fails this: assert ( strstr ( repository, "/./" ) == NULL ); */ 240 char *topmod = xstrdup (""); 241 err += do_module (db, topmod, MISC, "Listing", 242 ls_proc, NULL, 0, 0, 0, 0, NULL); 243 free (topmod); 244 } 245 close_module (db); 246 } 247 else 248 ls_proc (argc + 1, argv - 1, NULL, NULL, NULL, 0, 0, NULL, NULL); 249 250 return err; 251 } 252 253 254 255 struct long_format_data 256 { 257 char *header; 258 char *time; 259 char *footer; 260 }; 261 262 static int 263 ls_print (Node *p, void *closure) 264 { 265 if (long_format) 266 { 267 struct long_format_data *data = p->data; 268 cvs_output_tagged ("text", data->header); 269 if (data->time) 270 cvs_output_tagged ("date", data->time); 271 if (data->footer) 272 cvs_output_tagged ("text", data->footer); 273 cvs_output_tagged ("newline", NULL); 274 } 275 else 276 cvs_output (p->data, 0); 277 return 0; 278 } 279 280 281 282 static int 283 ls_print_dir (Node *p, void *closure) 284 { 285 static bool printed = false; 286 287 if (recurse && !(ls_prune_dirs && list_isempty (p->data))) 288 { 289 /* Keep track of whether we've printed. If we have, then put a blank 290 * line before directory headers, to separate the header from the 291 * listing of the previous directory. 292 */ 293 if (printed) 294 cvs_output ("\n", 1); 295 else 296 printed = true; 297 298 if (!strcmp (p->key, "")) 299 cvs_output (".", 1); 300 else 301 cvs_output (p->key, 0); 302 cvs_output (":\n", 2); 303 } 304 walklist (p->data, ls_print, NULL); 305 return 0; 306 } 307 308 309 310 /* 311 * Delproc for a node containing a struct long_format_data as data. 312 */ 313 static void 314 long_format_data_delproc (Node *n) 315 { 316 struct long_format_data *data = (struct long_format_data *)n->data; 317 if (data->header) free (data->header); 318 if (data->time) free (data->time); 319 if (data->footer) free (data->footer); 320 free (data); 321 } 322 323 324 325 /* A delproc for a List Node containing a List *. */ 326 static void 327 ls_delproc (Node *p) 328 { 329 dellist ((List **)&p->data); 330 } 331 332 333 334 /* 335 * Add a file to our list of data to print for a directory. 336 */ 337 /* ARGSUSED */ 338 static int 339 ls_fileproc (void *callerdat, struct file_info *finfo) 340 { 341 Vers_TS *vers; 342 char *regex_err; 343 Node *p, *n; 344 bool isdead; 345 const char *filename; 346 347 if (regexp_match) 348 { 349 #ifdef FILENAMES_CASE_INSENSITIVE 350 re_set_syntax (REG_ICASE|RE_SYNTAX_EGREP); 351 #else 352 re_set_syntax (RE_SYNTAX_EGREP); 353 #endif 354 if ((regex_err = re_comp (regexp_match)) != NULL) 355 { 356 error (1, 0, "bad regular expression passed to 'ls': %s", 357 regex_err); 358 } 359 if (re_exec (finfo->file) == 0) 360 return 0; /* no match */ 361 } 362 363 vers = Version_TS (finfo, NULL, show_tag, show_date, 1, 0); 364 /* Skip dead revisions unless specifically requested to do otherwise. 365 * We also bother to check for long_format so we can print the state. 366 */ 367 if (vers->vn_rcs && (!show_dead_revs || long_format)) 368 isdead = RCS_isdead (finfo->rcs, vers->vn_rcs); 369 else 370 isdead = false; 371 if (!vers->vn_rcs || (!show_dead_revs && isdead)) 372 { 373 freevers_ts (&vers); 374 return 0; 375 } 376 377 p = findnode (callerdat, finfo->update_dir); 378 if (!p) 379 { 380 /* This only occurs when a complete path to a file is specified on the 381 * command line. Put the file in the root list. 382 */ 383 filename = finfo->fullname; 384 385 /* Add update_dir node. */ 386 p = findnode (callerdat, "."); 387 if (!p) 388 { 389 p = getnode (); 390 p->key = xstrdup ("."); 391 p->data = getlist (); 392 p->delproc = ls_delproc; 393 addnode (callerdat, p); 394 } 395 } 396 else 397 filename = finfo->file; 398 399 n = getnode(); 400 if (entries_format) 401 { 402 char *outdate = entries_time (RCS_getrevtime (finfo->rcs, vers->vn_rcs, 403 0, 0)); 404 n->data = Xasprintf ("/%s/%s/%s/%s/%s%s\n", 405 filename, vers->vn_rcs, 406 outdate, vers->options, 407 show_tag ? "T" : "", show_tag ? show_tag : ""); 408 free (outdate); 409 } 410 else if (long_format) 411 { 412 struct long_format_data *out = 413 xmalloc (sizeof (struct long_format_data)); 414 out->header = Xasprintf ("%-5.5s", 415 vers->options[0] != '\0' ? vers->options 416 : "----"); 417 /* FIXME: Do we want to mimc the real `ls' command's date format? */ 418 out->time = gmformat_time_t (RCS_getrevtime (finfo->rcs, vers->vn_rcs, 419 0, 0)); 420 out->footer = Xasprintf (" %-9.9s%s %s%s", vers->vn_rcs, 421 strlen (vers->vn_rcs) > 9 ? "+" : " ", 422 show_dead_revs ? (isdead ? "dead " : " ") 423 : "", 424 filename); 425 n->data = out; 426 n->delproc = long_format_data_delproc; 427 } 428 else 429 n->data = Xasprintf ("%s\n", filename); 430 431 addnode (p->data, n); 432 433 freevers_ts (&vers); 434 return 0; 435 } 436 437 438 439 /* 440 * Add this directory to the list of data to be printed for a directory and 441 * decide whether to tell the recursion processor whether to continue 442 * recursing or not. 443 */ 444 static Dtype 445 ls_direntproc (void *callerdat, const char *dir, const char *repos, 446 const char *update_dir, List *entries) 447 { 448 Dtype retval; 449 Node *p; 450 451 /* Due to the way we called start_recursion() from ls_proc() with a single 452 * argument at a time, we can assume that if we don't yet have a parent 453 * directory in DIRS then this directory should be processed. 454 */ 455 456 if (strcmp (dir, ".")) 457 { 458 /* Search for our parent directory. */ 459 char *parent; 460 parent = xmalloc (strlen (update_dir) - strlen (dir) + 1); 461 strncpy (parent, update_dir, strlen (update_dir) - strlen (dir)); 462 parent[strlen (update_dir) - strlen (dir)] = '\0'; 463 strip_trailing_slashes (parent); 464 p = findnode (callerdat, parent); 465 } 466 else 467 p = NULL; 468 469 if (p) 470 { 471 /* Push this dir onto our parent directory's listing. */ 472 Node *n = getnode(); 473 474 if (entries_format) 475 n->data = Xasprintf ("D/%s////\n", dir); 476 else if (long_format) 477 { 478 struct long_format_data *out = 479 xmalloc (sizeof (struct long_format_data)); 480 out->header = xstrdup ("d--- "); 481 out->time = gmformat_time_t (unix_time_stamp (repos)); 482 out->footer = Xasprintf ("%12s%s%s", "", 483 show_dead_revs ? " " : "", dir); 484 n->data = out; 485 n->delproc = long_format_data_delproc; 486 } 487 else 488 n->data = Xasprintf ("%s\n", dir); 489 490 addnode (p->data, n); 491 } 492 493 if (!p || recurse) 494 { 495 /* Create a new list for this directory. */ 496 p = getnode (); 497 p->key = xstrdup (strcmp (update_dir, ".") ? update_dir : ""); 498 p->data = getlist (); 499 p->delproc = ls_delproc; 500 addnode (callerdat, p); 501 502 /* Create a local directory and mark it as needing deletion. This is 503 * the behavior the recursion processor relies upon, a la update & 504 * checkout. 505 */ 506 if (!isdir (dir)) 507 { 508 int nonbranch; 509 if (show_tag == NULL && show_date == NULL) 510 { 511 ParseTag (&show_tag, &show_date, &nonbranch); 512 set_tag = true; 513 } 514 515 if (!created_dir) 516 created_dir = xstrdup (update_dir); 517 518 make_directory (dir); 519 Create_Admin (dir, update_dir, repos, show_tag, show_date, 520 nonbranch, 0, 0); 521 Subdir_Register (entries, NULL, dir); 522 } 523 524 /* Tell do_recursion to keep going. */ 525 retval = R_PROCESS; 526 } 527 else 528 retval = R_SKIP_ALL; 529 530 return retval; 531 } 532 533 534 535 /* Clean up tags, dates, and dirs if we created this directory. 536 */ 537 static int 538 ls_dirleaveproc (void *callerdat, const char *dir, int err, 539 const char *update_dir, List *entries) 540 { 541 if (created_dir && !strcmp (created_dir, update_dir)) 542 { 543 if (set_tag) 544 { 545 if (show_tag) free (show_tag); 546 if (show_date) free (show_date); 547 show_tag = show_date = NULL; 548 set_tag = false; 549 } 550 551 if (CVS_CHDIR ("..") == -1) 552 error (0, errno, "Failed to chdir .."); 553 if (unlink_file_dir (dir)) 554 error (0, errno, "Failed to remove directory `%s'", 555 created_dir); 556 Subdir_Deregister (entries, NULL, dir); 557 558 free (created_dir); 559 created_dir = NULL; 560 } 561 return err; 562 } 563 564 565 566 static int 567 ls_proc (int argc, char **argv, char *xwhere, char *mwhere, char *mfile, 568 int shorten, int local, char *mname, char *msg) 569 { 570 char *repository; 571 int err = 0; 572 int which; 573 char *where; 574 int i; 575 576 if (is_rls) 577 { 578 char *myargv[2]; 579 580 if (!quiet) 581 error (0, 0, "Listing module: `%s'", 582 strcmp (mname, "") ? mname : "."); 583 584 repository = xmalloc (strlen (current_parsed_root->directory) 585 + strlen (argv[0]) 586 + (mfile == NULL ? 0 : strlen (mfile) + 1) 587 + 2); 588 (void)sprintf (repository, "%s/%s", current_parsed_root->directory, 589 argv[0]); 590 where = xmalloc (strlen (argv[0]) 591 + (mfile == NULL ? 0 : strlen (mfile) + 1) 592 + 1); 593 (void)strcpy (where, argv[0]); 594 595 /* If mfile isn't null, we need to set up to do only part of the 596 * module. 597 */ 598 if (mfile != NULL) 599 { 600 char *cp; 601 char *path; 602 603 /* If the portion of the module is a path, put the dir part on 604 * repos. 605 */ 606 if ((cp = strrchr (mfile, '/')) != NULL) 607 { 608 *cp = '\0'; 609 (void)strcat (repository, "/"); 610 (void)strcat (repository, mfile); 611 (void)strcat (where, "/"); 612 (void)strcat (where, mfile); 613 mfile = cp + 1; 614 } 615 616 /* take care of the rest */ 617 path = Xasprintf ("%s/%s", repository, mfile); 618 if (isdir (path)) 619 { 620 /* directory means repository gets the dir tacked on */ 621 (void)strcpy (repository, path); 622 (void)strcat (where, "/"); 623 (void)strcat (where, mfile); 624 } 625 else 626 { 627 myargv[1] = mfile; 628 argc = 2; 629 argv = myargv; 630 } 631 free (path); 632 } 633 634 /* cd to the starting repository */ 635 if (CVS_CHDIR (repository) < 0) 636 { 637 error (0, errno, "cannot chdir to %s", repository); 638 free (repository); 639 free (where); 640 return 1; 641 } 642 643 which = W_REPOS; 644 } 645 else /* !is_rls */ 646 { 647 repository = NULL; 648 where = NULL; 649 which = W_LOCAL | W_REPOS; 650 } 651 652 if (show_tag || show_date || show_dead_revs) 653 which |= W_ATTIC; 654 655 if (show_tag != NULL && !tag_validated) 656 { 657 tag_check_valid (show_tag, argc - 1, argv + 1, local, 0, repository, 658 false); 659 tag_validated = true; 660 } 661 662 /* Loop on argc so that we are guaranteed that any directory passed to 663 * ls_direntproc should be processed if its parent is not yet in DIRS. 664 */ 665 if (argc == 1) 666 { 667 List *dirs = getlist (); 668 err = start_recursion (ls_fileproc, NULL, ls_direntproc, 669 ls_dirleaveproc, dirs, 0, NULL, local, which, 0, 670 CVS_LOCK_READ, where, 1, repository); 671 walklist (dirs, ls_print_dir, NULL); 672 dellist (&dirs); 673 } 674 else 675 { 676 for (i = 1; i < argc; i++) 677 { 678 List *dirs = getlist (); 679 err = start_recursion (ls_fileproc, NULL, ls_direntproc, 680 NULL, dirs, 1, argv + i, local, which, 0, 681 CVS_LOCK_READ, where, 1, repository); 682 walklist (dirs, ls_print_dir, NULL); 683 dellist (&dirs); 684 } 685 } 686 687 if (!(which & W_LOCAL)) free (repository); 688 if (where) free (where); 689 690 return err; 691 } 692