xref: /freebsd/crypto/heimdal/appl/ftp/ftpd/ls.c (revision 0957b409)
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 
36 RCSID("$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
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 #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 
139 static size_t
140 block_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 
149 static int
150 make_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 
314 static void
315 print_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 
360 static int
361 compare_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 
370 static int
371 compare_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 
380 static int
381 compare_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 
390 static int list_dir(FILE*, const char*, int);
391 
392 static int
393 find_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
409 static int do_the_afs_dance = 1;
410 #endif
411 
412 static int
413 lstat_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 
504 static int
505 list_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 
735 static void
736 free_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 
745 static int
746 hide_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 
761 static int
762 list_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 
800 static int
801 parse_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 
876 int
877 builtin_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