ls.c revision 90926
1/*
2 * Copyright (c) 1999 - 2001 Kungliga Tekniska H�gskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of KTH nor the names of its contributors may be
18 *    used to endorse or promote products derived from this software without
19 *    specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY
22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
32
33#ifndef TEST
34#include "ftpd_locl.h"
35
36RCSID("$Id: ls.c,v 1.23 2001/09/14 11:32:52 joda Exp $");
37
38#else
39#include <stdio.h>
40#include <string.h>
41#include <stdlib.h>
42#include <time.h>
43#include <dirent.h>
44#include <sys/stat.h>
45#include <unistd.h>
46#include <pwd.h>
47#include <grp.h>
48#include <errno.h>
49
50#define sec_fprintf2 fprintf
51#define sec_fflush fflush
52static void list_files(FILE *out, const char **files, int n_files, int flags);
53static int parse_flags(const char *options);
54
55int
56main(int argc, char **argv)
57{
58    int i = 1;
59    int flags;
60    if(argc > 1 && argv[1][0] == '-') {
61	flags = parse_flags(argv[1]);
62	i = 2;
63    } else
64	flags = parse_flags(NULL);
65
66    list_files(stdout, (const char **)argv + i, argc - i, flags);
67    return 0;
68}
69#endif
70
71struct fileinfo {
72    struct stat st;
73    int inode;
74    int bsize;
75    char mode[11];
76    int n_link;
77    char *user;
78    char *group;
79    char *size;
80    char *major;
81    char *minor;
82    char *date;
83    char *filename;
84    char *link;
85};
86
87static void
88free_fileinfo(struct fileinfo *f)
89{
90    free(f->user);
91    free(f->group);
92    free(f->size);
93    free(f->major);
94    free(f->minor);
95    free(f->date);
96    free(f->filename);
97    free(f->link);
98}
99
100#define LS_DIRS		(1 << 0)
101#define LS_IGNORE_DOT	(1 << 1)
102#define LS_SORT_MODE	(3 << 2)
103#define SORT_MODE(f) ((f) & LS_SORT_MODE)
104#define LS_SORT_NAME	(1 << 2)
105#define LS_SORT_MTIME	(2 << 2)
106#define LS_SORT_SIZE	(3 << 2)
107#define LS_SORT_REVERSE	(1 << 4)
108
109#define LS_SIZE		(1 << 5)
110#define LS_INODE	(1 << 6)
111#define LS_TYPE		(1 << 7)
112#define LS_DISP_MODE	(3 << 8)
113#define DISP_MODE(f) ((f) & LS_DISP_MODE)
114#define LS_DISP_LONG	(1 << 8)
115#define LS_DISP_COLUMN	(2 << 8)
116#define LS_DISP_CROSS	(3 << 8)
117#define LS_SHOW_ALL	(1 << 10)
118#define LS_RECURSIVE	(1 << 11)
119#define LS_EXTRA_BLANK	(1 << 12)
120#define LS_SHOW_DIRNAME	(1 << 13)
121#define LS_DIR_FLAG	(1 << 14)	/* these files come via list_dir */
122
123#ifndef S_ISTXT
124#define S_ISTXT S_ISVTX
125#endif
126
127#ifndef S_ISSOCK
128#define S_ISSOCK(mode)  (((mode) & _S_IFMT) == S_IFSOCK)
129#endif
130
131#ifndef S_ISLNK
132#define S_ISLNK(mode)   (((mode) & _S_IFMT) == S_IFLNK)
133#endif
134
135static size_t
136block_convert(size_t blocks)
137{
138#ifdef S_BLKSIZE
139    return blocks * S_BLKSIZE / 1024;
140#else
141    return blocks * 512 / 1024;
142#endif
143}
144
145static void
146make_fileinfo(FILE *out, const char *filename, struct fileinfo *file, int flags)
147{
148    char buf[128];
149    int file_type = 0;
150    struct stat *st = &file->st;
151
152    file->inode = st->st_ino;
153    file->bsize = block_convert(st->st_blocks);
154
155    if(S_ISDIR(st->st_mode)) {
156	file->mode[0] = 'd';
157	file_type = '/';
158    }
159    else if(S_ISCHR(st->st_mode))
160	file->mode[0] = 'c';
161    else if(S_ISBLK(st->st_mode))
162	file->mode[0] = 'b';
163    else if(S_ISREG(st->st_mode)) {
164	file->mode[0] = '-';
165	if(st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
166	    file_type = '*';
167    }
168    else if(S_ISFIFO(st->st_mode)) {
169	file->mode[0] = 'p';
170	file_type = '|';
171    }
172    else if(S_ISLNK(st->st_mode)) {
173	file->mode[0] = 'l';
174	file_type = '@';
175    }
176    else if(S_ISSOCK(st->st_mode)) {
177	file->mode[0] = 's';
178	file_type = '=';
179    }
180#ifdef S_ISWHT
181    else if(S_ISWHT(st->st_mode)) {
182	file->mode[0] = 'w';
183	file_type = '%';
184    }
185#endif
186    else
187	file->mode[0] = '?';
188    {
189	char *x[] = { "---", "--x", "-w-", "-wx",
190		      "r--", "r-x", "rw-", "rwx" };
191	strcpy(file->mode + 1, x[(st->st_mode & S_IRWXU) >> 6]);
192	strcpy(file->mode + 4, x[(st->st_mode & S_IRWXG) >> 3]);
193	strcpy(file->mode + 7, x[(st->st_mode & S_IRWXO) >> 0]);
194	if((st->st_mode & S_ISUID)) {
195	    if((st->st_mode & S_IXUSR))
196		file->mode[3] = 's';
197	    else
198		file->mode[3] = 'S';
199	}
200	if((st->st_mode & S_ISGID)) {
201	    if((st->st_mode & S_IXGRP))
202		file->mode[6] = 's';
203	    else
204		file->mode[6] = 'S';
205	}
206	if((st->st_mode & S_ISTXT)) {
207	    if((st->st_mode & S_IXOTH))
208		file->mode[9] = 't';
209	    else
210		file->mode[9] = 'T';
211	}
212    }
213    file->n_link = st->st_nlink;
214    {
215	struct passwd *pwd;
216	pwd = getpwuid(st->st_uid);
217	if(pwd == NULL)
218	    asprintf(&file->user, "%u", (unsigned)st->st_uid);
219	else
220	    file->user = strdup(pwd->pw_name);
221    }
222    {
223	struct group *grp;
224	grp = getgrgid(st->st_gid);
225	if(grp == NULL)
226	    asprintf(&file->group, "%u", (unsigned)st->st_gid);
227	else
228	    file->group = strdup(grp->gr_name);
229    }
230
231    if(S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
232#if defined(major) && defined(minor)
233	asprintf(&file->major, "%u", (unsigned)major(st->st_rdev));
234	asprintf(&file->minor, "%u", (unsigned)minor(st->st_rdev));
235#else
236	/* Don't want to use the DDI/DKI crap. */
237	asprintf(&file->major, "%u", (unsigned)st->st_rdev);
238	asprintf(&file->minor, "%u", 0);
239#endif
240    } else
241	asprintf(&file->size, "%lu", (unsigned long)st->st_size);
242
243    {
244	time_t t = time(NULL);
245	time_t mtime = st->st_mtime;
246	struct tm *tm = localtime(&mtime);
247	if((t - mtime > 6*30*24*60*60) ||
248	   (mtime - t > 6*30*24*60*60))
249	    strftime(buf, sizeof(buf), "%b %e  %Y", tm);
250	else
251	    strftime(buf, sizeof(buf), "%b %e %H:%M", tm);
252	file->date = strdup(buf);
253    }
254    {
255	const char *p = strrchr(filename, '/');
256	if(p)
257	    p++;
258	else
259	    p = filename;
260	if((flags & LS_TYPE) && file_type != 0)
261	    asprintf(&file->filename, "%s%c", p, file_type);
262	else
263	    file->filename = strdup(p);
264    }
265    if(S_ISLNK(st->st_mode)) {
266	int n;
267	n = readlink((char *)filename, buf, sizeof(buf));
268	if(n >= 0) {
269	    buf[n] = '\0';
270	    file->link = strdup(buf);
271	} else
272	    sec_fprintf2(out, "readlink(%s): %s", filename, strerror(errno));
273    }
274}
275
276static void
277print_file(FILE *out,
278	   int flags,
279	   struct fileinfo *f,
280	   int max_inode,
281	   int max_bsize,
282	   int max_n_link,
283	   int max_user,
284	   int max_group,
285	   int max_size,
286	   int max_major,
287	   int max_minor,
288	   int max_date)
289{
290    if(f->filename == NULL)
291	return;
292
293    if(flags & LS_INODE) {
294	sec_fprintf2(out, "%*d", max_inode, f->inode);
295	sec_fprintf2(out, "  ");
296    }
297    if(flags & LS_SIZE) {
298	sec_fprintf2(out, "%*d", max_bsize, f->bsize);
299	sec_fprintf2(out, "  ");
300    }
301    sec_fprintf2(out, "%s", f->mode);
302    sec_fprintf2(out, "  ");
303    sec_fprintf2(out, "%*d", max_n_link, f->n_link);
304    sec_fprintf2(out, " ");
305    sec_fprintf2(out, "%-*s", max_user, f->user);
306    sec_fprintf2(out, "  ");
307    sec_fprintf2(out, "%-*s", max_group, f->group);
308    sec_fprintf2(out, "  ");
309    if(f->major != NULL && f->minor != NULL)
310	sec_fprintf2(out, "%*s, %*s", max_major, f->major, max_minor, f->minor);
311    else
312	sec_fprintf2(out, "%*s", max_size, f->size);
313    sec_fprintf2(out, " ");
314    sec_fprintf2(out, "%*s", max_date, f->date);
315    sec_fprintf2(out, " ");
316    sec_fprintf2(out, "%s", f->filename);
317    if(f->link)
318	sec_fprintf2(out, " -> %s", f->link);
319    sec_fprintf2(out, "\r\n");
320}
321
322static int
323compare_filename(struct fileinfo *a, struct fileinfo *b)
324{
325    if(a->filename == NULL)
326	return 1;
327    if(b->filename == NULL)
328	return -1;
329    return strcmp(a->filename, b->filename);
330}
331
332static int
333compare_mtime(struct fileinfo *a, struct fileinfo *b)
334{
335    if(a->filename == NULL)
336	return 1;
337    if(b->filename == NULL)
338	return -1;
339    return b->st.st_mtime - a->st.st_mtime;
340}
341
342static int
343compare_size(struct fileinfo *a, struct fileinfo *b)
344{
345    if(a->filename == NULL)
346	return 1;
347    if(b->filename == NULL)
348	return -1;
349    return b->st.st_size - a->st.st_size;
350}
351
352static void
353list_dir(FILE *out, const char *directory, int flags);
354
355static int
356log10(int num)
357{
358    int i = 1;
359    while(num > 10) {
360	i++;
361	num /= 10;
362    }
363    return i;
364}
365
366/*
367 * Operate as lstat but fake up entries for AFS mount points so we don't
368 * have to fetch them.
369 */
370
371#ifdef KRB4
372static int do_the_afs_dance = 1;
373#endif
374
375static int
376lstat_file (const char *file, struct stat *sb)
377{
378#ifdef KRB4
379    if (do_the_afs_dance &&
380	k_hasafs()
381	&& strcmp(file, ".")
382	&& strcmp(file, "..")
383	&& strcmp(file, "/"))
384    {
385	struct ViceIoctl    a_params;
386	char               *dir, *last;
387	char               *path_bkp;
388	static ino_t	   ino_counter = 0, ino_last = 0;
389	int		   ret;
390	const int	   maxsize = 2048;
391
392	path_bkp = strdup (file);
393	if (path_bkp == NULL)
394	    return -1;
395
396	a_params.out = malloc (maxsize);
397	if (a_params.out == NULL) {
398	    free (path_bkp);
399	    return -1;
400	}
401
402	/* If path contains more than the filename alone - split it */
403
404	last = strrchr (path_bkp, '/');
405	if (last != NULL) {
406	    if(last[1] == '\0')
407		/* if path ended in /, replace with `.' */
408		a_params.in = ".";
409	    else
410		a_params.in = last + 1;
411	    while(last > path_bkp && *--last == '/');
412	    if(*last != '/' || last != path_bkp) {
413		*++last = '\0';
414		dir = path_bkp;
415	    } else
416		/* we got to the start, so this must be the root dir */
417		dir = "/";
418	} else {
419	    /* file is relative to cdir */
420	    dir = ".";
421	    a_params.in = path_bkp;
422	}
423
424	a_params.in_size  = strlen (a_params.in) + 1;
425	a_params.out_size = maxsize;
426
427	ret = k_pioctl (dir, VIOC_AFS_STAT_MT_PT, &a_params, 0);
428	free (a_params.out);
429	if (ret < 0) {
430	    free (path_bkp);
431
432	    if (errno != EINVAL)
433		return ret;
434	    else
435		/* if we get EINVAL this is probably not a mountpoint */
436		return lstat (file, sb);
437	}
438
439	/*
440	 * wow this was a mountpoint, lets cook the struct stat
441	 * use . as a prototype
442	 */
443
444	ret = lstat (dir, sb);
445	free (path_bkp);
446	if (ret < 0)
447	    return ret;
448
449	if (ino_last == sb->st_ino)
450	    ino_counter++;
451	else {
452	    ino_last    = sb->st_ino;
453	    ino_counter = 0;
454	}
455	sb->st_ino += ino_counter;
456	sb->st_nlink = 3;
457
458	return 0;
459    }
460#endif /* KRB4 */
461    return lstat (file, sb);
462}
463
464#define IS_DOT_DOTDOT(X) ((X)[0] == '.' && ((X)[1] == '\0' || \
465				((X)[1] == '.' && (X)[2] == '\0')))
466
467static void
468list_files(FILE *out, const char **files, int n_files, int flags)
469{
470    struct fileinfo *fi;
471    int i;
472    int *dirs = NULL;
473    size_t total_blocks = 0;
474    int n_print = 0;
475
476    if(n_files > 1)
477	flags |= LS_SHOW_DIRNAME;
478
479    fi = calloc(n_files, sizeof(*fi));
480    if (fi == NULL) {
481	sec_fprintf2(out, "ouf of memory\r\n");
482	return;
483    }
484    for(i = 0; i < n_files; i++) {
485	if(lstat_file(files[i], &fi[i].st) < 0) {
486	    sec_fprintf2(out, "%s: %s\r\n", files[i], strerror(errno));
487	    fi[i].filename = NULL;
488	} else {
489	    int include_in_list = 1;
490	    total_blocks += block_convert(fi[i].st.st_blocks);
491	    if(S_ISDIR(fi[i].st.st_mode)) {
492		if(dirs == NULL)
493		    dirs = calloc(n_files, sizeof(*dirs));
494		if(dirs == NULL) {
495		    sec_fprintf2(out, "%s: %s\r\n",
496				 files[i], strerror(errno));
497		    goto out;
498		}
499		dirs[i] = 1;
500		if((flags & LS_DIRS) == 0)
501		    include_in_list = 0;
502	    }
503	    if(include_in_list) {
504		make_fileinfo(out, files[i], &fi[i], flags);
505		n_print++;
506	    }
507	}
508    }
509    switch(SORT_MODE(flags)) {
510    case LS_SORT_NAME:
511	qsort(fi, n_files, sizeof(*fi),
512	      (int (*)(const void*, const void*))compare_filename);
513	break;
514    case LS_SORT_MTIME:
515	qsort(fi, n_files, sizeof(*fi),
516	      (int (*)(const void*, const void*))compare_mtime);
517	break;
518    case LS_SORT_SIZE:
519	qsort(fi, n_files, sizeof(*fi),
520	      (int (*)(const void*, const void*))compare_size);
521	break;
522    }
523    if(DISP_MODE(flags) == LS_DISP_LONG) {
524	int max_inode = 0;
525	int max_bsize = 0;
526	int max_n_link = 0;
527	int max_user = 0;
528	int max_group = 0;
529	int max_size = 0;
530	int max_major = 0;
531	int max_minor = 0;
532	int max_date = 0;
533	for(i = 0; i < n_files; i++) {
534	    if(fi[i].filename == NULL)
535		continue;
536	    if(fi[i].inode > max_inode)
537		max_inode = fi[i].inode;
538	    if(fi[i].bsize > max_bsize)
539		max_bsize = fi[i].bsize;
540	    if(fi[i].n_link > max_n_link)
541		max_n_link = fi[i].n_link;
542	    if(strlen(fi[i].user) > max_user)
543		max_user = strlen(fi[i].user);
544	    if(strlen(fi[i].group) > max_group)
545		max_group = strlen(fi[i].group);
546	    if(fi[i].major != NULL && strlen(fi[i].major) > max_major)
547		max_major = strlen(fi[i].major);
548	    if(fi[i].minor != NULL && strlen(fi[i].minor) > max_minor)
549		max_minor = strlen(fi[i].minor);
550	    if(fi[i].size != NULL && strlen(fi[i].size) > max_size)
551		max_size = strlen(fi[i].size);
552	    if(strlen(fi[i].date) > max_date)
553		max_date = strlen(fi[i].date);
554	}
555	if(max_size < max_major + max_minor + 2)
556	    max_size = max_major + max_minor + 2;
557	else if(max_size - max_minor - 2 > max_major)
558	    max_major = max_size - max_minor - 2;
559	max_inode = log10(max_inode);
560	max_bsize = log10(max_bsize);
561	max_n_link = log10(max_n_link);
562
563	if(n_print > 0)
564	    sec_fprintf2(out, "total %lu\r\n", (unsigned long)total_blocks);
565	if(flags & LS_SORT_REVERSE)
566	    for(i = n_files - 1; i >= 0; i--)
567		print_file(out,
568			   flags,
569			   &fi[i],
570			   max_inode,
571			   max_bsize,
572			   max_n_link,
573			   max_user,
574			   max_group,
575			   max_size,
576			   max_major,
577			   max_minor,
578			   max_date);
579	else
580	    for(i = 0; i < n_files; i++)
581		print_file(out,
582			   flags,
583			   &fi[i],
584			   max_inode,
585			   max_bsize,
586			   max_n_link,
587			   max_user,
588			   max_group,
589			   max_size,
590			   max_major,
591			   max_minor,
592			   max_date);
593    } else if(DISP_MODE(flags) == LS_DISP_COLUMN ||
594	      DISP_MODE(flags) == LS_DISP_CROSS) {
595	int max_len = 0;
596	int size_len = 0;
597	int num_files = n_files;
598	int columns;
599	int j;
600	for(i = 0; i < n_files; i++) {
601	    if(fi[i].filename == NULL) {
602		num_files--;
603		continue;
604	    }
605	    if(strlen(fi[i].filename) > max_len)
606		max_len = strlen(fi[i].filename);
607	    if(log10(fi[i].bsize) > size_len)
608		size_len = log10(fi[i].bsize);
609	}
610	if(num_files == 0)
611	    goto next;
612	if(flags & LS_SIZE) {
613	    columns = 80 / (size_len + 1 + max_len + 1);
614	    max_len = 80 / columns - size_len - 1;
615	} else {
616	    columns = 80 / (max_len + 1); /* get space between columns */
617	    max_len = 80 / columns;
618	}
619	if(flags & LS_SIZE)
620	    sec_fprintf2(out, "total %lu\r\n",
621			 (unsigned long)total_blocks);
622	if(DISP_MODE(flags) == LS_DISP_CROSS) {
623	    for(i = 0, j = 0; i < n_files; i++) {
624		if(fi[i].filename == NULL)
625		    continue;
626		if(flags & LS_SIZE)
627		    sec_fprintf2(out, "%*u %-*s", size_len, fi[i].bsize,
628				 max_len, fi[i].filename);
629		else
630		    sec_fprintf2(out, "%-*s", max_len, fi[i].filename);
631		j++;
632		if(j == columns) {
633		    sec_fprintf2(out, "\r\n");
634		    j = 0;
635		}
636	    }
637	    if(j > 0)
638		sec_fprintf2(out, "\r\n");
639	} else {
640	    int skip = (num_files + columns - 1) / columns;
641	    j = 0;
642	    for(i = 0; i < skip; i++) {
643		for(j = i; j < n_files;) {
644		    while(j < n_files && fi[j].filename == NULL)
645			j++;
646		    if(flags & LS_SIZE)
647			sec_fprintf2(out, "%*u %-*s", size_len, fi[j].bsize,
648				     max_len, fi[j].filename);
649		    else
650			sec_fprintf2(out, "%-*s", max_len, fi[j].filename);
651		    j += skip;
652		}
653		sec_fprintf2(out, "\r\n");
654	    }
655	}
656    } else {
657	for(i = 0; i < n_files; i++) {
658	    if(fi[i].filename == NULL)
659		continue;
660	    sec_fprintf2(out, "%s\r\n", fi[i].filename);
661	}
662    }
663 next:
664    if(((flags & LS_DIRS) == 0 || (flags & LS_RECURSIVE)) && dirs != NULL) {
665	for(i = 0; i < n_files; i++) {
666	    if(dirs[i]) {
667		const char *p = strrchr(files[i], '/');
668		if(p == NULL)
669		    p = files[i];
670		else
671		    p++;
672		if(!(flags & LS_DIR_FLAG) || !IS_DOT_DOTDOT(p)) {
673		    if((flags & LS_SHOW_DIRNAME)) {
674			if ((flags & LS_EXTRA_BLANK))
675			    sec_fprintf2(out, "\r\n");
676			sec_fprintf2(out, "%s:\r\n", files[i]);
677		    }
678		    list_dir(out, files[i], flags | LS_DIRS | LS_EXTRA_BLANK);
679		}
680	    }
681	}
682    }
683 out:
684    for(i = 0; i < n_files; i++)
685	free_fileinfo(&fi[i]);
686    free(fi);
687    if(dirs != NULL)
688	free(dirs);
689}
690
691static void
692free_files (char **files, int n)
693{
694    int i;
695
696    for (i = 0; i < n; ++i)
697	free (files[i]);
698    free (files);
699}
700
701static int
702hide_file(const char *filename, int flags)
703{
704    if(filename[0] != '.')
705	return 0;
706    if((flags & LS_IGNORE_DOT))
707	return 1;
708    if(filename[1] == '\0' || (filename[1] == '.' && filename[2] == '\0')) {
709	if((flags & LS_SHOW_ALL))
710	    return 0;
711	else
712	    return 1;
713    }
714    return 0;
715}
716
717static void
718list_dir(FILE *out, const char *directory, int flags)
719{
720    DIR *d = opendir(directory);
721    struct dirent *ent;
722    char **files = NULL;
723    int n_files = 0;
724
725    if(d == NULL) {
726	sec_fprintf2(out, "%s: %s\r\n", directory, strerror(errno));
727	return;
728    }
729    while((ent = readdir(d)) != NULL) {
730	void *tmp;
731
732	if(hide_file(ent->d_name, flags))
733	    continue;
734	tmp = realloc(files, (n_files + 1) * sizeof(*files));
735	if (tmp == NULL) {
736	    sec_fprintf2(out, "%s: out of memory\r\n", directory);
737	    free_files (files, n_files);
738	    closedir (d);
739	    return;
740	}
741	files = tmp;
742	asprintf(&files[n_files], "%s/%s", directory, ent->d_name);
743	if (files[n_files] == NULL) {
744	    sec_fprintf2(out, "%s: out of memory\r\n", directory);
745	    free_files (files, n_files);
746	    closedir (d);
747	    return;
748	}
749	++n_files;
750    }
751    closedir(d);
752    list_files(out, (const char**)files, n_files, flags | LS_DIR_FLAG);
753}
754
755static int
756parse_flags(const char *options)
757{
758#ifdef TEST
759    int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_COLUMN;
760#else
761    int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_LONG;
762#endif
763
764    const char *p;
765    if(options == NULL || *options != '-')
766	return flags;
767    for(p = options + 1; *p; p++) {
768	switch(*p) {
769	case '1':
770	    flags = (flags & ~LS_DISP_MODE);
771	    break;
772	case 'a':
773	    flags |= LS_SHOW_ALL;
774	    /*FALLTHROUGH*/
775	case 'A':
776	    flags &= ~LS_IGNORE_DOT;
777	    break;
778	case 'C':
779	    flags = (flags & ~LS_DISP_MODE) | LS_DISP_COLUMN;
780	    break;
781	case 'd':
782	    flags |= LS_DIRS;
783	    break;
784	case 'f':
785	    flags = (flags & ~LS_SORT_MODE);
786	    break;
787	case 'F':
788	    flags |= LS_TYPE;
789	    break;
790	case 'i':
791	    flags |= LS_INODE;
792	    break;
793	case 'l':
794	    flags = (flags & ~LS_DISP_MODE) | LS_DISP_LONG;
795	    break;
796	case 'r':
797	    flags |= LS_SORT_REVERSE;
798	    break;
799	case 'R':
800	    flags |= LS_RECURSIVE;
801	    break;
802	case 's':
803	    flags |= LS_SIZE;
804	    break;
805	case 'S':
806	    flags = (flags & ~LS_SORT_MODE) | LS_SORT_SIZE;
807	    break;
808	case 't':
809	    flags = (flags & ~LS_SORT_MODE) | LS_SORT_MTIME;
810	    break;
811	case 'x':
812	    flags = (flags & ~LS_DISP_MODE) | LS_DISP_CROSS;
813	    break;
814	    /* these are a bunch of unimplemented flags from BSD ls */
815	case 'k': /* display sizes in kB */
816	case 'c': /* last change time */
817	case 'L': /* list symlink target */
818	case 'm': /* stream output */
819	case 'o': /* BSD file flags */
820	case 'p': /* display / after directories */
821	case 'q': /* print non-graphic characters */
822	case 'u': /* use last access time */
823	case 'T': /* display complete time */
824	case 'W': /* include whiteouts */
825	    break;
826	}
827    }
828    return flags;
829}
830
831void
832builtin_ls(FILE *out, const char *file)
833{
834    int flags;
835
836    if(*file == '-') {
837	flags = parse_flags(file);
838	file = ".";
839    } else
840	flags = parse_flags("");
841
842    list_files(out, &file, 1, flags);
843    sec_fflush(out);
844}
845