Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic ls implementation #31

Merged
merged 10 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/ls/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
OUT := ls

SRC := ls.c
SRC := ls_main.c
SRC += ls_list_directories.c
SRC += ls_print_info.c
SRC += ls_recursive_walk.c

include ../shared.mk
42 changes: 41 additions & 1 deletion src/ls/ls.1
Original file line number Diff line number Diff line change
@@ -1 +1,41 @@
\" TODO
.Dt %N 1
.Dd April 19, 2024
.Dt ls 1
.Os
.Sh NAME
.Nm ls
.Nd list directory contents (Canoutils implementation)
.Sh SYNOPSIS
.Nm
.Op Fl h
.TP
.
.Sh DESCRIPTION
.Nm
List information about the FILEs (the current directory by default).
.
.Sh OPTIONS
\fBls\fR interprets the following options when it is invoked:
.Pp
.Bl -tag -width Ds
.
.It Fl a
do not ignore entries starting with `.`
.It Fl l
use a long listing format
.It Fl R
list subdirectories recursively
.It Fl d
list directories themselves, not their contents
.It Fl r
reverse order while sorting
.It Fl t
sort by time, newest first
.
.El
.
.Sh Copyright
This project is dedicated to the public domain, which means you're free to use it however you like. While mentioning us is optional, we'd greatly appreciate it if you would consider acknowledging our contribution.
.Sh AUTHOR
Canoutils `ls` was written by \fBSigmanificient\fB.
.br
6 changes: 0 additions & 6 deletions src/ls/ls.c

This file was deleted.

60 changes: 60 additions & 0 deletions src/ls/ls.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#ifndef MY_LS_H
#define MY_LS_H

#include <stdbool.h>
#include <stddef.h>

#define ZERO_OR(expr, default) ((!!(expr)) * default)

#define __USE_XOPEN2K8
#define __USE_MISC

#include <linux/limits.h>
#include <sys/stat.h>

#define MIN_ALLOCATED_ENTRY (1024)

#if !defined(__clang__) || !defined(__GNUC__)
#define __attribute__(x) // omitted
#endif

enum {
F_ALL_FILES = 1 << 0,
F_LONG_FORM = 1 << 1,
F_RECURSIVE = 1 << 2,
F_DIRECTORY = 1 << 3,
F_REV_ORDER = 1 << 4,
F_SORT_TIME = 1 << 5,
F_SHOW_DIRS = 1 << 6,
};

typedef struct {
struct stat stat;
struct passwd *passwd;
struct group *group;
char name[NAME_MAX + 1];
} entry_t;

typedef struct {
char *name;
entry_t *entries;
size_t size;
bool is_file;
} dirbuff_t;

inline __attribute__((const)) int stridx(const char *str, char c) {
for (const char *p = str; *p != '\0'; p++)
if (*p == c)
return (int)(p - str);
return -1;
}

char *strdup(char const *s);

int list_dir(dirbuff_t *db, char flags);
size_t recurse(dirbuff_t *db, size_t count, char flags);

void print_entries(entry_t *entry, size_t count, char flags);
char *path_concat(char *dest, char *basepath, char *suffix);

#endif
95 changes: 95 additions & 0 deletions src/ls/ls_list_directories.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include <dirent.h>
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "ls.h"

static void get_file_info(const char *path, entry_t *entry) {
if (stat(path, &entry->stat) < 0)
return;
entry->passwd = getpwuid(entry->stat.st_uid);
entry->group = getgrgid(entry->stat.st_gid);
}

static __attribute__((nonnull)) size_t read_directory(dirbuff_t *db, DIR *dir,
char flags) {
static char path[PATH_MAX];
size_t i = 0;

for (struct dirent *dirent = readdir(dir); dirent != NULL;
dirent = readdir(dir)) {
if (dirent->d_name[0] == '.' && ~flags & F_ALL_FILES)
continue;
if (i == db->size) {
db->size <<= 1;
db->entries = realloc(db->entries, db->size * sizeof(*db->entries));
}
strcpy(db->entries[i].name, dirent->d_name);
if (flags & (F_LONG_FORM | F_SORT_TIME | F_RECURSIVE))
get_file_info(path_concat(path, db->name, db->entries[i].name),
&db->entries[i]);
i++;
}
return i;
}

static void print_error(char *dirname) {
char const *err = strerror(errno);

switch (errno) {
case ENOENT:
fprintf(stderr, "ls: cannot access '%s': %s\n", dirname, err);
return;
case EACCES:
fprintf(stderr, "ls: cannot open directory '%s': %s\n", dirname, err);
return;
default:
fprintf(stderr, "ls: %s\n", err);
}
}

static int compare_names(entry_t const *leftp, entry_t const *rightp) {
return strcoll(leftp->name, rightp->name);
}

static int compare_times(entry_t const *leftp, entry_t const *rightp) {
return (int)(rightp->stat.st_mtim.tv_sec - leftp->stat.st_mtim.tv_sec);
}

int list_dir(dirbuff_t *db, char flags) {
struct stat fi;
size_t count = 1;
DIR *dir;

if (stat(db->name, &fi) < 0)
return print_error(db->name), -1;
db->is_file = S_ISDIR(fi.st_mode) && ~flags & F_DIRECTORY;
if (db->is_file) {
dir = opendir(db->name);
if (dir == NULL)
return print_error(db->name), -1;
count = read_directory(db, dir, flags);
closedir(dir);
} else {
strcpy(db->entries[0].name, db->name);
get_file_info(db->name, &db->entries[0]);
}

qsort(db->entries, count, sizeof *db->entries, (__compar_fn_t)&compare_names);
if (flags & F_SORT_TIME)
qsort(db->entries, count, sizeof *db->entries,
(__compar_fn_t)&compare_times);
if (flags & (F_SHOW_DIRS | F_RECURSIVE) && !db->is_file)
printf("%s:\n", db->name);
print_entries(db->entries, count, flags);
if (flags & F_RECURSIVE && !db->is_file)
recurse(db, count, flags);
return 0;
}
61 changes: 61 additions & 0 deletions src/ls/ls_main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include <stdlib.h>

#include "ls.h"

static const char FLAGLIST[] = "alRdrt";
static char DEFAULT_LOCATION[] = ".";

static char compose_flaglist(int argc, char **argv) {
int flags = 0;

for (int i = 1; i < argc; i++) {
if (argv[i][0] != '-' || argv[i][1] == '\0')
continue;
for (int j = 1; argv[i][j] != '\0'; j++)
flags |= 1 << (stridx(FLAGLIST, argv[i][j]) + 1);
}
return (char)(flags >> 1);
}

static size_t count_targets(int argc, char **argv) {
int count = 0;

for (int i = 1; i < argc; i++)
if (argv[i][0] != '-' || argv[i][1] == '\0')
count++;
return count;
}

static bool list_dirs(dirbuff_t *db, int argc, char **argv, char flags) {
int err = 0;
size_t count = count_targets(argc, argv);

if (count == 0) {
db->name = DEFAULT_LOCATION;
err |= list_dir(db, flags);
}
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-' && argv[i][1] != '\0')
continue;
db->name = argv[i];
if (count > 1)
flags |= F_SHOW_DIRS;
err |= list_dir(db, flags);
}
return err;
}

int main(int argc, char **argv) {
dirbuff_t db = {.size = MIN_ALLOCATED_ENTRY};
char flags = compose_flaglist(argc, argv);
int err = 0;

db.entries = malloc(db.size * sizeof(*db.entries));
if (db.entries == NULL)
return EXIT_FAILURE;
if (flags & F_DIRECTORY)
flags &= ~F_RECURSIVE;
err |= !list_dirs(&db, argc, argv, flags);
free(db.entries);
return err ? EXIT_SUCCESS : EXIT_FAILURE;
}
95 changes: 95 additions & 0 deletions src/ls/ls_print_info.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include <dirent.h>
#include <grp.h>
#include <pwd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/sysmacros.h>
#include <sys/types.h>
#include <time.h>

#include "ls.h"

typedef unsigned char uchar;

static void get_file_right(char bits[static sizeof("rwxrwxrwx")],
entry_t *entry) {
char *bitsp = bits;
mode_t mode = entry->stat.st_mode;
static const char *s = "-rwx";

*bitsp++ = s[(uchar)ZERO_OR(mode & S_IRUSR, 1)];
*bitsp++ = s[(uchar)ZERO_OR(mode & S_IWUSR, 2)];
*bitsp++ = s[(uchar)ZERO_OR(mode & S_IXUSR, 3)];
*bitsp++ = s[(uchar)ZERO_OR(mode & S_IRGRP, 1)];
*bitsp++ = s[(uchar)ZERO_OR(mode & S_IWGRP, 2)];
*bitsp++ = s[(uchar)ZERO_OR(mode & S_IXGRP, 3)];
*bitsp++ = s[(uchar)ZERO_OR(mode & S_IROTH, 1)];
*bitsp++ = s[(uchar)ZERO_OR(mode & S_IWOTH, 2)];
*bitsp++ = s[(uchar)ZERO_OR(mode & S_IXOTH, 3)];
if (mode & S_ISUID)
bits[1] = (mode & S_IXUSR) ? 's' : 'S';
if (mode & S_ISGID)
bits[3] = (mode & S_IXGRP) ? 's' : 'l';
if (mode & S_ISVTX)
bits[8] = (mode & S_IXOTH) ? 't' : 'T';
}

static char get_file_type(entry_t *entry) {
const char typ[] = {
[S_IFBLK] = 'b', [S_IFCHR] = 'c', [S_IFDIR] = 'd', [S_IFIFO] = 'p',
[S_IFLNK] = 'l', [S_IFREG] = '-', [S_IFSOCK] = 's', [0] = '?'};

return typ[(entry->stat.st_mode & S_IFMT)];
}

static char *get_creation_time(entry_t *entry) {
static char fmt[sizeof("Jan 01 00:00")];
char *ct = ctime(&entry->stat.st_mtim.tv_sec);
time_t now = time(NULL);
const int six_month_sec = 6 * 24 * 3600 * 31;

if (strlen(ct) < 24)
return NULL;
if (entry->stat.st_mtim.tv_sec + six_month_sec < now) {
strncpy(fmt, ct + 4, 7);
strncpy(fmt + 7, ct + 19, 5);
} else
strncpy(fmt, ct + 4, 12);
return fmt;
}

static void print_file_infos(entry_t *entry) {
struct stat *fi = &entry->stat;
char perms[sizeof("-rwxr-xr-x")] = {[0] = get_file_type(entry), '\0'};
char const *owner = (entry->passwd == NULL) ? "?" : entry->passwd->pw_name;
char const *grp = (entry->group == NULL) ? "?" : entry->group->gr_name;
char *time = get_creation_time(entry);

get_file_right(perms + 1, entry);
printf("%.10s %ld %s %s ", perms, fi->st_nlink, owner, grp);
if (stridx("bc", perms[0]) != -1)
printf("%d, %d", major(fi->st_rdev), minor(fi->st_rdev));
else
printf("%ld", fi->st_size);
printf(" %.12s ", time);
}

void print_entries(entry_t *entry, size_t count, char flags) {
int d;

if (flags & F_REV_ORDER) {
d = -1;
entry += (count - 1);
} else
d = 1;
for (size_t i = 0; i < count; i++) {
if (flags & F_LONG_FORM)
print_file_infos(entry);
printf("%s", entry->name);
printf(((i + 1) == count || flags & F_LONG_FORM) ? "\n" : " ");
entry += d;
}
}
Loading