RCS_ID_C "$Id: print.c,v 1.12 1993/11/12 06:03:26 ppessi Exp $";
/*
 * print.c --- print a directory listing
 *
 * Author: ppessi <Pekka.Pessi@hut.fi>
 *
 * Copyright (c) 1991, 1993 Pekka Pessi. All rights reserved
 *
 * Last modified: Fri Nov 12 08:03:02 1993 ppessi
 *
 */

#include <stdlib.h>
#include <string.h>
#include <dos/dosasl.h>
#include <dos/dosextens.h>
#include <dos/datetime.h>
#include <clib/utility_protos.h>

#include <exec/io.h>
#include <exec/memory.h>
#include <devices/conunit.h>

#ifdef HAVE_MULTIUSER
#include <libraries/multiuser.h>
#else
/* Only thing we need to know is SUID bit */
#define muFIBB_SET_UID        (31)  /* Change owner during execution */
#define muFIBF_SET_UID        (1<<muFIBB_SET_UID)
#endif

#include "ls.h"

#define FUDGE 2			/* space between columns */
#define DEFAULTWIDTH 	77	/* -C width */

#ifndef FIBB_OTR_READ
/* 
 * These bits are undefined before DOS 3.0 
 * FIBB are bit definitions, FIBF are field definitions 
 * Regular RWED bits are 0 == allowed. 
 * NOTE: GRP and OTR RWED permissions are 0 == not allowed! 
 */
#define FIBB_OTR_READ      15   /* Other: file is readable */
#define FIBB_OTR_WRITE     14   /* Other: file is writable */
#define FIBB_OTR_EXECUTE   13   /* Other: file is executable */
#define FIBB_OTR_DELETE    12   /* Other: prevent file from being deleted */
#define FIBB_GRP_READ      11   /* Group: file is readable */
#define FIBB_GRP_WRITE     10   /* Group: file is writable */
#define FIBB_GRP_EXECUTE   9    /* Group: file is executable */
#define FIBB_GRP_DELETE    8    /* Group: prevent file from being deleted */
#endif

/*
 * checkbreak
 *      exit if CTRL-C signal received
 */ 
static void
checkbreak(void)
{
  if (CheckSignal(SIGBREAKF_CTRL_C)) {
    FPuts(Stderr, "***Break\n");
    exit(RETURN_FAIL);
  }
}

/*
 * getconwidth
 *      Try to find out the width of console window
 */
static int 
getconwidth(BPTR out) 
{
  int width;
  struct InfoData *id;
  struct MsgPort *conTask;
  
  if (!out || !IsInteractive(out)) {
    return 0;
  }
  conTask = ((struct FileHandle *)BADDR(out)) -> fh_Type;
  if ((long) conTask < 0)
    return 0;	
  id = (struct InfoData *) AllocMem(sizeof(struct InfoData), MEMF_CLEAR);
  if (!id) 
    return 0;

  if (DoPkt(conTask, ACTION_DISK_INFO, MKBADDR(id), NULL, NULL, NULL, NULL)) {
#if 0
    struct ConUnit * conu = (id->id_InUse);
    FPrintf(err, "Window id: %lx\n", (long) (id->id_VolumeNode));
    FPrintf(err, "Console Unit: %lx\n", (long) (id->id_InUse));
    FPrintf(err, "Window Size: X %ld Y %ld \n", conu->cu_XMax, conu->cu_YMax);
#endif
    width = ((struct ConUnit *)
	     ( ((struct IOStdReq *)(id->id_InUse))->io_Unit ))->cu_XMax;
  } else { 
    width = 0;
  }
  FreeMem(id, sizeof(*id));
  return width;
}

/*
 * count_rows
 *      calculate minimum amount of row the short listing fits
 */
static int
count_rows(const UBYTE *widths,
	   UBYTE * col_widths,
	   int entries,
	   int width,
	   int fudge)		/* fudge factor */
{
  int col, row, rows, row_width;
  UBYTE * col_width;

  for (rows = 1; rows < entries; rows ++) {
    row_width = 0;
    col_width = col_widths;
    for (col = 0; col < entries; col += rows, row_width += fudge) {
      int w = 0;
      for (row = 0; row < rows && col + row < entries; row ++)
        w = max(w, widths[col + row]);
      row_width += w;
      *col_width++ = w;
      if (w > width)
	return entries;
      if (row_width > width)
        break;
    }
    if (row_width <= width)
      break;
  }
  return rows;
}

/* 
 * type_char
 *      give appropriate prefix char for -F
 */
INLINE static char
type_char(const struct ExAllData * slot)
{
  char c = '\0';
  switch (slot->ed_Type) {
  case ST_ROOT:
  case ST_USERDIR:
    c = '/'; break;
  case ST_SOFTLINK:
  case ST_LINKDIR:
    c = '@'; break;
  case ST_LINKFILE:
    c = '#'; break;
  }
  return c;
}

/* 
 * total
 *    print total size of a directory 
 */
INLINE static void
total(const struct ExAllData ** slots, 
      int entries)
{
  /* Normally, each entry takes a block */
  short i;
  long used_blocks = 0;

  for (i = 0; i < entries; i++) {
    /* Correct for small FFS files only  */
    used_blocks += slots[i]->ed_Blocks;
  }
  /* Print only # of used blocks */
  FPrintf( Stdout, "total %ld\n", used_blocks + 1 >> 1);
}

/* ST_PIPEFILE (-5) -> "p" for pipes 
 * ST_LINKFILE (-4) -> "h" for hard link to file 
 * ST_FILE     (-3) -> "-" for plain files
 * ST_ROOT       1  -> "r" for root directory
 * ST_USERDIR	 2  -> "d" for ordinary directory
 * ST_SOFTLINK	 3  -> "l" for soft link
 * ST_LINKDIR	 4  -> "D" for hard link to dir
 */
static char f[10] = "ph-210rdlD";
static char r[2] = "-r";
static char w[8] = "-D-DWwWw";
static char x[8] = "-xTtSsSs";
static char pchars[11];

/*
 * getprot
 *      get a Unix style protection bit representation 
 */
static UBYTE *
getprot(BYTE type, LONG pbits, UWORD owner)
{
  UBYTE *p = pchars;

  if (type < ST_PIPEFILE)
    type = ST_PIPEFILE;
  else if (type > ST_LINKDIR)
    type = ST_LINKDIR;

  type -= ST_PIPEFILE;

  /* Invert lowest 4 bits */
  pbits ^= 0x0f;

  /* Is the file owned by Mr. Nobody? */
  if (owner == 0) {
    ULONG bits = pbits & (FIBF_READ | FIBF_DELETE | FIBF_WRITE | FIBF_EXECUTE);
    pbits |=  bits << FIBB_GRP_DELETE | bits << FIBB_OTR_DELETE ; 
  }

  *p++ = f[type];
  *p++ = r[1 & (pbits >> FIBB_READ)];
  *p++ = w[7 & (pbits >> FIBB_DELETE)];
  *p++ = x[(1 & (pbits >> FIBB_EXECUTE)) + 
	   (4 & (pbits >> muFIBB_SET_UID - 2))];
  *p++ = r[1 & (pbits >> FIBB_GRP_READ)];
  *p++ = w[7 & (pbits >> FIBB_GRP_DELETE)];
  *p++ = x[1 & (pbits >> FIBB_GRP_EXECUTE)];
  *p++ = r[1 & (pbits >> FIBB_OTR_READ)];
  *p++ = w[7 & (pbits >> FIBB_OTR_DELETE)];
  *p++ = x[(1 & (pbits >> FIBB_OTR_EXECUTE)) +
	   (2 & (pbits >> FIBB_PURE - 1)) + /* seen as 'sticky' */
	   (4 & (pbits >> FIBB_SCRIPT - 2))];
  *p++ = '\0';
  return pchars;
}

static int current_year = 0;
static UBYTE times[16];

/*
 * lsdate
 *      convert date into ls format
 */
static UBYTE *
lsdate(const struct ExAllData * slot)
{
  int year;
  UBYTE dates[LEN_DATSTRING];

#if __SASC == 0
  struct DateTime datetime =
    { { 0, 0, 0}, 0, 0, NULL, dates, times + 7};
#else
  struct DateTime datetime =
    { { 0, 0, 0}, 0, 0, NULL, NULL, NULL};

  datetime.dat_StrDate = dates;
  datetime.dat_StrTime = times + 7;
#endif
  if (!current_year) {
    (void) DateStamp(&datetime.dat_Stamp);
    (void) DateToStr(&datetime);
    current_year = 1900 + (dates[7] - '0') * 10 + dates[8] - '0';
  }

  datetime.dat_Stamp.ds_Days   = slot->ed_Days;
  datetime.dat_Stamp.ds_Minute = slot->ed_Mins;
  datetime.dat_Stamp.ds_Tick   = slot->ed_Ticks;
  (void) DateToStr(&datetime);

  /* if file's year is not current, print it instead of time */
  year = 1900 + (dates[7] - '0') * 10 + dates[8] - '0';
  if (year != current_year) {
    times[7] = '1';
    times[8] = '9';
    times[9] = dates[7];
    times[10] = dates[8];
    times[11] = ' ';
  }

  times[12] = '\0';		/* don't show seconds */

  /* Hack date field for printing */
  times[0] = dates[3];
  times[1] = dates[4];
  times[2] = dates[5];
  times[3] = ' ';
  if (dates[0] == '0')
    times[4] = ' ';
  else
    times[4] = dates[0];
  times[5] = dates[1];
  times[6] = ' ';

  return times;
}

/*
 * printlink
 *      print out the soft link
 */
static void
printlink(const char * path, 
	  const struct ExAllData * slot)
{
  static char *buffer = NULL;
  BPTR lock; 
  struct FileLock *fl;

  if (!buffer) {
    buffer = malloc(MAXPATHLEN);
    if (!buffer) return;
  }

  lock = Lock(path, SHARED_LOCK); 
  if (fl = (struct FileLock *)(lock << 2)) {
    if (ReadLink(fl->fl_Task, lock, slot->ed_Name, buffer, MAXPATHLEN))
      FPrintf(Stdout, " -> %s", buffer);
    UnLock(lock);
  }
}

/*
 * shortprint
 *      list a directory in short format
 */
static void
shortprint(const struct ExAllData ** slots,
	   int entries,
	   struct options options)
{
  int width;
  int rows, columns;
  int i, j;
  UBYTE * widths = malloc(entries + 1);
  UBYTE * col_widths = malloc(entries + 1);
  if (!widths || !col_widths)
    exit(RETURN_FAIL);

  if (options.singlecolumn)
    width = 0;
  else {
    width = getconwidth(Stdout);
    /* unknown width, but forced multicolumn */
    if (width == 0 && options.multicolumn)
      width = DEFAULTWIDTH;
  }

  if (width > FUDGE + 1) {
    for(i = 0; i < entries; i++) {
      widths[i] = strlen(slots[i]->ed_Name);
      if (options.kilos)
	widths[i] += 5;		/* no files longer than 9999 kilos ;) */
      if (options.inode)
	widths[i] += 7;		/* actually no key blocks > 4194303 */
      if (options.filetype && type_char(slots[i]))
	widths[i]++;
    }
    rows = count_rows(widths, col_widths, entries, width, FUDGE);
    columns = entries / (rows + 1) + 1;
  } else {
    rows = entries;
    columns = 1;
  }

  if (options.kilos)
    total(slots, entries);

  for (i = 0; i < rows; i ++) {
    UBYTE * col_width = col_widths;

    checkbreak();

    for (j = i; j < entries; j += rows ) {
      char filetype;
      if (options.inode)
	FPrintf(Stdout, "%6ld ", slots[j]->ed_Key);
      /* if -s is wanted */
      if (options.kilos)
	FPrintf(Stdout, "%4ld ", slots[j]->ed_Blocks + 1 >> 1);
      FPuts(Stdout, (char *)(slots[j]->ed_Name));
      if (options.filetype && (filetype = type_char(slots[j])))
	  FPutC(Stdout, filetype);

      if (j + rows < entries) { /* tabulate */
        FWrite(Stdout,
	       "                                        "
	       "                                        ",
	       /* 80 spaces, max filename lenght == 80 */
	       sizeof(char), *col_width++ - widths[j] + FUDGE);
      } else {
	FPutC(Stdout, '\n');
      }
    }
  }
  free(widths);
  free(col_widths);
}

/*
 * longprint
 *      list directory in long format
 */
static void
longprint(const char * path, const struct ExAllData ** slots,
	  int entries, struct options options)
{
  int i;
  char filetype;

  total(slots, entries);

  for (i = 0; i < entries; i++) {
    long va_list[10];
    const struct ExAllData * slot = slots[i];

    checkbreak();

    /* if -i is wanted */
    if (options.inode)
      FPrintf(Stdout, "%6ld ", slot->ed_Key);
    /* if -s is wanted */
    if (options.kilos)
      FPrintf(Stdout, "%4ld ", slot->ed_Blocks + 1 >> 1);

    /* normal -l listing */
    va_list[0] = (long)getprot(slot->ed_Type, slot->ed_Prot, slot->ed_OwnerUID);
    va_list[1] = 1;
    va_list[2] = (long)user(slot->ed_OwnerUID);
    if (options.group) 
      va_list[3] = (long)group(slot->ed_OwnerGID);
    else
      va_list[3] = (long)"";
    va_list[4] = slot->ed_Size;
    /* This is a kludge */
    va_list[5] = (long)lsdate(slot);
    va_list[6] = (long)(options.pathname ? path : "");
    va_list[7] = (long)slot->ed_Name;
    VFPrintf(Stdout, options.group ? "%s %2ld %s %s %8ld %s %s%s" :
	             "%s %2ld %s %s%8ld %s %s%s", va_list);

    if (options.filetype && (filetype = type_char(slot)))
      FPutC(Stdout, filetype);
    if (!options.symbolic && (slot->ed_Type == ST_SOFTLINK)) {
      printlink(path, slot);
    }
    FPutC(Stdout, '\n');
  }
}

static int
compare_name(const struct ExAllData * p, const struct ExAllData * v)
{
  return Stricmp(p->ed_Name, v->ed_Name);
}

static int
compare_name_rev(const struct ExAllData * p, const struct ExAllData * v)
{
  /* p and v reversed... */
  return Stricmp(v->ed_Name, p->ed_Name);
}

static int
compare_time(const struct ExAllData * p, const struct ExAllData * v)
{
  return CompareDates((struct DateStamp*)&(p->ed_Days),
		      (struct DateStamp*)&(v->ed_Days));
}

static int
compare_time_rev(const struct ExAllData * p, const struct ExAllData * v)
{
  /* p and v reversed... */
  return CompareDates((struct DateStamp*)&(v->ed_Days),
		      (struct DateStamp*)&(p->ed_Days));
}

static short
complete(char * path)
{
  short pathlen = strlen(path);
  /* add directory separator to path, if none */
  if (pathlen > MAXPATHLEN - MAXFILENAMELEN - 1)
    return -1;
  if (pathlen > 0 && path[pathlen - 1] != ':' && path[pathlen - 1] != '/') {
    path[pathlen++] = '/'; path[pathlen] = '\0';
  }
  return pathlen;
}

void
doprint(char * path,
	const struct ExAllList *buffers,
	struct options options)
{
  int entry = 0, dentry = 0, i, di;
  int entries = buffers->El_Number;
  struct ExAllData **dslots, **slots;
  short pathlen;

  checkbreak();

  if (!entries) {
    FPuts(Stdout, "Volume or directory is empty.\n");
    return;
  }
  slots = malloc((entries + 1) * sizeof(char *));
  if (!slots) {
    exit(RETURN_FAIL);
  }

  if ((pathlen = complete(path)) < 0) {
    /* pathname overflow - break... */
    FPrintf(Stderr, "%s: too long pathname\n", path);
    return;
  }

  for (; buffers; buffers = buffers->El_Next) {
    struct ExAllData *ptr;
    for (ptr = &(buffers->El_Data); ptr; ptr = ptr->ed_Next) {
      /* Add entry into table */
      dentry += (ptr->ed_Type > 0);
      slots[entry++] = ptr;
      if (entry > entries) {
        FPrintf(Stderr,
		"ls: Fatal error, entry count "
		"mismatch %ld != %ld\n", entries, entry);
        exit(RETURN_FAIL);
      }
    }
  }
  if (!options.fast && entries > 1) {
    int (*compare)(void *, void *) = NULL;

    if ( options.reverse ) {
      compare = options.sort_time ? (compare_time_rev) : (compare_name_rev);
    } else {
      compare = options.sort_time ? (compare_time) : (compare_name);
    }

    quick_sort((void **)slots, entries, compare);
  }
  dslots = malloc((dentry + 1) * sizeof(char *));

  if (!dslots)
    exit(RETURN_FAIL);

  for (i = 0, di = 0, entry = 0; i < entries; i++) {
    if (slots[i]->ed_Type > 0) {
      dslots[di++] = slots[i];
      if (!options.dir) continue;
    }
    slots[entry++] = slots[i];
  }

  if (entry) {
    if (options.longformat) {
      longprint(path, slots, entry, options);
    } else {
      shortprint(slots, entry, options);
    }
  }

  free(slots);
  if (!options.dir) {
    static int donewline = 0;
    struct ExAllList *listing;

    options.dir = !options.recursion;

    for (i = 0; i < dentry; i++) {
      /* concatenate new entry to path name */
      strncpy(path + pathlen, (char *)(dslots[i]->ed_Name), 
	      MAXPATHLEN - pathlen);
      /* avoid newline at top */
      if (donewline)
      	FPutC(Stdout, '\n');
      if (dentry > 1 || donewline)
	FPrintf(Stderr, "%s:\n", path[0] ? path : "\"\"" );
      donewline = 1;
      listing = listdir(path, options);
      if (listing)
	doprint(path, listing, options);
      bfree(listing);
    }
  }
  free(dslots);
}
