155682Smarkm/*
2233294Sstas * Copyright (c) 1999 - 2002 Kungliga Tekniska H��gskolan
3233294Sstas * (Royal Institute of Technology, Stockholm, Sweden).
4233294Sstas * All rights reserved.
555682Smarkm *
6233294Sstas * Redistribution and use in source and binary forms, with or without
7233294Sstas * modification, are permitted provided that the following conditions
8233294Sstas * are met:
955682Smarkm *
10233294Sstas * 1. Redistributions of source code must retain the above copyright
11233294Sstas *    notice, this list of conditions and the following disclaimer.
1255682Smarkm *
13233294Sstas * 2. Redistributions in binary form must reproduce the above copyright
14233294Sstas *    notice, this list of conditions and the following disclaimer in the
15233294Sstas *    documentation and/or other materials provided with the distribution.
1655682Smarkm *
1755682Smarkm * 3. Neither the name of KTH nor the names of its contributors may be
1855682Smarkm *    used to endorse or promote products derived from this software without
1955682Smarkm *    specific prior written permission.
2055682Smarkm *
2155682Smarkm * THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY
2255682Smarkm * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2355682Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2455682Smarkm * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE
2555682Smarkm * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2655682Smarkm * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2755682Smarkm * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
2855682Smarkm * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
2955682Smarkm * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
3055682Smarkm * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
3155682Smarkm * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
3255682Smarkm
3372445Sassar#ifndef TEST
3455682Smarkm#include "ftpd_locl.h"
3555682Smarkm
36233294SstasRCSID("$Id$");
3755682Smarkm
3872445Sassar#else
3972445Sassar#include <stdio.h>
4072445Sassar#include <string.h>
4172445Sassar#include <stdlib.h>
4272445Sassar#include <time.h>
4372445Sassar#include <dirent.h>
4472445Sassar#include <sys/stat.h>
4572445Sassar#include <unistd.h>
4672445Sassar#include <pwd.h>
4772445Sassar#include <grp.h>
4872445Sassar#include <errno.h>
4972445Sassar
5072445Sassar#define sec_fprintf2 fprintf
5172445Sassar#define sec_fflush fflush
5290926Snectarstatic void list_files(FILE *out, const char **files, int n_files, int flags);
5390926Snectarstatic int parse_flags(const char *options);
5490926Snectar
5572445Sassarint
5672445Sassarmain(int argc, char **argv)
5772445Sassar{
5890926Snectar    int i = 1;
5990926Snectar    int flags;
6090926Snectar    if(argc > 1 && argv[1][0] == '-') {
6190926Snectar	flags = parse_flags(argv[1]);
6290926Snectar	i = 2;
6390926Snectar    } else
6490926Snectar	flags = parse_flags(NULL);
6590926Snectar
6690926Snectar    list_files(stdout, (const char **)argv + i, argc - i, flags);
6772445Sassar    return 0;
6872445Sassar}
6972445Sassar#endif
7072445Sassar
7155682Smarkmstruct fileinfo {
7255682Smarkm    struct stat st;
7355682Smarkm    int inode;
7455682Smarkm    int bsize;
7555682Smarkm    char mode[11];
7655682Smarkm    int n_link;
7755682Smarkm    char *user;
7855682Smarkm    char *group;
7955682Smarkm    char *size;
8055682Smarkm    char *major;
8155682Smarkm    char *minor;
8255682Smarkm    char *date;
8355682Smarkm    char *filename;
8455682Smarkm    char *link;
8555682Smarkm};
8655682Smarkm
8755682Smarkmstatic void
8855682Smarkmfree_fileinfo(struct fileinfo *f)
8955682Smarkm{
9055682Smarkm    free(f->user);
9155682Smarkm    free(f->group);
9255682Smarkm    free(f->size);
9355682Smarkm    free(f->major);
9455682Smarkm    free(f->minor);
9555682Smarkm    free(f->date);
9655682Smarkm    free(f->filename);
9755682Smarkm    free(f->link);
9855682Smarkm}
9955682Smarkm
10072445Sassar#define LS_DIRS		(1 << 0)
10172445Sassar#define LS_IGNORE_DOT	(1 << 1)
10272445Sassar#define LS_SORT_MODE	(3 << 2)
10355682Smarkm#define SORT_MODE(f) ((f) & LS_SORT_MODE)
10472445Sassar#define LS_SORT_NAME	(1 << 2)
10572445Sassar#define LS_SORT_MTIME	(2 << 2)
10672445Sassar#define LS_SORT_SIZE	(3 << 2)
10772445Sassar#define LS_SORT_REVERSE	(1 << 4)
10855682Smarkm
10972445Sassar#define LS_SIZE		(1 << 5)
11072445Sassar#define LS_INODE	(1 << 6)
11172445Sassar#define LS_TYPE		(1 << 7)
11272445Sassar#define LS_DISP_MODE	(3 << 8)
11372445Sassar#define DISP_MODE(f) ((f) & LS_DISP_MODE)
11472445Sassar#define LS_DISP_LONG	(1 << 8)
11572445Sassar#define LS_DISP_COLUMN	(2 << 8)
11672445Sassar#define LS_DISP_CROSS	(3 << 8)
11790926Snectar#define LS_SHOW_ALL	(1 << 10)
11890926Snectar#define LS_RECURSIVE	(1 << 11)
11990926Snectar#define LS_EXTRA_BLANK	(1 << 12)
12090926Snectar#define LS_SHOW_DIRNAME	(1 << 13)
12190926Snectar#define LS_DIR_FLAG	(1 << 14)	/* these files come via list_dir */
12255682Smarkm
12355682Smarkm#ifndef S_ISTXT
12455682Smarkm#define S_ISTXT S_ISVTX
12555682Smarkm#endif
12655682Smarkm
127102644Snectar#if !defined(_S_IFMT) && defined(S_IFMT)
128102644Snectar#define _S_IFMT S_IFMT
129102644Snectar#endif
130102644Snectar
13155682Smarkm#ifndef S_ISSOCK
13255682Smarkm#define S_ISSOCK(mode)  (((mode) & _S_IFMT) == S_IFSOCK)
13355682Smarkm#endif
13455682Smarkm
13555682Smarkm#ifndef S_ISLNK
13655682Smarkm#define S_ISLNK(mode)   (((mode) & _S_IFMT) == S_IFLNK)
13755682Smarkm#endif
13855682Smarkm
13990926Snectarstatic size_t
14090926Snectarblock_convert(size_t blocks)
14190926Snectar{
14290926Snectar#ifdef S_BLKSIZE
14390926Snectar    return blocks * S_BLKSIZE / 1024;
14490926Snectar#else
14590926Snectar    return blocks * 512 / 1024;
14690926Snectar#endif
14790926Snectar}
14890926Snectar
149178825Sdfrstatic int
15090926Snectarmake_fileinfo(FILE *out, const char *filename, struct fileinfo *file, int flags)
15155682Smarkm{
15255682Smarkm    char buf[128];
15372445Sassar    int file_type = 0;
15455682Smarkm    struct stat *st = &file->st;
155233294Sstas
15655682Smarkm    file->inode = st->st_ino;
15790926Snectar    file->bsize = block_convert(st->st_blocks);
158233294Sstas
15972445Sassar    if(S_ISDIR(st->st_mode)) {
16055682Smarkm	file->mode[0] = 'd';
16172445Sassar	file_type = '/';
16272445Sassar    }
16355682Smarkm    else if(S_ISCHR(st->st_mode))
16455682Smarkm	file->mode[0] = 'c';
16555682Smarkm    else if(S_ISBLK(st->st_mode))
16655682Smarkm	file->mode[0] = 'b';
16772445Sassar    else if(S_ISREG(st->st_mode)) {
16855682Smarkm	file->mode[0] = '-';
16972445Sassar	if(st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
17072445Sassar	    file_type = '*';
17172445Sassar    }
17272445Sassar    else if(S_ISFIFO(st->st_mode)) {
17355682Smarkm	file->mode[0] = 'p';
17472445Sassar	file_type = '|';
17572445Sassar    }
17672445Sassar    else if(S_ISLNK(st->st_mode)) {
17755682Smarkm	file->mode[0] = 'l';
17872445Sassar	file_type = '@';
17972445Sassar    }
18072445Sassar    else if(S_ISSOCK(st->st_mode)) {
18155682Smarkm	file->mode[0] = 's';
18272445Sassar	file_type = '=';
18372445Sassar    }
18455682Smarkm#ifdef S_ISWHT
18572445Sassar    else if(S_ISWHT(st->st_mode)) {
18655682Smarkm	file->mode[0] = 'w';
18772445Sassar	file_type = '%';
18872445Sassar    }
18955682Smarkm#endif
190233294Sstas    else
19155682Smarkm	file->mode[0] = '?';
19255682Smarkm    {
193233294Sstas	char *x[] = { "---", "--x", "-w-", "-wx",
19455682Smarkm		      "r--", "r-x", "rw-", "rwx" };
19555682Smarkm	strcpy(file->mode + 1, x[(st->st_mode & S_IRWXU) >> 6]);
19655682Smarkm	strcpy(file->mode + 4, x[(st->st_mode & S_IRWXG) >> 3]);
19755682Smarkm	strcpy(file->mode + 7, x[(st->st_mode & S_IRWXO) >> 0]);
19855682Smarkm	if((st->st_mode & S_ISUID)) {
19955682Smarkm	    if((st->st_mode & S_IXUSR))
20055682Smarkm		file->mode[3] = 's';
20155682Smarkm	    else
20255682Smarkm		file->mode[3] = 'S';
20355682Smarkm	}
20455682Smarkm	if((st->st_mode & S_ISGID)) {
20555682Smarkm	    if((st->st_mode & S_IXGRP))
20655682Smarkm		file->mode[6] = 's';
20755682Smarkm	    else
20855682Smarkm		file->mode[6] = 'S';
20955682Smarkm	}
21055682Smarkm	if((st->st_mode & S_ISTXT)) {
21155682Smarkm	    if((st->st_mode & S_IXOTH))
21255682Smarkm		file->mode[9] = 't';
21355682Smarkm	    else
21455682Smarkm		file->mode[9] = 'T';
21555682Smarkm	}
21655682Smarkm    }
21755682Smarkm    file->n_link = st->st_nlink;
21855682Smarkm    {
21955682Smarkm	struct passwd *pwd;
22055682Smarkm	pwd = getpwuid(st->st_uid);
221178825Sdfr	if(pwd == NULL) {
222178825Sdfr	    if (asprintf(&file->user, "%u", (unsigned)st->st_uid) == -1)
223178825Sdfr		file->user = NULL;
224178825Sdfr	} else
22555682Smarkm	    file->user = strdup(pwd->pw_name);
226178825Sdfr	if (file->user == NULL) {
227178825Sdfr	    syslog(LOG_ERR, "out of memory");
228178825Sdfr	    return -1;
229178825Sdfr	}
23055682Smarkm    }
23155682Smarkm    {
23255682Smarkm	struct group *grp;
23355682Smarkm	grp = getgrgid(st->st_gid);
234178825Sdfr	if(grp == NULL) {
235178825Sdfr	    if (asprintf(&file->group, "%u", (unsigned)st->st_gid) == -1)
236178825Sdfr		file->group = NULL;
237178825Sdfr	} else
23855682Smarkm	    file->group = strdup(grp->gr_name);
239178825Sdfr	if (file->group == NULL) {
240178825Sdfr	    syslog(LOG_ERR, "out of memory");
241178825Sdfr	    return -1;
242178825Sdfr	}
24355682Smarkm    }
244233294Sstas
24555682Smarkm    if(S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
24655682Smarkm#if defined(major) && defined(minor)
247178825Sdfr	if (asprintf(&file->major, "%u", (unsigned)major(st->st_rdev)) == -1)
248178825Sdfr	    file->major = NULL;
249178825Sdfr	if (asprintf(&file->minor, "%u", (unsigned)minor(st->st_rdev)) == -1)
250178825Sdfr	    file->minor = NULL;
25155682Smarkm#else
25255682Smarkm	/* Don't want to use the DDI/DKI crap. */
253178825Sdfr	if (asprintf(&file->major, "%u", (unsigned)st->st_rdev) == -1)
254178825Sdfr	    file->major = NULL;
255178825Sdfr	if (asprintf(&file->minor, "%u", 0) == -1)
256178825Sdfr	    file->minor = NULL;
25755682Smarkm#endif
258178825Sdfr	if (file->major == NULL || file->minor == NULL) {
259178825Sdfr	    syslog(LOG_ERR, "out of memory");
260178825Sdfr	    return -1;
261178825Sdfr	}
262178825Sdfr    } else {
263178825Sdfr	if (asprintf(&file->size, "%lu", (unsigned long)st->st_size) == -1)
264178825Sdfr	    file->size = NULL;
265178825Sdfr    }
26655682Smarkm
26755682Smarkm    {
26855682Smarkm	time_t t = time(NULL);
26972445Sassar	time_t mtime = st->st_mtime;
27072445Sassar	struct tm *tm = localtime(&mtime);
27172445Sassar	if((t - mtime > 6*30*24*60*60) ||
27272445Sassar	   (mtime - t > 6*30*24*60*60))
27355682Smarkm	    strftime(buf, sizeof(buf), "%b %e  %Y", tm);
27455682Smarkm	else
27555682Smarkm	    strftime(buf, sizeof(buf), "%b %e %H:%M", tm);
27655682Smarkm	file->date = strdup(buf);
277178825Sdfr	if (file->date == NULL) {
278178825Sdfr	    syslog(LOG_ERR, "out of memory");
279178825Sdfr	    return -1;
280178825Sdfr	}
28155682Smarkm    }
28255682Smarkm    {
28355682Smarkm	const char *p = strrchr(filename, '/');
28455682Smarkm	if(p)
28555682Smarkm	    p++;
28655682Smarkm	else
28755682Smarkm	    p = filename;
288178825Sdfr	if((flags & LS_TYPE) && file_type != 0) {
289178825Sdfr	    if (asprintf(&file->filename, "%s%c", p, file_type) == -1)
290178825Sdfr		file->filename = NULL;
291178825Sdfr	} else
29272445Sassar	    file->filename = strdup(p);
293178825Sdfr	if (file->filename == NULL) {
294178825Sdfr	    syslog(LOG_ERR, "out of memory");
295178825Sdfr	    return -1;
296178825Sdfr	}
29755682Smarkm    }
29855682Smarkm    if(S_ISLNK(st->st_mode)) {
29955682Smarkm	int n;
300120945Snectar	n = readlink((char *)filename, buf, sizeof(buf) - 1);
30155682Smarkm	if(n >= 0) {
30255682Smarkm	    buf[n] = '\0';
30355682Smarkm	    file->link = strdup(buf);
304178825Sdfr	    if (file->link == NULL) {
305178825Sdfr		syslog(LOG_ERR, "out of memory");
306178825Sdfr		return -1;
307178825Sdfr	    }
30855682Smarkm	} else
30990926Snectar	    sec_fprintf2(out, "readlink(%s): %s", filename, strerror(errno));
31055682Smarkm    }
311178825Sdfr    return 0;
31255682Smarkm}
31355682Smarkm
31455682Smarkmstatic void
31555682Smarkmprint_file(FILE *out,
31655682Smarkm	   int flags,
31755682Smarkm	   struct fileinfo *f,
31855682Smarkm	   int max_inode,
31955682Smarkm	   int max_bsize,
32055682Smarkm	   int max_n_link,
32155682Smarkm	   int max_user,
32255682Smarkm	   int max_group,
32355682Smarkm	   int max_size,
32455682Smarkm	   int max_major,
32555682Smarkm	   int max_minor,
32655682Smarkm	   int max_date)
32755682Smarkm{
32855682Smarkm    if(f->filename == NULL)
32955682Smarkm	return;
33055682Smarkm
33155682Smarkm    if(flags & LS_INODE) {
33255682Smarkm	sec_fprintf2(out, "%*d", max_inode, f->inode);
33355682Smarkm	sec_fprintf2(out, "  ");
33455682Smarkm    }
33555682Smarkm    if(flags & LS_SIZE) {
33655682Smarkm	sec_fprintf2(out, "%*d", max_bsize, f->bsize);
33755682Smarkm	sec_fprintf2(out, "  ");
33855682Smarkm    }
33955682Smarkm    sec_fprintf2(out, "%s", f->mode);
34055682Smarkm    sec_fprintf2(out, "  ");
34155682Smarkm    sec_fprintf2(out, "%*d", max_n_link, f->n_link);
34255682Smarkm    sec_fprintf2(out, " ");
34355682Smarkm    sec_fprintf2(out, "%-*s", max_user, f->user);
34455682Smarkm    sec_fprintf2(out, "  ");
34555682Smarkm    sec_fprintf2(out, "%-*s", max_group, f->group);
34655682Smarkm    sec_fprintf2(out, "  ");
34755682Smarkm    if(f->major != NULL && f->minor != NULL)
34855682Smarkm	sec_fprintf2(out, "%*s, %*s", max_major, f->major, max_minor, f->minor);
34955682Smarkm    else
35055682Smarkm	sec_fprintf2(out, "%*s", max_size, f->size);
35155682Smarkm    sec_fprintf2(out, " ");
35255682Smarkm    sec_fprintf2(out, "%*s", max_date, f->date);
35355682Smarkm    sec_fprintf2(out, " ");
35455682Smarkm    sec_fprintf2(out, "%s", f->filename);
35555682Smarkm    if(f->link)
35655682Smarkm	sec_fprintf2(out, " -> %s", f->link);
35755682Smarkm    sec_fprintf2(out, "\r\n");
35855682Smarkm}
35955682Smarkm
36055682Smarkmstatic int
36155682Smarkmcompare_filename(struct fileinfo *a, struct fileinfo *b)
36255682Smarkm{
36355682Smarkm    if(a->filename == NULL)
36455682Smarkm	return 1;
36555682Smarkm    if(b->filename == NULL)
36655682Smarkm	return -1;
36755682Smarkm    return strcmp(a->filename, b->filename);
36855682Smarkm}
36955682Smarkm
37055682Smarkmstatic int
37155682Smarkmcompare_mtime(struct fileinfo *a, struct fileinfo *b)
37255682Smarkm{
37355682Smarkm    if(a->filename == NULL)
37455682Smarkm	return 1;
37555682Smarkm    if(b->filename == NULL)
37655682Smarkm	return -1;
37772445Sassar    return b->st.st_mtime - a->st.st_mtime;
37855682Smarkm}
37955682Smarkm
38055682Smarkmstatic int
38155682Smarkmcompare_size(struct fileinfo *a, struct fileinfo *b)
38255682Smarkm{
38355682Smarkm    if(a->filename == NULL)
38455682Smarkm	return 1;
38555682Smarkm    if(b->filename == NULL)
38655682Smarkm	return -1;
38772445Sassar    return b->st.st_size - a->st.st_size;
38855682Smarkm}
38955682Smarkm
390102644Snectarstatic int list_dir(FILE*, const char*, int);
39155682Smarkm
39255682Smarkmstatic int
393178825Sdfrfind_log10(int num)
39455682Smarkm{
39555682Smarkm    int i = 1;
39655682Smarkm    while(num > 10) {
39755682Smarkm	i++;
39855682Smarkm	num /= 10;
39955682Smarkm    }
40055682Smarkm    return i;
40155682Smarkm}
40255682Smarkm
40355682Smarkm/*
40455682Smarkm * Operate as lstat but fake up entries for AFS mount points so we don't
40555682Smarkm * have to fetch them.
40655682Smarkm */
40755682Smarkm
408233294Sstas#ifdef KRB5
40972445Sassarstatic int do_the_afs_dance = 1;
41072445Sassar#endif
41172445Sassar
41255682Smarkmstatic int
41355682Smarkmlstat_file (const char *file, struct stat *sb)
41455682Smarkm{
415233294Sstas#ifdef KRB5
41672445Sassar    if (do_the_afs_dance &&
417233294Sstas	k_hasafs()
41855682Smarkm	&& strcmp(file, ".")
41972445Sassar	&& strcmp(file, "..")
42072445Sassar	&& strcmp(file, "/"))
42155682Smarkm    {
42255682Smarkm	struct ViceIoctl    a_params;
42372445Sassar	char               *dir, *last;
42455682Smarkm	char               *path_bkp;
42555682Smarkm	static ino_t	   ino_counter = 0, ino_last = 0;
42655682Smarkm	int		   ret;
42755682Smarkm	const int	   maxsize = 2048;
428233294Sstas
42955682Smarkm	path_bkp = strdup (file);
43055682Smarkm	if (path_bkp == NULL)
43155682Smarkm	    return -1;
432233294Sstas
43355682Smarkm	a_params.out = malloc (maxsize);
434233294Sstas	if (a_params.out == NULL) {
43555682Smarkm	    free (path_bkp);
43655682Smarkm	    return -1;
43755682Smarkm	}
438233294Sstas
43955682Smarkm	/* If path contains more than the filename alone - split it */
440233294Sstas
44155682Smarkm	last = strrchr (path_bkp, '/');
44255682Smarkm	if (last != NULL) {
44372445Sassar	    if(last[1] == '\0')
44472445Sassar		/* if path ended in /, replace with `.' */
44572445Sassar		a_params.in = ".";
44672445Sassar	    else
44772445Sassar		a_params.in = last + 1;
44872445Sassar	    while(last > path_bkp && *--last == '/');
44972445Sassar	    if(*last != '/' || last != path_bkp) {
45072445Sassar		*++last = '\0';
45172445Sassar		dir = path_bkp;
45272445Sassar	    } else
45372445Sassar		/* we got to the start, so this must be the root dir */
45472445Sassar		dir = "/";
45572445Sassar	} else {
45672445Sassar	    /* file is relative to cdir */
45772445Sassar	    dir = ".";
45872445Sassar	    a_params.in = path_bkp;
45972445Sassar	}
460233294Sstas
46155682Smarkm	a_params.in_size  = strlen (a_params.in) + 1;
46255682Smarkm	a_params.out_size = maxsize;
463233294Sstas
46472445Sassar	ret = k_pioctl (dir, VIOC_AFS_STAT_MT_PT, &a_params, 0);
46555682Smarkm	free (a_params.out);
46655682Smarkm	if (ret < 0) {
46755682Smarkm	    free (path_bkp);
46855682Smarkm
46955682Smarkm	    if (errno != EINVAL)
47055682Smarkm		return ret;
47155682Smarkm	    else
47255682Smarkm		/* if we get EINVAL this is probably not a mountpoint */
47355682Smarkm		return lstat (file, sb);
47455682Smarkm	}
47555682Smarkm
476233294Sstas	/*
47755682Smarkm	 * wow this was a mountpoint, lets cook the struct stat
47855682Smarkm	 * use . as a prototype
47955682Smarkm	 */
48055682Smarkm
48172445Sassar	ret = lstat (dir, sb);
48255682Smarkm	free (path_bkp);
48355682Smarkm	if (ret < 0)
48455682Smarkm	    return ret;
48555682Smarkm
48655682Smarkm	if (ino_last == sb->st_ino)
48755682Smarkm	    ino_counter++;
48855682Smarkm	else {
48955682Smarkm	    ino_last    = sb->st_ino;
49055682Smarkm	    ino_counter = 0;
49155682Smarkm	}
49255682Smarkm	sb->st_ino += ino_counter;
49355682Smarkm	sb->st_nlink = 3;
49455682Smarkm
49555682Smarkm	return 0;
49655682Smarkm    }
497233294Sstas#endif /* KRB5 */
49855682Smarkm    return lstat (file, sb);
49955682Smarkm}
50055682Smarkm
50190926Snectar#define IS_DOT_DOTDOT(X) ((X)[0] == '.' && ((X)[1] == '\0' || \
50290926Snectar				((X)[1] == '.' && (X)[2] == '\0')))
50390926Snectar
504102644Snectarstatic int
50555682Smarkmlist_files(FILE *out, const char **files, int n_files, int flags)
50655682Smarkm{
50755682Smarkm    struct fileinfo *fi;
50855682Smarkm    int i;
50990926Snectar    int *dirs = NULL;
51090926Snectar    size_t total_blocks = 0;
51190926Snectar    int n_print = 0;
512102644Snectar    int ret = 0;
51355682Smarkm
514102644Snectar    if(n_files == 0)
515102644Snectar	return 0;
516102644Snectar
51790926Snectar    if(n_files > 1)
51890926Snectar	flags |= LS_SHOW_DIRNAME;
51990926Snectar
52055682Smarkm    fi = calloc(n_files, sizeof(*fi));
52155682Smarkm    if (fi == NULL) {
522102644Snectar	syslog(LOG_ERR, "out of memory");
523102644Snectar	return -1;
52455682Smarkm    }
52555682Smarkm    for(i = 0; i < n_files; i++) {
52655682Smarkm	if(lstat_file(files[i], &fi[i].st) < 0) {
52755682Smarkm	    sec_fprintf2(out, "%s: %s\r\n", files[i], strerror(errno));
52855682Smarkm	    fi[i].filename = NULL;
52955682Smarkm	} else {
53090926Snectar	    int include_in_list = 1;
53190926Snectar	    total_blocks += block_convert(fi[i].st.st_blocks);
53290926Snectar	    if(S_ISDIR(fi[i].st.st_mode)) {
53390926Snectar		if(dirs == NULL)
53490926Snectar		    dirs = calloc(n_files, sizeof(*dirs));
53590926Snectar		if(dirs == NULL) {
536102644Snectar		    syslog(LOG_ERR, "%s: %m", files[i]);
537102644Snectar		    ret = -1;
53890926Snectar		    goto out;
53990926Snectar		}
54090926Snectar		dirs[i] = 1;
54190926Snectar		if((flags & LS_DIRS) == 0)
54290926Snectar		    include_in_list = 0;
54355682Smarkm	    }
54490926Snectar	    if(include_in_list) {
545178825Sdfr		ret = make_fileinfo(out, files[i], &fi[i], flags);
546178825Sdfr		if (ret)
547178825Sdfr		    goto out;
54890926Snectar		n_print++;
54990926Snectar	    }
55055682Smarkm	}
55155682Smarkm    }
55255682Smarkm    switch(SORT_MODE(flags)) {
55355682Smarkm    case LS_SORT_NAME:
554233294Sstas	qsort(fi, n_files, sizeof(*fi),
55555682Smarkm	      (int (*)(const void*, const void*))compare_filename);
55655682Smarkm	break;
55755682Smarkm    case LS_SORT_MTIME:
558233294Sstas	qsort(fi, n_files, sizeof(*fi),
55955682Smarkm	      (int (*)(const void*, const void*))compare_mtime);
56055682Smarkm	break;
56155682Smarkm    case LS_SORT_SIZE:
562233294Sstas	qsort(fi, n_files, sizeof(*fi),
56355682Smarkm	      (int (*)(const void*, const void*))compare_size);
56455682Smarkm	break;
56555682Smarkm    }
56672445Sassar    if(DISP_MODE(flags) == LS_DISP_LONG) {
56755682Smarkm	int max_inode = 0;
56855682Smarkm	int max_bsize = 0;
56955682Smarkm	int max_n_link = 0;
57055682Smarkm	int max_user = 0;
57155682Smarkm	int max_group = 0;
57255682Smarkm	int max_size = 0;
57355682Smarkm	int max_major = 0;
57455682Smarkm	int max_minor = 0;
57555682Smarkm	int max_date = 0;
57655682Smarkm	for(i = 0; i < n_files; i++) {
57755682Smarkm	    if(fi[i].filename == NULL)
57855682Smarkm		continue;
57955682Smarkm	    if(fi[i].inode > max_inode)
58055682Smarkm		max_inode = fi[i].inode;
58155682Smarkm	    if(fi[i].bsize > max_bsize)
58255682Smarkm		max_bsize = fi[i].bsize;
58355682Smarkm	    if(fi[i].n_link > max_n_link)
58455682Smarkm		max_n_link = fi[i].n_link;
58555682Smarkm	    if(strlen(fi[i].user) > max_user)
58655682Smarkm		max_user = strlen(fi[i].user);
58755682Smarkm	    if(strlen(fi[i].group) > max_group)
58855682Smarkm		max_group = strlen(fi[i].group);
58955682Smarkm	    if(fi[i].major != NULL && strlen(fi[i].major) > max_major)
59055682Smarkm		max_major = strlen(fi[i].major);
59155682Smarkm	    if(fi[i].minor != NULL && strlen(fi[i].minor) > max_minor)
59255682Smarkm		max_minor = strlen(fi[i].minor);
59355682Smarkm	    if(fi[i].size != NULL && strlen(fi[i].size) > max_size)
59455682Smarkm		max_size = strlen(fi[i].size);
59555682Smarkm	    if(strlen(fi[i].date) > max_date)
59655682Smarkm		max_date = strlen(fi[i].date);
59755682Smarkm	}
59855682Smarkm	if(max_size < max_major + max_minor + 2)
59955682Smarkm	    max_size = max_major + max_minor + 2;
60055682Smarkm	else if(max_size - max_minor - 2 > max_major)
60155682Smarkm	    max_major = max_size - max_minor - 2;
602178825Sdfr	max_inode = find_log10(max_inode);
603178825Sdfr	max_bsize = find_log10(max_bsize);
604178825Sdfr	max_n_link = find_log10(max_n_link);
605233294Sstas
60690926Snectar	if(n_print > 0)
60790926Snectar	    sec_fprintf2(out, "total %lu\r\n", (unsigned long)total_blocks);
60855682Smarkm	if(flags & LS_SORT_REVERSE)
60955682Smarkm	    for(i = n_files - 1; i >= 0; i--)
61055682Smarkm		print_file(out,
61155682Smarkm			   flags,
61255682Smarkm			   &fi[i],
61355682Smarkm			   max_inode,
61455682Smarkm			   max_bsize,
61555682Smarkm			   max_n_link,
61655682Smarkm			   max_user,
61755682Smarkm			   max_group,
61855682Smarkm			   max_size,
61955682Smarkm			   max_major,
62055682Smarkm			   max_minor,
62155682Smarkm			   max_date);
62255682Smarkm	else
62355682Smarkm	    for(i = 0; i < n_files; i++)
62455682Smarkm		print_file(out,
62555682Smarkm			   flags,
62655682Smarkm			   &fi[i],
62755682Smarkm			   max_inode,
62855682Smarkm			   max_bsize,
62955682Smarkm			   max_n_link,
63055682Smarkm			   max_user,
63155682Smarkm			   max_group,
63255682Smarkm			   max_size,
63355682Smarkm			   max_major,
63455682Smarkm			   max_minor,
63555682Smarkm			   max_date);
636233294Sstas    } else if(DISP_MODE(flags) == LS_DISP_COLUMN ||
63772445Sassar	      DISP_MODE(flags) == LS_DISP_CROSS) {
63872445Sassar	int max_len = 0;
63990926Snectar	int size_len = 0;
64072445Sassar	int num_files = n_files;
64172445Sassar	int columns;
64272445Sassar	int j;
64372445Sassar	for(i = 0; i < n_files; i++) {
64472445Sassar	    if(fi[i].filename == NULL) {
64572445Sassar		num_files--;
64672445Sassar		continue;
64772445Sassar	    }
64872445Sassar	    if(strlen(fi[i].filename) > max_len)
64972445Sassar		max_len = strlen(fi[i].filename);
650178825Sdfr	    if(find_log10(fi[i].bsize) > size_len)
651178825Sdfr		size_len = find_log10(fi[i].bsize);
65272445Sassar	}
65390926Snectar	if(num_files == 0)
65490926Snectar	    goto next;
65590926Snectar	if(flags & LS_SIZE) {
65690926Snectar	    columns = 80 / (size_len + 1 + max_len + 1);
65790926Snectar	    max_len = 80 / columns - size_len - 1;
65890926Snectar	} else {
65990926Snectar	    columns = 80 / (max_len + 1); /* get space between columns */
66090926Snectar	    max_len = 80 / columns;
66190926Snectar	}
66290926Snectar	if(flags & LS_SIZE)
663233294Sstas	    sec_fprintf2(out, "total %lu\r\n",
66490926Snectar			 (unsigned long)total_blocks);
66572445Sassar	if(DISP_MODE(flags) == LS_DISP_CROSS) {
66672445Sassar	    for(i = 0, j = 0; i < n_files; i++) {
66772445Sassar		if(fi[i].filename == NULL)
66872445Sassar		    continue;
66990926Snectar		if(flags & LS_SIZE)
670233294Sstas		    sec_fprintf2(out, "%*u %-*s", size_len, fi[i].bsize,
67190926Snectar				 max_len, fi[i].filename);
67290926Snectar		else
67390926Snectar		    sec_fprintf2(out, "%-*s", max_len, fi[i].filename);
67472445Sassar		j++;
67572445Sassar		if(j == columns) {
67672445Sassar		    sec_fprintf2(out, "\r\n");
67772445Sassar		    j = 0;
67872445Sassar		}
67972445Sassar	    }
68072445Sassar	    if(j > 0)
68190926Snectar		sec_fprintf2(out, "\r\n");
68272445Sassar	} else {
68372445Sassar	    int skip = (num_files + columns - 1) / columns;
684233294Sstas
68572445Sassar	    for(i = 0; i < skip; i++) {
68672445Sassar		for(j = i; j < n_files;) {
68772445Sassar		    while(j < n_files && fi[j].filename == NULL)
68872445Sassar			j++;
68990926Snectar		    if(flags & LS_SIZE)
690233294Sstas			sec_fprintf2(out, "%*u %-*s", size_len, fi[j].bsize,
69190926Snectar				     max_len, fi[j].filename);
69290926Snectar		    else
69390926Snectar			sec_fprintf2(out, "%-*s", max_len, fi[j].filename);
69472445Sassar		    j += skip;
69572445Sassar		}
69672445Sassar		sec_fprintf2(out, "\r\n");
69772445Sassar	    }
69872445Sassar	}
69972445Sassar    } else {
70072445Sassar	for(i = 0; i < n_files; i++) {
70172445Sassar	    if(fi[i].filename == NULL)
70272445Sassar		continue;
70372445Sassar	    sec_fprintf2(out, "%s\r\n", fi[i].filename);
70472445Sassar	}
70555682Smarkm    }
70690926Snectar next:
70790926Snectar    if(((flags & LS_DIRS) == 0 || (flags & LS_RECURSIVE)) && dirs != NULL) {
70890926Snectar	for(i = 0; i < n_files; i++) {
70990926Snectar	    if(dirs[i]) {
71090926Snectar		const char *p = strrchr(files[i], '/');
71190926Snectar		if(p == NULL)
71290926Snectar		    p = files[i];
713233294Sstas		else
71490926Snectar		    p++;
71590926Snectar		if(!(flags & LS_DIR_FLAG) || !IS_DOT_DOTDOT(p)) {
71690926Snectar		    if((flags & LS_SHOW_DIRNAME)) {
71790926Snectar			if ((flags & LS_EXTRA_BLANK))
71890926Snectar			    sec_fprintf2(out, "\r\n");
71990926Snectar			sec_fprintf2(out, "%s:\r\n", files[i]);
72090926Snectar		    }
72190926Snectar		    list_dir(out, files[i], flags | LS_DIRS | LS_EXTRA_BLANK);
72290926Snectar		}
72390926Snectar	    }
72490926Snectar	}
72590926Snectar    }
72690926Snectar out:
72772445Sassar    for(i = 0; i < n_files; i++)
72872445Sassar	free_fileinfo(&fi[i]);
72972445Sassar    free(fi);
73090926Snectar    if(dirs != NULL)
73190926Snectar	free(dirs);
732102644Snectar    return ret;
73355682Smarkm}
73455682Smarkm
73555682Smarkmstatic void
73655682Smarkmfree_files (char **files, int n)
73755682Smarkm{
73855682Smarkm    int i;
73955682Smarkm
74055682Smarkm    for (i = 0; i < n; ++i)
74155682Smarkm	free (files[i]);
74255682Smarkm    free (files);
74355682Smarkm}
74455682Smarkm
74590926Snectarstatic int
74690926Snectarhide_file(const char *filename, int flags)
74790926Snectar{
74890926Snectar    if(filename[0] != '.')
74990926Snectar	return 0;
75090926Snectar    if((flags & LS_IGNORE_DOT))
75190926Snectar	return 1;
75290926Snectar    if(filename[1] == '\0' || (filename[1] == '.' && filename[2] == '\0')) {
75390926Snectar	if((flags & LS_SHOW_ALL))
75490926Snectar	    return 0;
75590926Snectar	else
75690926Snectar	    return 1;
75790926Snectar    }
75890926Snectar    return 0;
75990926Snectar}
76090926Snectar
761102644Snectarstatic int
76255682Smarkmlist_dir(FILE *out, const char *directory, int flags)
76355682Smarkm{
76455682Smarkm    DIR *d = opendir(directory);
76555682Smarkm    struct dirent *ent;
76655682Smarkm    char **files = NULL;
76755682Smarkm    int n_files = 0;
768178825Sdfr    int ret;
76955682Smarkm
77055682Smarkm    if(d == NULL) {
771102644Snectar	syslog(LOG_ERR, "%s: %m", directory);
772102644Snectar	return -1;
77355682Smarkm    }
77455682Smarkm    while((ent = readdir(d)) != NULL) {
77555682Smarkm	void *tmp;
77655682Smarkm
77790926Snectar	if(hide_file(ent->d_name, flags))
77890926Snectar	    continue;
77955682Smarkm	tmp = realloc(files, (n_files + 1) * sizeof(*files));
78055682Smarkm	if (tmp == NULL) {
781102644Snectar	    syslog(LOG_ERR, "%s: out of memory", directory);
78255682Smarkm	    free_files (files, n_files);
78355682Smarkm	    closedir (d);
784102644Snectar	    return -1;
78555682Smarkm	}
78655682Smarkm	files = tmp;
787178825Sdfr	ret = asprintf(&files[n_files], "%s/%s", directory, ent->d_name);
788178825Sdfr	if (ret == -1) {
789102644Snectar	    syslog(LOG_ERR, "%s: out of memory", directory);
79055682Smarkm	    free_files (files, n_files);
79155682Smarkm	    closedir (d);
792102644Snectar	    return -1;
79355682Smarkm	}
79455682Smarkm	++n_files;
79555682Smarkm    }
79655682Smarkm    closedir(d);
797102644Snectar    return list_files(out, (const char**)files, n_files, flags | LS_DIR_FLAG);
79855682Smarkm}
79955682Smarkm
80090926Snectarstatic int
80190926Snectarparse_flags(const char *options)
80290926Snectar{
80390926Snectar#ifdef TEST
80490926Snectar    int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_COLUMN;
80590926Snectar#else
80690926Snectar    int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_LONG;
80790926Snectar#endif
80890926Snectar
80990926Snectar    const char *p;
81090926Snectar    if(options == NULL || *options != '-')
81190926Snectar	return flags;
81290926Snectar    for(p = options + 1; *p; p++) {
81390926Snectar	switch(*p) {
81490926Snectar	case '1':
81590926Snectar	    flags = (flags & ~LS_DISP_MODE);
81690926Snectar	    break;
81790926Snectar	case 'a':
81890926Snectar	    flags |= LS_SHOW_ALL;
81990926Snectar	    /*FALLTHROUGH*/
82090926Snectar	case 'A':
82190926Snectar	    flags &= ~LS_IGNORE_DOT;
82290926Snectar	    break;
82390926Snectar	case 'C':
82490926Snectar	    flags = (flags & ~LS_DISP_MODE) | LS_DISP_COLUMN;
82590926Snectar	    break;
82690926Snectar	case 'd':
82790926Snectar	    flags |= LS_DIRS;
82890926Snectar	    break;
82990926Snectar	case 'f':
83090926Snectar	    flags = (flags & ~LS_SORT_MODE);
83190926Snectar	    break;
83290926Snectar	case 'F':
83390926Snectar	    flags |= LS_TYPE;
83490926Snectar	    break;
83590926Snectar	case 'i':
83690926Snectar	    flags |= LS_INODE;
83790926Snectar	    break;
83890926Snectar	case 'l':
83990926Snectar	    flags = (flags & ~LS_DISP_MODE) | LS_DISP_LONG;
84090926Snectar	    break;
84190926Snectar	case 'r':
84290926Snectar	    flags |= LS_SORT_REVERSE;
84390926Snectar	    break;
84490926Snectar	case 'R':
84590926Snectar	    flags |= LS_RECURSIVE;
84690926Snectar	    break;
84790926Snectar	case 's':
84890926Snectar	    flags |= LS_SIZE;
84990926Snectar	    break;
85090926Snectar	case 'S':
85190926Snectar	    flags = (flags & ~LS_SORT_MODE) | LS_SORT_SIZE;
85290926Snectar	    break;
85390926Snectar	case 't':
85490926Snectar	    flags = (flags & ~LS_SORT_MODE) | LS_SORT_MTIME;
85590926Snectar	    break;
85690926Snectar	case 'x':
85790926Snectar	    flags = (flags & ~LS_DISP_MODE) | LS_DISP_CROSS;
85890926Snectar	    break;
85990926Snectar	    /* these are a bunch of unimplemented flags from BSD ls */
86090926Snectar	case 'k': /* display sizes in kB */
86190926Snectar	case 'c': /* last change time */
86290926Snectar	case 'L': /* list symlink target */
86390926Snectar	case 'm': /* stream output */
86490926Snectar	case 'o': /* BSD file flags */
86590926Snectar	case 'p': /* display / after directories */
86690926Snectar	case 'q': /* print non-graphic characters */
86790926Snectar	case 'u': /* use last access time */
86890926Snectar	case 'T': /* display complete time */
86990926Snectar	case 'W': /* include whiteouts */
87090926Snectar	    break;
87190926Snectar	}
87290926Snectar    }
87390926Snectar    return flags;
87490926Snectar}
87590926Snectar
876102644Snectarint
87755682Smarkmbuiltin_ls(FILE *out, const char *file)
87855682Smarkm{
87990926Snectar    int flags;
880102644Snectar    int ret;
88155682Smarkm
88255682Smarkm    if(*file == '-') {
88390926Snectar	flags = parse_flags(file);
88455682Smarkm	file = ".";
88590926Snectar    } else
88690926Snectar	flags = parse_flags("");
88790926Snectar
888102644Snectar    ret = list_files(out, &file, 1, flags);
88955682Smarkm    sec_fflush(out);
890102644Snectar    return ret;
89155682Smarkm}
892