/* 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 #ifndef USE_MW_HEADERS #include #include #endif #include #include #include /* 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 #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