Goto sanos source index

//
// ls.c
//
// List directory contents
//
// Copyright (C) 2012 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <dirent.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <shlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>

#define HALFYEAR (365 * 24 * 60 * 60 / 2)

struct options {
  int detail;
  int onecol;
  int recurse;
  int all;
  
  int width;
  int nodir;
  time_t now;
};

struct file {
  char *dir;
  char *name;
  struct stat stat;
};

struct filelist {
  struct file **begin;
  struct file **end;
  struct file **limit;
};

static void add_file(struct filelist *list, char *dir, char *name, struct stat *st) {
  struct file *file;

  // Extend list if it is full
  if (list->end == list->limit) {
    int size = list->limit - list->begin;
    int newsize = size ? size * 2 : 32;
    list->begin = (struct file **) realloc(list->begin, newsize * sizeof(struct file *));
    list->end = list->begin + size;
    list->limit = list->begin + newsize;
  }
  
  // Add new file entry to list
  file = (struct file *) malloc(sizeof(struct file));
  file->dir = strdup(dir);
  file->name = strdup(name);
  memcpy(&file->stat, st, sizeof(struct stat));
  *list->end++ = file;
}

static void delete_file_list(struct filelist *list) {
  struct file **f;

  for (f = list->begin; f != list->end; f++) {
    free((*f)->dir);
    free((*f)->name);
    free(*f);
  }
}

static int compare_filename(const void *d1, const void *d2) {
  struct file *f1 = *(struct file **) d1;
  struct file *f2 = *(struct file **) d2;
  int cmp = strcmp(f1->dir, f2->dir);
  if (!cmp) cmp = strcmp(f1->name, f2->name);
  return cmp;
}

static void sort_list(struct filelist *list) {
  qsort(list->begin, list->end - list->begin, sizeof(struct file *), compare_filename);
}

void collect_directory(struct filelist *list, char *dir, struct options *opts) {
  struct stat st;
  struct dirent *dp;
  DIR *dirp;
  char *fn;

  dirp = opendir(dir ? dir : ".");
  if (!dirp) {
    perror(dir);
    return;
  }

  while ((dp = readdir(dirp))) {
    if (!opts->all && dp->d_name[0] == '.') continue;
    fn = join_path(dir, dp->d_name);
    if (!fn) break;
    if (stat(fn, &st) >= 0) {
      add_file(list, dir, dp->d_name, &st);
      if (opts->recurse && S_ISDIR(st.st_mode)) {
        collect_directory(list, fn, opts);
      }
    }
    free(fn);
  }
  closedir(dirp);
}

void collect_files(struct filelist *list, char *path, struct options *opts) {
  struct stat st;
  
  // Stat file
  if (stat(path ? path : ".", &st) < 0) {
    perror(path);
    return;
  }

  if (!S_ISDIR(st.st_mode) || opts->nodir) {
    // Add single file
    add_file(list, "", path, &st);
  } else {
    // Add directory files
    collect_directory(list, path, opts);
  }
}

static int numlen(int n) {
  int l = 0;
  if (n == 0) return 1;
  while (n > 0) {
    l++;
    n /= 10;
  }
  return l;
}

void print_details(struct file *files[], int numfiles, struct options *opts) {
  char perms[11];
  char date[14];
  struct tm tm;
  int old;
  struct passwd *pwd;
  struct group *grp;
  int i, l;

  // Find width of variable size columns
  int lwidth = 1;
  int owidth = 1;
  int gwidth = 1;
  int swidth = 1;
  int lastuid = -1;
  int lastgid = -1;
  for (i = 0; i < numfiles; i++) {
    struct file *file = files[i];
    struct stat *st = &file->stat;

    l = numlen(st->st_nlink);
    if (l > lwidth) lwidth = l;

    if (st->st_uid != lastuid) {
      pwd = getpwuid(st->st_uid);
      l = pwd ? strlen(pwd->pw_name) : numlen(st->st_uid);
      if (l > owidth) owidth = l;
      lastuid = st->st_uid;
    }

    if (st->st_gid != lastgid) {
      grp = getgrgid(st->st_gid);
      l = grp ? strlen(grp->gr_name) : numlen(st->st_gid);
      if (l > gwidth) gwidth = l;
      lastgid = st->st_gid;
    }
    
    l = numlen(st->st_size);
    if (l > swidth) swidth = l;
  }

  for (i = 0; i < numfiles; i++) {
    struct file *file = files[i];
    struct stat *st = &file->stat;

    // Print permissions and number of links
    printf("%s %*d ", get_symbolic_mode(st->st_mode, perms), lwidth, st->st_nlink);

    // Print owner
    pwd = getpwuid(st->st_uid);
    if (pwd) {
      printf("%-*s ", owidth, pwd->pw_name);
    } else {
      printf("%*d ", owidth, st->st_uid);
    }
  
    // Print group
    grp = getgrgid(st->st_gid);
    if (grp) {
      printf("%-*s ", gwidth, grp->gr_name);
    } else {
      printf("%*d ", gwidth, st->st_gid);
    }

    // Print size
    printf("%*d ", swidth, st->st_size);

    // Print modification date
    gmtime_r(&st->st_mtime, &tm);
    old = opts->now - st->st_mtime > HALFYEAR;
    strftime(date, sizeof(date), old ? "%b %e  %Y" : "%b %e %H:%M", &tm);
    printf("%s ", date);
  
    // Print filename
    printf("%s%s\n", file->name, S_ISDIR(st->st_mode) ? "/" : "");
  }
}

void print_columns(struct file *files[], int numfiles, struct options *opts) {
  int i, l;
  int colwidth;
  int cols, rows;
  int r, c;

  // Find column width and number of columns
  colwidth = 1;
  for (i = 0; i < numfiles; i++) {
    struct file *file = files[i];
    l = strlen(file->name);
    if (S_ISDIR(file->stat.st_mode)) l++;
    if (l > colwidth) colwidth = l;
  }
  colwidth += 2;
  cols = opts->width / colwidth;
  if (cols < 1) cols = 1;
  rows = (numfiles + cols - 1) / cols;

  // Output rows and columns
  for (r = 0; r < rows; r++) {
    for (c = 0; c < cols; c++) {
      int n = c * rows + r;
      if (n < numfiles) {
        struct file *file = files[n];
        l = strlen(file->name);
        printf("%s", file->name);
        if (S_ISDIR(file->stat.st_mode)) {
          printf("/");
          l++;
        }
        if (c < cols - 1) {
          l = colwidth - l;
          while (l-- > 0) putchar(' ');
        }
      } else {
        break;
      }
    }
    printf("\n");
  }
}

static void print_files(struct filelist *list, struct options *opts) {
  struct file **f;

  if (opts->onecol) {
    // Print list of file with one file per line
    for (f = list->begin; f != list->end; f++) {
      struct file *file = *f;
      if (strcmp(file->dir, ".") == 0) {
        printf("%s\n", file->name);
      } else {
        printf("%s/%s\n", file->dir, file->name);
      }
    }
  } else {
    struct file **dir;
    int multidir = 0;
    int firstdir = 1;

    // Check for multiple directories
    for (f = list->begin + 1; f != list->end; f++) {
      if (strcmp(f[0]->dir, f[-1]->dir) != 0) {
        multidir = 1;
        break;
      }
    }

    // Output files for each directory
    dir = f = list->begin;
    for (f = list->begin; f <= list->end; f++) {
      if (f == list->end || strcmp((*dir)->dir, (*f)->dir) != 0) {
        if (multidir) {
          if (!firstdir) printf("\n");
          printf("%s:\n", (*dir)->dir);
          firstdir = 0;
        }
        if (opts->detail) {
          print_details(dir, f - dir, opts);
        } else {
          print_columns(dir, f - dir, opts);
        }
        dir = f;
      }
    }
  }
}

static void usage() {
  fprintf(stderr, "usage: ls [OPTIONS] FILE...\n\n");
  fprintf(stderr, "  -l      List file details\n");
  fprintf(stderr, "  -1      List files in one column\n");
  fprintf(stderr, "  -d      List directory entries instead of contents\n");
  fprintf(stderr, "  -R      Recursively list subdirectories\n");
  fprintf(stderr, "  -A      List all files starting with .\n");
  
  exit(1);
}

shellcmd(ls) {
  struct options opts;
  int c;
  int i;
  struct filelist list;

  // Parse command line options
  memset(&opts, 0, sizeof(struct options));
  opts.width = 80;
  while ((c = getopt(argc, argv, "aAdl1R?")) != EOF) {
    switch (c) {
      case 'A':
      case 'a':
        opts.all = 1;
        break;

      case 'd':
        opts.nodir = 1;
        break;

      case 'l':
        opts.detail = 1;
        break;

      case '1':
        opts.onecol = 1;
        break;

      case 'R':
        opts.recurse = 1;
        break;

      case '?':
      default:
        usage();
    }
  }

  // Collect files
  opts.now = time(NULL);
  memset(&list, 0, sizeof(struct filelist));
  if (optind == argc) {
    collect_files(&list, ".", &opts);
  } else {
    for (i = optind; i < argc; i++) {
      collect_files(&list, argv[i], &opts);
    }
  }

  // List files
  if (list.begin != list.end) {
    sort_list(&list);
    print_files(&list, &opts);
  }
  delete_file_list(&list);
  
  return 0;
}