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