Initial import from FreeBSD RELENG_4:
[dragonfly.git] / crypto / heimdal / appl / ftp / ftpd / ls.c
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: 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
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 void
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             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
280 static void
281 print_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
326 static int
327 compare_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
336 static int
337 compare_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
346 static int
347 compare_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
356 static int list_dir(FILE*, const char*, int);
357
358 static int
359 log10(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
375 static int do_the_afs_dance = 1;
376 #endif
377
378 static int
379 lstat_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
470 static int
471 list_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
699 static void
700 free_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
709 static int
710 hide_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
725 static int
726 list_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
763 static int
764 parse_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
839 int
840 builtin_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 }