diff options
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | README.md | 52 | ||||
-rw-r--r-- | columns.c | 173 | ||||
-rw-r--r-- | columns.h | 14 | ||||
-rw-r--r-- | data_struct.c | 153 | ||||
-rw-r--r-- | data_struct.h | 60 | ||||
-rw-r--r-- | ls.png | bin | 0 -> 118061 bytes | |||
-rw-r--r-- | ls_imitation.c | 620 |
8 files changed, 1093 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cf3989a --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +SRCMODULES = ls_imitation.c data_struct.c columns.c +OBJMODULES = $(SRCMODULES:.c=.o) +CC = gcc +CFLAGS = -Wall -g + +all: ls_imitation + +%.o: %.c %.h + $(CC) $(CFLAGS) $< -o $@ + +ls_imitation: $(OBJMODULES) + $(CC) $^ -o $@ + +ifneq (clean, $(MAKECMDGOALS)) +-include deps.mk +endif + +deps.mk: $(SRCMODULES) + $(CC) -MM $^ > $@ +clean: + rm -f *.o ls_imitation diff --git a/README.md b/README.md new file mode 100644 index 0000000..03b5f29 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# ls-imitation + +C project that reimplements the functionality of the Unix `ls -l` command. The +program parses command-line arguments, retrieves file information, and formats +the output similar to GNU `ls`. + +<img src="ls.png" /> + +## Features + +- Implements `ls -l` style output: + - File type and permissions + - Number of links + - Owner and group + - File size + - Modification time + - Filename +- Handles both files and directories from command-line arguments +- Prints directory headers and separators when multiple directories are listed +- Detects whether output is a terminal (`isatty`) and adjusts formatting +- Maintains a linked-list of file entries with conversion to arrays +- Supports sorting file entries by filename +- Column alignment for neat tabular output +- Edge cases handled (empty input, single file, multiple directories, etc.) +- Simple build system via `Makefile` + +## Building + +### On Linux + +```bash +git clone https://git.scratko.xyz/ls-imitation +cd ls-imitation +make +./ls-imitation +``` + +## Usage + +The program mimics the behavior of `ls -l`. Instead of calling `ls -l`, run: + +```bash +./ls-imitation <files or directories> +``` +### Examples + +```bash +./ls-imitation +./ls-imitation file.txt +./ls-imitation dir1 dir2 +./ls-imitation file1.txt dir/ +``` diff --git a/columns.c b/columns.c new file mode 100644 index 0000000..fe35070 --- /dev/null +++ b/columns.c @@ -0,0 +1,173 @@ +#include "columns.h" +#include "data_struct.h" + +#include <string.h> +#include <stddef.h> + +/* column indexes used for alignment */ +enum column_type { + col_links = 1, + col_owner = 2, + col_group = 3, + col_size = 4 +}; + +/* + * find pointer to beginning of column in line. + * pad_spaces -- column index + */ +static char *find_start_column_pos(char *line, enum column_type pad_spaces) +{ + size_t found_whitespace = 0; + + while(found_whitespace != pad_spaces) { + if(*line == ' ') { + ++found_whitespace; + /* skip consecutive spaces */ + while(*line == ' ') + ++line; + } else + ++line; + } + return line; +} + +/* Find pointer to the end of current column */ +static char *find_end_column_pos(char *line) +{ + while(*line && *line != ' ') + ++line; + + return line; +} + +/* + * Count number of symbols in column. + * Special case: when dealing with major and minor numbers (contains ','). + */ +static size_t compute_symbols(const char *line, enum column_type pad_spaces) +{ + size_t symbols_counter = 0; + + while(*line && *line != ' ') { + ++symbols_counter; + /* extra space for alignment after comma */ + if(*line == ',' && pad_spaces == col_size) { + ++symbols_counter; + ++line; + } + ++line; + } + return symbols_counter; +} + +/* scan all items and find maximum column width */ +static size_t find_max_column(struct file_item *list, + enum column_type pad_spaces) +{ + size_t max_columns = 0; + size_t current_columns = 0; + char *pos = NULL; + + while(list) { + pos = find_start_column_pos(list->info_line, pad_spaces); + current_columns = compute_symbols(pos, pad_spaces); + if(current_columns > max_columns) + max_columns = current_columns; + list = list->next; + } + return max_columns; +} + +/* check if column must be left aligned */ +static int is_left_alignment(enum column_type pad_spaces) +{ + return pad_spaces == col_owner || pad_spaces == col_group; +} + +/* calculate remaining string length including null symbol */ +static size_t get_remaining_line_length(const char *line) +{ + size_t symbols_counter = 0; + + while(*line) { + ++symbols_counter; + ++line; + } + /* null-terminated */ + ++symbols_counter; + + return symbols_counter; +} + +/* fill alignment gap with spaces */ +static void pad_with_spaces(char *pos, int alignment) { + for (; alignment > 0; --alignment, ++pos) + *pos = ' '; +} + +/* shift contents to the right and pad with spaces */ +static void shift_and_pad(char *insert_pos, size_t alignment, + char *start_buffer_pos) +{ + size_t remaining_symbols = get_remaining_line_length(insert_pos); + size_t offset = (size_t)(insert_pos - start_buffer_pos); + + if (offset + remaining_symbols + alignment <= buffer_size) { + memmove(insert_pos + alignment, insert_pos, remaining_symbols); + pad_with_spaces(insert_pos, alignment); + } +} + +/* insert spaces after column (left alignment) */ +static void left_align(char *column_pos, size_t alignment, + char *start_buffer_pos) +{ + char *end_column_pos = find_end_column_pos(column_pos); + + shift_and_pad(end_column_pos, alignment, start_buffer_pos); +} + +/* insert spaces before column (right alignment) */ +static void right_align(char *column_pos, size_t alignment, + char *start_buffer_pos) +{ + shift_and_pad(column_pos, alignment, start_buffer_pos); +} + +/* align single column*/ +static void align_column(struct file_item *list, enum column_type pad_spaces) +{ + char *column_pos = NULL; + size_t max_columns = find_max_column(list, pad_spaces); + size_t current_columns = 0; + size_t different = 0; + + if(max_columns == 1) + return; + + while(list) { + column_pos = find_start_column_pos(list->info_line, pad_spaces); + current_columns = compute_symbols(column_pos, pad_spaces); + different = max_columns - current_columns; + if(different > 0) { + if(is_left_alignment(pad_spaces)) + left_align(column_pos, different, list->info_line); + else + right_align(column_pos, different, list->info_line); + } + list = list->next; + } +} + +/* + * Align all columns (links, owner, group, size). + * Must be called after building info_line + */ +void align_columns(struct file_item *list) +{ + align_column(list, col_links); + align_column(list, col_owner); + align_column(list, col_group); + align_column(list, col_size); +} diff --git a/columns.h b/columns.h new file mode 100644 index 0000000..b681703 --- /dev/null +++ b/columns.h @@ -0,0 +1,14 @@ +#ifndef COLUMNS_H_SENTRY +#define COLUMNS_H_SENTRY + +/* + * columns.h + * + * Function for aligning text columns + */ + +struct file_item; + +void align_columns(struct file_item *list); + +#endif diff --git a/data_struct.c b/data_struct.c new file mode 100644 index 0000000..bd551ab --- /dev/null +++ b/data_struct.c @@ -0,0 +1,153 @@ +#include "data_struct.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +void add_new_item(struct file_item **list, struct file_item **entry, + const char *file_name) +{ + *entry = malloc(sizeof(struct file_item)); + if(!*entry) { + perror("malloc"); + exit(1); + } + (*entry)->filename = malloc(strlen(file_name)+1); + if(!(*entry)->filename) { + perror("malloc"); + exit(1); + } + /* safety */ + strcpy((*entry)->filename, file_name); + (*entry)->next = *list; + (*entry)->info_line = NULL; + *list = *entry; +} +/* + * Initialize arg_arrays: allocate memory for directories and files + * arrays and zero them + */ +void init_arg_arrays(struct arg_arrays *split, int argc) +{ + split->dirs = malloc(argc * sizeof(char*)); + if(!split->dirs) { + perror("malloc"); + exit(1); + } + split->files = malloc(argc * sizeof(char*)); + if(!split->files) { + perror("malloc"); + exit(1); + } + + memset(split->dirs, 0, argc * sizeof(char*)); + memset(split->files, 0, argc * sizeof(char*)); +} + +void init_item_array(struct file_item_array *arr) +{ + arr->array = NULL; + arr->length = 0; +} + +static size_t get_list_length(struct file_item *list) +{ + size_t counter = 0; + + while(list) { + ++counter; + list = list->next; + } + return counter; +} + +void copy_list_to_array(struct file_item *list, struct file_item_array *arr) +{ + size_t list_length = get_list_length(list); + size_t i; + + arr->array = malloc(list_length * sizeof(struct file_item*)); + if(!arr->array) { + perror("malloc"); + exit(1); + } + arr->length = list_length; + + for(i = 0; i < list_length; list = list->next, ++i) + arr->array[i] = list; +} + +void free_file_item_array(struct file_item_array *arr, struct file_item **list) +{ + size_t i; + + for(i = 0; i < arr->length; ++i) { + free(arr->array[i]->filename); + free(arr->array[i]->info_line); + free(arr->array[i]); + } + if(arr->array) + free(arr->array); + arr->array = NULL; + arr->length = 0; + *list = NULL; +} + +void free_arg_arrays(struct arg_arrays *split) +{ + free(split->dirs); + free(split->files); +} + +int is_empty_array(const char **paths_array, int argc) +{ + int empty_flag = 1; + size_t i; + + for(i = 0; i < argc; ++i) + if(paths_array[i] != 0) { + empty_flag = 0; + break; + } + return empty_flag; +} + +int is_last_arr_element(const char **paths_array, int argc, size_t pos) +{ + int last_flag = 1; + + for(++pos ; pos < argc; ++pos) + if(paths_array[pos]) { + last_flag = 0; + break; + } + return last_flag; +} + +static void swap(struct file_item **prev_element, + struct file_item **next_element) +{ + struct file_item *tmp_element = NULL; + + if(prev_element != next_element) + if(strcmp((*prev_element)->filename, (*next_element)->filename) > 0) { + tmp_element = *prev_element; + *prev_element = *next_element; + *next_element = tmp_element; + } +} + +/* + * bubble sort + */ +void sort_array_by_names(struct file_item_array *arr) +{ + size_t i, j; + + if(arr->length == 1) + return; + + for(i = arr->length-1; i > 0; --i) + for(j = 1; j <= i; ++j) + swap(arr->array+j-1, arr->array+j); +} diff --git a/data_struct.h b/data_struct.h new file mode 100644 index 0000000..694150a --- /dev/null +++ b/data_struct.h @@ -0,0 +1,60 @@ +#ifndef DATA_STRUCT_H_SENTRY +#define DATA_STRUCT_H_SENTRY + +#include <stddef.h> + +/* + * data_struct.h + * + * This module provides basic data structures and helper functions + * for handling file information, directory/file arguments, and + * transforming them between linked lists and arrays. + * + */ + +enum { + buffer_size = 512 +}; + +/* + * Stores separated command-line arguments: + * dirs is array of directory paths + * files is array of file paths + */ +struct arg_arrays { + const char **dirs; + const char **files; +}; + +/* + * Single-linked list node containing file entry: + * info_line -- formatted string with file information + * filename used for sorting + */ +struct file_item { + char *info_line; + char *filename; + struct file_item *next; +}; + +/* + * array-based representation of file_item list: + * array -- dynamic array of pointers to file_item + */ +struct file_item_array { + struct file_item **array; + size_t length; +}; + +void init_arg_arrays(struct arg_arrays *split, int argc); +void init_item_array(struct file_item_array *arr); +void add_new_item(struct file_item **list, struct file_item **entry, + const char *file_name); +void copy_list_to_array(struct file_item *list, struct file_item_array *arr); +void free_file_item_array(struct file_item_array *arr, struct file_item **list); +void free_arg_arrays(struct arg_arrays *split); +int is_empty_array(const char **paths_array, int argc); +int is_last_arr_element(const char **paths_array, int argc, size_t pos); +void sort_array_by_names(struct file_item_array *arr); + +#endif Binary files differdiff --git a/ls_imitation.c b/ls_imitation.c new file mode 100644 index 0000000..e396efa --- /dev/null +++ b/ls_imitation.c @@ -0,0 +1,620 @@ +/* + * ls_imitation + * + * A simplified implementation of the Unix ls -l command. + * Lists files and directories with detailed information including: + * file type and permissions + * ownership (user and group) + * number of hard links + * size or device numbers + * modification date + * symbolic link target (if any) + * + * Colors are shown when output is to a terminal. + * + * Author: Andrey Kuznetsov + */ + +#include "data_struct.h" +#include "columns.h" + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <dirent.h> +#include <sys/stat.h> +#include <pwd.h> +#include <grp.h> +#include <time.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/sysmacros.h> + +/* ANSI color codes for different file types */ +#define RESET "\033[0m" +#define DIR_COLOR "\033[1;34m" +#define EXEC_COLOR "\033[1;32m" +#define SYMLINK_COLOR "\033[1;36m" +#define DEVICE_COLOR "\033[1;33m" +#define SOCK_COLOR "\033[1;35m" +#define FIFO_COLOR "\033[33m" +#define SUID_COLOR "\033[37;41m" +#define SGID_COLOR "\033[30;43m" +#define STICKY_COLOR "\033[37;44m" +#define STICKY_OTHER_WRITABLE "\033[30;42m" + +enum permissions_t { + user_p, group_p, other_p +}; + +enum { + half_year_time = 15552000, /* Half a year in seconds */ + block_size = 512, + display_block_size = 1024, + min_args_for_header = 2 /* minimum number of paths required to show + directory headers */ +}; + +enum arg_type { + arg_file, + arg_directory +}; + +struct permissions_mask { + mode_t owner_rights; + mode_t group_rights; + mode_t other_rights; +}; + +struct line_builder { + char *line_buffer; + size_t offset; + const char *color; /* ANSI color escape sequence */ + const char *link_target; /* NULL if not symbolic link. Otherwise points to + link target */ + int device_flag; /* Non-zero if file is block or char device */ +}; + +struct print_options { + int show_directory_header; + int output_to_tty; +}; + +/* Bit masks for read, write, execute checks */ +static const int r_bits[] = { S_IRUSR, S_IRGRP, S_IROTH }; +static const int w_bits[] = { S_IWUSR, S_IWGRP, S_IWOTH }; +static const int x_bits[] = { S_IXUSR, S_IXGRP, S_IXOTH }; + +/* + * Append -> target suffix to symlink. + * Assumes line->line_buffer already contains filename and newline. + * checks for line->link_target are performed here. + * offset-1 remove '\n' symbol that append_file_name() is added + */ +static void append_symlink_name(struct line_builder *line) +{ + if(line->offset >= buffer_size) + return; + + if(line->link_target) + line->offset += snprintf(line->line_buffer + line->offset-1, + buffer_size - line->offset-1, " -> %s\n", + line->link_target); +} + +/* Checks whether stdout is a terminal (tty) to adjust output formatting */ +int check_output_to_tty() +{ + return isatty(STDOUT_FILENO); +} + +/* Append colored filename to builder buffer */ +static void append_file_name(struct line_builder *line, + const char *file_name, + const struct print_options *opts) +{ + if(line->offset >= buffer_size) + return; + + if(!opts->output_to_tty) + line->color = ""; + + line->offset += + snprintf(line->line_buffer + line->offset, buffer_size - line->offset, + " %s%s" RESET "\n", line->color, file_name); +} + +/* + * If mtime is more than 6 months ago, print "M D Y" + * otherwise print "M D H:M" + */ +static void append_modification_date(struct line_builder *line, + const struct stat *file_info) +{ + time_t current_time = time(NULL); + struct tm *full_time = localtime(&file_info->st_mtime); + + if(full_time == NULL) + return; + + if(line->offset >= buffer_size) + return; + + if((current_time - file_info->st_mtime) > half_year_time) + line->offset += strftime(line->line_buffer + line->offset, + buffer_size - line->offset, + "%b %e %Y", full_time); + else + line->offset += strftime(line->line_buffer + line->offset, + buffer_size - line->offset, + "%b %e %H:%M", full_time); +} + +int is_write_other_bit_is_set(mode_t permissions_mask, enum permissions_t mode) +{ + return mode == other_p && permissions_mask & w_bits[mode]; +} + +/* Append execute bit x for user, group, other. Handles SUID, SGID, STICKY + * and assigns color when special bits are set + */ +static void append_x_bit(struct line_builder *line, mode_t permissions_mask, + enum permissions_t mode, mode_t file_mode, + int write_other_bit_flag) +{ + int symbol; + + switch(mode) { + case user_p: + if(file_mode & S_ISUID) { + symbol = permissions_mask & x_bits[mode] ? 's' : 'S'; + line->offset += + snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", symbol); + line->color = SUID_COLOR; + } else { + line->offset += + snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", + permissions_mask & x_bits[mode] ? 'x' : '-'); + } + break; + case group_p: + if(file_mode & S_ISGID) { + symbol = permissions_mask & x_bits[mode] ? 's' : 'S'; + line->offset += + snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", symbol); + line->color = SGID_COLOR; + } else { + line->offset += + snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", + permissions_mask & x_bits[mode] ? 'x' : '-'); + } + break; + case other_p: + if(file_mode & S_ISVTX) { + symbol = permissions_mask & x_bits[mode] ? 't' : 'T'; + line->offset += + snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", symbol); + line->color = + write_other_bit_flag ? STICKY_OTHER_WRITABLE : STICKY_COLOR; + } else { + line->offset += + snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", + permissions_mask & x_bits[mode] ? 'x' : '-'); + } + } +} + +/* Append rwx permissions for one user category (user, group, other) */ +static void append_permissions(struct line_builder *line, + mode_t permissions_mask, enum permissions_t who, + mode_t file_mode) +{ + int write_other_bit_flag = 0; + + line->offset += + snprintf(line->line_buffer + line->offset, buffer_size - line->offset, + "%c", permissions_mask & r_bits[who] ? 'r' : '-'); + line->offset += + snprintf(line->line_buffer + line->offset, buffer_size - line->offset, + "%c", permissions_mask & w_bits[who] ? 'w' : '-'); + + write_other_bit_flag = is_write_other_bit_is_set(permissions_mask, who); + append_x_bit(line, permissions_mask, who, file_mode, + write_other_bit_flag); +} + +/* + * Append owner and group names retrieved from UID, GID. + * Uses getpwuid and getgrgid + */ +static void append_owner_and_group(struct line_builder *line, + const struct stat *file_info) +{ + struct passwd *user = getpwuid(file_info->st_uid); + struct group *group = getgrgid(file_info->st_gid); + + if(user == NULL || group == NULL) + return; + + line->offset += + snprintf(line->line_buffer + line->offset, buffer_size - line->offset, + " %s %s", user->pw_name, group->gr_name); +} + +/* Append device major and minor or file size */ +static void append_size_or_device(struct line_builder *line, + const struct stat *file_info) +{ + int major_dev, minor_dev; + + if(line->offset >= buffer_size) + return; + + if(line->device_flag) { + major_dev = major(file_info->st_rdev); + minor_dev = minor(file_info->st_rdev); + + line->offset += + snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, " %u, %u ", major_dev, + minor_dev); + } else + line->offset += + snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, " %lu ",file_info->st_size); +} + +/* Append number of hard links */ +static void append_hard_links(struct line_builder *line, + const struct stat *file_info) +{ + line->offset += + snprintf(line->line_buffer + line->offset, buffer_size - line->offset, + " %ld", file_info->st_nlink); +} + +/* Return true if any execute bit is set for regular file */ +static int is_reg_executable_file(const struct permissions_mask *permissions) +{ + return + permissions->owner_rights & x_bits[user_p] || + permissions->group_rights & x_bits[group_p] || + permissions->other_rights & x_bits[other_p]; +} + +/* Append file type character and set color depending on type */ +static void append_file_type(struct line_builder *line, + const mode_t file_mode, + const struct permissions_mask *permissions) +{ + if(S_ISREG(file_mode)) { + line->offset += snprintf(line->line_buffer, buffer_size, "%c", '-'); + line->color = is_reg_executable_file(permissions) ? EXEC_COLOR : RESET; + + } else if(S_ISDIR(file_mode)) { + line->offset += snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", 'd'); + line->color = DIR_COLOR; + + } else if(line->link_target) { + line->offset += snprintf(line->line_buffer + line->offset , + buffer_size - line->offset, "%c", 'l'); + line->color = SYMLINK_COLOR; + + } else if(S_ISBLK(file_mode)) { + line->offset += snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", 'b'); + line->device_flag = 1; + line->color = DEVICE_COLOR; + + } else if(S_ISCHR(file_mode)) { + line->offset += snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", 'c'); + line->device_flag = 1; + line->color = DEVICE_COLOR; + + } else if(S_ISFIFO(file_mode)) { + line->offset += snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", 'p'); + line->color = FIFO_COLOR; + + } else if(S_ISSOCK(file_mode)) { + line->offset += snprintf(line->line_buffer + line->offset, + buffer_size - line->offset, "%c", 's'); + line->color = SOCK_COLOR; + } +} + +/* + * Build full output line (permissions, links, owner, size, date, name). + * Allocates buffer and assigns it to entry->info_line + */ +static void generate_information_line(const struct stat *file_info, + struct file_item *entry, + const char *link_target, + const struct print_options *opts) +{ + mode_t file_mode = file_info->st_mode; + struct permissions_mask permissions; + permissions.owner_rights = file_mode & S_IRWXU; + permissions.group_rights = file_mode & S_IRWXG; + permissions.other_rights = file_mode & S_IRWXO; + + struct line_builder line; + + line.line_buffer = malloc(buffer_size); + if (!line.line_buffer) { + perror("malloc"); + exit(1); + } + line.color = ""; + line.offset = 0; + line.device_flag = 0; + line.link_target = link_target; + + append_file_type(&line,file_mode, &permissions); + append_permissions(&line, permissions.owner_rights, user_p, file_mode); + append_permissions(&line, permissions.group_rights, group_p, file_mode); + append_permissions(&line, permissions.other_rights, other_p, file_mode); + append_hard_links(&line, file_info); + append_owner_and_group(&line,file_info); + append_size_or_device(&line, file_info); + append_modification_date(&line, file_info); + append_file_name(&line, entry->filename, opts); + append_symlink_name(&line); + + entry->info_line = line.line_buffer; +} + +/* Print all prepared info lines */ +static void print_lines(const struct file_item_array *arr) +{ + int i; + + for(i = 0; i < arr->length; ++i) + printf("%s", arr->array[i]->info_line); +} + +/* Construct full path string: path + "/" + file_name */ +static void create_file_path(const char *file_name, const char *path, + char **file_path) +{ + size_t len = 0; + + len = strlen(file_name) + 1 + strlen(path) + 1; + *file_path = malloc(len); + if(!*file_path) { + perror("malloc"); + exit(1); + } + snprintf(*file_path, len, "%s/%s", path, file_name); +} + +static int is_hidden_file(const char *file_name) +{ + return file_name[0] == '.'; +} + +static int should_skip_file(const struct stat *file_info, + const char *file_name) +{ + return + !strcmp(file_name, ".") || !strcmp(file_name, "..") || + is_hidden_file(file_name); +} + +/* + * Process single file: lstat, optional readlink(), create list item and + * line + */ +static void process_file(const char *file_path, const char *file_name, + struct file_item **list, + const struct print_options *opts) +{ + struct stat file_info; + int lstat_result; + ssize_t readlink_length = 0; + struct file_item *tmp = NULL; + char link_target[buffer_size]; + + lstat_result = lstat(file_path, &file_info); + if(lstat_result == -1) { + perror(file_path); + return; + } + + if(S_ISLNK(file_info.st_mode)) { + readlink_length = readlink(file_path, link_target, buffer_size-1); + + if(readlink_length == -1) { + perror(file_path); + return; + } + + link_target[readlink_length] = '\0'; + } + add_new_item(list, &tmp, file_name); + generate_information_line(&file_info, tmp, + S_ISLNK(file_info.st_mode) ? link_target : NULL, + opts); +} + +/* + * Process directory: iterate files with readdir(), skip entries, compute total + * blocks, and build lines for each visible file + */ +static void process_directory(const char *path, struct file_item **list, + const struct print_options *opts) +{ + DIR *directory = NULL; + struct dirent *file = NULL; + struct stat file_info; + const char *file_name = NULL; + char *file_path = NULL; + blkcnt_t blocks_counter = 0; + int stat_result; + + errno = 0; + directory = opendir(path); + + if(directory == NULL) { + perror(path); + exit(1); + } + + while((file = readdir(directory)) != NULL) { + file_name = file->d_name; + + create_file_path(file_name, path, &file_path); + /* need to get st_blocks and filename */ + stat_result = lstat(file_path, &file_info); + + if(stat_result == -1 || should_skip_file(&file_info, file_name)) { + free(file_path); + continue; + } + + blocks_counter += file_info.st_blocks; + process_file(file_path, file_name, list, opts); + free(file_path); + } + if(!file && errno) { + perror(path); + exit(1); + } + closedir(directory); + /* st_blocks are 512-byte units; display in 1024-byte units */ + printf("total %ld\n", blocks_counter * block_size / display_block_size); +} + +/* Prints the list of files from a sorted array with proper formatting */ +static void print_sorted_files(int argc, struct file_item **list, + struct file_item_array *array) +{ + /* if empty directory */ + if(!*list) + return; + align_columns(*list); + copy_list_to_array(*list, array); + sort_array_by_names(array); + print_lines(array); + free_file_item_array(array, list); +} + +/* Prints the directory name before listing its contents */ +static void print_directory_header(const char *dir_name, + int show_header) +{ + if(show_header) + printf("%s:\n", dir_name); +} + +/* Prints a blank line to visually separate output between directories */ +static void print_directory_separator(const char **paths_arr, int argc, + size_t pos, int show_header, + enum arg_type mode) +{ + if(show_header && + (mode == arg_file || !is_last_arr_element(paths_arr, argc, pos))) + { + putchar('\n'); + } +} + +/* + * Process either files or directories from paths_arr, align columns, sort, + * print lines and free allocated resources. + * + * For arg_file, the same string is used as both path and filename + * because user argument may include path. This differs from + * arg_directory, where path is combined with filenames from directory contents. + * print_directory_separator() receives 0, 0 for arg_file because + * last element check is only relevant for directories. + */ +static void process_args(const char **paths_arr, int argc, + enum arg_type mode, struct file_item **list, + struct file_item_array *array, + const struct print_options *opts) +{ + size_t i; + + /* + * start from 0 index cause first element + * may contain "." if no user arguments + */ + for(i = 0; i < argc; ++i) { + if(paths_arr[i] == 0) + continue; + if(mode == arg_directory) { + print_directory_header(paths_arr[i], opts->show_directory_header); + process_directory(paths_arr[i], list, opts); + print_sorted_files(argc, list, array); + print_directory_separator(paths_arr, argc, i, + opts->show_directory_header, mode); + } else if(mode == arg_file) + process_file(paths_arr[i], paths_arr[i], list, opts); + } + if(mode == arg_file && !is_empty_array(paths_arr, argc)) { + print_sorted_files(argc, list, array); + print_directory_separator(paths_arr, 0, 0, opts->show_directory_header, + mode); + } +} + +/* Determines whether the directory name should be shown as a header */ +int needs_directory_header(struct arg_arrays *split, int argc) +{ + size_t i, counter = 0; + + if(!is_empty_array(split->dirs, argc)) { + for(i = 1; i < argc; ++i) + if(split->files[i] || split->dirs[i]) + ++counter; + } + + return counter >= min_args_for_header; +} + +int main(int argc, const char **argv) +{ + struct stat st; + struct file_item *list = NULL; + struct file_item_array array; + struct arg_arrays split; + struct print_options opt; + size_t i; + + init_item_array(&array); + init_arg_arrays(&split, argc); + opt.output_to_tty = check_output_to_tty(); + opt.show_directory_header = 0; + + if(argc == 1) { + split.dirs[0] = "."; + process_args(split.dirs, argc, arg_directory, &list, &array, &opt); + } else { + for(i = 1; argv[i]; ++i) { + if(lstat(argv[i], &st) == -1) { + perror(argv[i]); + continue; + } + if(S_ISDIR(st.st_mode)) + split.dirs[i] = argv[i]; + else + split.files[i] = argv[i]; + } + opt.show_directory_header = needs_directory_header(&split, argc); + process_args(split.files, argc, arg_file, &list, &array, &opt); + process_args(split.dirs, argc, arg_directory, &list, &array, &opt); + } + free_arg_arrays(&split); + + return 0; +} |