2 * Copyright (c) 1999 - 2002 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
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.
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.
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. */
34 #include "ftpd_locl.h"
36 RCSID("$Id: ls.c,v 1.26 2003/02/25 10:51:30 lha Exp $");
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);
56 main(int argc, char **argv)
60 if(argc > 1 && argv[1][0] == '-') {
61 flags = parse_flags(argv[1]);
64 flags = parse_flags(NULL);
66 list_files(stdout, (const char **)argv + i, argc - i, flags);
88 free_fileinfo(struct fileinfo *f)
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)
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 */
124 #define S_ISTXT S_ISVTX
127 #if !defined(_S_IFMT) && defined(S_IFMT)
128 #define _S_IFMT S_IFMT
132 #define S_ISSOCK(mode) (((mode) & _S_IFMT) == S_IFSOCK)
136 #define S_ISLNK(mode) (((mode) & _S_IFMT) == S_IFLNK)
140 block_convert(size_t blocks)
143 return blocks * S_BLKSIZE / 1024;
145 return blocks * 512 / 1024;
150 make_fileinfo(FILE *out, const char *filename, struct fileinfo *file, int flags)
154 struct stat *st = &file->st;
156 file->inode = st->st_ino;
157 file->bsize = block_convert(st->st_blocks);
159 if(S_ISDIR(st->st_mode)) {
163 else if(S_ISCHR(st->st_mode))
165 else if(S_ISBLK(st->st_mode))
167 else if(S_ISREG(st->st_mode)) {
169 if(st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
172 else if(S_ISFIFO(st->st_mode)) {
176 else if(S_ISLNK(st->st_mode)) {
180 else if(S_ISSOCK(st->st_mode)) {
185 else if(S_ISWHT(st->st_mode)) {
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))
204 if((st->st_mode & S_ISGID)) {
205 if((st->st_mode & S_IXGRP))
210 if((st->st_mode & S_ISTXT)) {
211 if((st->st_mode & S_IXOTH))
217 file->n_link = st->st_nlink;
220 pwd = getpwuid(st->st_uid);
222 asprintf(&file->user, "%u", (unsigned)st->st_uid);
224 file->user = strdup(pwd->pw_name);
228 grp = getgrgid(st->st_gid);
230 asprintf(&file->group, "%u", (unsigned)st->st_gid);
232 file->group = strdup(grp->gr_name);
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));
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);
245 asprintf(&file->size, "%lu", (unsigned long)st->st_size);
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);
255 strftime(buf, sizeof(buf), "%b %e %H:%M", tm);
256 file->date = strdup(buf);
259 const char *p = strrchr(filename, '/');
264 if((flags & LS_TYPE) && file_type != 0)
265 asprintf(&file->filename, "%s%c", p, file_type);
267 file->filename = strdup(p);
269 if(S_ISLNK(st->st_mode)) {
271 n = readlink((char *)filename, buf, sizeof(buf) - 1);
274 file->link = strdup(buf);
276 sec_fprintf2(out, "readlink(%s): %s", filename, strerror(errno));
281 print_file(FILE *out,
294 if(f->filename == NULL)
297 if(flags & LS_INODE) {
298 sec_fprintf2(out, "%*d", max_inode, f->inode);
299 sec_fprintf2(out, " ");
301 if(flags & LS_SIZE) {
302 sec_fprintf2(out, "%*d", max_bsize, f->bsize);
303 sec_fprintf2(out, " ");
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);
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);
322 sec_fprintf2(out, " -> %s", f->link);
323 sec_fprintf2(out, "\r\n");
327 compare_filename(struct fileinfo *a, struct fileinfo *b)
329 if(a->filename == NULL)
331 if(b->filename == NULL)
333 return strcmp(a->filename, b->filename);
337 compare_mtime(struct fileinfo *a, struct fileinfo *b)
339 if(a->filename == NULL)
341 if(b->filename == NULL)
343 return b->st.st_mtime - a->st.st_mtime;
347 compare_size(struct fileinfo *a, struct fileinfo *b)
349 if(a->filename == NULL)
351 if(b->filename == NULL)
353 return b->st.st_size - a->st.st_size;
356 static int list_dir(FILE*, const char*, int);
370 * Operate as lstat but fake up entries for AFS mount points so we don't
371 * have to fetch them.
375 static int do_the_afs_dance = 1;
379 lstat_file (const char *file, struct stat *sb)
382 if (do_the_afs_dance &&
385 && strcmp(file, "..")
386 && strcmp(file, "/"))
388 struct ViceIoctl a_params;
391 static ino_t ino_counter = 0, ino_last = 0;
393 const int maxsize = 2048;
395 path_bkp = strdup (file);
396 if (path_bkp == NULL)
399 a_params.out = malloc (maxsize);
400 if (a_params.out == NULL) {
405 /* If path contains more than the filename alone - split it */
407 last = strrchr (path_bkp, '/');
410 /* if path ended in /, replace with `.' */
413 a_params.in = last + 1;
414 while(last > path_bkp && *--last == '/');
415 if(*last != '/' || last != path_bkp) {
419 /* we got to the start, so this must be the root dir */
422 /* file is relative to cdir */
424 a_params.in = path_bkp;
427 a_params.in_size = strlen (a_params.in) + 1;
428 a_params.out_size = maxsize;
430 ret = k_pioctl (dir, VIOC_AFS_STAT_MT_PT, &a_params, 0);
438 /* if we get EINVAL this is probably not a mountpoint */
439 return lstat (file, sb);
443 * wow this was a mountpoint, lets cook the struct stat
444 * use . as a prototype
447 ret = lstat (dir, sb);
452 if (ino_last == sb->st_ino)
455 ino_last = sb->st_ino;
458 sb->st_ino += ino_counter;
464 return lstat (file, sb);
467 #define IS_DOT_DOTDOT(X) ((X)[0] == '.' && ((X)[1] == '\0' || \
468 ((X)[1] == '.' && (X)[2] == '\0')))
471 list_files(FILE *out, const char **files, int n_files, int flags)
476 size_t total_blocks = 0;
484 flags |= LS_SHOW_DIRNAME;
486 fi = calloc(n_files, sizeof(*fi));
488 syslog(LOG_ERR, "out of memory");
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;
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)) {
500 dirs = calloc(n_files, sizeof(*dirs));
502 syslog(LOG_ERR, "%s: %m", files[i]);
507 if((flags & LS_DIRS) == 0)
510 if(include_in_list) {
511 make_fileinfo(out, files[i], &fi[i], flags);
516 switch(SORT_MODE(flags)) {
518 qsort(fi, n_files, sizeof(*fi),
519 (int (*)(const void*, const void*))compare_filename);
522 qsort(fi, n_files, sizeof(*fi),
523 (int (*)(const void*, const void*))compare_mtime);
526 qsort(fi, n_files, sizeof(*fi),
527 (int (*)(const void*, const void*))compare_size);
530 if(DISP_MODE(flags) == LS_DISP_LONG) {
540 for(i = 0; i < n_files; i++) {
541 if(fi[i].filename == NULL)
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);
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);
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--)
587 for(i = 0; i < n_files; i++)
600 } else if(DISP_MODE(flags) == LS_DISP_COLUMN ||
601 DISP_MODE(flags) == LS_DISP_CROSS) {
604 int num_files = n_files;
607 for(i = 0; i < n_files; i++) {
608 if(fi[i].filename == NULL) {
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);
619 if(flags & LS_SIZE) {
620 columns = 80 / (size_len + 1 + max_len + 1);
621 max_len = 80 / columns - size_len - 1;
623 columns = 80 / (max_len + 1); /* get space between columns */
624 max_len = 80 / columns;
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)
634 sec_fprintf2(out, "%*u %-*s", size_len, fi[i].bsize,
635 max_len, fi[i].filename);
637 sec_fprintf2(out, "%-*s", max_len, fi[i].filename);
640 sec_fprintf2(out, "\r\n");
645 sec_fprintf2(out, "\r\n");
647 int skip = (num_files + columns - 1) / columns;
649 for(i = 0; i < skip; i++) {
650 for(j = i; j < n_files;) {
651 while(j < n_files && fi[j].filename == NULL)
654 sec_fprintf2(out, "%*u %-*s", size_len, fi[j].bsize,
655 max_len, fi[j].filename);
657 sec_fprintf2(out, "%-*s", max_len, fi[j].filename);
660 sec_fprintf2(out, "\r\n");
664 for(i = 0; i < n_files; i++) {
665 if(fi[i].filename == NULL)
667 sec_fprintf2(out, "%s\r\n", fi[i].filename);
671 if(((flags & LS_DIRS) == 0 || (flags & LS_RECURSIVE)) && dirs != NULL) {
672 for(i = 0; i < n_files; i++) {
674 const char *p = strrchr(files[i], '/');
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]);
685 list_dir(out, files[i], flags | LS_DIRS | LS_EXTRA_BLANK);
691 for(i = 0; i < n_files; i++)
692 free_fileinfo(&fi[i]);
700 free_files (char **files, int n)
704 for (i = 0; i < n; ++i)
710 hide_file(const char *filename, int flags)
712 if(filename[0] != '.')
714 if((flags & LS_IGNORE_DOT))
716 if(filename[1] == '\0' || (filename[1] == '.' && filename[2] == '\0')) {
717 if((flags & LS_SHOW_ALL))
726 list_dir(FILE *out, const char *directory, int flags)
728 DIR *d = opendir(directory);
734 syslog(LOG_ERR, "%s: %m", directory);
737 while((ent = readdir(d)) != NULL) {
740 if(hide_file(ent->d_name, flags))
742 tmp = realloc(files, (n_files + 1) * sizeof(*files));
744 syslog(LOG_ERR, "%s: out of memory", directory);
745 free_files (files, n_files);
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);
760 return list_files(out, (const char**)files, n_files, flags | LS_DIR_FLAG);
764 parse_flags(const char *options)
767 int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_COLUMN;
769 int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_LONG;
773 if(options == NULL || *options != '-')
775 for(p = options + 1; *p; p++) {
778 flags = (flags & ~LS_DISP_MODE);
781 flags |= LS_SHOW_ALL;
784 flags &= ~LS_IGNORE_DOT;
787 flags = (flags & ~LS_DISP_MODE) | LS_DISP_COLUMN;
793 flags = (flags & ~LS_SORT_MODE);
802 flags = (flags & ~LS_DISP_MODE) | LS_DISP_LONG;
805 flags |= LS_SORT_REVERSE;
808 flags |= LS_RECURSIVE;
814 flags = (flags & ~LS_SORT_MODE) | LS_SORT_SIZE;
817 flags = (flags & ~LS_SORT_MODE) | LS_SORT_MTIME;
820 flags = (flags & ~LS_DISP_MODE) | LS_DISP_CROSS;
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 */
840 builtin_ls(FILE *out, const char *file)
846 flags = parse_flags(file);
849 flags = parse_flags("");
851 ret = list_files(out, &file, 1, flags);