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
ls(int argc,char ** argv)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
ls_print(Node * p,void * closure)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
ls_print_dir(Node * p,void * closure)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
long_format_data_delproc(Node * n)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
ls_delproc(Node * p)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
ls_fileproc(void * callerdat,struct file_info * finfo)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
ls_direntproc(void * callerdat,const char * dir,const char * repos,const char * update_dir,List * entries)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
ls_dirleaveproc(void * callerdat,const char * dir,int err,const char * update_dir,List * entries)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
ls_proc(int argc,char ** argv,char * xwhere,char * mwhere,char * mfile,int shorten,int local,char * mname,char * msg)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