/* `ls' directory listing program for GNU.
   Copyright (C) 1985 Richard M. Stallman.


		       NO WARRANTY

  BECAUSE THIS PROGRAM IS LICENSED FREE OF CHARGE, WE PROVIDE ABSOLUTELY
NO WARRANTY, TO THE EXTENT PERMITTED BY APPLICABLE STATE LAW.  EXCEPT
WHEN OTHERWISE STATED IN WRITING, FREE SOFTWARE FOUNDATION, INC,
RICHARD M. STALLMAN AND/OR OTHER PARTIES PROVIDE THIS PROGRAM "AS IS"
WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY
AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.

 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW WILL RICHARD M.
STALLMAN, THE FREE SOFTWARE FOUNDATION, INC., AND/OR ANY OTHER PARTY
WHO MAY MODIFY AND REDISTRIBUTE THIS PROGRAM AS PERMITTED BELOW, BE
LIABLE TO YOU FOR DAMAGES, INCLUDING ANY LOST PROFITS, LOST MONIES, OR
OTHER SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR
DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY THIRD PARTIES OR
A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS) THIS
PROGRAM, EVEN IF YOU HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES, OR FOR ANY CLAIM BY ANY OTHER PARTY.

		GENERAL PUBLIC LICENSE TO COPY

  1. You may copy and distribute verbatim copies of this source file
as you receive it, in any medium, provided that you conspicuously
and appropriately publish on each copy a valid copyright notice
"Copyright (C) 1986 Martin Minow, Richard M. Stallman"; and include
following the copyright notice a verbatim copy of the above disclaimer
of warranty and of this License.

  2. You may modify your copy or copies of this source file or
any portion of it, and copy and distribute such modifications under
the terms of Paragraph 1 above, provided that you also do the following:

    a) cause the modified files to carry prominent notices stating
    that you changed the files and the date of any change; and

    b) cause the whole of any work that you distribute or publish,
    that in whole or in part contains or is a derivative of this
    program or any part thereof, to be freely distributed
    and licensed to all third parties on terms identical to those
    contained in this License Agreement (except that you may choose
    to grant more extensive warranty protection to third parties,
    at your option).

  3. You may copy and distribute this program or any portion of it in
compiled, executable or object code form under the terms of Paragraphs
1 and 2 above provided that you do the following:

    a) cause each such copy to be accompanied by the
    corresponding machine-readable source code, which must
    be distributed under the terms of Paragraphs 1 and 2 above; or,

    b) cause each such copy to be accompanied by a
    written offer, with no time limit, to give any third party
    free (except for a nominal shipping charge) a machine readable
    copy of the corresponding source code, to be distributed
    under the terms of Paragraphs 1 and 2 above; or,

    c) in the case of a recipient of this program in compiled, executable
    or object code form (without the corresponding source code) you
    shall cause copies you distribute to be accompanied by a copy
    of the written offer of source code which you received along
    with the copy you received.

  4. You may not copy, sublicense, distribute or transfer this program
except as expressly provided under this License Agreement.  Any attempt
otherwise to copy, sublicense, distribute or transfer this program is void and
your rights to use the program under this License agreement shall be
automatically terminated.  However, parties who have received computer
software programs from you with this License Agreement will not have
their licenses terminated so long as such parties remain in full compliance.

 In other words, you are welcome to use, share and improve this program.
 You are forbidden to forbid anyone else to use, share and improve
 what you give them.   Help stamp out software-hoarding!  */


#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sgtty.h>
#include <stdio.h>

/* If the macro MULTI_COL is defined,
   then multi-column format is the default regardless
   of the type of output device,

   if the macro LONG_FORMAT is defined,
   then long format is the default regardless of the
   type of output device.

   These setting will be used for GNU's preferred
   directory listing programs, but not for GNU's ls,
   which will exist for compatibility.  */

char *copystring ();
int compare_atime (), compare_mtime (), compare_ctime (), compare_names ();

extern int errno;
extern int sys_nerr;
extern char *sys_errlist[];

/* The table of files in the current directory:

   `files' points to a vector of `struct file', one per file.
   `nfiles' is the number of elements space has been allocated for.
   `files_index' is the number actually in use.
*/

enum filetype
  {
    normal, directory, symbolic_link,
  };

struct file
  {
    char *name;			/* The file name */
    struct stat stat;
    char *linkname;		/* name linked to, for symbolic link,
				   otherwise zero */
    enum filetype filetype;	/* directory, file or symlink */
  };

/* Address of block containing the files that are described.  */

struct file *files;

/* Length of block that `files' points to, measured in files.  */

int nfiles;

/* Index of first unused in `files'.  */

int files_index;

/* Record of one pending directory waiting to be listed.  */

struct pending
  {
    char *name;
    struct pending *next;
  };

struct pending *pending_dirs;

/* Option flags */

/* 0 for lots of info, one per line.
   1 for just names, one per line.
   2 for just names, many per line.
   3 for many per line, horizontally.
   4 for many per line, separated by commas, no tabs.  */
/* -l, -1, -C, -x and -m control this parameter.  */

enum format
  {
    long_format,
    one_per_line,
    many_per_line,
    horizontal,
    with_commas,
  }
format;

/* type of time to print or sort by.  Controlled by -c and -u.  */

enum time_type
  {
    time_mtime,			/* default */
    time_ctime,			/* -c */
    time_atime,			/* -u */
  }
time_type;

/* Current time (seconds since 1970).  When we are printing a file's time,
   include the year if it is more than 10 months before this time.  */

long current_time;

/* Nonzero means sort by time (-t).  Zero, sort by name.  */

int sort_by_time;

/* Direction of sort.
   0 means highest first if numeric,
   lowest first if alphabetic;
   these happen to be the defaults.
   1 means the opposite order in each case.  -r  */

int sort_reverse;

/* mention the group of each file.  -g  */
/*** ???? DOES NOT WORK YET ***/

int print_groups;

/* mention size in blocks of each file.  -s  */

int print_block_size;

/* Number of digits to use for block sizes.
   4, or more if needed for bigger numbers.  */

int block_size_size;

/* 1 means mention type of file.  -F  */
/* Also -p sets this to -1, which means do so except for executables.  */

int print_filetype;

/* mention inode number of each file.  -i  */

int print_inode;

/* When a symbolic link is found, display info on the file linked to.  -L  */

int trace_links;

/* When a directory is found, display info on its contents.  -R  */

int trace_dirs;

/* When an argument is a directory name, display info on it itself.  -d  */

int immediate_dirs;

/* Don't omit files whose names suggest they are uninteresting.  -A  */

int all_files;

/* Don't omit files "." and ".." -a.  This flag implies all_files */

int really_all_files;

/* Make backup files appear uninteresting.  Better might be options to:
   1) list numbered backups in csh syntax "foo{.~12~,~13~}",  or
   2) ignoring all files matching a regexp that the user specifies. */
int ignore_backup_files;

#define BACKUP_SUFFIX '~'	/* used for detecting backup files */

/* Quote nongraphic chars in file names.  -b  */

int quote_funny_chars;

/* Output each file name using C syntax for a string.  
   Always accompanied by quote_funny_chars.
   This mode, together with -x or -C or -m,
   and without such frills as -F or -s,
   is guaranteed to make it possible for a program receiving
   the output to tell exactly what file names are present.
   -Q  */

int quote_as_string;

/* Nonzero means we are listing working directory because no args given */

int dir_defaulted;

/* Line length to use for breaking lines
   in many-per-line format.  Can be set with -w.  */

int line_length;

main (argc, argv)
     int argc;
     char **argv;
{
  register int i;
  register char *thisdir;
  register struct pending *thispend;

  dir_defaulted = 1;
  pending_dirs = 0;
  current_time = time ();

  decode_switches (argc, argv);
  /* Note that decode_switches can set elements of argv to zero
     to prevent them from being taken as file names.  */

  nfiles = 100;
  files = (struct file *) xmalloc (sizeof (struct file) * nfiles);
  files_index = 0;

  clear_files ();

  for (i = 1; i < argc; i++)
    if (argv[i] != 0)
      {
	dir_defaulted = 0;
	gobble_file (argv[i], 1, "");
      }

  if (dir_defaulted)
/*  Mly 860917
 *  Removing the commented-out code is desirable for the following reason:
 *  'ls -d' is a useful thing to use as a csh alias for those
 *   of us who hate having directory structure recursively listed
 *   when we say 'ls foo*' and foo1/ is a directory.
 *  Having the '-d' switch go away when no directory is specified
 *   means that 'ls -d' will do something useful, instead of listing "." alone
 *  Nothing is lost through this change, since by specifying "." explicity
 *   it can still be listed 'ls -d .'
 *  Of course, the real solution would be for 'ls' to only gobble_directory
 *   arguments whose names end in a "/", which would make 'dir /u/' do a listing
 *   of "u/*" whilst 'dir /u' would do 'ls -d /u'
 *   This is what most sane filesystems do.  Of course, it is massively
 *   incompatible with unix 'ls'
 *
 *    if (immediate_dirs)
 *      gobble_file (".", 0, "");
 *    else
 */

/* Phr 22 Feb 87
 * I put back the above code because taking it out caused
 * an unnecessary and gratuituous incompatibility with un*x and confused
 * users such as myself who are used to the old behavior.  That the
 * old behavior can be simulated by saying "ls -d ." is irrelevant;
 * by that argument, changing all the flags around would also result
 * in "nothing being lost".
 */
   
     if (immediate_dirs)
       gobble_file (".", 0, "");
     else
       queue_directory (".");

  if (files_index)
    {
      sort_files ();
      if (!immediate_dirs)
	extract_dirs_from_files ("");
    }
  if (files_index)
    print_current_files ();

  while (pending_dirs)
    {
      thisdir = pending_dirs->name;
      thispend = pending_dirs;
      pending_dirs = pending_dirs->next;
      free (thispend);
      print_dir (thisdir);
    }

  return 0;
}

/* Set all the option flags according to the switches specified.
   Also set to zero any elements of argv 
   that are switches or args to switches.  */

decode_switches (argc, argv)
     int argc;
     char **argv;
{
  register int i;
  register char *p;
  struct sgttyb garbage;

  /* initialize all switches to default settings */

#ifdef MULTI_COL
  format = many_per_line;
#else
#ifdef LONG_FORMAT
  format = long_format;
#else
  if (ioctl (1, TIOCGETP, &garbage) == 0)
    format = many_per_line;
  else
    format = one_per_line;
#endif
#endif

  time_type = time_mtime;
  sort_by_time = 0;
  sort_reverse = 0;
  print_groups = 0;
  print_block_size = 0;
  print_filetype = 0;
  print_inode = 0;
  trace_links = 0;
  trace_dirs = 0;
  immediate_dirs = 0;
  all_files = 0;
  really_all_files = 0;
  ignore_backup_files = 0;
  quote_funny_chars = 0;
  quote_as_string = 0;
  p = (char *) getenv ("COLUMNS");
  if (p)
    line_length = atoi (p) - 1;
  else
    line_length = 80 - 1;

  for (i = 1; i < argc; i++)
    {
      if (argv[i][0] == '-')
	{
	  p = argv[i] + 1;
	  argv[i] = 0;
	  for (; *p; p++)
	    switch (*p)
	      {
	      case 'a':
		all_files = 1;
		really_all_files = 1;
		break;

	      case 'b':
		quote_funny_chars = 1;
		break;

	      case 'c':
		time_type = time_ctime;
		break;

	      case 'd':
		immediate_dirs = 1;
		break;

	      case 'g':
		print_groups = 1;
		break;

	      case 'i':
		print_inode = 1;
		break;

	      case 'l':
		format = long_format;
		break;

	      case 'm':
		format = with_commas;
		break;

	      case 'p':
		print_filetype = -1;
		break;

	      case 'r':
		sort_reverse = 1;
		break;

	      case 's':
		print_block_size = 1;
		break;

	      case 't':
		sort_by_time = 1;
		break;

	      case 'u':
		time_type = time_atime;
		break;

	      case 'w':
		if (p[1] && p[1] >= '0' && p[1] <= '9')
		  {
		    line_length = atoi (p + 1) - 1;
		    while (p[1] >= '0' && p[1] <= '9')
		      p++;
		    break;
		  }
		if (p[1] || i + 1 == argc)
		  fatal ("invalid -w switch: no number follows.", 0);
		line_length = atoi (argv[++i]) - 1;
		argv[i] = 0;
		break;

	      case 'x':
		format = horizontal;
		break;

	      case 'A':
		all_files = 1;
		break;

	      case 'B':
		ignore_backup_files = 1;
		break;

	      case 'C':
		format = many_per_line;
		break;

	      case 'F':
		print_filetype = 1;
		break;

	      case 'L':
		trace_links = 1;
		break;

	      case 'Q':
		quote_as_string = 1;
		quote_funny_chars = 1;
		break;

	      case 'R':
		trace_dirs = 1;
		break;

	      case '1':
		format = one_per_line;
		break;

	      default:
		fatal ("invalid switch; %c", *p, 0);
	      }
	}
    }
}

/* Request that the directory named `name' have its contents listed later.  */

queue_directory (name)
     char *name;
{
  register struct pending *new = (struct pending *) xmalloc (sizeof (struct pending));
  new->next = pending_dirs;
  pending_dirs = new;
  new->name = copystring (name);
}

/* Read directory `name', and list the files in it.  */

print_dir (name)
     char *name;
{
  register DIR *reading = opendir (name);
  register struct direct *next;
  register int total_blocks = 0;
  register int blocks;

  if (!reading)
    {
      perror_with_name (name);
      return;
    }

  /* Read the directory entries, and insert the subfiles
     into the `files' table.  */

  clear_files ();

  while (next = readdir (reading))
    {
	if (file_interesting_p (next))
	{
	  total_blocks += gobble_file (copystring (next->d_name), 0, name);
	}
    }

  closedir (reading);

  /* Sort the directory contents.  */
  sort_files ();

  /* If any member files are subdirectories,
     perhaps they should have their contents listed
     rather than being mentioned here as files.  */

  if (trace_dirs)
    extract_dirs_from_files (name);

  /* If we queued any subdirectories to be listed,
     print each listed directory's name, including this one's.  */

  if (pending_dirs)
    dir_defaulted = 0;

  if (!dir_defaulted)
    printf ("%s:\n", name);
  if (format == long_format || print_block_size)
    printf ("total %d\n", total_blocks);

  if (files_index)
    print_current_files ();

  if (pending_dirs)
    putchar ('\n');

  free (name);
}

/* return nonzero if file should be listed. */
int
file_interesting_p (next)
     register struct direct *next;
{

  if (really_all_files)
    return 1;
  if (ignore_backup_files && !all_files
      && next->d_name[strlen(next->d_name) - 1] == BACKUP_SUFFIX)
    return 0;
  if (next->d_name[0] != '.' ||
      (all_files
       && next->d_name[1] != '\000'
       && (next->d_name[1] != '.' || next->d_name[2] != '\000')))
    return 1;
  return 0;
}


/* Enter and remove entries in the table `files'.  */

/* Empty the table of files,  */

clear_files ()
{
  register int i;
  for (i = 0; i < files_index; i++)
    {
      free (files[i].name);
      free (files[i].linkname);
    }
  files_index = 0;
  block_size_size = 4;
}

/* Add a file to the current table of files.
   check nonzero means verify that the file exists, and
   print an error message if it does not.
   Returns the number of blocks that the file occupies.  */

gobble_file (name, explicit_arg, dirname)
     char *name;
     int explicit_arg;
     char *dirname;
{
  register int val;
  register char *buf;
  register int bufsiz = 100;
  register int blocks;
  register char *concat;

  if (files_index == nfiles)
    files = (struct file *) xrealloc (files, sizeof (struct file) * (nfiles *= 2));

  files[files_index].name = name;
  files[files_index].linkname = 0;

  if (explicit_arg || sort_by_time || format == long_format || trace_links
      || trace_dirs || print_filetype || print_block_size || print_inode)
    {
      /* `concat' gets the absolute pathname of this file.  */

      if (name[0] == '/' || dirname[0] == 0)
	concat = name;
      else
	{
	  concat = (char *) alloca (strlen (name) + strlen (dirname) + 2);
	  strcpy (concat, dirname);
	  strcat (concat, "/");
	  strcat (concat, name);
	}

      /* Read the file's properties.  */

      if (trace_links)
	val = stat (concat, &files[files_index].stat);
      else
	val = lstat (concat, &files[files_index].stat);
      if (val < 0)
	{
	  perror_with_name (concat);
	  return 0;
	}

      blocks = files[files_index].stat.st_blocks;
      if (blocks >= 10000 && block_size_size < 5)
	block_size_size = 5;
      if (blocks >= 100000 && block_size_size < 6)
	block_size_size = 6;
      if (blocks >= 1000000 && block_size_size < 7)
	block_size_size = 7;

      /* Fill in auxiliary fields about the file.  */

      files[files_index].filetype = normal;
      if ((files[files_index].stat.st_mode & S_IFMT) == S_IFLNK)
	files[files_index].filetype = symbolic_link;
      if ((files[files_index].stat.st_mode & S_IFMT) == S_IFDIR)
	files[files_index].filetype = directory;

      if (format == long_format
	  && files[files_index].filetype == symbolic_link)
	{
	  while (1)
	    {
	      buf = (char *) xmalloc (bufsiz);
	      val = readlink (concat, buf, bufsiz);
	      if (val < bufsiz)
		break;
	      free (buf);
	      bufsiz *= 2;
	    }
	  buf[val] = 0;
	  files[files_index].linkname = buf;
	}
    }
  files_index++;
  return blocks;
}


/* Remove any entries from `files' that are for directories,
   and queue them to be listed as directories instead.  */

extract_dirs_from_files (dirname)
     char *dirname;
{
  register int i, j = 0;
  register char *concat;

  /* Queue the directories last one first,
     because queueing reverses the order.  */
  for (i = files_index - 1; i >= 0; i--)
    if (files[i].filetype == directory)
      {
	if (files[i].name[0] == '/' || dirname[0] == 0)
	  concat = files[i].name;
	else
	  {
	    concat = (char *) alloca (strlen (files[i].name) + strlen (dirname) + 2);
	    strcpy (concat, dirname);
	    strcat (concat, "/");
	    strcat (concat, files[i].name);
	  }
	queue_directory (concat);
	free (files[i].name);
      }

  /* Now delete the directories from the table,
     compacting all the remaining entries.  */

  for (i = 0; i < files_index; i++)
    if (files[i].filetype != directory)
      files[j++] = files[i];
  files_index = j;
}

/* Sort the files now in the table.  */

sort_files ()
{
  if (sort_by_time)
    switch (time_type)
      {
      case time_ctime:
	qsort (files, files_index, sizeof (struct file), compare_ctime);
	break;

      case time_mtime:
	qsort (files, files_index, sizeof (struct file), compare_mtime);
	break;

      case time_atime:
	qsort (files, files_index, sizeof (struct file), compare_atime);
	break;
      }
  else
    qsort (files, files_index, sizeof (struct file), compare_names);

  if (sort_reverse)
    reverse_files ();
}

/* Auxiliary routines for sorting the files */

compare_ctime (file1, file2)
     struct file *file1, *file2;
{
  return file2->stat.st_ctime - file1->stat.st_ctime;
}

compare_mtime (file1, file2)
     struct file *file1, *file2;
{
  return file2->stat.st_mtime - file1->stat.st_mtime;
}

compare_atime (file1, file2)
     struct file *file1, *file2;
{
  return file2->stat.st_atime - file1->stat.st_atime;
}

compare_names (file1, file2)
     struct file *file1, *file2;
{
  return strcmp (file1->name, file2->name);
}

reverse_files ()
{
  register int i, j;
  struct file temp;
  for (i = 0, j = files_index - 1; i < j; i++, j--)
    {
      temp = files[i];
      files[i] = files[j];
      files[j] = temp;
    }
}

/* List all the files now in the table.  */

print_current_files ()
{
  register int i;

  switch (format)
    {
    case one_per_line:
      for (i = 0; i < files_index; i++)
	{
	  print_file_name_and_frills (files + i);
	  putchar ('\n');
	}
      break;

    case many_per_line:
      print_many_per_line ();
      break;

    case horizontal:
      print_horizontal ();
      break;

    case with_commas:
      print_with_commas ();
      break;

    case long_format:
      for (i = 0; i < files_index; i++)
	{
	  print_long_format (files + i);
	  putchar ('\n');
	}
      break;
    }
}

print_long_format (f)
     struct file *f;
{
  char mbuf[20];
  char tbuf[40];
  long time;

  filemodestring (&f->stat, mbuf);
  if (f->filetype == symbolic_link)
    mbuf[0] = 'l';
  mbuf[10] = 0;

  switch (time_type)
    {
    case time_ctime:
      time = f->stat.st_ctime;
      break;
    case time_mtime:
      time = f->stat.st_mtime;
      break;
    case time_atime:
      time = f->stat.st_atime;
    }

  strcpy (tbuf, ctime (&time));
  if (current_time - time > 300 * 24 * 60 * 60)
    {
      /* File is at least 300 days old.
	 Show its year instead of the time of day.  */
      strcpy (tbuf + 11, tbuf + 19);
    }
  tbuf[16] = 0;

  if (print_inode)
    printf ("%5d ", f->stat.st_ino);

  if (print_block_size)
    printf ("%*d ", block_size_size, f->stat.st_blocks);

  printf ("%s%3d %-9s", mbuf, f->stat.st_nlink, getuser (f->stat.st_uid));
  if((f->stat.st_mode & S_IFMT) == S_IFCHR || (f->stat.st_mode & S_IFMT) == S_IFBLK)
    {
      printf("%3d, %3d %s ",major(f->stat.st_rdev),minor(f->stat.st_rdev), tbuf + 4);
    }
  else
    {
      printf("%8d %s ", f->stat.st_size, tbuf + 4);
    }
/*  printf ("%s%3d %-9s%8d %s ", mbuf, f->stat.st_nlink,
	  getuser (f->stat.st_uid), f->stat.st_size,
	  tbuf + 4); */
  print_name_with_quoting (f->name);

  if (f->filetype == symbolic_link)
    {
      printf (" -> ");
      print_name_with_quoting (f->linkname);
    }
}

print_name_with_quoting (p)
     register char *p;
{
  register char c;

  if (quote_as_string)
    putchar ('"');

  while (c = *p++)
    {
      if (quote_funny_chars)
	{
	  switch (c)
	    {
	    case '\\':
	      printf ("\\\\");
	      break;

	    case '\n':
	      printf ("\\n");
	      break;

	    case '\b':
	      printf ("\\b");
	      break;

	    case '\r':
	      printf ("\\r");
	      break;

	    case '\t':
	      printf ("\\t");
	      break;

	    case '\f':
	      printf ("\\f");
	      break;

	    case ' ':
	      printf ("\\ ");
	      break;

	    case '"':
	      printf ("\\\"");
	      break;

	    default:
	      if (c > 040 && c < 0177)
		putchar (c);
	      else
		printf ("\\%03o", c);
	    }
	}
      else
	{
	  if (c >= 040 && c < 0177)
	    putchar (c);
	  else
	    putchar ('?');
	}
    }

  if (quote_as_string)
    putchar ('"');
}

/* Print a file name with appropriate quoting.
   Also print file size, inode number, and filetype indicator character,
   as requested by switches.  */

print_file_name_and_frills (f)
     struct file *f;
{
  if (print_inode)
    printf ("%5d ", f->stat.st_ino);

  if (print_block_size)
    printf ("%*d ", block_size_size, f->stat.st_blocks);

  print_name_with_quoting (f->name);

  if (print_filetype)
    switch (f->filetype)
      {
      case directory:
	putchar ('/');
	break;

      case symbolic_link:
	putchar ('@');
	break;

      case normal:
	if (print_filetype > 0 && (f->stat.st_mode &
		(S_IEXEC|S_IEXEC>>3|S_IEXEC>>6)))
	  putchar ('*');
/*	else		JF because it makes -Fm look funny 
	  putchar (' '); */
      }
}

length_of_file_name_and_frills (f)
     struct file *f;
{
  register char *p = f->name;
  register char c;
  register int len = 0;

  if (print_inode)
    len += 6;

  if (print_block_size)
    len += 1 + block_size_size;

  if (quote_as_string)
    len += 2;

  while (c = *p++)
    {
      if (quote_funny_chars)
	{
	  switch (c)
	    {
	    case '\\':
	    case '\n':
	    case '\b':
	    case '\r':
	    case '\t':
	    case '\f':
	    case ' ':
	      len += 2;
	      break;

	    case '"':
	      if (quote_as_string)
		len += 2;
	      else
		len += 1;
	      break;

	    default:
	      if (c >= 040 && c < 0177)
		len += 1;
	      else
		len += 4;
	    }
	}
      else
	len += 1;
    }

  if (print_filetype) {
    if(f->filetype!=normal)
      len += 1;
    else if (print_filetype > 0 && (f->stat.st_mode & 
           (S_IEXEC|S_IEXEC>>3|S_IEXEC>>6)))
      len+=1;
  }

  return len;
}

print_many_per_line ()
{
  int i;
  int i1;
  int max;
  int tem;
  int pos;
  int perline;
  int nlines;
  int longcols;
  int start_shortcol;

  /* Compute the maximum file name length.  */
  max = 0;
  for (i = 0; i < files_index; i++)
    {
      tem = length_of_file_name_and_frills (files + i);
      if (tem > max) max = tem;
    }

  /* Allow at least two spaces between names.  */
  max += 2;

  perline = line_length / max;
  nlines = (files_index + perline - 1) / perline;
  longcols = files_index % perline;
  if (!longcols) longcols = perline;
  start_shortcol = longcols * nlines;

  for (i1 = 0; i1 < nlines; i1++)
    {
      i = i1;
      pos = 0;
      /* Print the next line of output.  */
      while (1)
	{
	  print_file_name_and_frills (files + i);
	  tem = length_of_file_name_and_frills (files + i);

	  if (i >= start_shortcol)
	    i--;
	  i += nlines;
	  if (i >= files_index)
	    break;
	  if (i1 == nlines - 1 && i >= start_shortcol)
	    break;

	  indent (pos + tem, pos + max);
	  pos += max;
	}
      putchar ('\n');
    }
}

print_horizontal ()
{
  int i;
  int max;
  int tem;
  int perline;
  int pos;

  /* Compute the maximum file name length.  */
  max = 0;
  for (i = 0; i < files_index; i++)
    {
      tem = length_of_file_name_and_frills (files + i);
      if (tem > max) max = tem;
    }

  /* Allow two spaces between names.  */
  max += 2;

  perline = line_length / max;

  pos = 0;
  tem = 0;

  for (i = 0; i < files_index; i++)
    {
      if (i != 0)
	{
	  if (i % perline == 0)
	    {
	      putchar ('\n');
	      pos = 0;
	    }
	  else
	    {
	      indent (pos + tem, pos + max);
	      pos += max;
	    }
	}

      print_file_name_and_frills (files + i);

      tem = length_of_file_name_and_frills (files + i);
    }
  putchar ('\n');
}

print_with_commas ()
{
  int i;
  int col, ocol;

  col = 0;

  for (i = 0; i < files_index; i++)
    {
      ocol = col;

      col += length_of_file_name_and_frills (files + i);
      if (i + 1 < files_index)
	col += 2;			/* For the comma and space */
      
      if (ocol != 0 && col > line_length)
	{
	  putchar ('\n');
	  col -= ocol;
	}

      print_file_name_and_frills (files + i);
      if (i + 1 < files_index)
	{
	  putchar (',');
	  putchar (' ');
	}
    }
  putchar ('\n');
}

/* Assuming cursor is at position FROM, indent up to position TO.  */

indent (from, to)
     int from, to;
{
  while (from < to)
    {
      if ((to / 8) > (from / 8))
	{
	  putchar ('\t');
	  from += 8 - from % 8;
	}
      else
	{
	  putchar (' ');
	  from++;
	}
    }
}

/* Low level subroutines of general use,
   not specifically related to the task of listing a directory.  */

xrealloc (obj, size)
     int obj, size;
{
  int val = realloc (obj, size);

  if (!val)
    fatal ("memory exhausted", 0, 0);

  return val;
}

xmalloc (size)
     int size;
{
  int val = malloc (size);

  if (!val)
    fatal ("memory exhausted", 0, 0);

  return val;
}

fatal (string, arg, arg2)
     char *string;
     int arg, arg2;
{
  error (string, arg, arg2);
  exit (1);
}

error (string, arg, arg2)
     char *string;
     int arg, arg2;
{
  fprintf (stderr, "ls: ");
  fprintf (stderr, string, arg, arg2);
  fprintf (stderr, "\n");
}


perror_with_name (name)
     char *name;
{
  int err = errno;

  if (err <= sys_nerr)
    error ("%s: %s", name, sys_errlist[err]);
  else
    error ("%s: %s", name, "unknown system error");
}

char *
copystring (string)
     char *string;
{
  int len = strlen (string);
  char *new = (char *) xmalloc (len + 1);

  strcpy (new, string);
  new[len] = 0;
  return new;
}
