599 lines
12 KiB
C
599 lines
12 KiB
C
/* ls.c */
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <stdlib.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
|
|
/* FIXME */
|
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
|
|
|
/* Prefs */
|
|
typedef int Prefs;
|
|
#define PREFS_a 1
|
|
#define PREFS_c 2
|
|
#define PREFS_d 4
|
|
#define PREFS_l 8
|
|
#define PREFS_u 16
|
|
#define PREFS_1 32
|
|
#define PREFS_F 64
|
|
#define PREFS_R 128
|
|
|
|
static int _prefs_parse(Prefs * prefs, int argc, char * argv[])
|
|
{
|
|
int o;
|
|
|
|
memset(prefs, 0, sizeof(Prefs));
|
|
while((o = getopt(argc, argv, "CFRacdlu1")) != -1)
|
|
{
|
|
switch(o)
|
|
{
|
|
case 'C':
|
|
fprintf(stderr, "%s%c%s", "ls: -", o,
|
|
": Not implemented yet\n");
|
|
return 1;
|
|
case 'F':
|
|
*prefs |= PREFS_F;
|
|
break;
|
|
case 'R':
|
|
*prefs |= PREFS_R;
|
|
break;
|
|
case 'a':
|
|
*prefs |= PREFS_a;
|
|
break;
|
|
case 'c':
|
|
*prefs -= *prefs & PREFS_u;
|
|
*prefs |= PREFS_c;
|
|
break;
|
|
case 'd':
|
|
*prefs |= PREFS_d;
|
|
break;
|
|
case 'l':
|
|
*prefs |= PREFS_l;
|
|
break;
|
|
case 'u':
|
|
*prefs -= *prefs & PREFS_c;
|
|
*prefs |= PREFS_u;
|
|
break;
|
|
case '1':
|
|
*prefs |= PREFS_1;
|
|
break;
|
|
case '?':
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* SList */
|
|
/* SListCell */
|
|
typedef struct _SListCell {
|
|
void * data;
|
|
struct _SListCell * next;
|
|
} SListCell;
|
|
typedef SListCell * SList;
|
|
|
|
static SListCell * _slistcell_new(void * data, SListCell * next)
|
|
{
|
|
SListCell * slc;
|
|
|
|
if((slc = malloc(sizeof(SListCell))) == NULL)
|
|
return NULL;
|
|
slc->data = data;
|
|
slc->next = next;
|
|
return slc;
|
|
}
|
|
|
|
static void _slistcell_delete(SListCell * slistcell)
|
|
{
|
|
free(slistcell);
|
|
}
|
|
|
|
static SList * slist_new(void)
|
|
{
|
|
SList * sl;
|
|
|
|
if((sl = malloc(sizeof(SList))) == NULL)
|
|
return NULL;
|
|
*sl = NULL;
|
|
return sl;
|
|
}
|
|
|
|
static void slist_delete(SList * slist)
|
|
{
|
|
SListCell * slc = *slist;
|
|
SListCell * p;
|
|
|
|
while(slc != NULL)
|
|
{
|
|
p = slc->next;
|
|
_slistcell_delete(slc);
|
|
slc = p;
|
|
}
|
|
free(slist);
|
|
}
|
|
|
|
/* returns */
|
|
static void * slist_data(SList * slist)
|
|
{
|
|
if(*slist == NULL)
|
|
return NULL;
|
|
return (*slist)->data;
|
|
}
|
|
|
|
static void slist_next(SList * slist)
|
|
{
|
|
if(*slist == NULL)
|
|
return;
|
|
*slist = (*slist)->next;
|
|
}
|
|
|
|
/* static void slist_last(SList * slist)
|
|
{
|
|
if(*slist == NULL)
|
|
return;
|
|
while((*slist)->next != NULL)
|
|
*slist = (*slist)->next;
|
|
} */
|
|
|
|
/* static int slist_append(SList * slist, void * data)
|
|
{
|
|
SList sl = *slist;
|
|
|
|
if(sl == NULL)
|
|
{
|
|
*slist = _slistcell_new(data, NULL);
|
|
return *slist != NULL ? 0 : 2;
|
|
}
|
|
slist_last(&sl);
|
|
sl->next = _slistcell_new(data, NULL);
|
|
return sl->next != NULL ? 0 : 2;
|
|
} */
|
|
|
|
static void slist_apply(SList * slist, int (*func)(void *, void *), void * user)
|
|
{
|
|
SListCell * slc = *slist;
|
|
|
|
for(slc = *slist; slc != NULL; slc = slc->next)
|
|
func(slc->data, user);
|
|
}
|
|
|
|
static int slist_insert_sorted(SList * slist, void * data,
|
|
int (*func)(void *, void *))
|
|
{
|
|
SListCell * slc = *slist;
|
|
SListCell * p = NULL;
|
|
|
|
while(slc != NULL && func(slc->data, data) < 0)
|
|
{
|
|
p = slc;
|
|
slc = slc->next;
|
|
}
|
|
if(slc == NULL) /* empty or last */
|
|
{
|
|
if((slc = _slistcell_new(data, NULL)) == NULL)
|
|
return 1;
|
|
if(p == NULL)
|
|
*slist = slc; /* empty */
|
|
else
|
|
p->next = slc; /* last */
|
|
return 0;
|
|
}
|
|
if((p = _slistcell_new(slc->data, slc->next)) == NULL)
|
|
return 1;
|
|
slc->data = data;
|
|
slc->next = p;
|
|
return 0;
|
|
}
|
|
|
|
/* static size_t slist_length(SList * slist)
|
|
{
|
|
SListCell * slc = *slist;
|
|
size_t len;
|
|
|
|
for(len = 0; slc != NULL; len++)
|
|
slc = slc->next;
|
|
return len;
|
|
} */
|
|
|
|
|
|
/* ls */
|
|
static int _ls_error(char const * message, int ret);
|
|
static int _ls_directory_do(char * dir, Prefs * prefs);
|
|
static int _ls_args(SList ** files, SList ** dirs);
|
|
static int _is_directory(char * dir);
|
|
static int _ls_do(char * directory, SList * files, SList * dirs, Prefs * prefs);
|
|
typedef int (*compare_func)(void*, void*);
|
|
static int _ls(int argc, char * argv[], Prefs * prefs)
|
|
{
|
|
SList * files;
|
|
SList * dirs;
|
|
int res = 0;
|
|
int i;
|
|
int isdir;
|
|
char * str;
|
|
|
|
if(argc == 0)
|
|
return _ls_directory_do(".", prefs);
|
|
if(_ls_args(&files, &dirs) != 0)
|
|
return 2;
|
|
for(i = 0; i < argc; i++)
|
|
{
|
|
if((isdir = _is_directory(argv[i])) == 2)
|
|
res++;
|
|
else if((str = strdup(argv[i])) == NULL)
|
|
res += _ls_error("malloc", 1);
|
|
else if(*prefs & PREFS_d)
|
|
res += slist_insert_sorted(files, str,
|
|
(compare_func)strcmp);
|
|
else
|
|
res += slist_insert_sorted(isdir ? dirs : files, str,
|
|
(compare_func)strcmp);
|
|
}
|
|
res += _ls_do(NULL, files, dirs, prefs);
|
|
return res == 1 ? 2 : res;
|
|
}
|
|
|
|
static int _ls_error(char const * message, int ret)
|
|
{
|
|
fprintf(stderr, "%s", "ls: ");
|
|
perror(message);
|
|
return ret;
|
|
}
|
|
|
|
static int _ls_directory_do(char * directory, Prefs * prefs)
|
|
{
|
|
SList * files;
|
|
SList * dirs;
|
|
int res = 0;
|
|
DIR * dir;
|
|
struct dirent * de;
|
|
char * file = NULL;
|
|
char * p;
|
|
int pos = 1;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "_ls_directory_do(%s, ...)\n", directory);
|
|
#endif
|
|
if((dir = opendir(directory)) == NULL)
|
|
return _ls_error(directory, 2);
|
|
_ls_args(&files, &dirs);
|
|
for(; (de = readdir(dir)) != NULL; pos++)
|
|
{
|
|
if(*(de->d_name) == '.' && !(*prefs & PREFS_a))
|
|
continue;
|
|
slist_insert_sorted(files, strdup(de->d_name),
|
|
(compare_func)strcmp);
|
|
if(pos <= 2)
|
|
continue;
|
|
if((p = realloc(file, strlen(directory) + strlen(de->d_name)
|
|
+ 2)) == NULL)
|
|
{
|
|
_ls_error("malloc", 0);
|
|
continue;
|
|
}
|
|
file = p;
|
|
sprintf(file, "%s/%s", directory, de->d_name);
|
|
if((*prefs & PREFS_R) && _is_directory(file) == 1)
|
|
slist_insert_sorted(dirs, strdup(file),
|
|
(compare_func)strcmp);
|
|
}
|
|
free(file);
|
|
closedir(dir);
|
|
_ls_do(directory, files, dirs, prefs);
|
|
return res;
|
|
}
|
|
|
|
static int _ls_args(SList ** files, SList ** dirs)
|
|
{
|
|
if((*files = slist_new()) == NULL)
|
|
return _ls_error("slist", 1);
|
|
if((*dirs = slist_new()) == NULL)
|
|
{
|
|
slist_delete(*files);
|
|
return _ls_error("slist", 1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int _is_directory(char * file)
|
|
{
|
|
struct stat st;
|
|
|
|
if((stat(file, &st)) != 0)
|
|
return _ls_error(file, 2);
|
|
return S_ISDIR(st.st_mode) ? 1 : 0;
|
|
}
|
|
|
|
static int _ls_do_files(char * directory, SList * files, Prefs * prefs);
|
|
static int _ls_do_dirs(SList * dirs, Prefs * prefs);
|
|
static int _ls_do(char * directory, SList * files, SList * dirs, Prefs * prefs)
|
|
{
|
|
int res = 0;
|
|
|
|
res += _ls_do_files(directory, files, prefs);
|
|
res += _ls_do_dirs(dirs, prefs);
|
|
return res;
|
|
}
|
|
|
|
static int _ls_free(void * data, void * user);
|
|
static int _ls_do_files_short(char * directory, SList * files, Prefs * prefs);
|
|
static int _ls_do_files_long(char * directory, SList * files, Prefs * prefs);
|
|
static int _ls_do_files(char * directory, SList * files, Prefs * prefs)
|
|
{
|
|
int res = 0;
|
|
|
|
if(*prefs & PREFS_l)
|
|
res = _ls_do_files_long(directory, files, prefs);
|
|
else
|
|
res = _ls_do_files_short(directory, files, prefs);
|
|
slist_apply(files, _ls_free, NULL);
|
|
slist_delete(files);
|
|
return res;
|
|
}
|
|
|
|
static char _short_file_mode(char const * directory, char const * file);
|
|
static int _ls_do_files_short(char * directory, SList * files, Prefs * prefs)
|
|
{
|
|
char * cols;
|
|
char * p;
|
|
unsigned int len = 0;
|
|
unsigned int lenmax = 0;
|
|
unsigned int colnb = 0;
|
|
unsigned int i = 0;
|
|
unsigned int j = 0;
|
|
char c;
|
|
SList cur;
|
|
|
|
if(((*prefs & PREFS_1) == 0) && (cols = getenv("COLUMNS")) != NULL
|
|
&& *cols != '\0' && (len = strtol(cols, &p, 10)) > 0
|
|
&& *p == '\0')
|
|
{
|
|
for(cur = *files; cur != NULL; slist_next(&cur))
|
|
lenmax = max(lenmax, strlen(slist_data(&cur)));
|
|
if(lenmax > 0)
|
|
colnb = len / ++lenmax;
|
|
}
|
|
for(cur = *files; cur != NULL; slist_next(&cur))
|
|
{
|
|
p = slist_data(&cur);
|
|
j = strlen(p);
|
|
fwrite(p, sizeof(char), j, stdout);
|
|
if((*prefs & PREFS_F) && (c = _short_file_mode(directory, p)))
|
|
{
|
|
fputc(c, stdout);
|
|
j++;
|
|
}
|
|
if(i + 1 < colnb)
|
|
{
|
|
for(i++; j < lenmax; j++)
|
|
fputc(' ', stdout);
|
|
continue;
|
|
}
|
|
fputc('\n', stdout);
|
|
i = 0;
|
|
}
|
|
if(i != 0)
|
|
fputc('\n', stdout);
|
|
return 0;
|
|
}
|
|
|
|
static char _file_mode_letter(mode_t mode);
|
|
static char _short_file_mode(char const * directory, char const * file)
|
|
{
|
|
struct stat st;
|
|
char * p;
|
|
char c = '\0';
|
|
|
|
if((p = malloc(strlen(directory) + 1 + strlen(file) + 1)) == NULL)
|
|
return _ls_error("malloc", 0);
|
|
sprintf(p, "%s/%s", directory, file);
|
|
if(stat(p, &st) != 0)
|
|
_ls_error(file, 0);
|
|
else
|
|
c = _file_mode_letter(st.st_mode);
|
|
free(p);
|
|
return c;
|
|
}
|
|
|
|
static void _long_mode(char str[11], mode_t mode);
|
|
static char * _long_owner(uid_t uid);
|
|
static char * _long_group(gid_t gid);
|
|
static void _long_date(time_t date, char buf[15]);
|
|
static char _file_mode_letter(mode_t mode);
|
|
static int _ls_do_files_long(char * directory, SList * files, Prefs * prefs)
|
|
{
|
|
SList cur;
|
|
char * file = NULL;
|
|
char * p;
|
|
struct stat st;
|
|
char mode[11];
|
|
char * owner;
|
|
char * group;
|
|
char date[15];
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "DEBUG _ls_do_files_long(%s, ...)\n", directory);
|
|
#endif
|
|
for(cur = *files; cur != NULL; slist_next(&cur))
|
|
{
|
|
/* FIXME */
|
|
p = slist_data(&cur);
|
|
if((p = realloc(file, strlen(directory) + strlen(p) + 2))
|
|
== NULL)
|
|
{
|
|
_ls_error("malloc", 0);
|
|
continue;
|
|
}
|
|
file = p;
|
|
p = slist_data(&cur);
|
|
sprintf(file, "%s/%s", directory, p);
|
|
if(stat(file, &st) != 0)
|
|
{
|
|
_ls_error(file, 0);
|
|
continue;
|
|
}
|
|
_long_mode(mode, st.st_mode);
|
|
owner = _long_owner(st.st_uid);
|
|
group = _long_group(st.st_gid);
|
|
if(*prefs & PREFS_u)
|
|
_long_date(st.st_atime, date);
|
|
else if(*prefs & PREFS_c)
|
|
_long_date(st.st_ctime, date);
|
|
else
|
|
_long_date(st.st_mtime, date);
|
|
printf("%s %u %s %s %lu %s %s%c\n", mode, st.st_nlink,
|
|
owner, group, st.st_size, date, p,
|
|
(*prefs & PREFS_F)
|
|
? _file_mode_letter(st.st_mode) : '\0');
|
|
}
|
|
free(file);
|
|
return 0;
|
|
}
|
|
|
|
static void _long_mode(char str[11], mode_t mode)
|
|
{
|
|
unsigned int i;
|
|
|
|
str[0] = '-';
|
|
if(!S_ISREG(mode))
|
|
{
|
|
if(S_ISLNK(mode))
|
|
str[0] = 'l';
|
|
else if(S_ISBLK(mode))
|
|
str[0] = 'b';
|
|
else if(S_ISCHR(mode))
|
|
str[0] = 'c';
|
|
else if(S_ISFIFO(mode))
|
|
str[0] = 'p';
|
|
else if(S_ISDIR(mode))
|
|
str[0] = 'd';
|
|
}
|
|
for(i = 1; i < 10; i++)
|
|
str[i] = '-';
|
|
if(mode & S_IRUSR)
|
|
str[1] = 'r';
|
|
if(mode & S_IWUSR)
|
|
str[2] = 'w';
|
|
if(mode & S_IXUSR)
|
|
str[3] = (mode & S_ISUID ? 's' : 'x');
|
|
else if(mode & S_ISUID)
|
|
str[3] = 'S';
|
|
if(mode & S_IRGRP)
|
|
str[4] = 'r';
|
|
if(mode & S_IWGRP)
|
|
str[5] = 'w';
|
|
if(mode & S_IXGRP)
|
|
str[6] = 'x';
|
|
if(mode & S_IROTH)
|
|
str[7] = 'r';
|
|
if(mode & S_IWOTH)
|
|
str[8] = 'w';
|
|
if(mode & S_IXOTH)
|
|
str[9] = 'x';
|
|
str[10] = '\0';
|
|
}
|
|
|
|
static char * _long_owner(uid_t uid)
|
|
{
|
|
struct passwd * pwd;
|
|
|
|
if((pwd = getpwuid(uid)) == NULL)
|
|
return "unknown";
|
|
return pwd->pw_name;
|
|
}
|
|
|
|
static char * _long_group(gid_t gid)
|
|
{
|
|
struct group * grp;
|
|
|
|
if((grp = getgrgid(gid)) == NULL)
|
|
return "unknown";
|
|
return grp->gr_name;
|
|
}
|
|
|
|
static void _long_date(time_t date, char buf[15])
|
|
{
|
|
struct tm tm;
|
|
static time_t sixmonths = -1;
|
|
|
|
if(sixmonths == -1)
|
|
sixmonths = time(NULL) - 15552000;
|
|
localtime_r(&date, &tm);
|
|
if(date < sixmonths)
|
|
strftime(buf, 14, "%b %e %Y", &tm);
|
|
else
|
|
strftime(buf, 14, "%b %e %H:%M", &tm);
|
|
}
|
|
|
|
static char _file_mode_letter(mode_t mode)
|
|
{
|
|
if(S_ISLNK(mode)) /* FIXME not in POSIX? */
|
|
return '@';
|
|
if(S_ISDIR(mode))
|
|
return '/';
|
|
if(S_ISFIFO(mode))
|
|
return '|';
|
|
if(mode & (S_IXUSR | S_IXGRP | S_IXOTH))
|
|
return '*';
|
|
return '\0';
|
|
}
|
|
|
|
static int _ls_free(void * data, void * user)
|
|
{
|
|
free(data);
|
|
return 0;
|
|
user = user;
|
|
}
|
|
|
|
static int _ls_do_dirs(SList * dirs, Prefs * prefs)
|
|
{
|
|
int res = 0;
|
|
SList cur;
|
|
char * dir = NULL;
|
|
|
|
for(cur = *dirs; cur != NULL; slist_next(&cur))
|
|
{
|
|
dir = slist_data(&cur);
|
|
printf("\n%s%s", dir, ":\n");
|
|
res += _ls_directory_do(dir, prefs);
|
|
}
|
|
slist_apply(dirs, _ls_free, NULL);
|
|
slist_delete(dirs);
|
|
return res;
|
|
}
|
|
|
|
|
|
/* usage */
|
|
static int _usage(void)
|
|
{
|
|
fprintf(stderr, "%s", "Usage: ls [-CFRacdilqrtu1][-H | -L]\n\
|
|
-R recursively list subdirectories encountered\n\
|
|
-a write out all hidden directory entries\n\
|
|
-c use time of last modification of file status\n\
|
|
-l write out in long format\n\
|
|
-u use time of last access\n");
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* main */
|
|
int main(int argc, char * argv[])
|
|
{
|
|
Prefs p;
|
|
|
|
if(_prefs_parse(&p, argc, argv) != 0)
|
|
return _usage();
|
|
return _ls(argc - optind, &argv[optind], &p);
|
|
}
|