xref: /netbsd-src/external/gpl2/xcvs/dist/src/ls.c (revision 5a6c14c844c4c665da5632061aebde7bb2cb5766)
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