#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "dir_management.h"
#include "ls.h"
#include "MyLinkedList.h"
#include "utils.h"
ls_options_t ls_options = {
.include_hidden_files = 0,
.include_all_files = 0,
.long_format = '\0',
.directories_as_files_and_no_symlink_indir = 0,
.reports_size_in_kilobytes = 0,
.report_size_nicely = 0,
.sort_size = 0,
.sort_time = 0,
.sort_reverse = 0,
.sort_none = 0,
.recurse = 0,
.show_inode_number = 0,
.show_blocks_used = 0,
.block_size = BLOCKSIZE_DEFAULT,
.time_field = '\0',
.nonprint_mode = '\0',
.classify_special_files = 0,
.valid_arguments_passed = 0
};
/* ls [ −AacdFfhiklnqRrSstuw] [file ...] */
int
main(int argc, char* argv[])
{
int opt;
int any_flags_set = 0;
/* Set defaults for print mode */
ls_options.nonprint_mode = isatty(STDOUT_FILENO) ? 'q' : 'w';
/* ':' supresses getopt's error handling (iirc from CS 392!!!) */
while ((opt = getopt(argc, argv, ":AacdFfhiklnqRrSstuw")) != -1)
{
any_flags_set = 1;
switch (opt)
{
/* List all entries other than '.' and '..'. Always active for super
* user */
case 'A':
ls_options.include_hidden_files = 1;
break;
/* Include entries whose names begin with '.' */
case 'a':
ls_options.include_all_files = 1;
break;
/* Use time when file status was last changed, not when it was last
* modified (for -t and -l)*/
case 'c':
ls_options.time_field = 'c';
break;
/*
* Directories are listed as plain files (not searched recursively) AND
* symbolic links in the argument list are not indirected through.
*/
case 'd':
ls_options.directories_as_files_and_no_symlink_indir = 1;
ls_options.recurse = 0;
break;
/* Classify files with suffixes (/ @ * etc.) */
case 'F':
ls_options.classify_special_files = 1;
break;
/* Output is not sorted. */
case 'f':
ls_options.sort_none = 1;
break;
/* Makes -s and -l reporting size in bytes. Overrides -k*/
case 'h':
ls_options.reports_size_in_kilobytes = 0;
ls_options.report_size_nicely = 1;
break;
/* Include inode numbers in the output */
case 'i':
ls_options.show_inode_number = 1;
break;
/* Modifies -s, reports size in kilobytes. */
case 'k':
ls_options.reports_size_in_kilobytes = 1;
ls_options.report_size_nicely = 0;
break;
/* list in long format */
case 'l':
ls_options.long_format = 'l';
break;
/* -l but owner and group ids are numbers and not strings */
case 'n':
ls_options.long_format = 'n';
break;
/* force printing of non-printable characters as '?' (default when
* output is not a terminal)*/
case 'q':
ls_options.nonprint_mode = 'q';
break;
/* Recurse into directories*/
case 'R':
ls_options.recurse = 1;
ls_options.directories_as_files_and_no_symlink_indir = 0;
break;
/* Reverse the order of the sort */
case 'r':
ls_options.sort_reverse = 1;
break;
/* Sort by file size */
case 'S':
ls_options.sort_size = 1;
ls_options.sort_time = 0;
break;
/*
* display system blocks used by each file in 512 bytes or BLOCKSIZE.
* If the output is to a terminal, a total sum for all the file sizes
* is output on a line before the listing.
*/
case 's':
ls_options.show_blocks_used = 1;
break;
/* Sort by file modification time before sorting by alphabetical order
*/
case 't':
ls_options.sort_size = 0;
ls_options.sort_time = 1;
ls_options.time_field = 'm';
break;
/* Use time of last access, instead of last modification of the file
* for sorting ( −t ) orprinting ( −l) */
case 'u':
ls_options.time_field = 'u';
break;
/* force printing of non-printable characters (default when output is
* not a terminal)*/
case 'w':
ls_options.nonprint_mode = 'w';
break;
default:
/* ls(1) seems to propagate errors in this format */
fprintf(stderr, "%s: invalid option -- %c\n", getprogname(),
optopt); /* optopt to get what actually caused the error
and not '?' sometimes */
return EXIT_FAILURE;
}
}
/*
* If neither -h nor -k were specified, BLOCKSIZE enviornmental variable
* should be set. (Inspired by how ls(1) works)
*/
if (ls_options.reports_size_in_kilobytes == 0 &&
ls_options.report_size_nicely == 0)
{
int b_len;
long block_size = 0;
(void)getbsize(&b_len, &block_size);
ls_options.block_size = (int)block_size;
}
/* TZ=US/Eastern, US/Pacific, etc.*/
const char* tz = getenv("TZ");
if (tz != NULL)
{
tzset();
}
/* "-A [...] Always set for the super-user" */
if (geteuid() == 0)
{
ls_options.include_hidden_files = 1;
}
/*
* This linked list implementation takes a function pointer for freeing
* entries in the create function for what is hopefully nice and easy
* clean up.
*/
LinkedList* original_entries = list_create(free_ls_entry);
if (original_entries == NULL)
{
return EXIT_FAILURE;
}
int failed_parse = 0;
for (int i = any_flags_set + 1; i < argc; i++)
{
struct stat path_stat;
if ((ls_options.directories_as_files_and_no_symlink_indir
? lstat(argv[i], &path_stat)
: stat(argv[i], &path_stat)) == -1)
{
fprintf(stderr, "%s: '%s': %s\n", getprogname(), argv[i],
strerror(errno));
failed_parse = 1;
continue;
}
file_entry_t* entry = (file_entry_t*)malloc(sizeof(file_entry_t));
if (entry == NULL)
{
fprintf(stderr,
"%s: could not allocate memory for new file entry\n",
getprogname());
goto error_exit;
}
/*
* malloced memory from strdup will be
* cleaned up by the free_ls_entry function
*/
entry->pathname = strdup(argv[i]);
entry->filename = strdup(basename(argv[i]));
entry->statbuf = path_stat;
entry->is_cmdline_arg = 1;
if (entry->pathname == NULL || entry->filename == NULL)
{
fprintf(stderr, "%s: error duplicating strings for file entry\n",
getprogname());
/*
* Doesn't matter that much since we're exiting, but just in
* case one succeeded and the other failed, free the memory
*/
if (entry->pathname != NULL)
{
free(entry->pathname);
}
if (entry->filename != NULL)
{
free(entry->filename);
}
/* Entry itself needs to be freed */
free(entry);
goto error_exit;
}
if (list_add_last(original_entries, entry))
{
fprintf(stderr, "%s: could not add file entry to list\n",
getprogname());
free_ls_entry(entry);
goto error_exit;
}
}
/* If no files or directories are found, we list the current directory */
if (list_size(original_entries) == 0)
{
/*
* If the user provided command line arguments and none of them
* succeeded, the error message is output enough.
* (Inspired by how ls(1) behaves).
*/
if (failed_parse)
{
goto error_exit;
}
struct stat path_stat;
if (lstat(".", &path_stat) == -1)
{
fprintf(stderr, "%s: '%s': %s\n", getprogname(), ".",
strerror(errno));
goto error_exit;
}
file_entry_t* entry = (file_entry_t*)malloc(sizeof(file_entry_t));
if (entry == NULL)
{
fprintf(stderr,
"%s: could not allocate memory for new file entry\n",
getprogname());
goto error_exit;
}
entry->pathname = strdup(".");
entry->filename = strdup(".");
entry->statbuf = path_stat;
entry->is_cmdline_arg = 1;
if (entry->pathname == NULL || entry->filename == NULL)
{
fprintf(stderr, "%s: error duplicating strings for file entry\n",
getprogname());
if (entry->pathname != NULL)
{
free(entry->pathname);
}
if (entry->filename != NULL)
{
free(entry->filename);
}
/* Entry itself needs to be freed */
free(entry);
goto error_exit;
}
if (list_add_last(original_entries, entry))
{
fprintf(stderr, "%s: could not add file entry to list\n",
getprogname());
/* Frees the pathnames from strdup(3) and the entry itself */
free_ls_entry(entry);
goto error_exit;
}
}
ls_options.valid_arguments_passed = list_size(original_entries);
ls_sort_entries(original_entries);
/*
* Nice formatting case where ls puts regular files before directories
* as to not randomly insert files from the commandline in between
* recursive listings, giving the impression a file is located within a
* sub-directory, when it is not.
*/
list_foreach_where(original_entries, process_entry, f_entry_isnt_dir);
/*
* If files and directories, we should list the contents of were provided,
* there should be a newline present after the regular files to indicate
* we are now listing contents of a directory.
* If there are no directories, or there are only regular files,
* there is no need for a newline. (There are of course only
* "regular files" if the -d flag is specified).
*/
int dir_count = ls_count_dirs(original_entries);
if (ls_options.valid_arguments_passed != dir_count && dir_count > 0 &&
!ls_options.directories_as_files_and_no_symlink_indir)
{
printf("\n");
}
list_foreach_where(original_entries, process_entry, f_entry_is_dir);
(void)list_destroy(original_entries);
return EXIT_SUCCESS;
/* Normally unreachable; for errors only */
error_exit:
(void)list_destroy(original_entries);
return EXIT_FAILURE;
}