Path: blob/main/crypto/heimdal/appl/ftp/ftpd/ls.c
107833 views
/*1* Copyright (c) 1999 - 2002 Kungliga Tekniska Högskolan2* (Royal Institute of Technology, Stockholm, Sweden).3* All rights reserved.4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8*9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer.11*12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in the14* documentation and/or other materials provided with the distribution.15*16* 3. Neither the name of KTH nor the names of its contributors may be17* used to endorse or promote products derived from this software without18* specific prior written permission.19*20* THIS SOFTWARE IS PROVIDED BY KTH AND ITS CONTRIBUTORS ``AS IS'' AND ANY21* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE22* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR23* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KTH OR ITS CONTRIBUTORS BE24* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR25* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF26* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR27* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,28* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR29* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF30* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */3132#ifndef TEST33#include "ftpd_locl.h"3435RCSID("$Id$");3637#else38#include <stdio.h>39#include <string.h>40#include <stdlib.h>41#include <time.h>42#include <dirent.h>43#include <sys/stat.h>44#include <unistd.h>45#include <pwd.h>46#include <grp.h>47#include <errno.h>4849#define sec_fprintf2 fprintf50#define sec_fflush fflush51static void list_files(FILE *out, const char **files, int n_files, int flags);52static int parse_flags(const char *options);5354int55main(int argc, char **argv)56{57int i = 1;58int flags;59if(argc > 1 && argv[1][0] == '-') {60flags = parse_flags(argv[1]);61i = 2;62} else63flags = parse_flags(NULL);6465list_files(stdout, (const char **)argv + i, argc - i, flags);66return 0;67}68#endif6970struct fileinfo {71struct stat st;72int inode;73int bsize;74char mode[11];75int n_link;76char *user;77char *group;78char *size;79char *major;80char *minor;81char *date;82char *filename;83char *link;84};8586static void87free_fileinfo(struct fileinfo *f)88{89free(f->user);90free(f->group);91free(f->size);92free(f->major);93free(f->minor);94free(f->date);95free(f->filename);96free(f->link);97}9899#define LS_DIRS (1 << 0)100#define LS_IGNORE_DOT (1 << 1)101#define LS_SORT_MODE (3 << 2)102#define SORT_MODE(f) ((f) & LS_SORT_MODE)103#define LS_SORT_NAME (1 << 2)104#define LS_SORT_MTIME (2 << 2)105#define LS_SORT_SIZE (3 << 2)106#define LS_SORT_REVERSE (1 << 4)107108#define LS_SIZE (1 << 5)109#define LS_INODE (1 << 6)110#define LS_TYPE (1 << 7)111#define LS_DISP_MODE (3 << 8)112#define DISP_MODE(f) ((f) & LS_DISP_MODE)113#define LS_DISP_LONG (1 << 8)114#define LS_DISP_COLUMN (2 << 8)115#define LS_DISP_CROSS (3 << 8)116#define LS_SHOW_ALL (1 << 10)117#define LS_RECURSIVE (1 << 11)118#define LS_EXTRA_BLANK (1 << 12)119#define LS_SHOW_DIRNAME (1 << 13)120#define LS_DIR_FLAG (1 << 14) /* these files come via list_dir */121122#ifndef S_ISTXT123#define S_ISTXT S_ISVTX124#endif125126#if !defined(_S_IFMT) && defined(S_IFMT)127#define _S_IFMT S_IFMT128#endif129130#ifndef S_ISSOCK131#define S_ISSOCK(mode) (((mode) & _S_IFMT) == S_IFSOCK)132#endif133134#ifndef S_ISLNK135#define S_ISLNK(mode) (((mode) & _S_IFMT) == S_IFLNK)136#endif137138static size_t139block_convert(size_t blocks)140{141#ifdef S_BLKSIZE142return blocks * S_BLKSIZE / 1024;143#else144return blocks * 512 / 1024;145#endif146}147148static int149make_fileinfo(FILE *out, const char *filename, struct fileinfo *file, int flags)150{151char buf[128];152int file_type = 0;153struct stat *st = &file->st;154155file->inode = st->st_ino;156file->bsize = block_convert(st->st_blocks);157158if(S_ISDIR(st->st_mode)) {159file->mode[0] = 'd';160file_type = '/';161}162else if(S_ISCHR(st->st_mode))163file->mode[0] = 'c';164else if(S_ISBLK(st->st_mode))165file->mode[0] = 'b';166else if(S_ISREG(st->st_mode)) {167file->mode[0] = '-';168if(st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))169file_type = '*';170}171else if(S_ISFIFO(st->st_mode)) {172file->mode[0] = 'p';173file_type = '|';174}175else if(S_ISLNK(st->st_mode)) {176file->mode[0] = 'l';177file_type = '@';178}179else if(S_ISSOCK(st->st_mode)) {180file->mode[0] = 's';181file_type = '=';182}183#ifdef S_ISWHT184else if(S_ISWHT(st->st_mode)) {185file->mode[0] = 'w';186file_type = '%';187}188#endif189else190file->mode[0] = '?';191{192char *x[] = { "---", "--x", "-w-", "-wx",193"r--", "r-x", "rw-", "rwx" };194strcpy(file->mode + 1, x[(st->st_mode & S_IRWXU) >> 6]);195strcpy(file->mode + 4, x[(st->st_mode & S_IRWXG) >> 3]);196strcpy(file->mode + 7, x[(st->st_mode & S_IRWXO) >> 0]);197if((st->st_mode & S_ISUID)) {198if((st->st_mode & S_IXUSR))199file->mode[3] = 's';200else201file->mode[3] = 'S';202}203if((st->st_mode & S_ISGID)) {204if((st->st_mode & S_IXGRP))205file->mode[6] = 's';206else207file->mode[6] = 'S';208}209if((st->st_mode & S_ISTXT)) {210if((st->st_mode & S_IXOTH))211file->mode[9] = 't';212else213file->mode[9] = 'T';214}215}216file->n_link = st->st_nlink;217{218struct passwd *pwd;219pwd = getpwuid(st->st_uid);220if(pwd == NULL) {221if (asprintf(&file->user, "%u", (unsigned)st->st_uid) == -1)222file->user = NULL;223} else224file->user = strdup(pwd->pw_name);225if (file->user == NULL) {226syslog(LOG_ERR, "out of memory");227return -1;228}229}230{231struct group *grp;232grp = getgrgid(st->st_gid);233if(grp == NULL) {234if (asprintf(&file->group, "%u", (unsigned)st->st_gid) == -1)235file->group = NULL;236} else237file->group = strdup(grp->gr_name);238if (file->group == NULL) {239syslog(LOG_ERR, "out of memory");240return -1;241}242}243244if(S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {245#if defined(major) && defined(minor)246if (asprintf(&file->major, "%u", (unsigned)major(st->st_rdev)) == -1)247file->major = NULL;248if (asprintf(&file->minor, "%u", (unsigned)minor(st->st_rdev)) == -1)249file->minor = NULL;250#else251/* Don't want to use the DDI/DKI crap. */252if (asprintf(&file->major, "%u", (unsigned)st->st_rdev) == -1)253file->major = NULL;254if (asprintf(&file->minor, "%u", 0) == -1)255file->minor = NULL;256#endif257if (file->major == NULL || file->minor == NULL) {258syslog(LOG_ERR, "out of memory");259return -1;260}261} else {262if (asprintf(&file->size, "%lu", (unsigned long)st->st_size) == -1)263file->size = NULL;264}265266{267time_t t = time(NULL);268time_t mtime = st->st_mtime;269struct tm *tm = localtime(&mtime);270if((t - mtime > 6*30*24*60*60) ||271(mtime - t > 6*30*24*60*60))272strftime(buf, sizeof(buf), "%b %e %Y", tm);273else274strftime(buf, sizeof(buf), "%b %e %H:%M", tm);275file->date = strdup(buf);276if (file->date == NULL) {277syslog(LOG_ERR, "out of memory");278return -1;279}280}281{282const char *p = strrchr(filename, '/');283if(p)284p++;285else286p = filename;287if((flags & LS_TYPE) && file_type != 0) {288if (asprintf(&file->filename, "%s%c", p, file_type) == -1)289file->filename = NULL;290} else291file->filename = strdup(p);292if (file->filename == NULL) {293syslog(LOG_ERR, "out of memory");294return -1;295}296}297if(S_ISLNK(st->st_mode)) {298int n;299n = readlink((char *)filename, buf, sizeof(buf) - 1);300if(n >= 0) {301buf[n] = '\0';302file->link = strdup(buf);303if (file->link == NULL) {304syslog(LOG_ERR, "out of memory");305return -1;306}307} else308sec_fprintf2(out, "readlink(%s): %s", filename, strerror(errno));309}310return 0;311}312313static void314print_file(FILE *out,315int flags,316struct fileinfo *f,317int max_inode,318int max_bsize,319int max_n_link,320int max_user,321int max_group,322int max_size,323int max_major,324int max_minor,325int max_date)326{327if(f->filename == NULL)328return;329330if(flags & LS_INODE) {331sec_fprintf2(out, "%*d", max_inode, f->inode);332sec_fprintf2(out, " ");333}334if(flags & LS_SIZE) {335sec_fprintf2(out, "%*d", max_bsize, f->bsize);336sec_fprintf2(out, " ");337}338sec_fprintf2(out, "%s", f->mode);339sec_fprintf2(out, " ");340sec_fprintf2(out, "%*d", max_n_link, f->n_link);341sec_fprintf2(out, " ");342sec_fprintf2(out, "%-*s", max_user, f->user);343sec_fprintf2(out, " ");344sec_fprintf2(out, "%-*s", max_group, f->group);345sec_fprintf2(out, " ");346if(f->major != NULL && f->minor != NULL)347sec_fprintf2(out, "%*s, %*s", max_major, f->major, max_minor, f->minor);348else349sec_fprintf2(out, "%*s", max_size, f->size);350sec_fprintf2(out, " ");351sec_fprintf2(out, "%*s", max_date, f->date);352sec_fprintf2(out, " ");353sec_fprintf2(out, "%s", f->filename);354if(f->link)355sec_fprintf2(out, " -> %s", f->link);356sec_fprintf2(out, "\r\n");357}358359static int360compare_filename(struct fileinfo *a, struct fileinfo *b)361{362if(a->filename == NULL)363return 1;364if(b->filename == NULL)365return -1;366return strcmp(a->filename, b->filename);367}368369static int370compare_mtime(struct fileinfo *a, struct fileinfo *b)371{372if(a->filename == NULL)373return 1;374if(b->filename == NULL)375return -1;376return b->st.st_mtime - a->st.st_mtime;377}378379static int380compare_size(struct fileinfo *a, struct fileinfo *b)381{382if(a->filename == NULL)383return 1;384if(b->filename == NULL)385return -1;386return b->st.st_size - a->st.st_size;387}388389static int list_dir(FILE*, const char*, int);390391static int392find_log10(int num)393{394int i = 1;395while(num > 10) {396i++;397num /= 10;398}399return i;400}401402/*403* Operate as lstat but fake up entries for AFS mount points so we don't404* have to fetch them.405*/406407#ifdef KRB5408static int do_the_afs_dance = 1;409#endif410411static int412lstat_file (const char *file, struct stat *sb)413{414#ifdef KRB5415if (do_the_afs_dance &&416k_hasafs()417&& strcmp(file, ".")418&& strcmp(file, "..")419&& strcmp(file, "/"))420{421struct ViceIoctl a_params;422char *dir, *last;423char *path_bkp;424static ino_t ino_counter = 0, ino_last = 0;425int ret;426const int maxsize = 2048;427428path_bkp = strdup (file);429if (path_bkp == NULL)430return -1;431432a_params.out = malloc (maxsize);433if (a_params.out == NULL) {434free (path_bkp);435return -1;436}437438/* If path contains more than the filename alone - split it */439440last = strrchr (path_bkp, '/');441if (last != NULL) {442if(last[1] == '\0')443/* if path ended in /, replace with `.' */444a_params.in = ".";445else446a_params.in = last + 1;447while(last > path_bkp && *--last == '/');448if(*last != '/' || last != path_bkp) {449*++last = '\0';450dir = path_bkp;451} else452/* we got to the start, so this must be the root dir */453dir = "/";454} else {455/* file is relative to cdir */456dir = ".";457a_params.in = path_bkp;458}459460a_params.in_size = strlen (a_params.in) + 1;461a_params.out_size = maxsize;462463ret = k_pioctl (dir, VIOC_AFS_STAT_MT_PT, &a_params, 0);464free (a_params.out);465if (ret < 0) {466free (path_bkp);467468if (errno != EINVAL)469return ret;470else471/* if we get EINVAL this is probably not a mountpoint */472return lstat (file, sb);473}474475/*476* wow this was a mountpoint, lets cook the struct stat477* use . as a prototype478*/479480ret = lstat (dir, sb);481free (path_bkp);482if (ret < 0)483return ret;484485if (ino_last == sb->st_ino)486ino_counter++;487else {488ino_last = sb->st_ino;489ino_counter = 0;490}491sb->st_ino += ino_counter;492sb->st_nlink = 3;493494return 0;495}496#endif /* KRB5 */497return lstat (file, sb);498}499500#define IS_DOT_DOTDOT(X) ((X)[0] == '.' && ((X)[1] == '\0' || \501((X)[1] == '.' && (X)[2] == '\0')))502503static int504list_files(FILE *out, const char **files, int n_files, int flags)505{506struct fileinfo *fi;507int i;508int *dirs = NULL;509size_t total_blocks = 0;510int n_print = 0;511int ret = 0;512513if(n_files == 0)514return 0;515516if(n_files > 1)517flags |= LS_SHOW_DIRNAME;518519fi = calloc(n_files, sizeof(*fi));520if (fi == NULL) {521syslog(LOG_ERR, "out of memory");522return -1;523}524for(i = 0; i < n_files; i++) {525if(lstat_file(files[i], &fi[i].st) < 0) {526sec_fprintf2(out, "%s: %s\r\n", files[i], strerror(errno));527fi[i].filename = NULL;528} else {529int include_in_list = 1;530total_blocks += block_convert(fi[i].st.st_blocks);531if(S_ISDIR(fi[i].st.st_mode)) {532if(dirs == NULL)533dirs = calloc(n_files, sizeof(*dirs));534if(dirs == NULL) {535syslog(LOG_ERR, "%s: %m", files[i]);536ret = -1;537goto out;538}539dirs[i] = 1;540if((flags & LS_DIRS) == 0)541include_in_list = 0;542}543if(include_in_list) {544ret = make_fileinfo(out, files[i], &fi[i], flags);545if (ret)546goto out;547n_print++;548}549}550}551switch(SORT_MODE(flags)) {552case LS_SORT_NAME:553qsort(fi, n_files, sizeof(*fi),554(int (*)(const void*, const void*))compare_filename);555break;556case LS_SORT_MTIME:557qsort(fi, n_files, sizeof(*fi),558(int (*)(const void*, const void*))compare_mtime);559break;560case LS_SORT_SIZE:561qsort(fi, n_files, sizeof(*fi),562(int (*)(const void*, const void*))compare_size);563break;564}565if(DISP_MODE(flags) == LS_DISP_LONG) {566int max_inode = 0;567int max_bsize = 0;568int max_n_link = 0;569int max_user = 0;570int max_group = 0;571int max_size = 0;572int max_major = 0;573int max_minor = 0;574int max_date = 0;575for(i = 0; i < n_files; i++) {576if(fi[i].filename == NULL)577continue;578if(fi[i].inode > max_inode)579max_inode = fi[i].inode;580if(fi[i].bsize > max_bsize)581max_bsize = fi[i].bsize;582if(fi[i].n_link > max_n_link)583max_n_link = fi[i].n_link;584if(strlen(fi[i].user) > max_user)585max_user = strlen(fi[i].user);586if(strlen(fi[i].group) > max_group)587max_group = strlen(fi[i].group);588if(fi[i].major != NULL && strlen(fi[i].major) > max_major)589max_major = strlen(fi[i].major);590if(fi[i].minor != NULL && strlen(fi[i].minor) > max_minor)591max_minor = strlen(fi[i].minor);592if(fi[i].size != NULL && strlen(fi[i].size) > max_size)593max_size = strlen(fi[i].size);594if(strlen(fi[i].date) > max_date)595max_date = strlen(fi[i].date);596}597if(max_size < max_major + max_minor + 2)598max_size = max_major + max_minor + 2;599else if(max_size - max_minor - 2 > max_major)600max_major = max_size - max_minor - 2;601max_inode = find_log10(max_inode);602max_bsize = find_log10(max_bsize);603max_n_link = find_log10(max_n_link);604605if(n_print > 0)606sec_fprintf2(out, "total %lu\r\n", (unsigned long)total_blocks);607if(flags & LS_SORT_REVERSE)608for(i = n_files - 1; i >= 0; i--)609print_file(out,610flags,611&fi[i],612max_inode,613max_bsize,614max_n_link,615max_user,616max_group,617max_size,618max_major,619max_minor,620max_date);621else622for(i = 0; i < n_files; i++)623print_file(out,624flags,625&fi[i],626max_inode,627max_bsize,628max_n_link,629max_user,630max_group,631max_size,632max_major,633max_minor,634max_date);635} else if(DISP_MODE(flags) == LS_DISP_COLUMN ||636DISP_MODE(flags) == LS_DISP_CROSS) {637int max_len = 0;638int size_len = 0;639int num_files = n_files;640int columns;641int j;642for(i = 0; i < n_files; i++) {643if(fi[i].filename == NULL) {644num_files--;645continue;646}647if(strlen(fi[i].filename) > max_len)648max_len = strlen(fi[i].filename);649if(find_log10(fi[i].bsize) > size_len)650size_len = find_log10(fi[i].bsize);651}652if(num_files == 0)653goto next;654if(flags & LS_SIZE) {655columns = 80 / (size_len + 1 + max_len + 1);656max_len = 80 / columns - size_len - 1;657} else {658columns = 80 / (max_len + 1); /* get space between columns */659max_len = 80 / columns;660}661if(flags & LS_SIZE)662sec_fprintf2(out, "total %lu\r\n",663(unsigned long)total_blocks);664if(DISP_MODE(flags) == LS_DISP_CROSS) {665for(i = 0, j = 0; i < n_files; i++) {666if(fi[i].filename == NULL)667continue;668if(flags & LS_SIZE)669sec_fprintf2(out, "%*u %-*s", size_len, fi[i].bsize,670max_len, fi[i].filename);671else672sec_fprintf2(out, "%-*s", max_len, fi[i].filename);673j++;674if(j == columns) {675sec_fprintf2(out, "\r\n");676j = 0;677}678}679if(j > 0)680sec_fprintf2(out, "\r\n");681} else {682int skip = (num_files + columns - 1) / columns;683684for(i = 0; i < skip; i++) {685for(j = i; j < n_files;) {686while(j < n_files && fi[j].filename == NULL)687j++;688if(flags & LS_SIZE)689sec_fprintf2(out, "%*u %-*s", size_len, fi[j].bsize,690max_len, fi[j].filename);691else692sec_fprintf2(out, "%-*s", max_len, fi[j].filename);693j += skip;694}695sec_fprintf2(out, "\r\n");696}697}698} else {699for(i = 0; i < n_files; i++) {700if(fi[i].filename == NULL)701continue;702sec_fprintf2(out, "%s\r\n", fi[i].filename);703}704}705next:706if(((flags & LS_DIRS) == 0 || (flags & LS_RECURSIVE)) && dirs != NULL) {707for(i = 0; i < n_files; i++) {708if(dirs[i]) {709const char *p = strrchr(files[i], '/');710if(p == NULL)711p = files[i];712else713p++;714if(!(flags & LS_DIR_FLAG) || !IS_DOT_DOTDOT(p)) {715if((flags & LS_SHOW_DIRNAME)) {716if ((flags & LS_EXTRA_BLANK))717sec_fprintf2(out, "\r\n");718sec_fprintf2(out, "%s:\r\n", files[i]);719}720list_dir(out, files[i], flags | LS_DIRS | LS_EXTRA_BLANK);721}722}723}724}725out:726for(i = 0; i < n_files; i++)727free_fileinfo(&fi[i]);728free(fi);729if(dirs != NULL)730free(dirs);731return ret;732}733734static void735free_files (char **files, int n)736{737int i;738739for (i = 0; i < n; ++i)740free (files[i]);741free (files);742}743744static int745hide_file(const char *filename, int flags)746{747if(filename[0] != '.')748return 0;749if((flags & LS_IGNORE_DOT))750return 1;751if(filename[1] == '\0' || (filename[1] == '.' && filename[2] == '\0')) {752if((flags & LS_SHOW_ALL))753return 0;754else755return 1;756}757return 0;758}759760static int761list_dir(FILE *out, const char *directory, int flags)762{763DIR *d = opendir(directory);764struct dirent *ent;765char **files = NULL;766int n_files = 0;767int ret;768769if(d == NULL) {770syslog(LOG_ERR, "%s: %m", directory);771return -1;772}773while((ent = readdir(d)) != NULL) {774void *tmp;775776if(hide_file(ent->d_name, flags))777continue;778tmp = realloc(files, (n_files + 1) * sizeof(*files));779if (tmp == NULL) {780syslog(LOG_ERR, "%s: out of memory", directory);781free_files (files, n_files);782closedir (d);783return -1;784}785files = tmp;786ret = asprintf(&files[n_files], "%s/%s", directory, ent->d_name);787if (ret == -1) {788syslog(LOG_ERR, "%s: out of memory", directory);789free_files (files, n_files);790closedir (d);791return -1;792}793++n_files;794}795closedir(d);796return list_files(out, (const char**)files, n_files, flags | LS_DIR_FLAG);797}798799static int800parse_flags(const char *options)801{802#ifdef TEST803int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_COLUMN;804#else805int flags = LS_SORT_NAME | LS_IGNORE_DOT | LS_DISP_LONG;806#endif807808const char *p;809if(options == NULL || *options != '-')810return flags;811for(p = options + 1; *p; p++) {812switch(*p) {813case '1':814flags = (flags & ~LS_DISP_MODE);815break;816case 'a':817flags |= LS_SHOW_ALL;818/*FALLTHROUGH*/819case 'A':820flags &= ~LS_IGNORE_DOT;821break;822case 'C':823flags = (flags & ~LS_DISP_MODE) | LS_DISP_COLUMN;824break;825case 'd':826flags |= LS_DIRS;827break;828case 'f':829flags = (flags & ~LS_SORT_MODE);830break;831case 'F':832flags |= LS_TYPE;833break;834case 'i':835flags |= LS_INODE;836break;837case 'l':838flags = (flags & ~LS_DISP_MODE) | LS_DISP_LONG;839break;840case 'r':841flags |= LS_SORT_REVERSE;842break;843case 'R':844flags |= LS_RECURSIVE;845break;846case 's':847flags |= LS_SIZE;848break;849case 'S':850flags = (flags & ~LS_SORT_MODE) | LS_SORT_SIZE;851break;852case 't':853flags = (flags & ~LS_SORT_MODE) | LS_SORT_MTIME;854break;855case 'x':856flags = (flags & ~LS_DISP_MODE) | LS_DISP_CROSS;857break;858/* these are a bunch of unimplemented flags from BSD ls */859case 'k': /* display sizes in kB */860case 'c': /* last change time */861case 'L': /* list symlink target */862case 'm': /* stream output */863case 'o': /* BSD file flags */864case 'p': /* display / after directories */865case 'q': /* print non-graphic characters */866case 'u': /* use last access time */867case 'T': /* display complete time */868case 'W': /* include whiteouts */869break;870}871}872return flags;873}874875int876builtin_ls(FILE *out, const char *file)877{878int flags;879int ret;880881if(*file == '-') {882flags = parse_flags(file);883file = ".";884} else885flags = parse_flags("");886887ret = list_files(out, &file, 1, flags);888sec_fflush(out);889return ret;890}891892893