sortix-mirror/utils/ls.c

1003 lines
26 KiB
C

/*
* Copyright (c) 2011, 2012, 2013, 2014, 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* ls.c
* Lists directory contents.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <assert.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <libgen.h>
#include <locale.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include <wchar.h>
struct passwd* getpwuid_cache(uid_t uid)
{
static struct passwd* cache_pwd;
static uid_t cache_uid;
if ( cache_pwd && cache_uid == uid )
return cache_pwd;
if ( (cache_pwd = getpwuid(uid)) )
cache_uid = uid;
return cache_pwd;
}
struct group* getgrgid_cache(gid_t gid)
{
static struct group* cache_grp;
static gid_t cache_gid;
if ( cache_grp && cache_gid == gid )
return cache_grp;
if ( (cache_grp = getgrgid(gid)) )
cache_gid = gid;
return cache_grp;
}
enum dotfile
{
DOTFILE_NO,
DOTFILE_ALMOST,
DOTFILE_ALL,
};
enum sort
{
SORT_COLLATE,
SORT_SIZE,
SORT_TIME,
};
enum timestamp
{
TIMESTAMP_ATIME,
TIMESTAMP_CTIME,
TIMESTAMP_MTIME,
};
struct record
{
struct dirent* dirent;
struct stat st;
int stat_attempt;
char* symlink_path;
struct stat symlink_st;
int symlink_stat_attempt;
bool no_recurse;
};
static int order_normal(int comparison)
{
return comparison;
}
static int order_reverse(int comparison)
{
return comparison == 0 ? 0 : comparison < 0 ? 1 : -1;
}
static int current_year;
static enum dotfile option_all = DOTFILE_NO;
static bool option_colors = false;
static bool option_column = false;
static bool option_directory = false;
static bool option_human_readable = false;
static bool option_inode = false;
static bool option_long = false;
static bool option_multiple_operands = false;
static bool option_recursive = false;
static int (*option_reverse)(int) = order_normal;
static enum sort option_sort = SORT_COLLATE;
static enum timestamp option_time_type = TIMESTAMP_MTIME;
static size_t string_display_length(const char* str)
{
size_t display_length = 0;
mbstate_t ps;
memset(&ps, 0, sizeof(ps));
while ( true )
{
wchar_t wc;
size_t amount = mbrtowc(&wc, str, SIZE_MAX, &ps);
if ( amount == 0 )
break;
if ( amount == (size_t) -1 || amount == (size_t) -2 )
{
display_length++;
str++;
memset(&ps, 0, sizeof(ps));
continue;
}
int width = wcwidth(wc);
if ( width < 0 )
width = 0;
if ( SIZE_MAX - display_length < (size_t) width )
display_length = SIZE_MAX;
else
display_length += (size_t) width;
str += amount;
}
return display_length;
}
static void print_left_aligned(const char* string, size_t field_width)
{
size_t string_width = string_display_length(string);
fputs(string, stdout);
for ( size_t i = string_width; i < field_width; i++ )
putchar(' ');
}
static void print_right_aligned(const char* string, size_t field_width)
{
size_t string_width = string_display_length(string);
for ( size_t i = string_width; i < field_width; i++ )
putchar(' ');
fputs(string, stdout);
}
#define FORMAT_BYTES_LENGTH (sizeof(off_t) * 3 + 1 + 1 + 1)
static void format_bytes_amount(char* dest, size_t destsize, uintmax_t num_bytes)
{
uintmax_t value = num_bytes;
uintmax_t value_fraction = 0;
uintmax_t exponent = 1024;
char suffixes[] = { 'B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
size_t num_suffixes = sizeof(suffixes) / sizeof(suffixes[0]);
size_t suffix_index = 0;
while ( exponent <= value && suffix_index + 1 < num_suffixes)
{
value_fraction = value % exponent;
value /= exponent;
suffix_index++;
}
char suffix = suffixes[suffix_index];
if ( suffix_index == 0 )
{
snprintf(dest, destsize, "%ju%c", value, suffix);
return;
}
char value_fraction_char = '0' + (value_fraction / (1024 / 10 + 1)) % 10;
snprintf(dest, destsize, "%ju.%c%c", value, value_fraction_char, suffix);
}
static struct dirent* dirent_dup(struct dirent* entry)
{
size_t size = sizeof(struct dirent) + strlen(entry->d_name) + 1;
struct dirent* copy = (struct dirent*) malloc(size);
if ( !copy )
return NULL;
memcpy(copy, entry, size);
return copy;
}
static struct dirent* dirent_make(const char* name)
{
size_t size = sizeof(struct dirent) + strlen(name) + 1;
struct dirent* ret = (struct dirent*) malloc(size);
if ( !ret )
return NULL;
strcpy(ret->d_name, name);
return ret;
}
static struct timespec record_timestamp(struct record* record)
{
switch ( option_time_type )
{
case TIMESTAMP_ATIME: return record->st.st_atim;
case TIMESTAMP_CTIME: return record->st.st_ctim;
case TIMESTAMP_MTIME: return record->st.st_mtim;
}
__builtin_unreachable();
}
static int sort_records(const void* a_ptr, const void* b_ptr)
{
struct record* a = (struct record*) a_ptr;
struct record* b = (struct record*) b_ptr;
if ( option_sort == SORT_SIZE )
{
if ( a->st.st_size < b->st.st_size )
return option_reverse(1);
if ( b->st.st_size < a->st.st_size )
return option_reverse(1);
}
else if ( option_sort == SORT_TIME )
{
struct timespec a_ts = record_timestamp(a);
struct timespec b_ts = record_timestamp(b);
if ( a_ts.tv_sec < b_ts.tv_sec )
return option_reverse(1);
if ( a_ts.tv_sec > b_ts.tv_sec )
return option_reverse(-1);
if ( a_ts.tv_nsec < b_ts.tv_nsec )
return option_reverse(1);
if ( a_ts.tv_nsec > b_ts.tv_nsec )
return option_reverse(-1);
}
return option_reverse(strcoll(a->dirent->d_name, b->dirent->d_name));
}
static int sort_files_then_dirs(const void* a_ptr, const void* b_ptr)
{
struct record* a = (struct record*) a_ptr;
struct record* b = (struct record*) b_ptr;
if ( !option_directory )
{
bool a_as_dir = S_ISDIR(a->st.st_mode) && !a->no_recurse;
bool b_as_dir = S_ISDIR(b->st.st_mode) && !b->no_recurse;
if ( a_as_dir && !b_as_dir )
return 1;
if ( !a_as_dir && b_as_dir )
return -1;
}
return sort_records(a_ptr, b_ptr);
}
static unsigned char mode_to_dt(mode_t mode)
{
if ( S_ISBLK(mode) )
return DT_BLK;
if ( S_ISCHR(mode) )
return DT_CHR;
if ( S_ISDIR(mode) )
return DT_DIR;
if ( S_ISFIFO(mode) )
return DT_FIFO;
if ( S_ISLNK(mode) )
return DT_LNK;
if ( S_ISREG(mode) )
return DT_REG;
if ( S_ISSOCK(mode) )
return DT_SOCK;
return DT_UNKNOWN;
}
static void color(const char** pre,
const char** post,
struct stat* st,
bool failure)
{
*pre = "";
*post = "";
if ( !option_colors )
return;
if ( failure )
{
*pre = "\e[1;31m";
*post = "\e[m";
return;
}
*post = "\e[m";
switch ( mode_to_dt(st->st_mode) )
{
case DT_UNKNOWN: *pre = "\e[1;31m"; break;
case DT_BLK: *pre = "\e[1;33m"; break;
case DT_CHR: *pre = "\e[1;33m"; break;
case DT_DIR: *pre = "\e[1;34m"; break;
case DT_FIFO: *pre = "\e[33m"; break;
case DT_LNK: *pre = "\e[1;36m"; break;
case DT_SOCK: *pre = "\e[1;35m"; break;
case DT_REG:
if ( st->st_mode & 0111 )
*pre = "\e[1;32m";
else
*post = "";
break;
default:
*post = "";
}
}
static void color_record(const char** pre,
const char** post,
struct record* record)
{
bool failure = record->stat_attempt < 0 ||
(record->dirent->d_type == DT_LNK && !record->symlink_path);
color(pre, post, &record->st, failure);
}
static void color_symlink(const char** pre,
const char** post,
struct record* record)
{
color(pre, post, &record->symlink_st, record->symlink_stat_attempt < 0);
}
static bool should_stat(struct record* record)
{
(void) record;
return option_colors ||
option_long ||
option_recursive ||
option_sort != SORT_COLLATE;
}
static bool stat_symlink(DIR* dir, const char* dirpath, struct record* record)
{
if ( !(0 < record->st.st_size && record->st.st_size <= 65536) )
return true;
size_t symlink_path_size = record->st.st_size + 1;
if ( !(record->symlink_path = (char*) malloc(symlink_path_size)) )
err(1, "malloc");
ssize_t ret = readlinkat(dirfd(dir), record->dirent->d_name,
record->symlink_path, symlink_path_size);
if ( ret < 0 )
{
warn("readlink: %s/%s", dirpath, record->dirent->d_name);
return false;
}
record->symlink_path[ret] = '\0';
if ( fstatat(dirfd(dir), record->symlink_path, &record->symlink_st, 0) < 0 )
return record->symlink_stat_attempt = -1, true;
return record->symlink_stat_attempt = 1, true;
}
static bool stat_record(DIR* dir, const char* dirpath, struct record* record)
{
record->st.st_ino = record->dirent->d_ino;
record->st.st_dev = record->dirent->d_dev;
if ( !should_stat(record) )
return record->stat_attempt = 0, true;
if ( fstatat(dirfd(dir), record->dirent->d_name, &record->st,
AT_SYMLINK_NOFOLLOW) < 0 )
{
warn("stat: %s/%s", dirpath, record->dirent->d_name);
return record->stat_attempt = -1, false;
}
if ( record->dirent->d_type == DT_UNKNOWN )
record->dirent->d_type = mode_to_dt(record->st.st_mode);
record->stat_attempt = 1;
if ( S_ISLNK(record->st.st_mode) && !stat_symlink(dir, dirpath, record) )
return false;
return true;
}
static void show_simple(struct record* records, size_t count)
{
for ( size_t i = 0; i < count; i++ )
{
struct record* record = &records[i];
if ( option_inode )
printf("%ju ", (uintmax_t) record->st.st_ino);
const char* pre;
const char* post;
color_record(&pre, &post, record);
printf("%s%s%s\n", pre, record->dirent->d_name, post);
}
}
struct column_size
{
size_t width_inode;
size_t width_name;
};
static void show_column(struct record* records, size_t count)
{
static size_t display_width = 80;
static bool display_width_set = false;
if ( !display_width_set )
{
const char* display_width_env = getenv("COLUMNS");
struct winsize ws;
if ( display_width_env )
display_width = strtoul(display_width_env, NULL, 10);
else if ( tcgetwinsize(1, &ws) == 0 )
display_width = ws.ws_col;
}
if ( !count )
return;
// TODO: -x support.
struct column_size* column_sizes;
size_t columns = 0;
size_t rows = 0;
while ( true )
{
size_t attempt_rows = rows + 1;
size_t attempt_columns = count / attempt_rows +
(count % attempt_rows ? 1 : 0);
struct column_size* attempt_column_sizes = (struct column_size*)
reallocarray(NULL, attempt_columns, sizeof(struct column_size));
if ( !attempt_column_sizes )
err(1, "malloc");
size_t attempt_width = 0;
for ( size_t c = 0; c < attempt_columns; c++ )
{
struct column_size* c_sizes = &attempt_column_sizes[c];
memset(c_sizes, 0, sizeof(*c_sizes));
size_t c_off = attempt_rows * c;
for ( size_t r = 0; r < attempt_rows; r++ )
{
size_t i = c_off + r;
if ( count <= i )
break;
struct record* record = &records[i];
if ( option_inode )
{
char inode_str[sizeof(ino_t) * 3];
snprintf(inode_str, sizeof(inode_str),
"%ju", (uintmax_t) record->dirent->d_ino);
size_t inode_width = string_display_length(inode_str);
if ( c_sizes->width_inode < inode_width )
c_sizes->width_inode = inode_width;
}
const char* name = record->dirent->d_name;
size_t name_width = string_display_length(name);
if ( c_sizes->width_name < name_width )
c_sizes->width_name = name_width;
}
if ( c != 0 )
attempt_width += 2;
if ( option_inode )
attempt_width += c_sizes->width_inode + 1;
attempt_width += c_sizes->width_name;
}
rows = attempt_rows;
columns = attempt_columns;
if ( attempt_width <= display_width || attempt_rows == count )
{
column_sizes = attempt_column_sizes;
break;
}
free(attempt_column_sizes);
}
for ( size_t r = 0; r < rows; r++ )
{
for ( size_t c = 0; c < columns; c++ )
{
size_t i = c * rows + r;
if ( count <= i )
break;
struct column_size* c_sizes = &column_sizes[c];
struct record* record = &records[i];
if ( option_inode )
{
char inode_str[sizeof(ino_t) * 3];
snprintf(inode_str, sizeof(inode_str),
"%ju", (uintmax_t) record->dirent->d_ino);
print_right_aligned(inode_str, c_sizes->width_inode);
putchar(' ');
}
const char* pre;
const char* post;
color_record(&pre, &post, record);
fputs(pre, stdout);
if ( c + 1 == columns || count - i <= rows )
fputs(record->dirent->d_name, stdout);
else
print_left_aligned(record->dirent->d_name, c_sizes->width_name);
fputs(post, stdout);
if ( c + 1 == columns || count - i <= rows )
putchar('\n');
else
fputs(" ", stdout);
}
}
free(column_sizes);
}
static void show_long(struct record* records, size_t count)
{
size_t nlink_field_width = 0;
size_t owner_field_width = 0;
size_t group_field_width = 0;
size_t size_field_width = 0;
size_t time_field_width = 0;
for ( size_t i = 0; i < count; i++ )
{
struct record* record = &records[i];
nlink_t nlink = record->st.st_nlink;
char nlink_str[sizeof(nlink_t) * 3];
snprintf(nlink_str, sizeof(nlink_str), "%ju", (uintmax_t) nlink);
size_t nlink_width = string_display_length(nlink_str);
if ( nlink_field_width < nlink_width )
nlink_field_width = nlink_width;
char owner_fallback[sizeof(uid_t) * 3];
struct passwd* pwd;
const char* owner_str;
if ( record->stat_attempt != 1 )
owner_str = "?";
else if ( (pwd = getpwuid_cache(record->st.st_uid)) )
owner_str = pwd->pw_name;
else
{
snprintf(owner_fallback, sizeof(owner_fallback),
"%ju", (uintmax_t) record->st.st_uid);
owner_str = owner_fallback;
}
size_t owner_width = string_display_length(owner_str);
if ( owner_field_width < owner_width )
owner_field_width = owner_width;
char group_fallback[sizeof(gid_t) * 3];
struct group* grp;
const char* group_str;
if ( record->stat_attempt != 1 )
group_str = "?";
else if ( (grp = getgrgid_cache(record->st.st_gid)) )
group_str = grp->gr_name;
else
{
snprintf(group_fallback, sizeof(group_fallback),
"%ju", (uintmax_t) record->st.st_gid);
group_str = group_fallback;
}
size_t group_width = string_display_length(group_str);
if ( group_field_width < group_width )
group_field_width = group_width;
off_t size = record->st.st_size;
char size_str[FORMAT_BYTES_LENGTH + 1];
if ( option_human_readable )
format_bytes_amount(size_str, sizeof(size_str), size);
else
snprintf(size_str, sizeof(size_str), "%ji", (intmax_t) size);
size_t size_width = string_display_length(size_str);
if ( size_field_width < size_width )
size_field_width = size_width;
struct timespec ts = record_timestamp(record);
struct tm mod_tm;
localtime_r(&ts.tv_sec, &mod_tm);
char time_str[64];
if ( current_year == mod_tm.tm_year )
strftime(time_str, sizeof(time_str), "%b %e %H:%M", &mod_tm);
else
strftime(time_str, sizeof(time_str), "%b %e %Y", &mod_tm);
size_t time_width = string_display_length(time_str);
if ( time_field_width < time_width )
time_field_width = time_width;
}
// TODO: Show a total number of filesystem blocks.
// (But only when listing a directory?)
for ( size_t i = 0; i < count; i++ )
{
struct record* record = &records[i];
if ( option_inode )
printf("%ju ", (uintmax_t) record->st.st_ino);
mode_t mode = record->st.st_mode;
char perms[11];
switch ( record->dirent->d_type )
{
case DT_UNKNOWN: perms[0] = '?'; break;
case DT_BLK: perms[0] = 'b'; break;
case DT_CHR: perms[0] = 'c'; break;
case DT_DIR: perms[0] = 'd'; break;
case DT_FIFO: perms[0] = 'p'; break;
case DT_LNK: perms[0] = 'l'; break;
case DT_REG: perms[0] = '-'; break;
case DT_SOCK: perms[0] = 's'; break;
default: perms[0] = '?'; break;
}
const char flagnames[] = { 'x', 'w', 'r' };
for ( size_t n = 0; n < 9; n++ )
perms[9-n] = mode & (1 << n) ? flagnames[n % 3] : '-';
if ( mode & S_ISUID )
perms[3] = mode & 0100 ? 's' : 'S';
if ( mode & S_ISGID )
perms[6] = mode & 0010 ? 's' : 'S';
if ( mode & S_ISVTX )
perms[9] = mode & 0001 ? 't' : 'T';
if ( record->stat_attempt != 1 )
memset(perms, '?', sizeof(perms) - 1);
perms[10] = '\0';
fputs(perms, stdout);
putchar(' ');
nlink_t nlink = record->st.st_nlink;
char nlink_str[sizeof(nlink_t) * 3];
snprintf(nlink_str, sizeof(nlink_str), "%ju", (uintmax_t) nlink);
print_right_aligned(nlink_str, nlink_field_width);
putchar(' ');
char owner_fallback[sizeof(uid_t) * 3];
struct passwd* pwd;
const char* owner_str;
if ( record->stat_attempt != 1 )
owner_str = "?";
else if ( (pwd = getpwuid_cache(record->st.st_uid)) )
owner_str = pwd->pw_name;
else
{
snprintf(owner_fallback, sizeof(owner_fallback),
"%ju", (uintmax_t) record->st.st_uid);
owner_str = owner_fallback;
}
print_left_aligned(owner_str, owner_field_width);
putchar(' ');
char group_fallback[sizeof(gid_t) * 3];
struct group* grp;
const char* group_str;
if ( record->stat_attempt != 1 )
group_str = "?";
else if ( (grp = getgrgid_cache(record->st.st_gid)) )
group_str = grp->gr_name;
else
{
snprintf(group_fallback, sizeof(group_fallback),
"%ju", (uintmax_t) record->st.st_gid);
group_str = group_fallback;
}
print_left_aligned(group_str, group_field_width);
putchar(' ');
off_t size = record->st.st_size;
char size_str[FORMAT_BYTES_LENGTH + 1];
if ( option_human_readable )
format_bytes_amount(size_str, sizeof(size_str), size);
else
snprintf(size_str, sizeof(size_str), "%ji", (intmax_t) size);
print_right_aligned(size_str, size_field_width);
putchar(' ');
struct timespec ts = record_timestamp(record);
struct tm mod_tm;
localtime_r(&ts.tv_sec, &mod_tm);
char time_str[64];
if ( current_year == mod_tm.tm_year )
strftime(time_str, sizeof(time_str), "%b %e %H:%M", &mod_tm);
else
strftime(time_str, sizeof(time_str), "%b %e %Y", &mod_tm);
print_left_aligned(time_str, time_field_width);
putchar(' ');
const char* pre;
const char* post;
color_record(&pre, &post, record);
fputs(pre, stdout);
fputs(record->dirent->d_name, stdout);
fputs(post, stdout);
if ( record->symlink_path )
{
color_symlink(&pre, &post, record);
fputs(" -> ", stdout);
fputs(pre, stdout);
fputs(record->symlink_path, stdout);
fputs(post, stdout);
}
putchar('\n');
}
}
static void show(struct record* records, size_t count)
{
qsort(records, count, sizeof(*records), sort_records);
if ( option_long )
show_long(records, count);
else if ( option_column )
show_column(records, count);
else
show_simple(records, count);
}
static int ls_directory(int parentfd, const char* relpath, const char* path);
static int show_recursive(int fd, const char* path,
struct record* records, size_t count)
{
// TODO: Use a proper join path function below.
if ( path && !strcmp(path, "/") )
path = "";
if ( option_recursive && path )
show(records, count);
int ret = 0;
qsort(records, count, sizeof(*records), sort_files_then_dirs);
size_t nondir_count = count;
if ( !option_directory )
{
for ( nondir_count = 0; nondir_count < count; nondir_count++ )
{
if ( records[nondir_count].no_recurse )
continue;
if ( S_ISDIR(records[nondir_count].st.st_mode) )
break;
}
}
if ( !(option_recursive && path) )
show(records, nondir_count);
for ( size_t i = nondir_count; i < count; i++ )
{
struct record* record = &records[i];
static bool not_first_directory = false;
if ( (not_first_directory && option_recursive) || i != 0 )
putchar('\n');
not_first_directory = true;
const char* name = record->dirent->d_name;
char* subpath_storage = NULL;
if ( path && asprintf(&subpath_storage, "%s/%s", path, name) < 0 )
err(1, "malloc");
const char* subpath = path ? subpath_storage : name;
if ( option_recursive || 2 <= count )
printf("%s:\n", subpath);
ret |= ls_directory(fd, name, subpath);
free(subpath_storage);
}
return ret;
}
static int ls_directory(int parentfd, const char* relpath, const char* path)
{
int fd = openat(parentfd, relpath, O_RDONLY | O_DIRECTORY);
if ( fd < 0 )
{
warn("%s", path);
return 1;
}
DIR* dir = fdopendir(fd);
if ( !dir )
{
warn("fdopendir: %s", path);
close(fd);
return 1;
}
size_t records_used = 0;
size_t records_length = 64;
struct record* records = (struct record*)
reallocarray(NULL, records_length, sizeof(struct record));
if ( !records )
err(1, "malloc");
int ret = 0;
struct dirent* entry;
while ( (errno = 0, entry = readdir(dir)) )
{
const char* name = entry->d_name;
bool isdotdot = strcmp(name, ".") == 0 || strcmp(name, "..") == 0;
if ( isdotdot && option_all != DOTFILE_ALL )
continue;
bool isdotfile = !isdotdot && name[0] == '.';
if ( isdotfile && option_all == DOTFILE_NO )
continue;
if ( records_used == records_length )
{
struct record* new_records = (struct record*)
reallocarray(records, records_length, 2 * sizeof(struct record));
if ( !new_records )
err(1, "malloc");
records = new_records;
records_length *= 2;
}
struct record* record = &records[records_used++];
memset(record, 0, sizeof(*record));
if ( !(record->dirent = dirent_dup(entry)) )
err(1, "malloc");
if ( !stat_record(dir, path, record) )
ret = 1;
record->no_recurse = isdotdot;
}
// TODO: Stupid /dev/net/fs fake directory, so ignore ENOTDIR for now.
if ( errno != 0 && errno != ENOTDIR )
err(1, "%s", path);
if ( option_recursive )
show_recursive(dirfd(dir), path, records, records_used);
else
show(records, records_used);
closedir(dir);
for ( size_t i = 0; i < records_used; i++ )
{
free(records[i].dirent);
free(records[i].symlink_path);
}
free(records);
return ret;
}
static int ls_operands(char** paths, size_t paths_count)
{
int ret = 0;
struct record* records = (struct record*)
reallocarray(NULL, paths_count, sizeof(struct record));
if ( !records )
err(1, "malloc");
size_t records_used = 0;
for ( size_t i = 0; i < paths_count; i++ )
{
struct record* record = &records[records_used];
memset(record, 0, sizeof(*record));
const char* path = paths[i];
if ( fstatat(AT_FDCWD, path, &record->st, AT_SYMLINK_NOFOLLOW) < 0 )
{
warn("%s", path);
ret = 1;
continue;
}
record->stat_attempt = 1;
if ( !(record->dirent = dirent_make(paths[i])) )
err(1, "malloc");
record->dirent->d_ino = record->st.st_ino;
record->dirent->d_dev = record->st.st_dev;
record->dirent->d_type = mode_to_dt(record->st.st_mode);
records_used++;
}
show_recursive(AT_FDCWD, NULL, records, records_used);
for ( size_t i = 0; i < records_used; i++ )
free(records[i].dirent);
free(records);
return ret;
}
static void compact_arguments(int* argc, char*** argv)
{
for ( int i = 0; i < *argc; i++ )
{
while ( i < *argc && !(*argv)[i] )
{
for ( int n = i; n < *argc; n++ )
(*argv)[n] = (*argv)[n+1];
(*argc)--;
}
}
}
static void version(FILE* fp, const char* argv0)
{
fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR);
}
static void help(FILE* fp, const char* argv0)
{
fprintf(fp, "Usage: %s [OPTION]... [FILE]...\n", argv0);
}
int main(int argc, char** argv)
{
setlocale(LC_ALL, "");
if ( isatty(1) )
{
option_colors = true;
option_column = true;
}
const char* argv0 = argv[0];
for ( int i = 1; i < argc; i++ )
{
const char* arg = argv[i];
if ( arg[0] != '-' || !arg[1] )
continue;
argv[i] = NULL;
if ( !strcmp(arg, "--") )
break;
if ( arg[1] != '-' )
{
char c;
while ( (c = *++arg) ) switch ( c )
{
case '1': option_column = false; break; // TODO: Semantics?
case 'a': option_all = DOTFILE_ALL; break;
case 'A': option_all = DOTFILE_ALMOST; break;
case 'c': option_time_type = TIMESTAMP_CTIME; break;
case 'C': option_column = true; break; // TODO: Disable -l and such.
case 'd': option_directory = true; break;
// TODO: -f.
// TODO: -F.
case 'h': option_human_readable = true; break;
// TODO: -H.
case 'i': option_inode = true; break;
// TODO: -k.
case 'l': option_long = true; break;
// TODO: -L.
// TODO: -m.
// TODO: -n.
// TODO: -p.
// TODO: -q.
case 'r': option_reverse = order_reverse; break;
case 'R': option_recursive = true; break;
// TODO: -s.
case 'S': option_sort = SORT_SIZE; break;
case 't': option_sort = SORT_TIME; break;
case 'u': option_time_type = TIMESTAMP_ATIME; break;
// TODO: -x.
default:
fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c);
help(stderr, argv0);
exit(1);
}
}
else if ( !strcmp(arg, "--version") )
version(stdout, argv0), exit(0);
else if ( !strcmp(arg, "--help") )
help(stdout, argv0), exit(0);
else if ( !strcmp(arg, "--all") )
option_all = DOTFILE_ALL;
else if ( !strcmp(arg, "--almost-all") )
option_all = DOTFILE_ALMOST;
else if ( !strcmp(arg, "--color=always") ) // TODO: Proper parsing.
option_colors = true;
else if ( !strcmp(arg, "--color=auto") ) // TODO: Proper parsing.
;
else if ( !strcmp(arg, "--color=never") ) // TODO: Proper parsing.
option_colors=false;
else if ( !strcmp(arg, "--directory") )
option_directory = true;
else if ( !strcmp(arg, "--human-readable") )
option_human_readable = true;
else if ( !strcmp(arg, "--inode") )
option_inode = true;
else if ( !strcmp(arg, "--recursive") )
option_recursive = true;
else if ( !strcmp(arg, "--reverse") )
option_reverse = order_reverse;
else
{
fprintf(stderr, "%s: unrecognized option: %s\n", argv0, arg);
help(stderr, argv0);
exit(1);
}
}
compact_arguments(&argc, &argv);
time_t current_time;
struct tm current_year_tm;
time(&current_time);
localtime_r(&current_time, &current_year_tm);
current_year = current_year_tm.tm_year;
option_multiple_operands = 3 <= argc;
char* curdir = (char*) ".";
int ret = 2 <= argc ? ls_operands(argv + 1, argc - 1)
: ls_operands(&curdir, 1);
if ( ferror(stdout) || fflush(stdout) == EOF )
return 1;
return ret;
}