#include "common.h"
#include <getopt.h>
#include <error.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <dirent.h>
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
struct fileinfo_struct {
char *path;
int stat_err;
struct stat stat;
struct fileinfo_struct *next;
};
typedef int (*comparator_t)(const struct fileinfo_struct *, const struct fileinfo_struct *);
struct fieldwidths_struct {
int ino_fieldwidth;
int filesize_fieldwidth;
};
struct file_list_struct {
int list_directories;
comparator_t comparator;
struct fileinfo_struct *first;
struct fieldwidths_struct fieldwidths;
};
struct print_options_struct {
unsigned print_ino : 1;
unsigned long_format : 1;
};
struct options_struct {
int list_directories;
comparator_t comparator;
struct print_options_struct print_options;
};
#define has_dev(mode) (S_ISCHR(mode) || S_ISBLK(mode))
#define has_size(mode) S_ISREG(mode)
#define has_mtime(mode) (S_ISREG(mode) || S_ISDIR(mode))
static void init_fileinfo(struct fileinfo_struct *fileinfo, const char *path, const struct stat *statbuf)
{
if (!(fileinfo->path = strdup(path)))
error(EXIT_FAILURE, errno, "strdup");
if (statbuf) {
fileinfo->stat_err = 0;
fileinfo->stat = *statbuf;
}
else
fileinfo->stat_err = 1;
}
static void destroy_fileinfo(struct fileinfo_struct *fileinfo)
{
free(fileinfo->path);
}
static void init_file_list(struct file_list_struct *file_list, int list_directories, comparator_t comparator)
{
file_list->list_directories = list_directories;
file_list->comparator = comparator;
file_list->fieldwidths.ino_fieldwidth = 0;
file_list->fieldwidths.filesize_fieldwidth = 0;
file_list->first = NULL;
}
static void destroy_file_list(struct file_list_struct *file_list)
{
struct fileinfo_struct *fileinfo, *next;
fileinfo = file_list->first;
while (fileinfo) {
next = fileinfo->next;
destroy_fileinfo(fileinfo);
free(fileinfo);
fileinfo = next;
}
}
static int name_comparator(const struct fileinfo_struct *fileinfo1, const struct fileinfo_struct *fileinfo2)
{
return strcmp(fileinfo1->path, fileinfo2->path) < 0;
}
static int size_comparator(const struct fileinfo_struct *fileinfo1, const struct fileinfo_struct *fileinfo2)
{
if (fileinfo1->stat_err && fileinfo2->stat_err)
goto equal;
if (fileinfo1->stat_err && !fileinfo2->stat_err)
return 0;
if (!fileinfo1->stat_err && fileinfo2->stat_err)
return 1;
if (!has_size(fileinfo1->stat.st_mode) && !has_size(fileinfo2->stat.st_mode))
goto equal;
if (!has_size(fileinfo1->stat.st_mode) && has_size(fileinfo2->stat.st_mode))
return 0;
if (has_size(fileinfo1->stat.st_mode) && !has_size(fileinfo2->stat.st_mode))
return 1;
if (fileinfo1->stat.st_size == fileinfo2->stat.st_size)
goto equal;
return fileinfo1->stat.st_size > fileinfo2->stat.st_size;
equal:
return name_comparator(fileinfo1, fileinfo2);
}
static int mtime_comparator(const struct fileinfo_struct *fileinfo1, const struct fileinfo_struct *fileinfo2)
{
if (fileinfo1->stat_err && fileinfo2->stat_err)
goto equal;
if (fileinfo1->stat_err && !fileinfo2->stat_err)
return 0;
if (!fileinfo1->stat_err && fileinfo2->stat_err)
return 1;
if (!has_mtime(fileinfo1->stat.st_mode) && !has_mtime(fileinfo2->stat.st_mode))
goto equal;
if (!has_mtime(fileinfo1->stat.st_mode) && has_mtime(fileinfo2->stat.st_mode))
return 0;
if (has_mtime(fileinfo1->stat.st_mode) && !has_mtime(fileinfo2->stat.st_mode))
return 1;
if (fileinfo1->stat.st_mtime == fileinfo2->stat.st_mtime)
goto equal;
return fileinfo1->stat.st_mtime > fileinfo2->stat.st_mtime;
equal:
return name_comparator(fileinfo1, fileinfo2);
}
static int compare(const struct fileinfo_struct *fileinfo1, const struct fileinfo_struct *fileinfo2, const struct file_list_struct *file_list)
{
if (file_list->list_directories) {
if (S_ISDIR(fileinfo1->stat.st_mode) && !S_ISDIR(fileinfo2->stat.st_mode))
return 0;
if (!S_ISDIR(fileinfo1->stat.st_mode) && S_ISDIR(fileinfo2->stat.st_mode))
return 1;
}
return file_list->comparator(fileinfo1, fileinfo2);
}
static int dev_fieldwidth(dev_t dev)
{
return snprintf(NULL, 0, "%lu", (unsigned long)dev);
}
static int size_fieldwidth(off_t size)
{
return snprintf(NULL, 0, "%ld", size);
}
static void insert_into_file_list(struct file_list_struct *file_list, struct fileinfo_struct *fileinfo)
{
struct fileinfo_struct *fileinfo1, *fileinfo2;
int w;
fileinfo1 = NULL;
fileinfo2 = file_list->first;
while (fileinfo2 && !compare(fileinfo, fileinfo2, file_list)) {
fileinfo1 = fileinfo2;
fileinfo2 = fileinfo2->next;
}
fileinfo->next = fileinfo2;
if (!fileinfo1)
file_list->first = fileinfo;
else
fileinfo1->next = fileinfo;
if ((w = size_fieldwidth(fileinfo->stat.st_ino)) > file_list->fieldwidths.ino_fieldwidth)
file_list->fieldwidths.ino_fieldwidth = w;
if (has_dev(fileinfo->stat.st_mode)) {
if ((w = dev_fieldwidth(fileinfo->stat.st_rdev)) > file_list->fieldwidths.filesize_fieldwidth)
file_list->fieldwidths.filesize_fieldwidth = w;
}
else if (has_size(fileinfo->stat.st_mode))
if ((w = size_fieldwidth(fileinfo->stat.st_size)) > file_list->fieldwidths.filesize_fieldwidth)
file_list->fieldwidths.filesize_fieldwidth = w;
}
static int populate_file_list_from_directory(struct file_list_struct *file_list, const char *path)
{
int err;
DIR *dir;
struct dirent *dent;
char *buf;
struct stat statbuf;
struct fileinfo_struct *fileinfo;
err = 0;
if (!(dir = opendir(path))) {
error(0, errno, path);
err = 1;
goto end;
}
while (1) {
errno = 0;
if (!(dent = readdir(dir))) {
if (errno) {
error(0, errno, "reading directory %s", path);
err = 1;
goto closedir;
}
break;
}
if (!(buf = malloc(strlen(path) + 1 + strlen(dent->d_name) + 1)))
error(EXIT_FAILURE, errno, "malloc");
if (!(fileinfo = malloc(sizeof (struct fileinfo_struct))))
error(EXIT_FAILURE, errno, "malloc");
sprintf(buf, "%s/%s", path, dent->d_name);
if (lstat(buf, &statbuf) == -1) {
error(0, errno, "cannot access %s", buf);
err = 1;
init_fileinfo(fileinfo, dent->d_name, NULL);
}
else
init_fileinfo(fileinfo, dent->d_name, &statbuf);
free(buf);
insert_into_file_list(file_list, fileinfo);
}
closedir:
if (closedir(dir) == -1) {
error(0, errno, "closing directory %s", path);
err = 1;
}
end:
return !err;
}
static int populate_file_list_from_files(struct file_list_struct *file_list, char *const path[], int count)
{
int err;
int i;
struct stat statbuf;
struct fileinfo_struct *fileinfo;
err = 0;
for (i = 0; i < count; i++) {
if (lstat(path[i], &statbuf) == -1) {
error(0, errno, "cannot access %s", path[i]);
err = 1;
continue;
}
if (!(fileinfo = malloc(sizeof (struct fileinfo_struct))))
error(EXIT_FAILURE, errno, "malloc");
init_fileinfo(fileinfo, path[i], &statbuf);
insert_into_file_list(file_list, fileinfo);
}
return !err;
}
static char get_type_char(mode_t mode)
{
switch (mode & S_IFMT) {
case S_IFIFO:
return 'p';
case S_IFCHR:
return 'c';
case S_IFDIR:
return 'd';
case S_IFBLK:
return 'b';
case S_IFREG:
return '-';
default:
return '?';
}
}
static void print_date(time_t timep)
{
struct tm *tm;
char buf[13];
time_t current_time, six_months_ago_time;
int n;
if ((tm = localtime((time_t *)&timep))) {
if (time(¤t_time) == (time_t)-1)
error(EXIT_FAILURE, errno, "cannot get system date");
six_months_ago_time = current_time - 31556952 / 2;
if (timep >= six_months_ago_time && timep <= current_time)
n = strftime(buf, sizeof buf, "%b %e %H:%M", tm);
else
n = strftime(buf, sizeof buf, "%b %e %Y", tm);
printf("%12.*s", n, buf);
}
else
printf("%12c", '?');
}
static void print_fileinfo(const struct fileinfo_struct *fileinfo, const struct print_options_struct *print_options, const struct fieldwidths_struct *fieldwidths)
{
if (print_options->print_ino)
printf("%*ld ", fieldwidths->ino_fieldwidth, fileinfo->stat.st_ino);
if (print_options->long_format) {
if (fileinfo->stat_err)
printf("? %*s %12s ", fieldwidths->filesize_fieldwidth, "", "");
else {
printf("%c ", get_type_char(fileinfo->stat.st_mode));
if (has_dev(fileinfo->stat.st_mode))
printf("%*lu ", fieldwidths->filesize_fieldwidth, (unsigned long)fileinfo->stat.st_rdev);
else if (has_size(fileinfo->stat.st_mode))
printf("%*ld ", fieldwidths->filesize_fieldwidth, fileinfo->stat.st_size);
else
printf("%*s ", fieldwidths->filesize_fieldwidth, "");
if (has_mtime(fileinfo->stat.st_mode)) {
print_date(fileinfo->stat.st_mtime);
printf(" ");
}
else
printf("%12s ", "");
}
}
printf("%s\n", fileinfo->path);
}
static int list_directory(const char *path, comparator_t comparator, const struct print_options_struct *print_options);
static int print_file_list(const struct file_list_struct *file_list, const struct print_options_struct *print_options)
{
int print_directory_name;
int err;
const struct fileinfo_struct *fileinfo;
print_directory_name = file_list->first && file_list->first->next;
err = 0;
fileinfo = file_list->first;
while (fileinfo && (!file_list->list_directories || fileinfo->stat_err || !S_ISDIR(fileinfo->stat.st_mode))) {
print_fileinfo(fileinfo, print_options, &file_list->fieldwidths);
fileinfo = fileinfo->next;
}
while (fileinfo) {
if (fileinfo != file_list->first)
putchar('\n');
if (print_directory_name)
printf("%s:\n", fileinfo->path);
if (!list_directory(fileinfo->path, file_list->comparator, print_options))
err = 1;
fileinfo = fileinfo->next;
}
return !err;
}
static int list_directory(const char *path, comparator_t comparator, const struct print_options_struct *print_options)
{
int err;
struct file_list_struct file_list;
err = 0;
init_file_list(&file_list, 0, comparator);
if (!populate_file_list_from_directory(&file_list, path))
err = 1;
if (!print_file_list(&file_list, print_options))
err = 1;
destroy_file_list(&file_list);
return !err;
}
static int list_files(char *const path[], int count, const struct options_struct *options)
{
int err;
struct file_list_struct file_list;
err = 0;
init_file_list(&file_list, options->list_directories, options->comparator);
if (!populate_file_list_from_files(&file_list, path, count))
err = 1;
if (!print_file_list(&file_list, &options->print_options))
err = 1;
destroy_file_list(&file_list);
return !err;
}
static void __attribute__ ((noreturn)) die_bad_usage()
{
fprintf(stderr, "Try `%s --help' for more information.\n", program_invocation_name);
exit(EXIT_FAILURE);
}
static void print_help()
{
printf("Usage: %s [OPTION]... [FILE]...\n", program_invocation_name);
puts("List information about the FILEs (the current directory by default).");
putchar('\n');
puts(" -d, --directory list directory entries instead of contents");
puts(" -i, --inode print the index number of each file");
puts(" -l use a long listing format");
puts(" -S sort by file size");
puts(" -t sort by modification time");
puts(" -h, --help display this help and exit");
}
int main(int argc, char **argv)
{
static const struct option longopts[] = {
{"directory", no_argument, NULL, 'd'},
{"inode", no_argument, NULL, 'i'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
static char dot[] = ".";
static char *const default_files[] = {dot};
static const int default_count = 1;
struct options_struct options;
int c;
char *const *files;
int count;
atexit(close_stdout);
options.list_directories = 1;
options.comparator = name_comparator;
options.print_options.print_ino = 0;
options.print_options.long_format = 0;
while ((c = getopt_long(argc, argv, "dilSth", longopts, NULL)) != -1)
switch (c) {
case 'd':
options.list_directories = 0;
break;
case 'i':
options.print_options.print_ino = 1;
break;
case 'l':
options.print_options.long_format = 1;
break;
case 'S':
options.comparator = size_comparator;
break;
case 't':
options.comparator = mtime_comparator;
break;
case 'h':
print_help();
return EXIT_SUCCESS;
case '?':
die_bad_usage();
}
if (optind == argc) {
files = default_files;
count = default_count;
}
else {
files = argv + optind;
count = argc - optind;
}
return list_files(files, count, &options) ? EXIT_SUCCESS : EXIT_FAILURE;
}