xref: /freebsd/crypto/heimdal/appl/ftp/ftpd/ls.c (revision b52b9d56d4e96089873a75f9e29062eec19fabba)
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 
36 RCSID("$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
52 static void list_files(FILE *out, const char **files, int n_files, int flags);
53 static int parse_flags(const char *options);
54 
55 int
56 main(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 
71 struct 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 
87 static void
88 free_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 
135 static size_t
136 block_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 
145 static void
146 make_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 
276 static void
277 print_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 
322 static int
323 compare_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 
332 static int
333 compare_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 
342 static int
343 compare_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 
352 static void
353 list_dir(FILE *out, const char *directory, int flags);
354 
355 static int
356 log10(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
372 static int do_the_afs_dance = 1;
373 #endif
374 
375 static int
376 lstat_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 
467 static void
468 list_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 
691 static void
692 free_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 
701 static int
702 hide_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 
717 static void
718 list_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 
755 static int
756 parse_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 
831 void
832 builtin_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