/* MPW-Unix compatibility library.
   Copyright (C) 1993, 1994, 1995, 1996 Free Software Foundation, Inc.

This file is part of the libiberty library.
Libiberty is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

Libiberty 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with libiberty; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

/* This should only be compiled and linked under MPW. */

#include "mpw.h"

#include <stdlib.h>

#ifndef USE_MW_HEADERS
#include <sys/time.h>
#include <sys/resource.h>
#endif

#include <Types.h>
#include <Files.h>

#include <Timer.h>

/* Initialize to 0 at first, then set to errno_max() later.  */

int sys_nerr = 0;

/* Debug flag for pathname hacking.  Set this to one and rebuild. */

int DebugPI = -1;

void
mpwify_filename(char *unixname, char *macname)
{
  int i, j;

  /* (should truncate 255 chars from end of name, not beginning) */
  if (strlen (unixname) > 255)
    {
      fprintf (stderr, "Pathname \"%s\" is too long for Macs, truncating\n",
	       unixname);
    }
  j = 0;
  /* If you're going to end up with one or more colons in the middle of a
     path after an all-Unix relative path is translated, you must add a
     colon on the front, so that the first component is not thought to be
     a disk name.  */
  if (unixname[0] != '/' && ! strchr (unixname, ':') && strchr (unixname, '/'))
    {
      macname[j++] = ':';
    }
  for (i = 0; unixname[i] != '\0' && i < 255; ++i)
    {
      if (i == 0 && unixname[i] == '/')
	{
	  if (strncmp (unixname, "/tmp/", 5) == 0)
	    {
	      /* A temporary name, make a more Mac-flavored tmpname. */
	      /* A better choice would be {Boot}Trash:foo, but
		 that would require being able to identify the
		 boot disk's and trashcan's name.  Another option
		 would be to have an env var, so user can point it
		 at a ramdisk. */
	      macname[j++] = ':';
	      macname[j++] = 't';
	      macname[j++] = 'm';
	      macname[j++] = 'p';
	      macname[j++] = '_';
	      i += 4;
	    }
	  else
	    {
	      /* Don't copy the leading slash. */
	    }
	}
      else if (unixname[i] == ':' && unixname[i+1] == '/')
	{
	  macname[j++] = ':';
	  i += 1;
	}
      else if (unixname[i] == '.' && unixname[i+1] == '/')
	{
	  macname[j++] = ':';
	  i += 1;
	}
      else if (unixname[i] == '.' && unixname[i+1] == '.' && unixname[i+2] == '/')
	{
	  macname[j++] = ':';
	  macname[j++] = ':';
	  i += 2;
	}
      else if (unixname[i] == '/')
	{
	  macname[j++] = ':';
	}
      else
	{
	  macname[j++] = unixname[i];
	}
    }
  macname[j] = '\0';
  /* Allow for getting the debug flag from an env var; quite useful. */
  if (DebugPI < 0)
    DebugPI = (*(getenv ("DEBUG_PATHNAMES")) == '1' ? 1 : 0);
  if (DebugPI)
    {
      fprintf (stderr, "# Made \"%s\"\n", unixname);
      fprintf (stderr, "# into \"%s\"\n", macname);
    }
}

/* MPW-flavored basename finder. */

char *
mpw_basename (name)
  char *name;
{
  char *base = name;

  while (*name)
    {
      if (*name++ == ':')
	{
	  base = name;
	}
    }
  return base;
}

/* Mixed MPW/Unix basename finder.  This can be led astray by
   filenames with slashes in them and come up with a basename that
   either corresponds to no file or (worse) to some other file, so
   should only be tried if other methods of finding a file via a
   basename have failed.  */

char *
mpw_mixed_basename (name)
  char *name;
{
  char *base = name;

  while (*name)
    {
      if (*name == '/' || *name == ':')
	{
	  base = name + 1;
	}
      ++name;
    }
  return base;
}

/* This function is fopen() modified to create files that are type TEXT
   or 'BIN ', and always of type 'MPS '.  */

FILE *
mpw_fopen (char *name, char *mode)
{
#undef fopen
  int errnum;
  FILE *fp;
  char tmpname[256];

  mpwify_filename (name, tmpname);
  PROGRESS (1);
  fp = fopen (tmpname, mode);
  errnum = errno;

  /* If writing, need to set type and creator usefully. */
  if (strchr (mode, 'w'))
    {
      char *pname = (char *) malloc (strlen (tmpname) + 2);
      OSErr e;
      struct FInfo fi;

      pname[0] = strlen (tmpname);
      strcpy (pname+1, tmpname);
	
      e = GetFInfo ((ConstStr255Param) pname, 0, &fi);
      /* should do spiffier error handling */
      if (e != 0)
	fprintf(stderr, "GetFInfo returns %d\n", e);
      if (strchr (mode, 'b'))
	{
	  fi.fdType = (OSType) 'BIN ';
	}
      else
	{
	  fi.fdType = (OSType) 'TEXT';
	}
      fi.fdCreator = (OSType) 'MPS ';
      e = SetFInfo ((ConstStr255Param) pname, 0, &fi);
      if (e != 0)
	fprintf(stderr, "SetFInfo returns %d\n", e);
      free (pname);
    }
  if (fp == NULL)
    errno = errnum;
  return fp;
}

/* This is a version of fseek() modified to fill the file with zeros
   if seeking past the end of it.  */

#define ZEROBLKSIZE 4096

char zeros[ZEROBLKSIZE];

int
mpw_fseek (FILE *fp, int offset, int whence)
{
#undef fseek
  int cursize, numleft;

  PROGRESS (1);
  if (whence == SEEK_SET)
    {
      fseek (fp, 0, SEEK_END);
      cursize = ftell (fp);
      if (offset > cursize)
	{
	  numleft = offset - cursize;
	  while (numleft > ZEROBLKSIZE)
	    {
	      /* This might fail, should check for that. */
	      PROGRESS (1);
	      fwrite (zeros, 1, ZEROBLKSIZE, fp);
	      numleft -= ZEROBLKSIZE;
	    }
	  PROGRESS (1);
	  fwrite (zeros, 1, numleft, fp);
	  fflush (fp);
	}
    }
  return fseek (fp, offset, whence);
}

int
mpw_fread (char *ptr, int size, int nitems, FILE *stream)
{
#undef fread
  int rslt;

  PROGRESS (1);
  rslt = fread (ptr, size, nitems, stream);
  PROGRESS (1);
  return rslt;
}

int
mpw_fwrite (char *ptr, int size, int nitems, FILE *stream)
{
#undef fwrite
  int rslt;

  PROGRESS (1);
  rslt = fwrite (ptr, size, nitems, stream);
  PROGRESS (1);
  return rslt;
}

int
link ()
{
  fprintf (stderr, "link not available!\n");
  mpw_abort ();
}

int
fork ()
{
  fprintf (stderr, "fork not available!\n");
  mpw_abort ();
}

int
vfork ()
{
  fprintf (stderr, "vfork not available!\n");
  mpw_abort ();
  return (-1);
}

int
pipe (int *fd)
{
  fprintf (stderr, "pipe not available!\n");
  mpw_abort ();
  return (-1);
}

#ifndef USE_MW_HEADERS
int
execvp (char *file, char **argv)
{
  fprintf (stderr, "execvp not available!\n");
  mpw_abort ();
  return (-1);
}

int
execv (char *path, char **argv)
{
  fprintf (stderr, "execv not available!\n");
  mpw_abort ();
  return (-1);
}
#endif

int
kill (int pid, int sig)
{
  fprintf (stderr, "kill not available!\n");
  mpw_abort ();
  return (-1);
}

int
wait (int *status)
{
  *status = 0;
  return 0;
}

#ifndef USE_MW_HEADERS
int
sleep (int seconds)
{
  unsigned long start_time, now;

  time (&start_time);

  while (1)
    {
      PROGRESS (1);
      time (&now);
      if (now > start_time + seconds)
	return 0;
    }
}
#endif

void
putenv (char *str)
{
  /* The GCC driver calls this to do things for collect2, but we
     don't care about collect2. */
}

int
chmod (char *path, int mode)
{
  /* Pretend it was all OK. */
  return 0;
}

#ifndef USE_MW_HEADERS
int
getuid ()
{
  /* One value is as good as another... */
  return 0;
}

int
getgid ()
{
  /* One value is as good as another... */
  return 0;
}
#endif

/* Instead of coredumping, which is not a normal Mac facility, we
   drop into Macsbug.  If we then "g" from Macsbug, the program will
   exit cleanly. */

void
mpw_abort ()
{
  /* Make sure no output still buffered up, then zap into MacsBug. */
  fflush(stdout);
  fflush(stderr);
  printf("## Abort! ##\n");
#ifdef MPW_SADE
  SysError(8005);
#else 
  Debugger();
#endif
  /* "g" in MacsBug will then cause a regular error exit. */
  exit (1);
}

/* Imitation getrusage based on the ANSI clock() function. */

int
getrusage (int who, struct rusage *rusage)
{
  int clk = clock ();

#if 0
  rusage->ru_utime.tv_sec = clk / CLOCKS_PER_SEC;
  rusage->ru_utime.tv_usec = ((clk * 1000) / CLOCKS_PER_SEC) * 1000;
  rusage->ru_stime.tv_sec = 0;
  rusage->ru_stime.tv_usec = 0;
#endif
}

int
sbrk ()
{
  return 0;
}

#ifndef USE_MW_HEADERS
int
isatty (int fd)
{
  return 0;
}

/* This is inherited from Timothy Murray's Posix library. */

#include "utime.h"

int
utime (char *filename, struct utimbuf *times)
{
  CInfoPBRec cipbr;
  HFileInfo *fpb = (HFileInfo *) &cipbr;
  DirInfo *dpb = (DirInfo *) &cipbr;
  unsigned char pname[256];
  short err;
  
  strcpy ((char *) pname, filename);
  c2pstr (pname);

  dpb->ioDrDirID = 0L;
  fpb->ioNamePtr = pname;
  fpb->ioVRefNum = 0;
  fpb->ioFDirIndex = 0;
  fpb->ioFVersNum = 0;
  err = PBGetCatInfo (&cipbr, 0);
  if (err != noErr) {
    errno = ENOENT;
    return -1;
  }
  dpb->ioDrDirID = 0L;
  fpb->ioFlMdDat = times->modtime;
  fpb->ioFlCrDat = times->actime;
  err = PBSetCatInfo (&cipbr, 0);
  if (err != noErr) {
    errno = EACCES;
    return -1;
  }
  return 0;
}

int
mkdir (char *path, int mode)
{
  errno = ENOSYS;
  return -1;
}

int
rmdir ()
{
  errno = ENOSYS;
  return -1;
}
#endif

chown ()
{
  errno = ENOSYS;
  return -1;
}

char *myenviron[] = {NULL};

char **environ = myenviron;

#ifndef USE_MW_HEADERS

/* Minimal 'stat' emulation: tells directories from files and
   gives length and mtime.

   Derived from code written by Guido van Rossum, CWI, Amsterdam
   and placed by him in the public domain.  */

extern int __uid, __gid;

int __uid = 0;
int __gid = 0;

/* Bits in ioFlAttrib: */
#define LOCKBIT	(1<<0)		/* File locked */
#define DIRBIT	(1<<4)		/* It's a directory */

/* Macified "stat" in which filename is given relative to a directory,
   specified by long DirID.  */

static int
_stat (char *name, long dirid, struct stat *buf)
{
  CInfoPBRec cipbr;
  HFileInfo *fpb = (HFileInfo*) &cipbr;
  DirInfo *dpb = (DirInfo*) &cipbr;
  Str255 pname;
  short err;

  /* Make a temp copy of the name and pascalize. */
  strcpy ((char *) pname, name);
  c2pstr (pname);
  
  cipbr.dirInfo.ioDrDirID = dirid;
  cipbr.hFileInfo.ioNamePtr = pname;
  cipbr.hFileInfo.ioVRefNum = 0;
  cipbr.hFileInfo.ioFDirIndex = 0;
  cipbr.hFileInfo.ioFVersNum = 0;
  err = PBGetCatInfo (&cipbr, 0);
  if (err != noErr)
    {
      errno = ENOENT;
      return -1;
    }
  /* Mac files are readable if they can be accessed at all. */
  buf->st_mode = 0444;
  /* Mark unlocked files as writeable. */
  if (!(fpb->ioFlAttrib & LOCKBIT))
    buf->st_mode |= 0222;
  if (fpb->ioFlAttrib & DIRBIT)
    {
      /* Mark directories as "executable". */
      buf->st_mode |= 0111 | S_IFDIR;
      buf->st_size = dpb->ioDrNmFls;
      buf->st_rsize = 0;
    }
  else
    {
      buf->st_mode |= S_IFREG;
      /* Mark apps as "executable". */
      if (fpb->ioFlFndrInfo.fdType == 'APPL')
	buf->st_mode |= 0111;
      /* Fill in the sizes of data and resource forks. */
      buf->st_size = fpb->ioFlLgLen;
      buf->st_rsize = fpb->ioFlRLgLen;
    }
  /* Fill in various times. */
  buf->st_atime = fpb->ioFlCrDat;
  buf->st_mtime = fpb->ioFlMdDat;
  buf->st_ctime = fpb->ioFlCrDat;
  /* Set up an imitation inode number. */
  buf->st_ino = (unsigned short) fpb->ioDirID;
  /* Set up an imitation device. */
  GetVRefNum (buf->st_ino, &buf->st_dev);
  buf->st_uid = __uid;
  buf->st_gid = __gid;
/*  buf->st_FlFndrInfo = fpb->ioFlFndrInfo;  */
  return 0;
}

/* stat() sets up an empty dirid. */

int
stat (char *path, struct stat *buf)
{
  long rslt, errnum;
  char tmpname[256];

  mpwify_filename (path, tmpname);
  if (DebugPI)
    fprintf (stderr, "# stat (%s, %x)", tmpname, buf);
  PROGRESS (1);
  rslt = _stat (tmpname, 0L, buf);
  errnum = errno;
  if (DebugPI)
    {
      fprintf (stderr, " -> %d", rslt);
      if (rslt != 0)
	fprintf (stderr, " (errno is %d)", errnum);
      fprintf (stderr, "\n");
      fflush (stderr);
    }
  if (rslt != 0)
    errno = errnum;
  return rslt;
}

int
fstat (int fd, struct stat *buf)
{
  FCBPBRec fcb;
  FILE *fp;
  Str255 pathname;
  long dirid = 0L, temp;
  long rslt, errnum;
  short err;

  if (DebugPI < 0)
    DebugPI = (*(getenv ("DEBUG_PATHNAMES")) == '1' ? 1 : 0);
  if (DebugPI)
    fprintf (stderr, "# fstat (%d, %x)", fd, buf);
  PROGRESS (1);
  pathname[0] = 0;
#ifdef FIOFNAME
  /* Use an MPW-specific ioctl to get the pathname associated with
     the file descriptor.  */
  ioctl (fd, FIOFNAME, (long *) pathname); 
#else
  you lose
#endif
  if (DebugPI)
    fprintf (stderr, " (name is %s)", pathname);
  dirid = 0L /* fcb.ioFCBParID */ ;
  rslt = _stat ((char *) pathname, dirid, buf);
  errnum = errno;
  if (DebugPI)
    {
      fprintf (stderr, " -> %d", rslt);
      if (rslt != 0)
	fprintf (stderr, " (errno is %d)", errnum);
      fprintf (stderr, "\n");
      fflush (stderr);
    }
  if (rslt != 0)
    errno = errnum;
  return rslt;
}

#endif /* n USE_MW_HEADERS */

chdir ()
{
  errno = ENOSYS;
  return (-1);
}

char *
getcwd (char *buf, int size)
{
  if (buf == NULL)
    buf = (char *) malloc (size);
  strcpy(buf, ":");
  return buf;
}

/* This should probably be more elaborate for MPW. */

char *
getpwd ()
{
  return ":";
}

int
mpw_open (char *filename, int arg2, int arg3)
{
#undef open
  int fd, errnum = 0;
  char tmpname[256];

  mpwify_filename (filename, tmpname);
  fd = open (tmpname, arg2);
  errnum = errno;

  if (DebugPI)
    {
      fprintf (stderr, "# open (%s, %d, %d)", tmpname, arg2, arg3);
      fprintf (stderr, " -> %d", fd);
      if (fd == -1)
	fprintf (stderr, " (errno is %d)", errnum);
      fprintf (stderr, "\n");
    }
  if (fd == -1)
    errno = errnum;
  return fd;
}

int
mpw_access (char *filename, unsigned int cmd)
{
#undef access

  int rslt, errnum = 0;
  struct stat st;
  char tmpname[256];

  mpwify_filename (filename, tmpname);
  if (cmd & R_OK || cmd & X_OK)
    {
      rslt = stat (tmpname, &st);
      errnum = errno;
      if (rslt >= 0)
	{
	  if (((st.st_mode & 004 == 0) && (cmd & R_OK))
	      || ((st.st_mode & 002 == 0) && (cmd & W_OK))
	      || ((st.st_mode & 001 == 0) && (cmd & X_OK)))
	    {
	      rslt = -1;
	      errnum = EACCES;
	    }
	}
    }
  if (DebugPI)
    {
      fprintf (stderr, "# mpw_access (%s, %d)", tmpname, cmd);
      fprintf (stderr, " -> %d", rslt);
      if (rslt != 0)
	fprintf (stderr, " (errno is %d)", errnum);
      fprintf (stderr, "\n");
    }
  if (rslt != 0)
    errno = errnum;
  return rslt;
}

/* The MPW library creat() has no mode argument. */

int
mpw_creat (char *path, /* mode_t */ int mode)
{
#undef creat

#ifdef USE_MW_HEADERS
  return creat (path, mode);
#else
  return creat (path);
#endif
}

/* This is a hack to get control in an MPW tool before it crashes the
   machine.  */

mpw_special_init (name)
     char *name;
{
  if (strstr (name, "DEBUG"))
    DebugStr("\pat beginning of program");
}

static int current_umask;

int
umask(int mask)
{
  int oldmask = current_umask;

  current_umask = mask;
  return oldmask;
}

/* Cursor-spinning stuff that includes metering of spin rate and delays.  */

/* Nonzero when cursor spinning has been set up properly.  */

int cursor_inited;

/* Nonzero if spin should be measured and excessive delays reported.  */

int measure_spin;

/* Nonzero if spin histogram and rate data should be written out.  */

int dump_spin_data;

long warning_threshold = 400000;

long bucket_size = 1024;

long bucket_power = 10;

long numbuckets = 300;

int *delay_counts;

int overflow_count;

char *current_progress;

static UnsignedWide last_microseconds;

static char *last_spin_file = "";

static int last_spin_line;

void
warn_if_spin_delay (char *file, int line)
{
  long diff, ix;
  UnsignedWide now;

  Microseconds(&now);

  diff = now.lo - last_microseconds.lo;

  if (diff > warning_threshold)
    fprintf (stderr, "# %s: %ld.%06ld sec delay getting from %s:%d to %s:%d\n",
	     (current_progress ? current_progress : ""),
	     diff / 1000000, diff % 1000000,
	     last_spin_file, last_spin_line, file, line);
  if (dump_spin_data)
    {
      if (diff >= 0)
	{
	  ix = diff >> bucket_power;
	  if (ix >= 0 && ix < numbuckets && delay_counts != NULL)
	    ++delay_counts[ix];
	  else
	    ++overflow_count;
	}
      else
	fprintf (stderr, "raw diff is %ld (?)\n", diff);
    }
}

void
record_for_spin_delay (char *file, int line)
{
  Microseconds (&last_microseconds);
  last_spin_file = file;
  last_spin_line = line;
}

void
mpw_start_progress (char *str, int n, char *file, int line)
{
  int i;
  char *measure, *threshold;

  if (!cursor_inited)
    {
      InitCursorCtl (nil);
      cursor_inited = 1;
      record_for_spin_delay (file, line);
      measure = getenv ("MEASURE_SPIN");
      if (measure != NULL && measure[0] != '\0')
	{
	  measure_spin = 1;
	  if (strcmp (measure, "all") == 0)
	    dump_spin_data = 1;
	}
      threshold = getenv ("SPIN_WARN_THRESHOLD");
      if (threshold != NULL && threshold[0] != '\0')
	warning_threshold = atol (threshold);
      if (dump_spin_data)
	{
	  if (delay_counts == NULL)
	    delay_counts = (int *) malloc (numbuckets * sizeof (int));
	  for (i = 0; i < numbuckets; ++i)
	    delay_counts[i] = 0;
	  overflow_count = 0;
	}
    }
  current_progress = str;

  sys_nerr = errno_max ();

  mpw_special_init (str);
}

void
mpw_progress (int n)
{
  SpinCursor (32);
}

void
mpw_progress_measured (int n, char *file, int line)
{
  if (measure_spin)
    warn_if_spin_delay (file, line);
  SpinCursor (32);
  if (measure_spin)
    record_for_spin_delay (file, line);
}

void
mpw_end_progress (char *str, char *file, int line)
{
  long i, delay, count = 0, sum = 0, avgdelay, spinrate;
  long curpower = 0, curgroup = 0;

  /* Warn if it's been a while since the last spin.  */
  if (measure_spin)
    warn_if_spin_delay (file, line);

  /* Dump all the nonzero delay counts and an approximation of the delay.  */
  if (dump_spin_data && delay_counts != NULL)
    {
      for (i = 0; i < numbuckets; ++i)
	{
	  delay = (i + 1) * bucket_size;
	  sum += delay_counts[i] * (i + 1);
	  count += delay_counts[i];
	  if (delay <= (1 << curpower))
	    {
	      curgroup += delay_counts[i];
	    }
	  else
	    {
	      if (curgroup > 0)
		fprintf (stderr,
			 "# %s: %d delays between %ld.%06ld and %ld.%06ld sec\n",
			 (str ? str : ""),
			 curgroup,
			 (1 << curpower) / 1000000,
			 (1 << curpower) % 1000000,
			 (1 << (curpower + 1)) / 1000000,
			 (1 << (curpower + 1)) % 1000000);
	      ++curpower;
	      curgroup = 0;
	    }
	}
      if (count > 0)
	{
	  avgdelay = (sum * bucket_size) / count;
	  spinrate = 1000000 / avgdelay;
	  fprintf (stderr, "# %s: Average spin rate is %d times/sec\n",
		   (str ? str : ""), spinrate);
	}
    }
}

#ifdef PROGRESS_TEST

/* Test program.  */

main ()
{
  int i, j;
  double x = 1.0, y = 2.4;
  long start = Microseconds (), tm;  FIXME

  START_PROGRESS ("hi", 0);

  for (i = 0; i < 1000; ++i)
    {
      PROGRESS (1);

      for (j = 0; j < (i * 100); ++j)
	{
	  x += (x * y) / j;
	}
    }
  
  END_PROGRESS ("hi");
  
  tm = Microseconds () - start;

  printf ("Total time is %d.%d secs\n", tm / 1000000, tm % 1000000);
}

#endif

#ifdef USE_MW_HEADERS
/* Empty definitions for Metrowerks' SIOUX console library. */

#ifndef __CONSOLE__
#include <console.h>
#endif

short
InstallConsole(short fd)
{
#pragma unused (fd)
	return 0;
}

void
RemoveConsole(void)
{
}

long
WriteCharsToConsole(char *buf, long n)
{
#pragma unused (buf, n)
	return 0;
}

long ReadCharsFromConsole(char *buf, long n)
{
#pragma unused (buf, n)
	return 0;
}

extern char *
__ttyname(long fd)
{
	static char *__devicename = "null device";

	if (fd >= 0 && fd <= 2)
	  return (__devicename);
	return NULL;
}

#endif
