xref: /freebsd/crypto/heimdal/appl/ftp/ftpd/ls.c (revision 1b6c76a2fe091c74f08427e6c870851025a9cf67)
1 /*
2  * Copyright (c) 1999 - 2000 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.20 2001/01/25 01:33:15 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 void
53 builtin_ls(FILE *out, const char *file);
54 int
55 main(int argc, char **argv)
56 {
57     int i;
58     for(i = 1; i < argc; i++)
59 	builtin_ls(stdout, argv[i]);
60     return 0;
61 }
62 #endif
63 
64 struct fileinfo {
65     struct stat st;
66     int inode;
67     int bsize;
68     char mode[11];
69     int n_link;
70     char *user;
71     char *group;
72     char *size;
73     char *major;
74     char *minor;
75     char *date;
76     char *filename;
77     char *link;
78 };
79 
80 static void
81 free_fileinfo(struct fileinfo *f)
82 {
83     free(f->user);
84     free(f->group);
85     free(f->size);
86     free(f->major);
87     free(f->minor);
88     free(f->date);
89     free(f->filename);
90     free(f->link);
91 }
92 
93 #define LS_DIRS		(1 << 0)
94 #define LS_IGNORE_DOT	(1 << 1)
95 #define LS_SORT_MODE	(3 << 2)
96 #define SORT_MODE(f) ((f) & LS_SORT_MODE)
97 #define LS_SORT_NAME	(1 << 2)
98 #define LS_SORT_MTIME	(2 << 2)
99 #define LS_SORT_SIZE	(3 << 2)
100 #define LS_SORT_REVERSE	(1 << 4)
101 
102 #define LS_SIZE		(1 << 5)
103 #define LS_INODE	(1 << 6)
104 #define LS_TYPE		(1 << 7)
105 #define LS_DISP_MODE	(3 << 8)
106 #define DISP_MODE(f) ((f) & LS_DISP_MODE)
107 #define LS_DISP_LONG	(1 << 8)
108 #define LS_DISP_COLUMN	(2 << 8)
109 #define LS_DISP_CROSS	(3 << 8)
110 
111 #ifndef S_ISTXT
112 #define S_ISTXT S_ISVTX
113 #endif
114 
115 #ifndef S_ISSOCK
116 #define S_ISSOCK(mode)  (((mode) & _S_IFMT) == S_IFSOCK)
117 #endif
118 
119 #ifndef S_ISLNK
120 #define S_ISLNK(mode)   (((mode) & _S_IFMT) == S_IFLNK)
121 #endif
122 
123 static void
124 make_fileinfo(const char *filename, struct fileinfo *file, int flags)
125 {
126     char buf[128];
127     int file_type = 0;
128     struct stat *st = &file->st;
129 
130     file->inode = st->st_ino;
131 #ifdef S_BLKSIZE
132     file->bsize = st->st_blocks * S_BLKSIZE / 1024;
133 #else
134     file->bsize = st->st_blocks * 512 / 1024;
135 #endif
136 
137     if(S_ISDIR(st->st_mode)) {
138 	file->mode[0] = 'd';
139 	file_type = '/';
140     }
141     else if(S_ISCHR(st->st_mode))
142 	file->mode[0] = 'c';
143     else if(S_ISBLK(st->st_mode))
144 	file->mode[0] = 'b';
145     else if(S_ISREG(st->st_mode)) {
146 	file->mode[0] = '-';
147 	if(st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
148 	    file_type = '*';
149     }
150     else if(S_ISFIFO(st->st_mode)) {
151 	file->mode[0] = 'p';
152 	file_type = '|';
153     }
154     else if(S_ISLNK(st->st_mode)) {
155 	file->mode[0] = 'l';
156 	file_type = '@';
157     }
158     else if(S_ISSOCK(st->st_mode)) {
159 	file->mode[0] = 's';
160 	file_type = '=';
161     }
162 #ifdef S_ISWHT
163     else if(S_ISWHT(st->st_mode)) {
164 	file->mode[0] = 'w';
165 	file_type = '%';
166     }
167 #endif
168     else
169 	file->mode[0] = '?';
170     {
171 	char *x[] = { "---", "--x", "-w-", "-wx",
172 		      "r--", "r-x", "rw-", "rwx" };
173 	strcpy(file->mode + 1, x[(st->st_mode & S_IRWXU) >> 6]);
174 	strcpy(file->mode + 4, x[(st->st_mode & S_IRWXG) >> 3]);
175 	strcpy(file->mode + 7, x[(st->st_mode & S_IRWXO) >> 0]);
176 	if((st->st_mode & S_ISUID)) {
177 	    if((st->st_mode & S_IXUSR))
178 		file->mode[3] = 's';
179 	    else
180 		file->mode[3] = 'S';
181 	}
182 	if((st->st_mode & S_ISGID)) {
183 	    if((st->st_mode & S_IXGRP))
184 		file->mode[6] = 's';
185 	    else
186 		file->mode[6] = 'S';
187 	}
188 	if((st->st_mode & S_ISTXT)) {
189 	    if((st->st_mode & S_IXOTH))
190 		file->mode[9] = 't';
191 	    else
192 		file->mode[9] = 'T';
193 	}
194     }
195     file->n_link = st->st_nlink;
196     {
197 	struct passwd *pwd;
198 	pwd = getpwuid(st->st_uid);
199 	if(pwd == NULL)
200 	    asprintf(&file->user, "%u", (unsigned)st->st_uid);
201 	else
202 	    file->user = strdup(pwd->pw_name);
203     }
204     {
205 	struct group *grp;
206 	grp = getgrgid(st->st_gid);
207 	if(grp == NULL)
208 	    asprintf(&file->group, "%u", (unsigned)st->st_gid);
209 	else
210 	    file->group = strdup(grp->gr_name);
211     }
212 
213     if(S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
214 #if defined(major) && defined(minor)
215 	asprintf(&file->major, "%u", (unsigned)major(st->st_rdev));
216 	asprintf(&file->minor, "%u", (unsigned)minor(st->st_rdev));
217 #else
218 	/* Don't want to use the DDI/DKI crap. */
219 	asprintf(&file->major, "%u", (unsigned)st->st_rdev);
220 	asprintf(&file->minor, "%u", 0);
221 #endif
222     } else
223 	asprintf(&file->size, "%lu", (unsigned long)st->st_size);
224 
225     {
226 	time_t t = time(NULL);
227 	time_t mtime = st->st_mtime;
228 	struct tm *tm = localtime(&mtime);
229 	if((t - mtime > 6*30*24*60*60) ||
230 	   (mtime - t > 6*30*24*60*60))
231 	    strftime(buf, sizeof(buf), "%b %e  %Y", tm);
232 	else
233 	    strftime(buf, sizeof(buf), "%b %e %H:%M", tm);
234 	file->date = strdup(buf);
235     }
236     {
237 	const char *p = strrchr(filename, '/');
238 	if(p)
239 	    p++;
240 	else
241 	    p = filename;
242 	if((flags & LS_TYPE) && file_type != 0)
243 	    asprintf(&file->filename, "%s%c", p, file_type);
244 	else
245 	    file->filename = strdup(p);
246     }
247     if(S_ISLNK(st->st_mode)) {
248 	int n;
249 	n = readlink((char *)filename, buf, sizeof(buf));
250 	if(n >= 0) {
251 	    buf[n] = '\0';
252 	    file->link = strdup(buf);
253 	} else
254 	    warn("%s: readlink", filename);
255     }
256 }
257 
258 static void
259 print_file(FILE *out,
260 	   int flags,
261 	   struct fileinfo *f,
262 	   int max_inode,
263 	   int max_bsize,
264 	   int max_n_link,
265 	   int max_user,
266 	   int max_group,
267 	   int max_size,
268 	   int max_major,
269 	   int max_minor,
270 	   int max_date)
271 {
272     if(f->filename == NULL)
273 	return;
274 
275     if(flags & LS_INODE) {
276 	sec_fprintf2(out, "%*d", max_inode, f->inode);
277 	sec_fprintf2(out, "  ");
278     }
279     if(flags & LS_SIZE) {
280 	sec_fprintf2(out, "%*d", max_bsize, f->bsize);
281 	sec_fprintf2(out, "  ");
282     }
283     sec_fprintf2(out, "%s", f->mode);
284     sec_fprintf2(out, "  ");
285     sec_fprintf2(out, "%*d", max_n_link, f->n_link);
286     sec_fprintf2(out, " ");
287     sec_fprintf2(out, "%-*s", max_user, f->user);
288     sec_fprintf2(out, "  ");
289     sec_fprintf2(out, "%-*s", max_group, f->group);
290     sec_fprintf2(out, "  ");
291     if(f->major != NULL && f->minor != NULL)
292 	sec_fprintf2(out, "%*s, %*s", max_major, f->major, max_minor, f->minor);
293     else
294 	sec_fprintf2(out, "%*s", max_size, f->size);
295     sec_fprintf2(out, " ");
296     sec_fprintf2(out, "%*s", max_date, f->date);
297     sec_fprintf2(out, " ");
298     sec_fprintf2(out, "%s", f->filename);
299     if(f->link)
300 	sec_fprintf2(out, " -> %s", f->link);
301     sec_fprintf2(out, "\r\n");
302 }
303 
304 static int
305 compare_filename(struct fileinfo *a, struct fileinfo *b)
306 {
307     if(a->filename == NULL)
308 	return 1;
309     if(b->filename == NULL)
310 	return -1;
311     return strcmp(a->filename, b->filename);
312 }
313 
314 static int
315 compare_mtime(struct fileinfo *a, struct fileinfo *b)
316 {
317     if(a->filename == NULL)
318 	return 1;
319     if(b->filename == NULL)
320 	return -1;
321     return b->st.st_mtime - a->st.st_mtime;
322 }
323 
324 static int
325 compare_size(struct fileinfo *a, struct fileinfo *b)
326 {
327     if(a->filename == NULL)
328 	return 1;
329     if(b->filename == NULL)
330 	return -1;
331     return b->st.st_size - a->st.st_size;
332 }
333 
334 static void
335 list_dir(FILE *out, const char *directory, int flags);
336 
337 static int
338 log10(int num)
339 {
340     int i = 1;
341     while(num > 10) {
342 	i++;
343 	num /= 10;
344     }
345     return i;
346 }
347 
348 /*
349  * Operate as lstat but fake up entries for AFS mount points so we don't
350  * have to fetch them.
351  */
352 
353 #ifdef KRB4
354 static int do_the_afs_dance = 1;
355 #endif
356 
357 static int
358 lstat_file (const char *file, struct stat *sb)
359 {
360 #ifdef KRB4
361     if (do_the_afs_dance &&
362 	k_hasafs()
363 	&& strcmp(file, ".")
364 	&& strcmp(file, "..")
365 	&& strcmp(file, "/"))
366     {
367 	struct ViceIoctl    a_params;
368 	char               *dir, *last;
369 	char               *path_bkp;
370 	static ino_t	   ino_counter = 0, ino_last = 0;
371 	int		   ret;
372 	const int	   maxsize = 2048;
373 
374 	path_bkp = strdup (file);
375 	if (path_bkp == NULL)
376 	    return -1;
377 
378 	a_params.out = malloc (maxsize);
379 	if (a_params.out == NULL) {
380 	    free (path_bkp);
381 	    return -1;
382 	}
383 
384 	/* If path contains more than the filename alone - split it */
385 
386 	last = strrchr (path_bkp, '/');
387 	if (last != NULL) {
388 	    if(last[1] == '\0')
389 		/* if path ended in /, replace with `.' */
390 		a_params.in = ".";
391 	    else
392 		a_params.in = last + 1;
393 	    while(last > path_bkp && *--last == '/');
394 	    if(*last != '/' || last != path_bkp) {
395 		*++last = '\0';
396 		dir = path_bkp;
397 	    } else
398 		/* we got to the start, so this must be the root dir */
399 		dir = "/";
400 	} else {
401 	    /* file is relative to cdir */
402 	    dir = ".";
403 	    a_params.in = path_bkp;
404 	}
405 
406 	a_params.in_size  = strlen (a_params.in) + 1;
407 	a_params.out_size = maxsize;
408 
409 	ret = k_pioctl (dir, VIOC_AFS_STAT_MT_PT, &a_params, 0);
410 	free (a_params.out);
411 	if (ret < 0) {
412 	    free (path_bkp);
413 
414 	    if (errno != EINVAL)
415 		return ret;
416 	    else
417 		/* if we get EINVAL this is probably not a mountpoint */
418 		return lstat (file, sb);
419 	}
420 
421 	/*
422 	 * wow this was a mountpoint, lets cook the struct stat
423 	 * use . as a prototype
424 	 */
425 
426 	ret = lstat (dir, sb);
427 	free (path_bkp);
428 	if (ret < 0)
429 	    return ret;
430 
431 	if (ino_last == sb->st_ino)
432 	    ino_counter++;
433 	else {
434 	    ino_last    = sb->st_ino;
435 	    ino_counter = 0;
436 	}
437 	sb->st_ino += ino_counter;
438 	sb->st_nlink = 3;
439 
440 	return 0;
441     }
442 #endif /* KRB4 */
443     return lstat (file, sb);
444 }
445 
446 static void
447 list_files(FILE *out, const char **files, int n_files, int flags)
448 {
449     struct fileinfo *fi;
450     int i;
451 
452     fi = calloc(n_files, sizeof(*fi));
453     if (fi == NULL) {
454 	sec_fprintf2(out, "ouf of memory\r\n");
455 	return;
456     }
457     for(i = 0; i < n_files; i++) {
458 	if(lstat_file(files[i], &fi[i].st) < 0) {
459 	    sec_fprintf2(out, "%s: %s\r\n", files[i], strerror(errno));
460 	    fi[i].filename = NULL;
461 	} else {
462 	    if((flags & LS_DIRS) == 0 && S_ISDIR(fi[i].st.st_mode)) {
463 		if(n_files > 1)
464 		    sec_fprintf2(out, "%s:\r\n", files[i]);
465 		list_dir(out, files[i], flags);
466 	    } else {
467 		make_fileinfo(files[i], &fi[i], flags);
468 	    }
469 	}
470     }
471     switch(SORT_MODE(flags)) {
472     case LS_SORT_NAME:
473 	qsort(fi, n_files, sizeof(*fi),
474 	      (int (*)(const void*, const void*))compare_filename);
475 	break;
476     case LS_SORT_MTIME:
477 	qsort(fi, n_files, sizeof(*fi),
478 	      (int (*)(const void*, const void*))compare_mtime);
479 	break;
480     case LS_SORT_SIZE:
481 	qsort(fi, n_files, sizeof(*fi),
482 	      (int (*)(const void*, const void*))compare_size);
483 	break;
484     }
485     if(DISP_MODE(flags) == LS_DISP_LONG) {
486 	int max_inode = 0;
487 	int max_bsize = 0;
488 	int max_n_link = 0;
489 	int max_user = 0;
490 	int max_group = 0;
491 	int max_size = 0;
492 	int max_major = 0;
493 	int max_minor = 0;
494 	int max_date = 0;
495 	for(i = 0; i < n_files; i++) {
496 	    if(fi[i].filename == NULL)
497 		continue;
498 	    if(fi[i].inode > max_inode)
499 		max_inode = fi[i].inode;
500 	    if(fi[i].bsize > max_bsize)
501 		max_bsize = fi[i].bsize;
502 	    if(fi[i].n_link > max_n_link)
503 		max_n_link = fi[i].n_link;
504 	    if(strlen(fi[i].user) > max_user)
505 		max_user = strlen(fi[i].user);
506 	    if(strlen(fi[i].group) > max_group)
507 		max_group = strlen(fi[i].group);
508 	    if(fi[i].major != NULL && strlen(fi[i].major) > max_major)
509 		max_major = strlen(fi[i].major);
510 	    if(fi[i].minor != NULL && strlen(fi[i].minor) > max_minor)
511 		max_minor = strlen(fi[i].minor);
512 	    if(fi[i].size != NULL && strlen(fi[i].size) > max_size)
513 		max_size = strlen(fi[i].size);
514 	    if(strlen(fi[i].date) > max_date)
515 		max_date = strlen(fi[i].date);
516 	}
517 	if(max_size < max_major + max_minor + 2)
518 	    max_size = max_major + max_minor + 2;
519 	else if(max_size - max_minor - 2 > max_major)
520 	    max_major = max_size - max_minor - 2;
521 	max_inode = log10(max_inode);
522 	max_bsize = log10(max_bsize);
523 	max_n_link = log10(max_n_link);
524 
525 	if(flags & LS_SORT_REVERSE)
526 	    for(i = n_files - 1; i >= 0; i--)
527 		print_file(out,
528 			   flags,
529 			   &fi[i],
530 			   max_inode,
531 			   max_bsize,
532 			   max_n_link,
533 			   max_user,
534 			   max_group,
535 			   max_size,
536 			   max_major,
537 			   max_minor,
538 			   max_date);
539 	else
540 	    for(i = 0; i < n_files; i++)
541 		print_file(out,
542 			   flags,
543 			   &fi[i],
544 			   max_inode,
545 			   max_bsize,
546 			   max_n_link,
547 			   max_user,
548 			   max_group,
549 			   max_size,
550 			   max_major,
551 			   max_minor,
552 			   max_date);
553     } else if(DISP_MODE(flags) == LS_DISP_COLUMN ||
554 	      DISP_MODE(flags) == LS_DISP_CROSS) {
555 	int max_len = 0;
556 	int num_files = n_files;
557 	int columns;
558 	int j;
559 	for(i = 0; i < n_files; i++) {
560 	    if(fi[i].filename == NULL) {
561 		num_files--;
562 		continue;
563 	    }
564 	    if(strlen(fi[i].filename) > max_len)
565 		max_len = strlen(fi[i].filename);
566 	}
567 	columns = 80 / (max_len + 1); /* get space between columns */
568 	max_len = 80 / columns;
569 	if(DISP_MODE(flags) == LS_DISP_CROSS) {
570 	    for(i = 0, j = 0; i < n_files; i++) {
571 		if(fi[i].filename == NULL)
572 		    continue;
573 		sec_fprintf2(out, "%-*s", max_len, fi[i].filename);
574 		j++;
575 		if(j == columns) {
576 		    sec_fprintf2(out, "\r\n");
577 		    j = 0;
578 		}
579 	    }
580 	    if(j > 0)
581 		    sec_fprintf2(out, "\r\n");
582 	} else {
583 	    int skip = (num_files + columns - 1) / columns;
584 	    j = 0;
585 	    for(i = 0; i < skip; i++) {
586 		for(j = i; j < n_files;) {
587 		    while(j < n_files && fi[j].filename == NULL)
588 			j++;
589 		    sec_fprintf2(out, "%-*s", max_len, fi[j].filename);
590 		    j += skip;
591 		}
592 		sec_fprintf2(out, "\r\n");
593 	    }
594 	}
595     } else {
596 	for(i = 0; i < n_files; i++) {
597 	    if(fi[i].filename == NULL)
598 		continue;
599 	    sec_fprintf2(out, "%s\r\n", fi[i].filename);
600 	}
601     }
602     for(i = 0; i < n_files; i++)
603 	free_fileinfo(&fi[i]);
604     free(fi);
605 }
606 
607 static void
608 free_files (char **files, int n)
609 {
610     int i;
611 
612     for (i = 0; i < n; ++i)
613 	free (files[i]);
614     free (files);
615 }
616 
617 static void
618 list_dir(FILE *out, const char *directory, int flags)
619 {
620     DIR *d = opendir(directory);
621     struct dirent *ent;
622     char **files = NULL;
623     int n_files = 0;
624 
625     if(d == NULL) {
626 	sec_fprintf2(out, "%s: %s\r\n", directory, strerror(errno));
627 	return;
628     }
629     while((ent = readdir(d)) != NULL) {
630 	void *tmp;
631 
632 	if(ent->d_name[0] == '.') {
633 	    if (flags & LS_IGNORE_DOT)
634 	        continue;
635 	    if (ent->d_name[1] == 0) /* Ignore . */
636 	        continue;
637 	    if (ent->d_name[1] == '.' && ent->d_name[2] == 0) /* Ignore .. */
638 	        continue;
639 	}
640 	tmp = realloc(files, (n_files + 1) * sizeof(*files));
641 	if (tmp == NULL) {
642 	    sec_fprintf2(out, "%s: out of memory\r\n", directory);
643 	    free_files (files, n_files);
644 	    closedir (d);
645 	    return;
646 	}
647 	files = tmp;
648 	asprintf(&files[n_files], "%s/%s", directory, ent->d_name);
649 	if (files[n_files] == NULL) {
650 	    sec_fprintf2(out, "%s: out of memory\r\n", directory);
651 	    free_files (files, n_files);
652 	    closedir (d);
653 	    return;
654 	}
655 	++n_files;
656     }
657     closedir(d);
658     list_files(out, (const char**)files, n_files, flags | LS_DIRS);
659 }
660 
661 void
662 builtin_ls(FILE *out, const char *file)
663 {
664     int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_LONG;
665 
666     if(*file == '-') {
667 	const char *p;
668 	for(p = file + 1; *p; p++) {
669 	    switch(*p) {
670 	    case '1':
671 		flags = (flags & ~LS_DISP_MODE);
672 		break;
673 	    case 'a':
674 	    case 'A':
675 		flags &= ~LS_IGNORE_DOT;
676 		break;
677 	    case 'C':
678 		flags = (flags & ~LS_DISP_MODE) | LS_DISP_COLUMN;
679 		break;
680 	    case 'd':
681 		flags |= LS_DIRS;
682 		break;
683 	    case 'f':
684 		flags = (flags & ~LS_SORT_MODE);
685 		break;
686 	    case 'F':
687 		flags |= LS_TYPE;
688 		break;
689 	    case 'i':
690 		flags |= LS_INODE;
691 		break;
692 	    case 'l':
693 		flags = (flags & ~LS_DISP_MODE) | LS_DISP_LONG;
694 		break;
695 	    case 't':
696 		flags = (flags & ~LS_SORT_MODE) | LS_SORT_MTIME;
697 		break;
698 	    case 's':
699 		flags |= LS_SIZE;
700 		break;
701 	    case 'S':
702 		flags = (flags & ~LS_SORT_MODE) | LS_SORT_SIZE;
703 		break;
704 	    case 'r':
705 		flags |= LS_SORT_REVERSE;
706 		break;
707 	    case 'x':
708 		flags = (flags & ~LS_DISP_MODE) | LS_DISP_CROSS;
709 		break;
710 	    }
711 	}
712 	file = ".";
713     }
714     list_files(out, &file, 1, flags);
715     sec_fflush(out);
716 }
717