View of xos/usr/utilities/ls.c


XOS | Parent Directory | View | Download

/* Copyright (C) 2008  Emmanuel Varoquaux
 
   This file is part of XOS.
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
#include "common.h"
 
#include <getopt.h>
#include <error.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <dirent.h>
#include <stdio.h>
#include <sys/stat.h>
#include <errno.h>
 
struct fileinfo_struct {
  char *path;
  int stat_err;
  struct stat stat;
 
  struct fileinfo_struct *next;
};
 
typedef int (*comparator_t)(const struct fileinfo_struct *, const struct fileinfo_struct *);
 
struct fieldwidths_struct {
  int ino_fieldwidth;
  int filesize_fieldwidth;
};
 
struct file_list_struct {
  int list_directories;
  comparator_t comparator;
  struct fileinfo_struct *first;
  struct fieldwidths_struct fieldwidths;
};
 
struct print_options_struct {
  unsigned print_ino : 1;
  unsigned long_format : 1;
};
 
struct options_struct {
  int list_directories;
  comparator_t comparator;
  struct print_options_struct print_options;
};
 
#define has_dev(mode)   (S_ISCHR(mode) || S_ISBLK(mode))
#define has_size(mode)  S_ISREG(mode)
#define has_mtime(mode) (S_ISREG(mode) || S_ISDIR(mode))
 
static void init_fileinfo(struct fileinfo_struct *fileinfo, const char *path, const struct stat *statbuf)
{
  if (!(fileinfo->path = strdup(path)))
    error(EXIT_FAILURE, errno, "strdup");
  if (statbuf) {
    fileinfo->stat_err = 0;
    fileinfo->stat = *statbuf;
  }
  else
    fileinfo->stat_err = 1;
}
 
static void destroy_fileinfo(struct fileinfo_struct *fileinfo)
{
  free(fileinfo->path);
}
 
static void init_file_list(struct file_list_struct *file_list, int list_directories, comparator_t comparator)
{
  file_list->list_directories = list_directories;
  file_list->comparator = comparator;
  file_list->fieldwidths.ino_fieldwidth = 0;
  file_list->fieldwidths.filesize_fieldwidth = 0;
  file_list->first = NULL;
}
 
static void destroy_file_list(struct file_list_struct *file_list)
{
  struct fileinfo_struct *fileinfo, *next;
 
  fileinfo = file_list->first;
  while (fileinfo) {
    next = fileinfo->next;
    destroy_fileinfo(fileinfo);
    free(fileinfo);
    fileinfo = next;
  }
}
 
static int name_comparator(const struct fileinfo_struct *fileinfo1, const struct fileinfo_struct *fileinfo2)
{
  return strcmp(fileinfo1->path, fileinfo2->path) < 0;
}
 
static int size_comparator(const struct fileinfo_struct *fileinfo1, const struct fileinfo_struct *fileinfo2)
{
  if (fileinfo1->stat_err && fileinfo2->stat_err)
    goto equal;
  if (fileinfo1->stat_err && !fileinfo2->stat_err)
    return 0;
  if (!fileinfo1->stat_err && fileinfo2->stat_err)
    return 1;
  if (!has_size(fileinfo1->stat.st_mode) && !has_size(fileinfo2->stat.st_mode))
    goto equal;
  if (!has_size(fileinfo1->stat.st_mode) && has_size(fileinfo2->stat.st_mode))
    return 0;
  if (has_size(fileinfo1->stat.st_mode) && !has_size(fileinfo2->stat.st_mode))
    return 1;
  if (fileinfo1->stat.st_size == fileinfo2->stat.st_size)
    goto equal;
  return fileinfo1->stat.st_size > fileinfo2->stat.st_size;
 
 equal:
  return name_comparator(fileinfo1, fileinfo2);
}
 
static int mtime_comparator(const struct fileinfo_struct *fileinfo1, const struct fileinfo_struct *fileinfo2)
{
  if (fileinfo1->stat_err && fileinfo2->stat_err)
    goto equal;
  if (fileinfo1->stat_err && !fileinfo2->stat_err)
    return 0;
  if (!fileinfo1->stat_err && fileinfo2->stat_err)
    return 1;
  if (!has_mtime(fileinfo1->stat.st_mode) && !has_mtime(fileinfo2->stat.st_mode))
    goto equal;
  if (!has_mtime(fileinfo1->stat.st_mode) && has_mtime(fileinfo2->stat.st_mode))
    return 0;
  if (has_mtime(fileinfo1->stat.st_mode) && !has_mtime(fileinfo2->stat.st_mode))
    return 1;
  if (fileinfo1->stat.st_mtime == fileinfo2->stat.st_mtime)
    goto equal;
  return fileinfo1->stat.st_mtime > fileinfo2->stat.st_mtime;
 
 equal:
  return name_comparator(fileinfo1, fileinfo2);
}
 
static int compare(const struct fileinfo_struct *fileinfo1, const struct fileinfo_struct *fileinfo2, const struct file_list_struct *file_list)
{
  if (file_list->list_directories) {
    if (S_ISDIR(fileinfo1->stat.st_mode) && !S_ISDIR(fileinfo2->stat.st_mode))
      return 0;
    if (!S_ISDIR(fileinfo1->stat.st_mode) && S_ISDIR(fileinfo2->stat.st_mode))
      return 1;
  }
  return file_list->comparator(fileinfo1, fileinfo2);
}
 
static int dev_fieldwidth(dev_t dev)
{
  return snprintf(NULL, 0, "%lu", (unsigned long)dev);
}
 
static int size_fieldwidth(off_t size)
{
  return snprintf(NULL, 0, "%ld", size);
}
 
static void insert_into_file_list(struct file_list_struct *file_list, struct fileinfo_struct *fileinfo)
{
  struct fileinfo_struct *fileinfo1, *fileinfo2;
  int w;
 
  fileinfo1 = NULL;
  fileinfo2 = file_list->first;
  while (fileinfo2 && !compare(fileinfo, fileinfo2, file_list)) {
    fileinfo1 = fileinfo2;
    fileinfo2 = fileinfo2->next;
  }
  fileinfo->next = fileinfo2;
  if (!fileinfo1)
    file_list->first = fileinfo;
  else
    fileinfo1->next = fileinfo;
 
  if ((w = size_fieldwidth(fileinfo->stat.st_ino)) > file_list->fieldwidths.ino_fieldwidth)
    file_list->fieldwidths.ino_fieldwidth = w;
  if (has_dev(fileinfo->stat.st_mode)) {
    if ((w = dev_fieldwidth(fileinfo->stat.st_rdev)) > file_list->fieldwidths.filesize_fieldwidth)
      file_list->fieldwidths.filesize_fieldwidth = w;
  }
  else if (has_size(fileinfo->stat.st_mode))
    if ((w = size_fieldwidth(fileinfo->stat.st_size)) > file_list->fieldwidths.filesize_fieldwidth)
      file_list->fieldwidths.filesize_fieldwidth = w;
}
 
static int populate_file_list_from_directory(struct file_list_struct *file_list, const char *path)
{
  int err;
  DIR *dir;
  struct dirent *dent;
  char *buf;
  struct stat statbuf;
  struct fileinfo_struct *fileinfo;
 
  err = 0;
  if (!(dir = opendir(path))) {
    error(0, errno, path);
    err = 1;
    goto end;
  }
  while (1) {
    errno = 0;
    if (!(dent = readdir(dir))) {
      if (errno) {
        error(0, errno, "reading directory %s", path);
        err = 1;
        goto closedir;
      }
      break;
    }
    if (!(buf = malloc(strlen(path) + 1 + strlen(dent->d_name) + 1)))
      error(EXIT_FAILURE, errno, "malloc");
    if (!(fileinfo = malloc(sizeof (struct fileinfo_struct))))
      error(EXIT_FAILURE, errno, "malloc");
    sprintf(buf, "%s/%s", path, dent->d_name);
    if (lstat(buf, &statbuf) == -1) {
      error(0, errno, "cannot access %s", buf);
      err = 1;
      init_fileinfo(fileinfo, dent->d_name, NULL);
    }
    else
      init_fileinfo(fileinfo, dent->d_name, &statbuf);
    free(buf);
    insert_into_file_list(file_list, fileinfo);
  }
 closedir:
  if (closedir(dir) == -1) {
    error(0, errno, "closing directory %s", path);
    err = 1;
  }
 end:
  return !err;
}
 
static int populate_file_list_from_files(struct file_list_struct *file_list, char *const path[], int count)
{
  int err;
  int i;
  struct stat statbuf;
  struct fileinfo_struct *fileinfo;
 
  err = 0;
  for (i = 0; i < count; i++) {
    if (lstat(path[i], &statbuf) == -1) {
      error(0, errno, "cannot access %s", path[i]);
      err = 1;
      continue;
    }
    if (!(fileinfo = malloc(sizeof (struct fileinfo_struct))))
      error(EXIT_FAILURE, errno, "malloc");
    init_fileinfo(fileinfo, path[i], &statbuf);
    insert_into_file_list(file_list, fileinfo);
  }
  return !err;
}
 
static char get_type_char(mode_t mode)
{
  switch (mode & S_IFMT) {
  case S_IFIFO:
    return 'p';
  case S_IFCHR:
    return 'c';
  case S_IFDIR:
    return 'd';
  case S_IFBLK:
    return 'b';
  case S_IFREG:
    return '-';
  default:
    return '?';
  }
}
 
static void print_date(time_t timep)
{
  struct tm *tm;
  char buf[13];
  time_t current_time, six_months_ago_time;
  int n;
 
  if ((tm = localtime((time_t *)&timep))) {
    if (time(&current_time) == (time_t)-1)
      error(EXIT_FAILURE, errno, "cannot get system date");
    six_months_ago_time = current_time - 31556952 / 2;
    if (timep >= six_months_ago_time && timep <= current_time)
      n = strftime(buf, sizeof buf, "%b %e %H:%M", tm);
    else
      n = strftime(buf, sizeof buf, "%b %e  %Y", tm);
    printf("%12.*s", n, buf);
  }
  else
    printf("%12c", '?');
}
 
static void print_fileinfo(const struct fileinfo_struct *fileinfo, const struct print_options_struct *print_options, const struct fieldwidths_struct *fieldwidths)
{
  if (print_options->print_ino)
    printf("%*ld ", fieldwidths->ino_fieldwidth, fileinfo->stat.st_ino);
  if (print_options->long_format) {
    if (fileinfo->stat_err)
      printf("? %*s %12s ", fieldwidths->filesize_fieldwidth, "", "");
    else {
      printf("%c ", get_type_char(fileinfo->stat.st_mode));
 
      if (has_dev(fileinfo->stat.st_mode))
        printf("%*lu ", fieldwidths->filesize_fieldwidth, (unsigned long)fileinfo->stat.st_rdev);
      else if (has_size(fileinfo->stat.st_mode))
        printf("%*ld ", fieldwidths->filesize_fieldwidth, fileinfo->stat.st_size);
      else
        printf("%*s ", fieldwidths->filesize_fieldwidth, "");
 
      if (has_mtime(fileinfo->stat.st_mode)) {
        print_date(fileinfo->stat.st_mtime);
        printf(" ");
      }
      else
        printf("%12s ", "");
    }
  }
  printf("%s\n", fileinfo->path);
}
 
static int list_directory(const char *path, comparator_t comparator, const struct print_options_struct *print_options);
 
static int print_file_list(const struct file_list_struct *file_list, const struct print_options_struct *print_options)
{
  int print_directory_name;
  int err;
  const struct fileinfo_struct *fileinfo;
 
  print_directory_name = file_list->first && file_list->first->next;
  err = 0;
  fileinfo = file_list->first;
  while (fileinfo && (!file_list->list_directories || fileinfo->stat_err || !S_ISDIR(fileinfo->stat.st_mode))) {
    print_fileinfo(fileinfo, print_options, &file_list->fieldwidths);
    fileinfo = fileinfo->next;
  }
  while (fileinfo) {
    if (fileinfo != file_list->first)
      putchar('\n');
    if (print_directory_name)
      printf("%s:\n", fileinfo->path);
    if (!list_directory(fileinfo->path, file_list->comparator, print_options))
      err = 1;
    fileinfo = fileinfo->next;
  }
  return !err;
}
 
static int list_directory(const char *path, comparator_t comparator, const struct print_options_struct *print_options)
{
  int err;
  struct file_list_struct file_list;
 
  err = 0;
  init_file_list(&file_list, 0, comparator);
  if (!populate_file_list_from_directory(&file_list, path))
    err = 1;
  if (!print_file_list(&file_list, print_options))
    err = 1;
  destroy_file_list(&file_list);
  return !err;
}
 
static int list_files(char *const path[], int count, const struct options_struct *options)
{
  int err;
  struct file_list_struct file_list;
 
  err = 0;
  init_file_list(&file_list, options->list_directories, options->comparator);
  if (!populate_file_list_from_files(&file_list, path, count))
    err = 1;
  if (!print_file_list(&file_list, &options->print_options))
    err = 1;
  destroy_file_list(&file_list);
  return !err;
}
 
static void __attribute__ ((noreturn)) die_bad_usage()
{
  fprintf(stderr, "Try `%s --help' for more information.\n", program_invocation_name);
  exit(EXIT_FAILURE);
}
 
static void print_help()
{
  printf("Usage: %s [OPTION]... [FILE]...\n", program_invocation_name);
  puts("List information about the FILEs (the current directory by default).");
  putchar('\n');
  puts("  -d, --directory  list directory entries instead of contents");
  puts("  -i, --inode      print the index number of each file");
  puts("  -l               use a long listing format");
  puts("  -S               sort by file size");
  puts("  -t               sort by modification time");
  puts("  -h, --help       display this help and exit");
}
 
int main(int argc, char **argv)
{
  static const struct option longopts[] = {
    {"directory", no_argument, NULL, 'd'},
    {"inode", no_argument, NULL, 'i'},
    {"help", no_argument, NULL, 'h'},
    {NULL, 0, NULL, 0}
  };
  static char dot[] = ".";
  static char *const default_files[] = {dot};
  static const int default_count = 1;
 
  struct options_struct options;
  int c;
  char *const *files;
  int count;
 
  atexit(close_stdout);
 
  options.list_directories = 1;
  options.comparator = name_comparator;
  options.print_options.print_ino = 0;
  options.print_options.long_format = 0;
  while ((c = getopt_long(argc, argv, "dilSth", longopts, NULL)) != -1)
    switch (c) {
    case 'd':
      options.list_directories = 0;
      break;
    case 'i':
      options.print_options.print_ino = 1;
      break;
    case 'l':
      options.print_options.long_format = 1;
      break;
    case 'S':
      options.comparator = size_comparator;
      break;
    case 't':
      options.comparator = mtime_comparator;
      break;
    case 'h':
      print_help();
      return EXIT_SUCCESS;
    case '?':
      die_bad_usage();
    }
 
  if (optind == argc) {
    files = default_files;
    count = default_count;
  }
  else {
    files = argv + optind;
    count = argc - optind;
  }
  return list_files(files, count, &options) ? EXIT_SUCCESS : EXIT_FAILURE;
}