#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "dir_management.h"
#include "ls.h"
#include "print.h"
#include "utils.h"
/* Calls print_entry with a void* argument to a file_entry_t */
void
reg_print_entry(void* ventry)
{
file_entry_t* entry = (file_entry_t*)ventry;
print_entry(entry->pathname, &entry->statbuf);
return;
}
/*
* Prints the string, replacing unprintable characters with '?' when
* appropriate, otherwise prints raw
*/
void
force_printf(const char* str, int size)
{
if (str == NULL || size <= 0)
{
return;
}
char buf[size];
for (int i = 0; i < size; i++)
{
/* isprint yells if not unsigned */
unsigned char c = (unsigned char)str[i];
buf[i] = (ls_options.nonprint_mode == 'q' && !isprint(c)) ? '?' : c;
}
for (int i = 0; i < size; i++)
{
printf("%c", buf[i]);
}
return;
}
/*
* Classifies a file based on its mode_t and returns the appropriate
* suffix character, or '\0' if no suffix is appropriate.
*/
char
classify(mode_t mode)
{
if (S_ISDIR(mode))
{
return '/';
}
if (S_ISLNK(mode))
{
return '@';
}
if (S_ISSOCK(mode))
{
return '=';
}
if (S_ISFIFO(mode))
{
return '|';
}
#ifdef S_WHITEOUT
if (S_ISWHT(mode))
{
return '%';
}
#endif
if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
{
return '*';
}
return '\0';
}
/*
* Shows the mode_t permission string and other information like links, owner,
* group, size (or major/minor for device files), modification time, name,
* and classification suffix. Also prints the target of symlinks if applicable.
*/
void
print_entry(const char* name, struct stat* st)
{
char perms[11]; /* type + rwxrwxrwx + null terminator for printing */
/*
* the format string being used (" %b %d %H:%M:%S %Y") can produce
* characters in the 25-30 character range (more or less depending
* on locale), so a buffer of 64 is about double that
* (and a multiple of 2!), so it should be safe.
*/
char timebuf[64];
struct tm lt;
char suf;
if (ls_options.show_inode_number)
{
printf("%lu ", (unsigned long)st->st_ino);
}
if (ls_options.show_blocks_used)
{
char nice_size_buf[BUFSIZ];
/*
* ls -shl SPECIFICALLY has this type of logic,
* which is "display bytes used on disk"
*/
long blocks_or_bytes =
ls_options.long_format && ls_options.report_size_nicely
? (st->st_blocks * ls_options.block_size)
: get_blocks_from_sectors(st);
if (ls_options.report_size_nicely)
{
if (humanize_number(nice_size_buf, HUMANIZE_BUFSIZ, blocks_or_bytes,
"", HN_AUTOSCALE, HUMANIZE_OPTIONS) == -1)
{
fprintf(stderr,
"%s: Warning - could not humanize entry's blocks / "
"bytes.\n",
getprogname());
goto humanize_fallback;
}
}
else
{
humanize_fallback:
snprintf(nice_size_buf, sizeof(nice_size_buf), "%ld",
blocks_or_bytes);
}
printf("%s ", nice_size_buf);
}
/* Long format case handled seperately down below */
if (!ls_options.long_format)
{
/*
* Manually stripping just in case basename affects the source
* string, which according to the man page it doesn't, but still,
* just in case.
*/
const char* basename = strrchr(name, '/');
basename = basename ? basename + 1 : name;
force_printf(basename, strlen(basename));
if (ls_options.classify_special_files)
{
suf = classify(st->st_mode);
if (suf)
{
printf("%c", suf);
}
}
printf("\n");
return;
}
strmode(st->st_mode, perms);
/* skips leading ./ for -l */
const char* displayname = name;
const char* base = strrchr(name, '/');
displayname = base ? base + 1 : name;
/* Start checking for group names */
struct passwd* pw = NULL;
struct group* gr = NULL;
if (ls_options.long_format == 'l')
{
pw = getpwuid(st->st_uid);
gr = getgrgid(st->st_gid);
}
long gu_name_bufsiz = sysconf(_SC_LOGIN_NAME_MAX);
if (gu_name_bufsiz == -1)
{
gu_name_bufsiz = BUFSIZ; /* Fallback value just to be safe */
}
char uname_buf[gu_name_bufsiz];
char gname_buf[gu_name_bufsiz];
const char* uname =
pw ? pw->pw_name
: (snprintf(uname_buf, sizeof(uname_buf), "%u", st->st_uid),
uname_buf);
const char* gname =
gr ? gr->gr_name
: (snprintf(gname_buf, sizeof(gname_buf), "%u", st->st_gid),
gname_buf);
/* End checking for group names */
time_t display_time;
switch (ls_options.time_field)
{
case 'c':
display_time = st->st_ctime;
break;
case 'u':
display_time = st->st_atime;
break;
case 'm':
default:
display_time = st->st_mtime;
break;
}
if (localtime_r(&display_time, <) == NULL)
{
fprintf(stderr, "%s: localtime_r failed: %s\n", getprogname(),
strerror(errno));
return;
}
if (strftime(timebuf, sizeof timebuf, " %b %d %H:%M", <) == 0)
{
fprintf(stderr, "%s: strftime failed: %s\n", getprogname(),
strerror(errno));
return;
}
/* remove leading space from the error check */
memmove(timebuf, timebuf + 1, strlen(timebuf));
char size_or_devnums[BUFSIZ];
if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode))
{
snprintf(size_or_devnums, sizeof(size_or_devnums), "%u,%u",
major(st->st_rdev), minor(st->st_rdev));
}
else
{
if (ls_options.report_size_nicely)
{
/*
* We want the function to think the array is max
* HUMANIZE_BUFSIZ, not BUFSIZ, bytes so it condenses the number
* accordingly. It has been my experience that the output is
* dependent on the passed size.
*/
if (humanize_number(size_or_devnums, HUMANIZE_BUFSIZ, st->st_size,
"", HN_AUTOSCALE, HUMANIZE_OPTIONS) == -1)
{
fprintf(stderr,
"%s: Warning - could not humanize entry's blocks / "
"bytes.\n",
getprogname());
goto humanize_fallback2;
}
}
else
{
humanize_fallback2:
snprintf(size_or_devnums, sizeof(size_or_devnums), "%ld",
st->st_size);
}
}
printf("%s %lu %s %s %s %s ", perms, (unsigned long)st->st_nlink, uname,
gname, size_or_devnums, timebuf);
force_printf(displayname, strlen(displayname));
if (ls_options.classify_special_files)
{
suf = classify(st->st_mode);
if (suf)
{
printf("%c", suf);
}
}
/* if symlink, show " -> target" */
if (S_ISLNK(st->st_mode))
{
char target[PATH_MAX + 1];
ssize_t len = readlink(name, target, sizeof(target) - 1);
if (len < 0)
{
if (errno != ENAMETOOLONG)
{
fprintf(stderr, "%s: could not readlink '%s': %s\n",
getprogname(), name, strerror(errno));
}
else
{
errno = 0;
len = read_link_crawl(name, target, sizeof(target) - 1);
}
}
if (len >= 0)
{
target[len] = '\0';
printf(" -> ");
force_printf(target, len);
}
}
printf("\n");
}