ls.c revision 102644
1/*
2 * Copyright (c) 1999 - 2002 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.25 2002/08/22 08:31:03 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#if !defined(_S_IFMT) && defined(S_IFMT)
128#define _S_IFMT S_IFMT
129#endif
130
131#ifndef S_ISSOCK
132#define S_ISSOCK(mode)  (((mode) & _S_IFMT) == S_IFSOCK)
133#endif
134
135#ifndef S_ISLNK
136#define S_ISLNK(mode)   (((mode) & _S_IFMT) == S_IFLNK)
137#endif
138
139static size_t
140block_convert(size_t blocks)
141{
142#ifdef S_BLKSIZE
143    return blocks * S_BLKSIZE / 1024;
144#else
145    return blocks * 512 / 1024;
146#endif
147}
148
149static void
150make_fileinfo(FILE *out, const char *filename, struct fileinfo *file, int flags)
151{
152    char buf[128];
153    int file_type = 0;
154    struct stat *st = &file->st;
155
156    file->inode = st->st_ino;
157    file->bsize = block_convert(st->st_blocks);
158
159    if(S_ISDIR(st->st_mode)) {
160	file->mode[0] = 'd';
161	file_type = '/';
162    }
163    else if(S_ISCHR(st->st_mode))
164	file->mode[0] = 'c';
165    else if(S_ISBLK(st->st_mode))
166	file->mode[0] = 'b';
167    else if(S_ISREG(st->st_mode)) {
168	file->mode[0] = '-';
169	if(st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
170	    file_type = '*';
171    }
172    else if(S_ISFIFO(st->st_mode)) {
173	file->mode[0] = 'p';
174	file_type = '|';
175    }
176    else if(S_ISLNK(st->st_mode)) {
177	file->mode[0] = 'l';
178	file_type = '@';
179    }
180    else if(S_ISSOCK(st->st_mode)) {
181	file->mode[0] = 's';
182	file_type = '=';
183    }
184#ifdef S_ISWHT
185    else if(S_ISWHT(st->st_mode)) {
186	file->mode[0] = 'w';
187	file_type = '%';
188    }
189#endif
190    else
191	file->mode[0] = '?';
192    {
193	char *x[] = { "---", "--x", "-w-", "-wx",
194		      "r--", "r-x", "rw-", "rwx" };
195	strcpy(file->mode + 1, x[(st->st_mode & S_IRWXU) >> 6]);
196	strcpy(file->mode + 4, x[(st->st_mode & S_IRWXG) >> 3]);
197	strcpy(file->mode + 7, x[(st->st_mode & S_IRWXO) >> 0]);
198	if((st->st_mode & S_ISUID)) {
199	    if((st->st_mode & S_IXUSR))
200		file->mode[3] = 's';
201	    else
202		file->mode[3] = 'S';
203	}
204	if((st->st_mode & S_ISGID)) {
205	    if((st->st_mode & S_IXGRP))
206		file->mode[6] = 's';
207	    else
208		file->mode[6] = 'S';
209	}
210	if((st->st_mode & S_ISTXT)) {
211	    if((st->st_mode & S_IXOTH))
212		file->mode[9] = 't';
213	    else
214		file->mode[9] = 'T';
215	}
216    }
217    file->n_link = st->st_nlink;
218    {
219	struct passwd *pwd;
220	pwd = getpwuid(st->st_uid);
221	if(pwd == NULL)
222	    asprintf(&file->user, "%u", (unsigned)st->st_uid);
223	else
224	    file->user = strdup(pwd->pw_name);
225    }
226    {
227	struct group *grp;
228	grp = getgrgid(st->st_gid);
229	if(grp == NULL)
230	    asprintf(&file->group, "%u", (unsigned)st->st_gid);
231	else
232	    file->group = strdup(grp->gr_name);
233    }
234
235    if(S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
236#if defined(major) && defined(minor)
237	asprintf(&file->major, "%u", (unsigned)major(st->st_rdev));
238	asprintf(&file->minor, "%u", (unsigned)minor(st->st_rdev));
239#else
240	/* Don't want to use the DDI/DKI crap. */
241	asprintf(&file->major, "%u", (unsigned)st->st_rdev);
242	asprintf(&file->minor, "%u", 0);
243#endif
244    } else
245	asprintf(&file->size, "%lu", (unsigned long)st->st_size);
246
247    {
248	time_t t = time(NULL);
249	time_t mtime = st->st_mtime;
250	struct tm *tm = localtime(&mtime);
251	if((t - mtime > 6*30*24*60*60) ||
252	   (mtime - t > 6*30*24*60*60))
253	    strftime(buf, sizeof(buf), "%b %e  %Y", tm);
254	else
255	    strftime(buf, sizeof(buf), "%b %e %H:%M", tm);
256	file->date = strdup(buf);
257    }
258    {
259	const char *p = strrchr(filename, '/');
260	if(p)
261	    p++;
262	else
263	    p = filename;
264	if((flags & LS_TYPE) && file_type != 0)
265	    asprintf(&file->filename, "%s%c", p, file_type);
266	else
267	    file->filename = strdup(p);
268    }
269    if(S_ISLNK(st->st_mode)) {
270	int n;
271	n = readlink((char *)filename, buf, sizeof(buf));
272	if(n >= 0) {
273	    buf[n] = '\0';
274	    file->link = strdup(buf);
275	} else
276	    sec_fprintf2(out, "readlink(%s): %s", filename, strerror(errno));
277    }
278}
279
280static void
281print_file(FILE *out,
282	   int flags,
283	   struct fileinfo *f,
284	   int max_inode,
285	   int max_bsize,
286	   int max_n_link,
287	   int max_user,
288	   int max_group,
289	   int max_size,
290	   int max_major,
291	   int max_minor,
292	   int max_date)
293{
294    if(f->filename == NULL)
295	return;
296
297    if(flags & LS_INODE) {
298	sec_fprintf2(out, "%*d", max_inode, f->inode);
299	sec_fprintf2(out, "  ");
300    }
301    if(flags & LS_SIZE) {
302	sec_fprintf2(out, "%*d", max_bsize, f->bsize);
303	sec_fprintf2(out, "  ");
304    }
305    sec_fprintf2(out, "%s", f->mode);
306    sec_fprintf2(out, "  ");
307    sec_fprintf2(out, "%*d", max_n_link, f->n_link);
308    sec_fprintf2(out, " ");
309    sec_fprintf2(out, "%-*s", max_user, f->user);
310    sec_fprintf2(out, "  ");
311    sec_fprintf2(out, "%-*s", max_group, f->group);
312    sec_fprintf2(out, "  ");
313    if(f->major != NULL && f->minor != NULL)
314	sec_fprintf2(out, "%*s, %*s", max_major, f->major, max_minor, f->minor);
315    else
316	sec_fprintf2(out, "%*s", max_size, f->size);
317    sec_fprintf2(out, " ");
318    sec_fprintf2(out, "%*s", max_date, f->date);
319    sec_fprintf2(out, " ");
320    sec_fprintf2(out, "%s", f->filename);
321    if(f->link)
322	sec_fprintf2(out, " -> %s", f->link);
323    sec_fprintf2(out, "\r\n");
324}
325
326static int
327compare_filename(struct fileinfo *a, struct fileinfo *b)
328{
329    if(a->filename == NULL)
330	return 1;
331    if(b->filename == NULL)
332	return -1;
333    return strcmp(a->filename, b->filename);
334}
335
336static int
337compare_mtime(struct fileinfo *a, struct fileinfo *b)
338{
339    if(a->filename == NULL)
340	return 1;
341    if(b->filename == NULL)
342	return -1;
343    return b->st.st_mtime - a->st.st_mtime;
344}
345
346static int
347compare_size(struct fileinfo *a, struct fileinfo *b)
348{
349    if(a->filename == NULL)
350	return 1;
351    if(b->filename == NULL)
352	return -1;
353    return b->st.st_size - a->st.st_size;
354}
355
356static int list_dir(FILE*, const char*, int);
357
358static int
359log10(int num)
360{
361    int i = 1;
362    while(num > 10) {
363	i++;
364	num /= 10;
365    }
366    return i;
367}
368
369/*
370 * Operate as lstat but fake up entries for AFS mount points so we don't
371 * have to fetch them.
372 */
373
374#ifdef KRB4
375static int do_the_afs_dance = 1;
376#endif
377
378static int
379lstat_file (const char *file, struct stat *sb)
380{
381#ifdef KRB4
382    if (do_the_afs_dance &&
383	k_hasafs()
384	&& strcmp(file, ".")
385	&& strcmp(file, "..")
386	&& strcmp(file, "/"))
387    {
388	struct ViceIoctl    a_params;
389	char               *dir, *last;
390	char               *path_bkp;
391	static ino_t	   ino_counter = 0, ino_last = 0;
392	int		   ret;
393	const int	   maxsize = 2048;
394
395	path_bkp = strdup (file);
396	if (path_bkp == NULL)
397	    return -1;
398
399	a_params.out = malloc (maxsize);
400	if (a_params.out == NULL) {
401	    free (path_bkp);
402	    return -1;
403	}
404
405	/* If path contains more than the filename alone - split it */
406
407	last = strrchr (path_bkp, '/');
408	if (last != NULL) {
409	    if(last[1] == '\0')
410		/* if path ended in /, replace with `.' */
411		a_params.in = ".";
412	    else
413		a_params.in = last + 1;
414	    while(last > path_bkp && *--last == '/');
415	    if(*last != '/' || last != path_bkp) {
416		*++last = '\0';
417		dir = path_bkp;
418	    } else
419		/* we got to the start, so this must be the root dir */
420		dir = "/";
421	} else {
422	    /* file is relative to cdir */
423	    dir = ".";
424	    a_params.in = path_bkp;
425	}
426
427	a_params.in_size  = strlen (a_params.in) + 1;
428	a_params.out_size = maxsize;
429
430	ret = k_pioctl (dir, VIOC_AFS_STAT_MT_PT, &a_params, 0);
431	free (a_params.out);
432	if (ret < 0) {
433	    free (path_bkp);
434
435	    if (errno != EINVAL)
436		return ret;
437	    else
438		/* if we get EINVAL this is probably not a mountpoint */
439		return lstat (file, sb);
440	}
441
442	/*
443	 * wow this was a mountpoint, lets cook the struct stat
444	 * use . as a prototype
445	 */
446
447	ret = lstat (dir, sb);
448	free (path_bkp);
449	if (ret < 0)
450	    return ret;
451
452	if (ino_last == sb->st_ino)
453	    ino_counter++;
454	else {
455	    ino_last    = sb->st_ino;
456	    ino_counter = 0;
457	}
458	sb->st_ino += ino_counter;
459	sb->st_nlink = 3;
460
461	return 0;
462    }
463#endif /* KRB4 */
464    return lstat (file, sb);
465}
466
467#define IS_DOT_DOTDOT(X) ((X)[0] == '.' && ((X)[1] == '\0' || \
468				((X)[1] == '.' && (X)[2] == '\0')))
469
470static int
471list_files(FILE *out, const char **files, int n_files, int flags)
472{
473    struct fileinfo *fi;
474    int i;
475    int *dirs = NULL;
476    size_t total_blocks = 0;
477    int n_print = 0;
478    int ret = 0;
479
480    if(n_files == 0)
481	return 0;
482
483    if(n_files > 1)
484	flags |= LS_SHOW_DIRNAME;
485
486    fi = calloc(n_files, sizeof(*fi));
487    if (fi == NULL) {
488	syslog(LOG_ERR, "out of memory");
489	return -1;
490    }
491    for(i = 0; i < n_files; i++) {
492	if(lstat_file(files[i], &fi[i].st) < 0) {
493	    sec_fprintf2(out, "%s: %s\r\n", files[i], strerror(errno));
494	    fi[i].filename = NULL;
495	} else {
496	    int include_in_list = 1;
497	    total_blocks += block_convert(fi[i].st.st_blocks);
498	    if(S_ISDIR(fi[i].st.st_mode)) {
499		if(dirs == NULL)
500		    dirs = calloc(n_files, sizeof(*dirs));
501		if(dirs == NULL) {
502		    syslog(LOG_ERR, "%s: %m", files[i]);
503		    ret = -1;
504		    goto out;
505		}
506		dirs[i] = 1;
507		if((flags & LS_DIRS) == 0)
508		    include_in_list = 0;
509	    }
510	    if(include_in_list) {
511		make_fileinfo(out, files[i], &fi[i], flags);
512		n_print++;
513	    }
514	}
515    }
516    switch(SORT_MODE(flags)) {
517    case LS_SORT_NAME:
518	qsort(fi, n_files, sizeof(*fi),
519	      (int (*)(const void*, const void*))compare_filename);
520	break;
521    case LS_SORT_MTIME:
522	qsort(fi, n_files, sizeof(*fi),
523	      (int (*)(const void*, const void*))compare_mtime);
524	break;
525    case LS_SORT_SIZE:
526	qsort(fi, n_files, sizeof(*fi),
527	      (int (*)(const void*, const void*))compare_size);
528	break;
529    }
530    if(DISP_MODE(flags) == LS_DISP_LONG) {
531	int max_inode = 0;
532	int max_bsize = 0;
533	int max_n_link = 0;
534	int max_user = 0;
535	int max_group = 0;
536	int max_size = 0;
537	int max_major = 0;
538	int max_minor = 0;
539	int max_date = 0;
540	for(i = 0; i < n_files; i++) {
541	    if(fi[i].filename == NULL)
542		continue;
543	    if(fi[i].inode > max_inode)
544		max_inode = fi[i].inode;
545	    if(fi[i].bsize > max_bsize)
546		max_bsize = fi[i].bsize;
547	    if(fi[i].n_link > max_n_link)
548		max_n_link = fi[i].n_link;
549	    if(strlen(fi[i].user) > max_user)
550		max_user = strlen(fi[i].user);
551	    if(strlen(fi[i].group) > max_group)
552		max_group = strlen(fi[i].group);
553	    if(fi[i].major != NULL && strlen(fi[i].major) > max_major)
554		max_major = strlen(fi[i].major);
555	    if(fi[i].minor != NULL && strlen(fi[i].minor) > max_minor)
556		max_minor = strlen(fi[i].minor);
557	    if(fi[i].size != NULL && strlen(fi[i].size) > max_size)
558		max_size = strlen(fi[i].size);
559	    if(strlen(fi[i].date) > max_date)
560		max_date = strlen(fi[i].date);
561	}
562	if(max_size < max_major + max_minor + 2)
563	    max_size = max_major + max_minor + 2;
564	else if(max_size - max_minor - 2 > max_major)
565	    max_major = max_size - max_minor - 2;
566	max_inode = log10(max_inode);
567	max_bsize = log10(max_bsize);
568	max_n_link = log10(max_n_link);
569
570	if(n_print > 0)
571	    sec_fprintf2(out, "total %lu\r\n", (unsigned long)total_blocks);
572	if(flags & LS_SORT_REVERSE)
573	    for(i = n_files - 1; i >= 0; i--)
574		print_file(out,
575			   flags,
576			   &fi[i],
577			   max_inode,
578			   max_bsize,
579			   max_n_link,
580			   max_user,
581			   max_group,
582			   max_size,
583			   max_major,
584			   max_minor,
585			   max_date);
586	else
587	    for(i = 0; i < n_files; i++)
588		print_file(out,
589			   flags,
590			   &fi[i],
591			   max_inode,
592			   max_bsize,
593			   max_n_link,
594			   max_user,
595			   max_group,
596			   max_size,
597			   max_major,
598			   max_minor,
599			   max_date);
600    } else if(DISP_MODE(flags) == LS_DISP_COLUMN ||
601	      DISP_MODE(flags) == LS_DISP_CROSS) {
602	int max_len = 0;
603	int size_len = 0;
604	int num_files = n_files;
605	int columns;
606	int j;
607	for(i = 0; i < n_files; i++) {
608	    if(fi[i].filename == NULL) {
609		num_files--;
610		continue;
611	    }
612	    if(strlen(fi[i].filename) > max_len)
613		max_len = strlen(fi[i].filename);
614	    if(log10(fi[i].bsize) > size_len)
615		size_len = log10(fi[i].bsize);
616	}
617	if(num_files == 0)
618	    goto next;
619	if(flags & LS_SIZE) {
620	    columns = 80 / (size_len + 1 + max_len + 1);
621	    max_len = 80 / columns - size_len - 1;
622	} else {
623	    columns = 80 / (max_len + 1); /* get space between columns */
624	    max_len = 80 / columns;
625	}
626	if(flags & LS_SIZE)
627	    sec_fprintf2(out, "total %lu\r\n",
628			 (unsigned long)total_blocks);
629	if(DISP_MODE(flags) == LS_DISP_CROSS) {
630	    for(i = 0, j = 0; i < n_files; i++) {
631		if(fi[i].filename == NULL)
632		    continue;
633		if(flags & LS_SIZE)
634		    sec_fprintf2(out, "%*u %-*s", size_len, fi[i].bsize,
635				 max_len, fi[i].filename);
636		else
637		    sec_fprintf2(out, "%-*s", max_len, fi[i].filename);
638		j++;
639		if(j == columns) {
640		    sec_fprintf2(out, "\r\n");
641		    j = 0;
642		}
643	    }
644	    if(j > 0)
645		sec_fprintf2(out, "\r\n");
646	} else {
647	    int skip = (num_files + columns - 1) / columns;
648	    j = 0;
649	    for(i = 0; i < skip; i++) {
650		for(j = i; j < n_files;) {
651		    while(j < n_files && fi[j].filename == NULL)
652			j++;
653		    if(flags & LS_SIZE)
654			sec_fprintf2(out, "%*u %-*s", size_len, fi[j].bsize,
655				     max_len, fi[j].filename);
656		    else
657			sec_fprintf2(out, "%-*s", max_len, fi[j].filename);
658		    j += skip;
659		}
660		sec_fprintf2(out, "\r\n");
661	    }
662	}
663    } else {
664	for(i = 0; i < n_files; i++) {
665	    if(fi[i].filename == NULL)
666		continue;
667	    sec_fprintf2(out, "%s\r\n", fi[i].filename);
668	}
669    }
670 next:
671    if(((flags & LS_DIRS) == 0 || (flags & LS_RECURSIVE)) && dirs != NULL) {
672	for(i = 0; i < n_files; i++) {
673	    if(dirs[i]) {
674		const char *p = strrchr(files[i], '/');
675		if(p == NULL)
676		    p = files[i];
677		else
678		    p++;
679		if(!(flags & LS_DIR_FLAG) || !IS_DOT_DOTDOT(p)) {
680		    if((flags & LS_SHOW_DIRNAME)) {
681			if ((flags & LS_EXTRA_BLANK))
682			    sec_fprintf2(out, "\r\n");
683			sec_fprintf2(out, "%s:\r\n", files[i]);
684		    }
685		    list_dir(out, files[i], flags | LS_DIRS | LS_EXTRA_BLANK);
686		}
687	    }
688	}
689    }
690 out:
691    for(i = 0; i < n_files; i++)
692	free_fileinfo(&fi[i]);
693    free(fi);
694    if(dirs != NULL)
695	free(dirs);
696    return ret;
697}
698
699static void
700free_files (char **files, int n)
701{
702    int i;
703
704    for (i = 0; i < n; ++i)
705	free (files[i]);
706    free (files);
707}
708
709static int
710hide_file(const char *filename, int flags)
711{
712    if(filename[0] != '.')
713	return 0;
714    if((flags & LS_IGNORE_DOT))
715	return 1;
716    if(filename[1] == '\0' || (filename[1] == '.' && filename[2] == '\0')) {
717	if((flags & LS_SHOW_ALL))
718	    return 0;
719	else
720	    return 1;
721    }
722    return 0;
723}
724
725static int
726list_dir(FILE *out, const char *directory, int flags)
727{
728    DIR *d = opendir(directory);
729    struct dirent *ent;
730    char **files = NULL;
731    int n_files = 0;
732
733    if(d == NULL) {
734	syslog(LOG_ERR, "%s: %m", directory);
735	return -1;
736    }
737    while((ent = readdir(d)) != NULL) {
738	void *tmp;
739
740	if(hide_file(ent->d_name, flags))
741	    continue;
742	tmp = realloc(files, (n_files + 1) * sizeof(*files));
743	if (tmp == NULL) {
744	    syslog(LOG_ERR, "%s: out of memory", directory);
745	    free_files (files, n_files);
746	    closedir (d);
747	    return -1;
748	}
749	files = tmp;
750	asprintf(&files[n_files], "%s/%s", directory, ent->d_name);
751	if (files[n_files] == NULL) {
752	    syslog(LOG_ERR, "%s: out of memory", directory);
753	    free_files (files, n_files);
754	    closedir (d);
755	    return -1;
756	}
757	++n_files;
758    }
759    closedir(d);
760    return list_files(out, (const char**)files, n_files, flags | LS_DIR_FLAG);
761}
762
763static int
764parse_flags(const char *options)
765{
766#ifdef TEST
767    int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_COLUMN;
768#else
769    int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_LONG;
770#endif
771
772    const char *p;
773    if(options == NULL || *options != '-')
774	return flags;
775    for(p = options + 1; *p; p++) {
776	switch(*p) {
777	case '1':
778	    flags = (flags & ~LS_DISP_MODE);
779	    break;
780	case 'a':
781	    flags |= LS_SHOW_ALL;
782	    /*FALLTHROUGH*/
783	case 'A':
784	    flags &= ~LS_IGNORE_DOT;
785	    break;
786	case 'C':
787	    flags = (flags & ~LS_DISP_MODE) | LS_DISP_COLUMN;
788	    break;
789	case 'd':
790	    flags |= LS_DIRS;
791	    break;
792	case 'f':
793	    flags = (flags & ~LS_SORT_MODE);
794	    break;
795	case 'F':
796	    flags |= LS_TYPE;
797	    break;
798	case 'i':
799	    flags |= LS_INODE;
800	    break;
801	case 'l':
802	    flags = (flags & ~LS_DISP_MODE) | LS_DISP_LONG;
803	    break;
804	case 'r':
805	    flags |= LS_SORT_REVERSE;
806	    break;
807	case 'R':
808	    flags |= LS_RECURSIVE;
809	    break;
810	case 's':
811	    flags |= LS_SIZE;
812	    break;
813	case 'S':
814	    flags = (flags & ~LS_SORT_MODE) | LS_SORT_SIZE;
815	    break;
816	case 't':
817	    flags = (flags & ~LS_SORT_MODE) | LS_SORT_MTIME;
818	    break;
819	case 'x':
820	    flags = (flags & ~LS_DISP_MODE) | LS_DISP_CROSS;
821	    break;
822	    /* these are a bunch of unimplemented flags from BSD ls */
823	case 'k': /* display sizes in kB */
824	case 'c': /* last change time */
825	case 'L': /* list symlink target */
826	case 'm': /* stream output */
827	case 'o': /* BSD file flags */
828	case 'p': /* display / after directories */
829	case 'q': /* print non-graphic characters */
830	case 'u': /* use last access time */
831	case 'T': /* display complete time */
832	case 'W': /* include whiteouts */
833	    break;
834	}
835    }
836    return flags;
837}
838
839int
840builtin_ls(FILE *out, const char *file)
841{
842    int flags;
843    int ret;
844
845    if(*file == '-') {
846	flags = parse_flags(file);
847	file = ".";
848    } else
849	flags = parse_flags("");
850
851    ret = list_files(out, &file, 1, flags);
852    sec_fflush(out);
853    return ret;
854}
855