/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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(¤t_time); localtime_r(¤t_time, ¤t_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; }