toaruos/apps/ls.c

481 lines
11 KiB
C
Raw Normal View History

2018-02-25 08:13:54 +03:00
/* vim: tabstop=4 shiftwidth=4 noexpandtab
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2013-2018 K. Lange
2018-02-25 08:13:54 +03:00
*
* ls
*
* Lists files in a directory, with nice color
* output like any modern ls should have.
*/
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <termios.h>
2018-03-01 03:31:12 +03:00
#include <time.h>
2018-02-25 08:13:54 +03:00
#include <pwd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
2018-02-28 10:23:07 +03:00
#define TRACE_APP_NAME "ls"
//#include "lib/trace.h"
#define TRACE(...)
2018-03-19 05:38:11 +03:00
#include <toaru/list.h>
2018-02-25 08:13:54 +03:00
#define MIN_COL_SPACING 2
#define EXE_COLOR "1;32"
#define DIR_COLOR "1;34"
#define SYMLINK_COLOR "1;36"
#define REG_COLOR "0"
#define MEDIA_COLOR ""
#define SYM_COLOR ""
#define BROKEN_COLOR "1;"
#define DEVICE_COLOR "1;33;40"
#define SETUID_COLOR "37;41"
#define DEFAULT_TERM_WIDTH 0
#define DEFAULT_TERM_HEIGHT 0
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define LINE_LEN 4096
static int human_readable = 0;
static int stdout_is_tty = 1;
static int this_year = 0;
static int show_hidden = 0;
static int long_mode = 0;
static int print_dir = 0;
static int term_width = DEFAULT_TERM_WIDTH;
static int term_height = DEFAULT_TERM_HEIGHT;
struct tfile {
char * name;
struct stat statbuf;
char * link;
struct stat statbufl;
};
static const char * color_str(struct stat * sb) {
if (S_ISDIR(sb->st_mode)) {
/* Directory */
return DIR_COLOR;
} else if (S_ISLNK(sb->st_mode)) {
/* Symbolic Link */
return SYMLINK_COLOR;
} else if (sb->st_mode & S_ISUID) {
/* setuid - sudo, etc. */
return SETUID_COLOR;
} else if (sb->st_mode & 0111) {
/* Executable */
return EXE_COLOR;
} else if (S_ISBLK(sb->st_mode) || S_ISCHR(sb->st_mode)) {
/* Device file */
return DEVICE_COLOR;
} else {
/* Regular file */
return REG_COLOR;
}
}
static int filecmp(const void * c1, const void * c2) {
const struct tfile * d1 = *(const struct tfile **)c1;
const struct tfile * d2 = *(const struct tfile **)c2;
int a = S_ISDIR(d1->statbuf.st_mode);
int b = S_ISDIR(d2->statbuf.st_mode);
if (a == b) return strcmp(d1->name, d2->name);
else if (a < b) return -1;
else if (a > b) return 1;
2018-04-25 08:03:29 +03:00
return 0; /* impossible ? */
2018-02-25 08:13:54 +03:00
}
static int filecmp_notypesort(const void * c1, const void * c2) {
const struct tfile * d1 = *(const struct tfile **)c1;
const struct tfile * d2 = *(const struct tfile **)c2;
return strcmp(d1->name, d2->name);
}
static void print_entry(struct tfile * file, int colwidth) {
const char * ansi_color_str = color_str(&file->statbuf);
/* Print the file name */
if (stdout_is_tty) {
printf("\033[%sm%s\033[0m", ansi_color_str, file->name);
} else {
printf("%s", file->name);
}
/* Pad the rest of the column */
for (int rem = colwidth - strlen(file->name); rem > 0; rem--) {
printf(" ");
}
}
static int print_username(char * _out, int uid) {
2018-02-28 10:23:07 +03:00
TRACE("getpwuid");
2018-02-25 08:13:54 +03:00
struct passwd * p = getpwuid(uid);
int out = 0;
if (p) {
2018-02-28 10:23:07 +03:00
TRACE("p is set");
2018-02-25 08:13:54 +03:00
out = sprintf(_out, "%s", p->pw_name);
} else {
2018-02-28 10:23:07 +03:00
TRACE("p is not set");
2018-02-25 08:13:54 +03:00
out = sprintf(_out, "%d", uid);
}
endpwent();
return out;
}
static int print_human_readable_size(char * _out, size_t s) {
if (s >= 1<<20) {
size_t t = s / (1 << 20);
2018-04-25 08:03:29 +03:00
return sprintf(_out, "%d.%1dM", (int)t, (int)(s - t * (1 << 20)) / ((1 << 20) / 10));
2018-02-25 08:13:54 +03:00
} else if (s >= 1<<10) {
size_t t = s / (1 << 10);
2018-04-25 08:03:29 +03:00
return sprintf(_out, "%d.%1dK", (int)t, (int)(s - t * (1 << 10)) / ((1 << 10) / 10));
2018-02-25 08:13:54 +03:00
} else {
2018-04-25 08:03:29 +03:00
return sprintf(_out, "%d", (int)s);
2018-02-25 08:13:54 +03:00
}
}
static void update_column_widths(int * widths, struct tfile * file) {
char tmp[256];
int n;
/* Links */
2018-02-28 10:23:07 +03:00
TRACE("links");
2018-02-25 08:13:54 +03:00
n = sprintf(tmp, "%d", file->statbuf.st_nlink);
if (n > widths[0]) widths[0] = n;
/* User */
2018-02-28 10:23:07 +03:00
TRACE("user");
2018-02-25 08:13:54 +03:00
n = print_username(tmp, file->statbuf.st_uid);
if (n > widths[1]) widths[1] = n;
/* Group */
2018-02-28 10:23:07 +03:00
TRACE("group");
2018-02-25 08:13:54 +03:00
n = print_username(tmp, file->statbuf.st_gid);
if (n > widths[2]) widths[2] = n;
/* File size */
2018-02-28 10:23:07 +03:00
TRACE("file size");
2018-02-25 08:13:54 +03:00
if (human_readable) {
n = print_human_readable_size(tmp, file->statbuf.st_size);
} else {
2018-04-25 08:03:29 +03:00
n = sprintf(tmp, "%d", (int)file->statbuf.st_size);
2018-02-25 08:13:54 +03:00
}
if (n > widths[3]) widths[3] = n;
}
static void print_entry_long(int * widths, struct tfile * file) {
const char * ansi_color_str = color_str(&file->statbuf);
/* file permissions */
if (S_ISLNK(file->statbuf.st_mode)) { printf("l"); }
else if (S_ISCHR(file->statbuf.st_mode)) { printf("c"); }
else if (S_ISBLK(file->statbuf.st_mode)) { printf("b"); }
else if (S_ISDIR(file->statbuf.st_mode)) { printf("d"); }
else { printf("-"); }
printf( (file->statbuf.st_mode & S_IRUSR) ? "r" : "-");
printf( (file->statbuf.st_mode & S_IWUSR) ? "w" : "-");
if (file->statbuf.st_mode & S_ISUID) {
printf("s");
} else {
printf( (file->statbuf.st_mode & S_IXUSR) ? "x" : "-");
}
printf( (file->statbuf.st_mode & S_IRGRP) ? "r" : "-");
printf( (file->statbuf.st_mode & S_IWGRP) ? "w" : "-");
printf( (file->statbuf.st_mode & S_IXGRP) ? "x" : "-");
printf( (file->statbuf.st_mode & S_IROTH) ? "r" : "-");
printf( (file->statbuf.st_mode & S_IWOTH) ? "w" : "-");
printf( (file->statbuf.st_mode & S_IXOTH) ? "x" : "-");
printf( " %*d ", widths[0], file->statbuf.st_nlink); /* number of links, not supported */
char tmp[100];
print_username(tmp, file->statbuf.st_uid);
printf("%-*s ", widths[1], tmp);
print_username(tmp, file->statbuf.st_gid);
printf("%-*s ", widths[2], tmp);
if (human_readable) {
print_human_readable_size(tmp, file->statbuf.st_size);
printf("%*s ", widths[3], tmp);
} else {
2018-04-25 08:03:29 +03:00
printf("%*d ", widths[3], (int)file->statbuf.st_size);
2018-02-25 08:13:54 +03:00
}
char time_buf[80];
2018-04-25 08:03:29 +03:00
struct tm * timeinfo = localtime((time_t*)&file->statbuf.st_mtime);
2018-02-25 08:13:54 +03:00
if (timeinfo->tm_year == this_year) {
strftime(time_buf, 80, "%b %d %H:%M", timeinfo);
} else {
strftime(time_buf, 80, "%b %d %Y", timeinfo);
}
2018-03-01 03:31:12 +03:00
printf("%s ", time_buf);
2018-02-25 08:13:54 +03:00
/* Print the file name */
if (stdout_is_tty) {
printf("\033[%sm%s\033[0m", ansi_color_str, file->name);
if (S_ISLNK(file->statbuf.st_mode)) {
const char * s = color_str(&file->statbufl);
printf(" -> \033[%sm%s\033[0m", s, file->link);
}
} else {
printf("%s", file->name);
if (S_ISLNK(file->statbuf.st_mode)) {
printf(" -> %s", file->link);
}
}
printf("\n");
}
static void show_usage(int argc, char * argv[]) {
printf(
"ls - list files\n"
"\n"
"usage: %s [-lha] [path]\n"
"\n"
" -a \033[3mlist all files (including . files)\033[0m\n"
" -l \033[3muse a long listing format\033[0m\n"
" -h \033[3mhuman-readable file sizes\033[0m\n"
" -? \033[3mshow this help text\033[0m\n"
"\n", argv[0]);
}
static void display_tfiles(struct tfile ** ents_array, int numents) {
if (long_mode) {
2018-02-28 10:23:07 +03:00
TRACE("long mode display, column lengths");
2018-02-25 08:13:54 +03:00
int widths[4] = {0,0,0,0};
for (int i = 0; i < numents; i++) {
update_column_widths(widths, ents_array[i]);
}
2018-02-28 10:23:07 +03:00
TRACE("actual printing");
2018-02-25 08:13:54 +03:00
for (int i = 0; i < numents; i++) {
print_entry_long(widths, ents_array[i]);
}
} else {
/* Determine the gridding dimensions */
int ent_max_len = 0;
for (int i = 0; i < numents; i++) {
2018-04-25 08:03:29 +03:00
ent_max_len = MAX(ent_max_len, (int)strlen(ents_array[i]->name));
2018-02-25 08:13:54 +03:00
}
int col_ext = ent_max_len + MIN_COL_SPACING;
int cols = ((term_width - ent_max_len) / col_ext) + 1;
/* Print the entries */
for (int i = 0; i < numents;) {
/* Columns */
print_entry(ents_array[i++], ent_max_len);
for (int j = 0; (i < numents) && (j < (cols-1)); j++) {
printf(" ");
print_entry(ents_array[i++], ent_max_len);
}
printf("\n");
}
}
}
static int display_dir(char * p) {
/* Open the directory */
DIR * dirp = opendir(p);
if (dirp == NULL) {
return 2;
}
if (print_dir) {
printf("%s:\n", p);
}
/* Read the entries in the directory */
list_t * ents_list = list_create();
2018-02-28 10:23:07 +03:00
TRACE("reading entries");
2018-02-25 08:13:54 +03:00
struct dirent * ent = readdir(dirp);
while (ent != NULL) {
if (show_hidden || (ent->d_name[0] != '.')) {
struct tfile * f = malloc(sizeof(struct tfile));
f->name = strdup(ent->d_name);
char tmp[strlen(p)+strlen(ent->d_name)+2];
2018-02-25 08:13:54 +03:00
sprintf(tmp, "%s/%s", p, ent->d_name);
2018-04-25 08:03:29 +03:00
lstat(tmp, &f->statbuf);
2018-02-25 08:13:54 +03:00
if (S_ISLNK(f->statbuf.st_mode)) {
stat(tmp, &f->statbufl);
f->link = malloc(4096);
readlink(tmp, f->link, 4096);
}
list_insert(ents_list, (void *)f);
}
ent = readdir(dirp);
}
closedir(dirp);
2018-02-28 10:23:07 +03:00
TRACE("copying");
2018-02-25 08:13:54 +03:00
/* Now, copy those entries into an array (for sorting) */
2018-05-08 16:39:37 +03:00
if (!ents_list->length) return 0;
2018-02-25 08:13:54 +03:00
struct tfile ** file_arr = malloc(sizeof(struct tfile *) * ents_list->length);
int index = 0;
foreach(node, ents_list) {
file_arr[index++] = (struct tfile *)node->value;
}
list_free(ents_list);
2018-02-28 10:23:07 +03:00
TRACE("sorting");
2018-02-25 08:13:54 +03:00
qsort(file_arr, index, sizeof(struct tfile *), filecmp_notypesort);
2018-02-28 10:23:07 +03:00
TRACE("displaying");
2018-02-25 08:13:54 +03:00
display_tfiles(file_arr, index);
free(file_arr);
return 0;
}
int main (int argc, char * argv[]) {
/* Parse arguments */
char * p = ".";
if (argc > 1) {
2018-05-02 12:58:47 +03:00
int c;
2018-02-25 08:13:54 +03:00
while ((c = getopt(argc, argv, "ahl?")) != -1) {
switch (c) {
case 'a':
show_hidden = 1;
break;
case 'h':
human_readable = 1;
break;
case 'l':
long_mode = 1;
break;
case '?':
show_usage(argc, argv);
return 0;
}
}
if (optind < argc) {
p = argv[optind];
}
if (optind + 1 < argc) {
print_dir = 1;
}
}
2018-02-28 10:23:07 +03:00
stdout_is_tty = isatty(STDOUT_FILENO);
2018-02-25 08:13:54 +03:00
if (long_mode) {
struct tm * timeinfo;
struct timeval now;
gettimeofday(&now, NULL); //time(NULL);
timeinfo = localtime((time_t *)&now.tv_sec);
this_year = timeinfo->tm_year;
}
if (stdout_is_tty) {
2018-02-28 10:23:07 +03:00
TRACE("getting display size");
2018-02-25 08:13:54 +03:00
struct winsize w;
ioctl(1, TIOCGWINSZ, &w);
term_width = w.ws_col;
term_height = w.ws_row;
term_width -= 1; /* And this just helps clean up our math */
}
int out = 0;
if (argc == 1 || optind == argc) {
2018-02-28 10:23:07 +03:00
TRACE("no file to look up");
2018-02-25 08:13:54 +03:00
display_dir(p);
} else {
list_t * files = list_create();
while (p) {
struct tfile * f = malloc(sizeof(struct tfile));
f->name = p;
int t = stat(p, &f->statbuf);
if (t < 0) {
printf("ls: cannot access %s: No such file or directory\n", p);
free(f);
out = 2;
} else {
list_insert(files, f);
}
optind++;
if (optind >= argc) p = NULL;
else p = argv[optind];
}
struct tfile ** file_arr = malloc(sizeof(struct tfile *) * files->length);
int index = 0;
foreach(node, files) {
file_arr[index++] = (struct tfile *)node->value;
}
list_free(files);
qsort(file_arr, index, sizeof(struct tfile *), filecmp);
int first_directory = index;
for (int i = 0; i < index; ++i) {
if (S_ISDIR(file_arr[i]->statbuf.st_mode)) {
first_directory = i;
break;
}
}
if (first_directory) {
display_tfiles(file_arr, first_directory);
}
for (int i = first_directory; i < index; ++i) {
if (i != 0) {
printf("\n");
}
display_dir(file_arr[i]->name);
}
}
return out;
}
/*
* vim: tabstop=4
* vim: shiftwidth=4
* vim: noexpandtab
*/