Unix.st
author claus
Thu, 18 May 1995 17:10:35 +0200
changeset 348 5ac1b6b43600
parent 345 cf2301210c47
child 354 f8cdd814a21c
permissions -rw-r--r--
.

"
 COPYRIGHT (c) 1988 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

Object subclass:#OperatingSystem
       instanceVariableNames:''
       classVariableNames:'HostName LastErrorNumber LastExecStatus OSSignals
			   SlowFork ForkFailed'
       poolDictionaries:''
       category:'System-Support'
!

OperatingSystem comment:'
COPYRIGHT (c) 1988 by Claus Gittinger
	     All Rights Reserved

$Header: /cvs/stx/stx/libbasic/Attic/Unix.st,v 1.35 1995-05-18 15:10:35 claus Exp $
'!

!OperatingSystem primitiveDefinitions!

%{

#ifdef transputer
# define unlink(f)      ((remove(f) == 0) ? 0 : -1)
#else
# include <signal.h>

# ifdef SYSV
#  include <sys/types.h>
#  include <sys/param.h>
#  include <sys/times.h>
#  if ! defined(sco3_2)
#   include <unistd.h>
#  endif

#  if defined(isc3_2) || defined(sco3_2)
#   include <sys/time.h>
#  endif

#  if !defined(isc3_2)
#   if defined(PCS) && defined(mips)
#    include "/usr/include/bsd/sys/time.h"
#    include "/usr/include/sys/time.h"
#   else
#    include <time.h>
#   endif
#  endif

#  if defined(isc3_2)
#   include <sys/bsdtypes.h>
#  endif

# else /* not SYSV */

#  include <sys/time.h>
#  include <sys/types.h>
# endif

# include <pwd.h>
# include <grp.h>

# include <sys/stat.h>
# include <errno.h>

/* 
 * posix systems should define these ... 
 * but on some (older) systems, they are not.
 */
# ifndef S_IXUSR
#  ifdef S_IEXEC
#   define S_IXUSR S_IEXEC
#   define S_IXGRP (S_IEXEC>>3)
#   define S_IXOTH (S_IEXEC>>6)
#  endif
# endif

# ifndef S_IXUSR
#  define S_IXUSR 0100
#  define S_IXGRP 0010
#  define S_IXOTH 0001
# endif

# ifndef S_IRUSR
#  define S_IRUSR 0400
#  define S_IRGRP 0040
#  define S_IROTH 0004
# endif

# ifndef S_IWUSR
#  define S_IWUSR 0200
#  define S_IWGRP 0020
#  define S_IWOTH 0002
# endif

# ifndef MAXPATHLEN
#  include <sys/param.h>
#  ifndef MAXPATHLEN
#   define MAXPATHLEN 1024
#  endif
# endif

# if defined(HAS_UNAME)
#  include <sys/utsname.h>
# endif

#endif

#include <stdio.h>
#include <fcntl.h>

extern int __immediateInterrupt__;

/*
 * on some systems errno is a macro ... check for it here
 */
#ifndef errno
 extern errno;
#endif

/*
 * some (old ?) systems do not define this ...
 */
#ifndef R_OK
# define R_OK    4       /* Test for Read permission */ 
# define W_OK    2       /* Test for Write permission */
# define X_OK    1       /* Test for eXecute permission */
# define F_OK    0       /* Test for existence of File */
#endif
%}
! !

!OperatingSystem class methodsFor:'documentation'!

copyright
"
 COPYRIGHT (c) 1988 by Claus Gittinger
	      All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"
!

version
"
$Header: /cvs/stx/stx/libbasic/Attic/Unix.st,v 1.35 1995-05-18 15:10:35 claus Exp $
"
!

documentation
"
    this class gives access to some operating system services;
    some of it is very specific for unix, so do not depend on
    things available here in your applications - some may not
    be found in other OS's or be slightly dfferent ...
    Currently, there is only this class available - once more than
    Unix is supported, the corresponding class will be bound to the
    global variable OperatingSystem.

    (On the other hand: I do not want to hide all features
     from you - in some situations it MAY be interresting to be
     able to get down to a select or fork system call easily.
     You decide - portability vs. functionality)

    Class variables:

	HostName        <String>        remembered hostname

	LastErrorNumber <Integer>       the last value of errno

	LastExecStatus  <Integer>       the returned exec status after
					the last call of system

	OSSignals       <Array>         Array of signals to be raised for corresponding
					OperatingSystem signals.

	ForkFailed      <Boolean>       set if a fork (or popen) has failed;
					ST/X will avoid doing more forks/popens
					if this flag is set, for a slightly
					smoother operation.

	SlowFork      <Boolean>         if set, fork and popen are avoided;
"
! !

!OperatingSystem class methodsFor:'initialization'!

initialize
    "initialize the class"

    ObjectMemory addDependent:self
!

update:something
    something == #earlyRestart ifTrue:[
	"
	 flush cached data
	"
	HostName := nil.
	LastErrorNumber := nil.
	LastExecStatus := nil
    ]
! !

!OperatingSystem class methodsFor:'misc'!

slowFork:aBoolean
    SlowFork := aBoolean
!

exit
    "shutdown smalltalk immediately - 
     return 'good'-status (0) to parent unix process."

%{  /* NOCONTEXT */
    mainExit(0);
%}
    "OperatingSystem exit - dont evaluate this"
!

exit:exitCode
    "shutdown smalltalk immediately -
     returning an exit-code to the parent unix process."

%{  /* NOCONTEXT */
    if (! __isSmallInteger(exitCode))
	exit(1);
    mainExit(_intVal(exitCode));
%}
    "OperatingSystem exit:1 - dont evaluate this"
! 

exitWithCoreDump
    "shutdown smalltalk immediately - dumping core.
     This always returns 'bad'-status to the parent unix process.
     Use this only for debugging ST/X itself"

%{  /* NOCONTEXT */
    abort();
%}
    "OperatingSystem exitWithCoreDump - dont evaluate this"
! !

!OperatingSystem class methodsFor:'os queries'!

getEnvironment:aStringOrSymbol
    "get an environment string"

%{  /* NOCONTEXT */

    char *env;

    if (__isString(aStringOrSymbol) || __isSymbol(aStringOrSymbol)) {
	env =  (char *)getenv(_stringVal(aStringOrSymbol));
	if (env) {
	    RETURN ( _MKSTRING(env COMMA_CON) );
	}
    }
%}
.
    ^ nil

    "
     OperatingSystem getEnvironment:'LANG'
     OperatingSystem getEnvironment:'LOGIN'
     OperatingSystem getEnvironment:'HOME'
     OperatingSystem getEnvironment:'NNTPSERVER'
     OperatingSystem getEnvironment:'MAIL'
    "
!

getProcessId
    "return the (unix-)processId"

%{  /* NOCONTEXT */

    int pid = 0;
#ifndef transputer
    pid = getpid();
#endif
    RETURN ( _MKSMALLINT(pid) );
%}
    "
     OperatingSystem getProcessId
    "
!

getCPUType
    "return a string giving the type of machine we're running on.
     This may normally not be of any interrest to you ..."

    |cpu|
    
%{  /* NOCONTEXT */

#   ifdef vax
#    define CPU_STRING "vax"
#   endif
#   ifdef mips
#    define CPU_STRING "mips"
#   endif
#   ifdef i386
#    define CPU_STRING "i386"
#   endif
#   ifdef ns32k
#    define CPU_STRING "ns32k"
#   endif
#   ifdef mc68k
#    define CPU_STRING "mc68k"
#   endif
#   ifdef sparc
#    define CPU_STRING "sparc"
#   endif
#   ifdef hppa
#    define CPU_STRING "hppa"
#   endif
#   ifdef rs6000
#    define CPU_STRING "rs6000"
#   endif
#   ifdef alpha
#    define CPU_STRING "alpha"
#   endif
#   ifdef powerPC
#    define CPU_STRING "powerPC"
#   endif
#   ifdef transputer
#    define CPU_STRING "transputer"
#   endif

#   ifndef CPU_STRING
#    define CPU_STRING "unknown"
#   endif

    cpu = _MKSTRING(CPU_STRING COMMA_CON);
#   undef CPU_STRING
%}
.
    ^ cpu

    "
     OperatingSystem getCPUType
    "

    "examples: are we running on a ss-10/solaris ?"
    "
     (OperatingSystem getCPUType = 'sparc') 
     and:[OperatingSystem getOSType = 'solaris']
    "

    "or on a pc/solaris ?"
    "
     (OperatingSystem getCPUType = 'i386')
     and:[OperatingSystem getOSType = 'solaris']
    "
!

getOSType
    "return a string giving the type of OS we're running on.
     This can be used to adapt programs to certain environment
     differences (for example: mail-lock strategy ...)"

    |os|

%{  /* NOCONTEXT */

#   ifdef MSDOS
#    define OS_STRING "msdos"
#   endif

#   ifdef NT
#    define OS_STRING "nt"
#   endif

#   ifdef sinix
#    define OS_STRING "sinix"
#   endif

#   ifdef ultrix
#    define OS_STRING "ultrix"
#   endif

#   ifdef sco
#    define OS_STRING "sco"
#   endif

#   ifdef hpux
#    define OS_STRING "hpux"
#   endif

#   ifdef LINUX
#    define OS_STRING "linux"
#   endif

#   ifdef sunos
#    define OS_STRING "sunos"
#   endif

#   ifdef solaris
#    define OS_STRING "solaris"
#   endif

#   ifdef IRIS
#    define OS_STRING "irix"
#   endif

    /*
     * no concrete info; become somewhat vague ...
     */
#   ifndef OS_STRING
#    ifdef MACH
#     define OS_STRING "mach"
#    endif
#   endif

#   ifndef OS_STRING
#    ifdef BSD
#     define OS_STRING "bsd"
#    endif

#    ifdef SYSV
#     ifdef SYSV3
#      define OS_STRING "sys5.3"
#     else
#      ifdef SYSV4
#       define OS_STRING "sys5.4"
#      else
#       define OS_STRING "sys5"
#      endif
#     endif
#    endif
#   endif

    /*
     * become very vague ...
     */
#   ifndef OS_STRING
#    ifdef POSIX
#     define OS_STRING "posix"
#    endif
#   endif
#   ifndef OS_STRING
#    ifdef UNIX
#     define OS_STRING "unix"
#    endif
#   endif

#   ifndef OS_STRING
#    define OS_STRING "unknown"
#   endif

    os = _MKSTRING(OS_STRING COMMA_CON);
#   undef OS_STRING
%}
.
    ^ os

    "OperatingSystem getOSType"
!

getSystemType
    "return a string giving the type of system we're running on.
     This is almost the same as getOSType, but the returned string
     is slightly different for some systems (i.e. iris vs. irix).
     Dont depend on this - use getOSType. I dont really see a point
     here ... (except for slight differences between next/mach and other
     machs)"

    |sys|

%{
#   ifdef NEXT
#    define SYS_STRING "next"
#   endif

#   ifdef IRIS
#    define SYS_STRING "iris"
#   endif

#   ifdef SYS_STRING
     sys = _MKSTRING(SYS_STRING COMMA_CON);
#    undef SYS_STRING
#   endif
%}
.
    sys isNil ifTrue:[
	^ self getOSType
    ].
    ^ sys

    "
     OperatingSystem getSystemType
    "
!

getHostName
    "return the hostname we are running on - if there is
     a HOST environment variable, we are much faster here ..."

    |name p|

    HostName notNil ifTrue:[
	^ HostName
    ].
    name := self getEnvironment:'HOST'.

%{  /* STACK: 2048 */
#if defined(HAS_GETHOSTNAME)
    char buffer[128];

    if (gethostname(buffer, sizeof(buffer)) == 0) {
	name = _MKSTRING(buffer COMMA_CON);
    }
#else
# if defined(HAS_UNAME)
    struct utsname ubuff;

    if (uname(&ubuff) >= 0) {
	name = _MKSTRING(ubuff.nodename COMMA_CON);
    }
# endif
#endif
%}.
    name isNil ifTrue:[
	"since fork might be slow on some machines, give a warning ..."

	ForkFailed ifFalse:[
	    'please set the HOST shell variable for a faster startup' errorPrintNL.

	    PipeStream openErrorSignal handle:[:ex |
		ForkFailed := true.
		'UNIX: cannot fork/popen' errorPrintNL.
		ex return.
	    ] do:[
		p := PipeStream readingFrom:'hostname'.
		p notNil ifTrue:[
		    name := p nextLine.
		    p close
		]
	    ]
	]
    ].
    name isNil ifTrue:[
	'cannot find out hostname' errorPrintNL.
	name := 'unknown'.
    ].
    HostName := name.
    ^ name

    "
     OperatingSystem getHostName
    "
!

isBSDlike
    "return true, if the OS we're running on is a 'real' unix."

%{  /* NOCONTEXT */

#if defined(BSD) || defined(MACH) || defined(SYSV4)
    RETURN ( true );
#endif
%}
.
    ^ false
!

isUNIXlike
    "return true, if the OS we're running on is a unix like."

%{  /* NOCONTEXT */

#if defined(MSDOS) || defined(NT)
    RETURN ( false );
#endif
%}
.
    ^ true
!

isMSDOSlike
    "return true, if the OS we're running on is a unix like."

%{  /* NOCONTEXT */

#if defined(MSDOS) || defined(NT)
    RETURN ( true );
#endif
%}
.
    ^ false
!

maxFileNameLength
    "return the max number of characters in a filename.
     This is no problem on 'real' unixes, but those 
     'instant ****-stations' always make problems .."

%{  /* NOCONTEXT */
 
    /*
     * XXX: newer systems provide a query function for this ... use it
     */
#   if defined(BSD) || defined(SYSV4) || defined(LONGFILENAMES)
     RETURN ( _MKSMALLINT(255) );
#   endif

#   ifdef realIX
      RETURN ( _MKSMALLINT(127) );
#   endif

#   ifdef SYSV
      RETURN ( _MKSMALLINT(14) );
#   endif

#   ifdef MSDOS
     RETURN ( _MKSMALLINT(9) );
#   endif

#   ifdef NT
     /*
      * mhmh - depends on the filesystem type
      */
     RETURN ( _MKSMALLINT(9) );
#   endif
%}
.
    "unix default"
    ^ 14
!

supportsIOInterrupts
    "return true, if the OS supports IO availability interrupts 
     (i.e. SIGPOLL/SIGIO).

     Currently, this mechanism does not work at all ..."

%{  /* NOCONTEXT */
#ifdef NOTDEF

#   if defined(SIGPOLL) || defined(SIGIO)
#    if defined(F_GETFL) && defined(F_SETFL)
#     if defined(FASYNC)
      /*
       * mhmh they seem to NOT work on NS2.1
       */
#      if !defined(NEXT)
	RETURN (true);
#      endif
#     endif
#    endif
#   endif

#endif
%}
.
    ^ false
!

supportsNonBlockingIO
    "return true, if the OS supports nonblocking IO."

%{  /* NOCONTEXT */
#   if defined(F_GETFL) && defined(F_SETFL)
#    if defined(FNDELAY)
       RETURN (true);
#    endif
#   endif
%}
.
    ^ false
!

supportsSelect
    "return true, if the OS supports selecting on multiple
     filedescriptors via select."

%{  /* NOCONTEXT */
#if defined(sco)
    /*
     * sco has a broken select - always waiting 1 second
     */
    RETURN(false);
#endif
%}
.
    ^ true
! !

!OperatingSystem class methodsFor:'users & groups'!

getLoginName
    "return a string with the users name"

%{  /* NOCONTEXT */

    char *name = "you";
#ifndef transputer
    name = (char *)getlogin();
    if (! name || (name[0] == 0)) {
	name = (char *)getenv("LOGNAME");
    }
#endif
    RETURN ( _MKSTRING(name COMMA_CON) );
%}
    "
     OperatingSystem getLoginName
    "
!

getUserNameFromID:aNumber
    "return the user-name-string for a given numeric user-id"

%{  /* NOCONTEXT */

#ifndef transputer
    struct passwd *p;

    if (__isSmallInteger(aNumber)) {
	p = getpwuid(_intVal(aNumber));
	if (p) {
	    RETURN ( _MKSTRING(p->pw_name COMMA_CON) );
	}
    }
#endif
%}
.
    ^ '? (' , aNumber printString , ')'

    "
     OperatingSystem getUserNameFromID:0
     OperatingSystem getUserNameFromID:100
     OperatingSystem getUserNameFromID:9991 
    "
!

getGroupNameFromID:aNumber
    "return the group-name-string for a given numeric group-id"

%{  /* NOCONTEXT */

#ifndef transputer
    struct group *g;

    if (__isSmallInteger(aNumber)) {
	g = getgrgid(_intVal(aNumber));
	if (g) {
	    RETURN ( _MKSTRING(g->gr_name COMMA_CON) );
	}
    }
#endif
%}
.
    ^ '???'

    "
     OperatingSystem getGroupNameFromID:0
     OperatingSystem getGroupNameFromID:10
    "
!

getHomeDirectory
    "return the name of the users home directory"

    ^ OperatingSystem getEnvironment:'HOME'

    "
     OperatingSystem getHomeDirectory
    "
! !

!OperatingSystem class methodsFor:'error messages'!

currentErrorNumber
    "returns the OS's last error nr (i.e. the value of errno).
     Notice, that the value of this flag is only valid immediately
     after the error occurred - it gets updated with every other
     request to the OS.
     Use lastErrorNumber - currentErrorNumber is invalidated by
     many, many internal calls."

%{  /* NOCONTEXT */

     RETURN ( _MKSMALLINT(errno) );
%}
     "
      OperatingSystem currentErrorNumber
     "
!

lastErrorNumber
    "return the last error number"

    ^ LastErrorNumber

     "
      OperatingSystem lastErrorNumber
     "
!

lastExecStatus
    "return the last execution status"

    ^ LastExecStatus

     "
      OperatingSystem lastExecStatus
     "
!

lastErrorString
    "return a message string describing the last error"

    ^ self errorTextForNumber:LastErrorNumber

    "
     OperatingSystem lastErrorString
    "
!

errorTextForNumber:errNr
    "return a message string from a unix errorNumber 
     (as returned by a system call). 
     Should be replaced by a resource lookup."

%{
    /* claus:
     * I made this some primitive code, since errnos are not
     * standard across unixes
     */
    char *msg = "unknown error";
    char buffer[50];

    if (__isSmallInteger(errNr)) {
	switch (_intVal(errNr)) {
	    /*
	     * POSIX errnos - these should be defined
	     */
	    case EPERM:
		msg = "Operation not permitted";
		break;
	    case ENOENT:
		msg = "No such file or directory";
		break;
	    case ESRCH:
		msg = "No such process";
		break;
	    case EINTR:
		msg = "Interrupted system call";
		break;
	    case EIO:
		msg = "I/O error";
		break;
	    case ENXIO:
		msg = "No such device or address";
		break;
	    case E2BIG:
		msg = "Arg list too long";
		break;
	    case ENOEXEC:
		msg = "Exec format error";
		break;
	    case EBADF:
		msg = "Bad file number";
		break;
	    case ECHILD:
		msg = "No child processes";
		break;
#if !defined(EWOULDBLOCK) && defined(EAGAIN) && (EWOULDBLOCK != EAGAIN)
	    case EAGAIN:
		msg = "Try again";
		break;
#endif
	    case ENOMEM:
		msg = "Out of memory";
		break;
	    case EACCES:
		msg = "Permission denied";
		break;
	    case EFAULT:
		msg = "Bad address";
		break;
	    case EBUSY:
		msg = "Device or resource busy";
		break;
	    case EEXIST:
		msg = "File exists";
		break;
	    case EXDEV:
		msg = "Cross-device link";
		break;
	    case ENODEV:
		msg = "No such device";
		break;
	    case ENOTDIR:
		msg = "Not a directory";
		break;
	    case EISDIR:
		msg = "Is a directory";
		break;
	    case EINVAL:
		msg = "Invalid argument";
		break;
	    case ENFILE:
		msg = "File table overflow";
		break;
	    case EMFILE:
		msg = "Too many open files";
		break;
	    case ENOTTY:
		msg = "Not a typewriter";
		break;
	    case EFBIG:
		msg = "File too large";
		break;
	    case ENOSPC:
		msg = "No space left on device";
		break;
	    case ESPIPE:
		msg = "Illegal seek";
		break;
	    case EROFS:
		msg = "Read-only file system";
		break;
	    case EMLINK:
		msg = "Too many links";
		break;
	    case EPIPE:
		msg = "Broken pipe";
		break;
	    case EDOM:
		msg = "Math argument out of domain";
		break;
	    case ERANGE:
		msg = "Math result not representable";
		break;
#ifdef EDEADLK
# if EDEADLK != EWOULDBLOCK
	    case EDEADLK:
		msg = "Resource deadlock would occur";
		break;
# endif
#endif
#ifdef ENAMETOOLONG
	    case ENAMETOOLONG:
		msg = "File name too long";
		break;
#endif
#ifdef ENOLCK
	    case ENOLCK:
		msg = "No record locks available";
		break;
#endif
#ifdef ENOSYS
	    case ENOSYS:
		msg = "Function not implemented";
		break;
#endif
#ifdef ENOTEMPTY
	    case ENOTEMPTY:
		msg = "Directory not empty";
		break;
#endif
#ifdef EILSEQ
	    case EILSEQ:
		msg = "Illegal byte sequence";
		break;
#endif
	    /*
	     * XPG3 errnos - defined on most systems
	     */
#ifdef ENOTBLK
	    case ENOTBLK:
		msg = "Block device required";
		break;
#endif
#ifdef ETXTBSY
	    case ETXTBSY:
		msg = "Text file busy";
		break;
#endif
	    /*
	     * some others
	     */
#ifdef EWOULDBLOCK
	    case EWOULDBLOCK:
		msg = "Operation would block";
		break;
#endif
#ifdef ENOMSG
	    case ENOMSG:
		msg = "No message of desired type";
		break;
#endif
#ifdef ELOOP
	    case ELOOP:
		msg = "Too many levels of symbolic links";
		break;
#endif

	    /*
	     * some stream errors
	     */
#ifdef ETIME
	    case ETIME:
		msg = "Timer expired";
		break;
#endif
#ifdef ENOSR
	    case ENOSR:
		msg = "Out of streams resources";
		break;
#endif
#ifdef ENOSTR
	    case ENOSTR:
		msg = "Device not a stream";
		break;
#endif
#ifdef ECOMM
	    case ECOMM:
		msg = "Communication error on send";
		break;
#endif
#ifdef EPROTO
	    case EPROTO:
		msg = "Protocol error";
		break;
#endif
	    /*
	     * nfs errors
	     */
#ifdef ESTALE
	    case ESTALE:
		msg = "Stale NFS file handle";
		break;
#endif
#ifdef EREMOTE
	    case EREMOTE:
		msg = "Too many levels of remote in path";
		break;
#endif
	    /*
	     * some networking errors
	     */
#ifdef EINPROGRESS
	    case EINPROGRESS:
		msg = "Operation now in progress";
		break;
#endif
#ifdef EALREADY
	    case EALREADY:
		msg = "Operation already in progress";
		break;
#endif
#ifdef ENOTSOCK
	    case ENOTSOCK:
		msg = "Socket operation on non-socket";
		break;
#endif
#ifdef EDESTADDRREQ
	    case EDESTADDRREQ:
		msg = "Destination address required";
		break;
#endif
#ifdef EMSGSIZE
	    case EMSGSIZE:
		msg = "Message too long";
		break;
#endif
#ifdef EPROTOTYPE
	    case EPROTOTYPE:
		msg = "Protocol wrong type for socket";
		break;
#endif
#ifdef ENOPROTOOPT
	    case ENOPROTOOPT:
		msg = "Protocol not available";
		break;
#endif
#ifdef EPROTONOSUPPORT
	    case EPROTONOSUPPORT:
		msg = "Protocol not supported";
		break;
#endif
#ifdef ESOCKTNOSUPPORT
	    case ESOCKTNOSUPPORT:
		msg = "Socket type not supported";
		break;
#endif
#ifdef EOPNOTSUPP
	    case EOPNOTSUPP:
		msg = "Operation not supported on socket";
		break;
#endif
#ifdef EPFNOSUPPORT
	    case EPFNOSUPPORT:
		msg = "Protocol family not supported";
		break;
#endif
#ifdef EAFNOSUPPORT
	    case EAFNOSUPPORT:
		msg = "Address family not supported by protocol family";
		break;
#endif
#ifdef EADDRINUSE
	    case EADDRINUSE:
		msg = "Address already in use";
		break;
#endif
#ifdef EADDRNOTAVAIL
	    case EADDRNOTAVAIL:
		msg = "Can\'t assign requested address";
		break;
#endif
#ifdef ETIMEDOUT
	    case ETIMEDOUT:
		msg = "Connection timed out";
		break;
#endif
#ifdef ECONNREFUSED
	    case ECONNREFUSED:
		msg = "Connection refused";
		break;
#endif
	    default:
		sprintf(buffer, "ErrorNr: %d", _intVal(errNr));
		msg = buffer;
		break;
	}
    }
    RETURN (_MKSTRING(msg COMMA_CON));
%}
! !

!OperatingSystem class methodsFor:'OS signal constants'!

sigHUP
    "return the signal number for SIGHUP
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
    return _MKSMALLINT(SIGHUP);
%}
!

sigINT
    "return the signal number for SIGINT
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
    return _MKSMALLINT(SIGINT);
%}
!

sigQUIT
    "return the signal number for SIGQUIT
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
    return _MKSMALLINT(SIGQUIT);
%}
!

sigILL
    "return the signal number for SIGILL - 0 if not supported by OS
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGILL
    return _MKSMALLINT(SIGILL);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigTRAP
    "return the signal number for SIGTRAP - 0 if not supported by OS
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGTRAP
    return _MKSMALLINT(SIGTRAP);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigABRT
    "return the signal number for SIGABRT - 0 if not supported by OS
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGABRT
    return _MKSMALLINT(SIGABRT);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigIOT
    "return the signal number for SIGIOT - 0 if not supported by OS
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGIOT
    return _MKSMALLINT(SIGIOT);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigEMT
    "return the signal number for SIGEMT - 0 if not supported by OS
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGEMT
    return _MKSMALLINT(SIGEMT);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigFP
    "return the signal number for SIGFP - 0 if not supported by OS
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGFPE
    return _MKSMALLINT(SIGFPE);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigKILL
    "return the signal number for SIGKILL
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
    return _MKSMALLINT(SIGKILL);
%}
!

sigBUS
    "return the signal number for SIGBUS - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGBUS
    return _MKSMALLINT(SIGBUS);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigSEGV
    "return the signal number for SIGSEGV - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGSEGV
    return _MKSMALLINT(SIGSEGV);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigSYS
    "return the signal number for SIGSYS - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGSYS
    return _MKSMALLINT(SIGSYS);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigPIPE
    "return the signal number for SIGPIPE - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGPIPE
    return _MKSMALLINT(SIGPIPE);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigALRM
    "return the signal number for SIGALRM - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
    return _MKSMALLINT(SIGALRM);
%}
!

sigTERM
    "return the signal number for SIGTERM - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#ifdef SIGTERM
    return _MKSMALLINT(SIGTERM);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigURG
    "return the signal number for SIGURG - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGURG)
    return _MKSMALLINT(SIGURG);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigSTOP
    "return the signal number for SIGSTOP - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGSTOP)
    return _MKSMALLINT(SIGSTOP);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigTSTP
    "return the signal number for SIGTSTP - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGTSTP)
    return _MKSMALLINT(SIGTSTP);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigCONT
    "return the signal number for SIGCONT - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGCONT)
    return _MKSMALLINT(SIGCONT);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigCHLD
    "return the signal number for SIGCHLD - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGCHLD)
    return _MKSMALLINT(SIGCHLD);
#else
# if  defined(SIGCLD)
    return _MKSMALLINT(SIGCLD);
# else
    return _MKSMALLINT(0);
# endif
#endif
%}
!

sigTTIN
    "return the signal number for SIGTTIN - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGTTIN)
    return _MKSMALLINT(SIGTTIN);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigTTOU
    "return the signal number for SIGTTOU - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGTTOU)
    return _MKSMALLINT(SIGTTOU);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigIO
    "return the signal number for SIGIO - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGIO)
    return _MKSMALLINT(SIGIO);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigPOLL
    "return the signal number for SIGPOLL - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGPOLL)
    return _MKSMALLINT(SIGPOLL);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigXCPU
    "return the signal number for SIGXCPU - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGXCPU)
    return _MKSMALLINT(SIGXCPU);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigXFSZ
    "return the signal number for SIGXFSZ - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGXFSZ)
    return _MKSMALLINT(SIGXFSZ);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigVTALRM
    "return the signal number for SIGVTALRM - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGVTALRM)
    return _MKSMALLINT(SIGVTALRM);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigPROF
    "return the signal number for SIGPROF - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGPROF)
    return _MKSMALLINT(SIGPROF);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigWINCH
    "return the signal number for SIGWINCH - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGWINCH)
    return _MKSMALLINT(SIGWINCH);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigLOST
    "return the signal number for SIGLOST - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGLOST)
    return _MKSMALLINT(SIGLOST);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigUSR1
    "return the signal number for SIGUSR1 - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGUSR1)
    return _MKSMALLINT(SIGUSR1);
#else
    return _MKSMALLINT(0);
#endif
%}
!

sigUSR2
    "return the signal number for SIGUSR2 - 0 if not supported
     (the numeric value is not the same across unix-systems)"

%{  /* NOCONTEXT */
#if defined(SIGUSR2)
    return _MKSMALLINT(SIGUSR2);
#else
    return _MKSMALLINT(0);
#endif
%}
! !

!OperatingSystem class methodsFor:'interrupts & signals'!

nameForSignal:aSignalNumber
    "for a given Unix signalnumber, return a descriptive string"

    aSignalNumber == self sigHUP    ifTrue:[^ 'hangup'].
    aSignalNumber == self sigINT    ifTrue:[^ 'interrupt'].
    aSignalNumber == self sigKILL   ifTrue:[^ 'kill'].
    aSignalNumber == self sigQUIT   ifTrue:[^ 'quit'].
    aSignalNumber == self sigILL    ifTrue:[^ 'illegal instruction'].
    aSignalNumber == self sigTRAP   ifTrue:[^ 'trap'].
    aSignalNumber == self sigABRT   ifTrue:[^ 'abort'].
    aSignalNumber == self sigIOT    ifTrue:[^ 'iot trap'].
    aSignalNumber == self sigEMT    ifTrue:[^ 'emt trap'].
    aSignalNumber == self sigFP     ifTrue:[^ 'fp exception'].
    aSignalNumber == self sigBUS    ifTrue:[^ 'bus error'].
    aSignalNumber == self sigSEGV   ifTrue:[^ 'segmentation violation'].
    aSignalNumber == self sigSYS    ifTrue:[^ 'bad system call'].
    aSignalNumber == self sigPIPE   ifTrue:[^ 'broken pipe'].
    aSignalNumber == self sigALRM   ifTrue:[^ 'alarm timer'].
    aSignalNumber == self sigTERM   ifTrue:[^ 'termination'].
    aSignalNumber == self sigSTOP   ifTrue:[^ 'stop'].
    aSignalNumber == self sigTSTP   ifTrue:[^ 'tty stop'].
    aSignalNumber == self sigCONT   ifTrue:[^ 'continue'].
    aSignalNumber == self sigCHLD   ifTrue:[^ 'child death'].
    aSignalNumber == self sigTTIN   ifTrue:[^ 'background tty input'].
    aSignalNumber == self sigTTOU   ifTrue:[^ 'background tty output'].
    aSignalNumber == self sigIO     ifTrue:[^ 'io available'].
    aSignalNumber == self sigXCPU   ifTrue:[^ 'cpu time expired'].
    aSignalNumber == self sigXFSZ   ifTrue:[^ 'file size limit'].
    aSignalNumber == self sigVTALRM ifTrue:[^ 'virtual alarm timer'].
    aSignalNumber == self sigPROF   ifTrue:[^ 'profiling timer'].
    aSignalNumber == self sigWINCH  ifTrue:[^ 'winsize changed'].
    aSignalNumber == self sigLOST   ifTrue:[^ 'resource lost'].
    aSignalNumber == self sigUSR1   ifTrue:[^ 'user signal 1'].
    aSignalNumber == self sigUSR2   ifTrue:[^ 'user signal 2'].
    "notice: many systems map SIGPOLL and/or SIGUSR onto SIGIO
	     therefor, keep SIGIO always above the two below" 
    aSignalNumber == self sigPOLL   ifTrue:[^ 'io available'].
    aSignalNumber == self sigURG    ifTrue:[^ 'urgent'].

    ^ 'unknown signal'

    "
     OperatingSystem nameForSignal:9
     OperatingSystem nameForSignal:(OperatingSystem sigPOLL) 
    "
!

interruptPending
    "return true, if an interrupt is pending. The returned value is
     invalid if interrupts are not currently blocked, since otherwise 
     the interrupt is usually already handled before arriving here,
     or may be served while returning from here."

%{   /* NOCONTEXT */
     extern OBJ __INTERRUPTPENDING();

     RETURN ( __INTERRUPTPENDING() );
%}        
!

blockInterrupts
    "disable interrupt processing - if disabled, incoming
     interrupts will be registered and handled as soon as
     interrupts are reenabled by OperatingSystemclass>>unblockInterrupts.
     Returns the previous blocking status i.e. true if interrupts
     where already blocked. You need this information for proper
     unblocking, in case of nested block/unblock calls."

%{  /* NOCONTEXT */
    extern OBJ BLOCKINTERRUPTS();

    return ( BLOCKINTERRUPTS() );
%}
!

unblockInterrupts
    "enable interrupt processing - if any interrupts are pending,
     these will be handled immediately.
     When unblocking interrupts, take care of nested block/unblock
     calls - you must only unblock after a blockcall if they where
     really not blocked before. See OperatingSystemclass>>blockInterrupts."
%{
    extern void UNBLOCKINTERRUPTS();

    UNBLOCKINTERRUPTS(SENDER);
%}
!

disableSignal:signalNumber
    "disable (Unix-) signal processing for signalNumber.
     Dont confuse Unix signals with smalltalk signals.
     WARNING: for some signals, it is no good idea to disable
     them; for example, disabling the SIGINT signal turns off ^C
     handling.
     Also, NOTICE that signal numbers are not portable between unix
     systems - use OperatingSystem sigXXX to get the numeric value for
     a signal.
     Use only for fully debugged stand alone applications."

%{  /* NOCONTEXT */

    if (__isSmallInteger(signalNumber)) {
	signal(_intVal(signalNumber), SIG_IGN);
	RETURN (self);
    }
%}.
    "
     this error is triggered on non-integer argument
    "
    self primitiveFailed

    "
     'now, ^C is totally ignored ...' printNewline.
     OperatingSystem disableSignal:(OperatingSystem sigINT).
     1 to:1000000 do:[:i| ].
     OperatingSystem enableSignal:(OperatingSystem sigINT).
     '^C handled again.' printNewline
    "
!

defaultSignal:signalNumber
    "revert to the default action on arrival of a (Unix-)signal.
     Dont confuse Unix signals with smalltalk signals.
     WARNING: for some signals, it is no good idea to revert to default;
     for example, the default for SIGINT (i.e. ^C) is to exit; while the
     default for SIGQUIT (^ \) is to dump core.
     Also, NOTICE that signal numbers are not portable between unix
     systems - use OperatingSystem sigXXX to get the numeric value for
     a signal."

%{  /* NOCONTEXT */

    if (__isSmallInteger(signalNumber)) {
	signal(_intVal(signalNumber), SIG_DFL);
	RETURN (self);
    }
%}.
    "
     this error is triggered on non-integer argument
    "
    self primitiveFailed

    "you better save a snapshot image before trying this ..."
    "
     'if you hit ^C now, Smalltalk will exit immediately' printNewline.
     OperatingSystem defaultSignal:(OperatingSystem sigINT).
     1 to:1000000 do:[:i| ].
     OperatingSystem enableSignal:(OperatingSystem sigINT).
     'normal ^C handling again.' printNewline
    "
!

enableSignal:signalNumber
    "enable (Unix-)signal processing for signalNumber.
     Dont confuse Unix signals with smalltalk signals.
     The signal will be delivered to one of the standard handlers
     (SIGINT, SIGQUIT, etc) or to a general handler, which
     sends #signalInterrupt:.

     NOTICE that signal numbers are not portable between unix
     systems - use OperatingSystem sigXXX to get the numeric value for
     a signal."

%{  /* NOCONTEXT */

#ifdef NSIG
# define SIG_LIMIT NSIG
#else
# ifdef SIGUSR2
#  define SIG_LIMIT SIGUSR2
# else
#  ifdef SIGUSR
#   define SIG_LIMIT SIGUSR
#  endif
# endif
#endif

#if defined(SIGPOLL) && !defined(SIGIO)
# define SIGIO SIGPOLL
#endif

#ifdef SIGCHLD
# define CHILD_SIGNAL   SIGCHLD
#else
# ifdef SIGCLD
#  define CHILD_SIGNAL  SIGCLD
# endif
#endif

    int sigNr;
    extern void __signalUserInterrupt();
    extern void __signalFpExceptionInterrupt();
    extern void __signalIoInterrupt();
#ifdef CHILD_SIGNAL
    extern void __signalChildInterrupt();
#endif
    extern void __signalPIPEInterrupt();
    extern void __signalBUSInterrupt();
    extern void __signalSEGVInterrupt();
    extern void __signalTimerInterrupt();
    extern void __signalInterrupt();
    void (*handler)();
#ifdef HAS_SIGACTION
    struct sigaction act, oldAct;
#endif

    if (__isSmallInteger(signalNumber)
     && ((sigNr = _intVal(signalNumber)) > 0)
     &&  (sigNr <= SIG_LIMIT)) {
	/*
	 * standard signals are forced into standard handlers
	 * - all others go into general signalInterrupt
	 */
#if defined(SIGPOLL) && defined(SIGIO)
	if (sigNr == SIGPOLL)
	    sigNr = SIGIO;
#endif
	switch (sigNr) {
	    case SIGINT:
	    case SIGQUIT:
		handler = __signalUserInterrupt;
		break;

	    case SIGFPE:
		handler = __signalFpExceptionInterrupt;
		break;

	    case SIGPIPE:
		handler = __signalPIPEInterrupt;
		break;
#ifdef SIGBUS
	    case SIGBUS:
		handler = __signalBUSInterrupt;
		break;
#endif
	    case SIGSEGV:
		handler = __signalSEGVInterrupt;
		break;
#ifdef SIGIO
	    case SIGIO:
		handler = __signalIoInterrupt;
		break;
#endif

#ifdef CHILD_SIGNAL
	    case CHILD_SIGNAL:
		handler = __signalChildInterrupt;
		break;
#endif
	    case SIGALRM:
		handler = __signalTimerInterrupt;
		break;

	    default:
		handler = __signalInterrupt;
		break;
	}
#ifdef HAS_SIGACTION
	act.sa_flags = SA_RESTART;
	act.sa_mask = 0;
	act.sa_handler = function;
	sigaction(sigNr, &act, &oldAct);
#else
	signal(sigNr, handler);
#endif

	/*
	 * maybe, we should return the old enable-status
	 * as boolean here ...
	 */
	RETURN (self);
    }
%}.

    "
     this error is triggered on non-integer argument, or
     if the signal number is not in the valid range (1..NSIG)
    "
    self primitiveFailed
!

enableUserInterrupts
    "enable userInterrupt (^C) handling;
     when enabled, ^C in the terminal window will send the message
     'userInterrupt' to the UserInterruptHandler object."

    ^ self enableSignal:(self sigINT)
!

enableQuitInterrupts
    "enable quitInterrupt (usually ^\) handling, and make it a userinterrupt.
     (the default will dump core and exit - which is not a good idea for
      end-user applications ...)"

    ^ self enableSignal:(self sigQUIT)
!

disableUserInterrupts
    "disable userInterrupt processing;
     when disabled, no ^C processing takes place.
     Use this only for debugged stand-alone applications, since
     no exit to the debugger is possible with user interrupts disabled.
     Be WARNED."

    ^ self disableSignal:(self sigINT)
!

enableFpExceptionInterrupts
    "enable floating point exception interrupts (if the
     architecture supports it).
     after enabling, fpu-exceptions will send the message 
     'fpuExceptionInterrupt' to the FPUExceptionInterruptHandler object."

    ^ self enableSignal:(self sigFP)
!

enableHardSignalInterrupts
    "enable hard signal exception interrupts (trap, buserror & segm. violation).
     after enabling, these exceptions will send the message 
     'signalInterrupt' to the SignalInterruptHandler object."

%{  /* NOCONTEXT */

    extern void __signalPIPEInterrupt();
#ifdef SIGBUS
    extern void __signalBUSInterrupt();
#endif
    extern void __signalSEGVInterrupt();

    signal(SIGPIPE, __signalPIPEInterrupt);
#ifdef SIGBUS
    signal(SIGBUS,  __signalBUSInterrupt);
#endif
    signal(SIGSEGV, __signalSEGVInterrupt);
%}
!

enableIOInterruptsOn:fd
    "turn on IO interrupts for a filedescriptor"

%{  /* NOCONTEXT */

    int ret, flags, f;
    extern void __signalIoInterrupt();
    static int firstCall = 1;

#if defined(F_GETFL) && defined(F_SETFL)
# if defined(FASYNC)
    if (__isSmallInteger(fd)) {
	if (firstCall) {
	    firstCall = 0;
#  ifdef SIGIO
	    signal(SIGIO, __signalIoInterrupt);
#  endif
#  ifdef SIGPOLL
	    signal(SIGPOLL, __signalIoInterrupt);
#  endif
#  ifdef SIGURG
	    signal(SIGURG, __signalIoInterrupt);
#  endif
	}

	f = _intVal(fd);
	flags = fcntl(f, F_GETFL, 0);
	/*
	 * if already set, there is no need for this syscall ...
	 */
	if (flags & FASYNC) {
	    ret = flags;
	} else {
	    ret = fcntl(f, F_SETFL, flags | FASYNC);
	    if (ret >= 0) ret = flags;
	}
	RETURN ( _MKSMALLINT(ret) );
    }
# endif
#endif
%}.
    "
     this error is triggered on non-integer argument
     or if the system does not support SIGIO
    "
    self primitiveFailed
!

disableIOInterruptsOn:fd
    "turn off IO interrupts for a filedescriptor"

%{  /* NOCONTEXT */

    int ret, flags, f;

#if defined(F_GETFL) && defined(F_SETFL)
# if defined(FASYNC)
    if (__isSmallInteger(fd)) {
	f = _intVal(fd);
	flags = fcntl(f, F_GETFL, 0);
	/*
	 * if already clear, there is no need for this syscall ...
	 */
	if (flags & FASYNC) {
	    ret = fcntl(f, F_SETFL, flags & ~FASYNC);
	    if (ret >= 0) ret = flags;
	} else {
	    ret = flags;
	}
	RETURN ( _MKSMALLINT(ret) );
    }
# endif
#endif
%}.
    "
     this error is triggered on non-integer argument
     or if the OS does not support IO interrupts.
    "
    self primitiveFailed
!

enableChildSignalInterrupts
    "enable childSignal interrupts 
     (SIGCHLD, if the architecture supports it).
     after enabling, these signals will send the message 
     'childSignalInterrupt' to the ChildSignalInterruptHandler object."

    ^ self enableSignal:(self sigCHLD)
!

startSpyTimer
    "trigger a spyInterrupt, to be signalled after some short (virtual) time.
     This is used by MessageTally for profiling.
     Should be changed to use real profiling timer if available.
     On systems, where no virtual timer is available, use the real timer
     (which is of course less correct)"

%{  /* NOCONTEXT */

    extern void __spyInterrupt();
#if defined(ITIMER_VIRTUAL)
    struct itimerval dt;

# ifndef xxxSYSV4
#  if defined(BSD) || defined(HAS_SIGSETMASK)
    sigsetmask(0);
#  endif
# endif

# ifdef SIGVTALRM
    signal(SIGVTALRM, __spyInterrupt);
# else
    signal(SIGALRM, __spyInterrupt);
# endif

    dt.it_interval.tv_sec = 0;
    dt.it_interval.tv_usec = 0;
    dt.it_value.tv_sec = 0;
    dt.it_value.tv_usec = 1000;   /* 1000 Hz */
    setitimer(ITIMER_VIRTUAL, &dt, 0);

    RETURN (true);
#endif
%}
.
    ^ false
!

stopSpyTimer
    "stop spy timing - disable spy timer"

%{  /* NOCONTEXT */

#if defined(ITIMER_VIRTUAL)
    struct itimerval dt;

    dt.it_interval.tv_sec = 0;
    dt.it_interval.tv_usec = 0;
    dt.it_value.tv_sec = 0;
    dt.it_value.tv_usec = 0;
    setitimer(ITIMER_VIRTUAL, &dt, 0);
    RETURN (true);
#endif
%}
.
    ^ false
!

enableTimer:millis
    "trigger a timerInterrupt, to be signalled after some (real) time."

%{  /* NOCONTEXT */

    extern void __signalTimerInterrupt();

#if defined(ITIMER_REAL)
    struct itimerval dt;

# ifndef xxxSYSV4
#  if defined(BSD) || defined(HAS_SIGSETMASK)
    sigsetmask(0);
#  endif
# endif
    signal(SIGALRM, __signalTimerInterrupt);

    dt.it_interval.tv_sec = 0;
    dt.it_interval.tv_usec = 0;
    dt.it_value.tv_sec = _intVal(millis) / 1000;
    dt.it_value.tv_usec = (_intVal(millis) % 1000) * 1000;  
    setitimer(ITIMER_REAL, &dt, 0);
    RETURN (true);
#endif
%}
.
    ^ false
!

disableTimer
    "disable timer interrupt"

%{  /* NOCONTEXT */

#if defined(ITIMER_REAL)
    struct itimerval dt;

    dt.it_interval.tv_sec = 0;
    dt.it_interval.tv_usec = 0;
    dt.it_value.tv_sec = 0;
    dt.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, &dt, 0);
    RETURN (true);
#endif
%}
.
    ^ false
!

operatingSystemSignal:signalNumber
    "return the signal to be raised when an 
     operatingSystem-signal occurs, or nil"

    OSSignals notNil ifTrue:[
	^ OSSignals at:signalNumber ifAbsent:[nil]
    ].
    ^ nil
!

operatingSystemSignal:signalNumber install:aSignal
    "install a signal to be raised when an operatingSystem-signal occurs"

    OSSignals isNil ifTrue:[
	OSSignals := Array new:32
    ].
    OSSignals at:signalNumber put:aSignal
! !

!OperatingSystem class methodsFor:'time and date'!

getTimeLow
    "return low 16 bits of current time. 

     OBSOLETE: Dont use this method, use getTimeParts.
     This method will not always return the correct time 
     if used together with getTimeHi, since a wrap between 
     the two getTimeXXX sends could occur.
     WARNING: this method will vanish - dont use it."

%{  /* NOCONTEXT */

    RETURN ( _MKSMALLINT(time(0) & 0xFFFF) );
%}

    "OperatingSystem getTimeLow"
!

getTimeHi
    "return hi 16 bits of current time. 

     OBSOLETE: Dont use this method, use getTimeParts.
     This method will NOT always return the correct time
     if used together with getTimeHi, since a wrap between 
     the two getTimeXXX sends could occur.
     WARNING: this method will vanish - dont use it."

%{  /* NOCONTEXT */

    RETURN ( _MKSMALLINT((time(0) >> 16) & 0xFFFF) );
%}

    "OperatingSystem getTimeHi"
!

getTimeParts
    "return the current time as an array of 2 integers representing
     the current time (usually low 16bits / hi 16 bits) in some OS
     dependent way.

     On unix, the values represent the seconds since 1970, on other
     systems this may be different.
     WARNING:
       for portability, never use these values directly, but pass them
       to #computeTimeParts and/or #computeDatePartsOf:, which know how to 
       interpret them.
       Better yet, use Date>>today and Time>>now for ST-80 compatibility."

    |low hi|
%{
    int now;

    now = time(0);
    hi  = _MKSMALLINT((now >> 16) & 0xFFFF);
    low = _MKSMALLINT(now & 0xFFFF);
%}
.
    ^ Array with:low with:hi

    "OperatingSystem getTimeParts"
!

getTimeInto:aBlock
    "evaluate the argument aBlock, passing the time-parts of
     the current time as arguments.
     See comment in 'OperatingSystem>>getTimeParts' on what to
     do with those parts."

    |low hi|
%{ 
    int now;

    now = time(0);
    hi  = _MKSMALLINT((now >> 16) & 0xFFFF);
    low = _MKSMALLINT(now & 0xFFFF);
%}
.
    aBlock value:low value:hi

    "OperatingSystem getTimeInto:[:low :hi | low printNewline. hi printNewline]"
!

getTime
    "return current Time in some OS dependent representation.
     (on unix, its the seconds since 1970).

     WARNING: this is OperatingSystem dependent - for portable code,
     use getTimeParts and compute*PartsOf:and:for:.
     Better yet, use Date>>today or Time>>now for fully ST-80 compatible code."

    OperatingSystem getTimeInto:[:low :hi | ^ hi*16r10000 + low]

    "
     OperatingSystem getTime
    "
!

computeDatePartsOf:timeParts for:aBlock
    "compute year, month and day from the ostime in timeParts,
     and evaluate the argument, a 3-arg block with these.
     Conversion is to localtime including any daylight saving adjustments."

     ^ self computeDatePartsOf:(timeParts at:1) and:(timeParts at:2) for:aBlock
!

computeTimePartsOf:timeParts for:aBlock
    "compute hour, minute and seconds from the ostime in timeParts,
     and evaluate the argument, a 3-arg block with these.
     Conversion is to localtime including any daylight saving adjustments."

     ^ self computeTimePartsOf:(timeParts at:1) and:(timeParts at:2) for:aBlock
!

computeDatePartsOf:timeLow and:timeHi for:aBlock
    "compute year, month and day from the time-parts timeLow and
     timeHi and evaluate the argument, a 3-arg block with these.
     Conversion is to localtime including any daylight saving adjustments.

     This method was added to avoid LargeInteger arithmetic and to be
     independent of how the OperatingSystem represents time;
     the time-parts expected are those returned by getTimeParts."

    |year month day|

    ((timeLow isMemberOf:SmallInteger) and:[timeHi isMemberOf:SmallInteger])
    ifFalse:[
	^ self primitiveFailed
    ].
%{
    struct tm *tmPtr;
    long t;

    t = (_intVal(timeHi) << 16) | _intVal(timeLow);
    tmPtr = localtime(&t);
    year = _MKSMALLINT(tmPtr->tm_year + 1900);
    month = _MKSMALLINT(tmPtr->tm_mon + 1);
    day = _MKSMALLINT(tmPtr->tm_mday);
%}
.
    aBlock value:year value:month value:day
!

computeTimePartsOf:timeLow and:timeHi for:aBlock
    "compute hours, minutes and seconds from the time-parts timeLow and
     timeHi and evaluate the argument, a 3-arg block with these.
     Conversion is to localtime including any daylight saving adjustments.

     This method was added to avoid LargeInteger arithmetic and to be
     independent of how the OperatingSystem represents time;
     the time-parts expected are those returned by getTimeParts."

    |hours minutes seconds|

    ((timeLow isMemberOf:SmallInteger) and:[timeHi isMemberOf:SmallInteger])
    ifFalse:[
	^ self primitiveFailed
    ].
%{
    struct tm *tmPtr;
    long t;

    t = (_intVal(timeHi) << 16) | _intVal(timeLow);
    tmPtr = localtime(&t);
    hours = _MKSMALLINT(tmPtr->tm_hour);
    minutes = _MKSMALLINT(tmPtr->tm_min);
    seconds = _MKSMALLINT(tmPtr->tm_sec);
%}
.
    aBlock value:hours value:minutes value:seconds
!

computeTimeAndDateFrom:timeParts
    "given an Array containing the OS-dependent time, return an Array
     containing year, month, day, hour, minute and seconds.
     Conversion is to localtime including any daylight saving adjustments."

    |low hi year month day hours minutes seconds ret|

    low := timeParts at:1.
    hi := timeParts at:2.
%{
    struct tm *tmPtr;
    long t;

    if (__bothSmallInteger(low, hi)) {
	t = (_intVal(hi) << 16) | _intVal(low);
	tmPtr = localtime(&t);
	hours = _MKSMALLINT(tmPtr->tm_hour);
	minutes = _MKSMALLINT(tmPtr->tm_min);
	seconds = _MKSMALLINT(tmPtr->tm_sec);

	year = _MKSMALLINT(tmPtr->tm_year + 1900);
	month = _MKSMALLINT(tmPtr->tm_mon + 1);
	day = _MKSMALLINT(tmPtr->tm_mday);
    }
%}.
    year notNil ifTrue:[
	"I would love to have SELF-like inline objects ..."
	ret := Array new:6.
	ret at:1 put:year.
	ret at:2 put:month.
	ret at:3 put:day.
	ret at:4 put:hours.
	ret at:5 put:minutes.
	ret at:6 put:seconds.
	^ ret
    ].
    ^ self primitiveFailed
!

computeTimePartsFromYear:y month:m day:d hour:h minute:min seconds:s
    "return an Array containing the OS-dependent time for the given
     time and day. The arguments are assumed to be in localtime including
     any daylight saving adjustings."

    |low hi|

%{
    struct tm tm;
    long t;

    if (__bothSmallInteger(y, m) 
     && __bothSmallInteger(d, h)
     && __bothSmallInteger(min, s)) {
	tm.tm_hour = __intVal(h);
	tm.tm_min = __intVal(min);
	tm.tm_sec = __intVal(s);

	tm.tm_year = __intVal(y) - 1900;
	tm.tm_mon = __intVal(m) - 1;
	tm.tm_mday = __intVal(d);
	tm.tm_isdst = -1;

	t = mktime(&tm);
	low = _MKSMALLINT(t & 0xFFFF);
	hi = _MKSMALLINT((t >> 16) & 0xFFFF);
    }
%}.
    low notNil ifTrue:[
	^ Array with:low with:hi
    ].    
    ^ self primitiveFailed
!

maximumMillisecondTimeDelta
    "this returns the maximum delta supported by millisecondCounter
     based methods. The returned value is half the value at which the
     timer wraps."

%{  /* NOCONTEXT */
    RETURN ( _MKSMALLINT(0x0FFFFFFF) );
%}
!

getMillisecondTime
    "This returns the millisecond timers value. 
     The range is limited to 0..1fffffff (i.e. the SmallInteger range) to avoid
     LargeInteger arithmetic when doing timeouts and delays.
     SInce this value is wrapping around in regular intervals, this can only be used for 
     short relative time deltas.
     Use the millisecondTimeXXX:-methods to compare and add time deltas - these know about the wrap.

     BAD DESIGN:
	This should be changed to return some instance of RelativeTime,
	and these computations moved there.

     Dont use this method in application code since it is an internal (private)
     interface. For compatibility with ST-80, use Time millisecondClockValue.
    "

%{  /* NOCONTEXT */

    long t;
#if !defined(HAS_GETTIMEOFDAY) && defined(SYSV) && defined(HZ)
    /* 
     * sys5 time
     */
    long ticks;
    struct tms tb;

    ticks = times(&tb);
    t = (ticks * 1000) / HZ;
#else /* assume HAS_GETTIMEOFDAY */
    /*
     * bsd time
     */
    struct timeval tb;
    struct timezone tzb;

    gettimeofday(&tb, &tzb);
    t = tb.tv_sec*1000 + tb.tv_usec/1000;
#endif
    RETURN ( _MKSMALLINT(t & 0x1FFFFFFF) );
%}
!

millisecondTimeDeltaBetween:msTime1 and:msTime2
    "subtract two millisecond times (such as returned getMillisecondTime)
     and return the difference. Since milli-times wrap (at 16r01FFFFFFF), 
     some special handling is built-in here.
     The returned value is msTime1 - msTime2. The returned value is invalid
     if the delta is >= 0x10000000.

     This should really be moved to some RelativeTime class."

    (msTime1 > msTime2) ifTrue:[
	^ msTime1 - msTime2
    ].
    ^ msTime1 + 16r10000000 - msTime2

    "
     OperatingSystem millisecondTimeAdd:16r0FFFFFFF and:1   
     OperatingSystem millisecondTimeAdd:16r0FFFFFFF and:(16 / 3)  
     OperatingSystem millisecondTimeAdd:16r0FFFFFFF and:1000   

     OperatingSystem millisecondTimeDeltaBetween:0 and:16r0FFFFFFF  
     OperatingSystem millisecondTimeDeltaBetween:(13/3) and:16r0FFFFFFF     
     OperatingSystem millisecondTimeDeltaBetween:999 and:16r0FFFFFFF       

     OperatingSystem millisecondTime:0 isAfter:16r0FFFFFFF    
     OperatingSystem millisecondTime:(13/3) isAfter:16r0FFFFFFF   
     OperatingSystem millisecondTime:999 isAfter:16r0FFFFFFF       

     OperatingSystem millisecondTime:0 isAfter:0          
     OperatingSystem millisecondTime:(13/3) isAfter:0  
     OperatingSystem millisecondTime:999 isAfter:0       

     OperatingSystem millisecondTime:1 isAfter:0        
     OperatingSystem millisecondTime:(13/3) isAfter:2
     OperatingSystem millisecondTime:999 isAfter:900       

     |t1 t2|

     t1 := Time millisecondClockValue.
     (Delay forMilliseconds:1) wait.   
     t2 := Time millisecondClockValue.
     OperatingSystem millisecondTimeDeltaBetween:t2 and:t1 
    "
!

millisecondTime:msTime1 isAfter:msTime2
    "return true if msTime1 is after msTime2, false if not.
     The two arguments are supposed to be millisecond times 
     (such as returned getMillisecondTime) which wrap at 16r1FFFFFFF.

     This should really be moved to some RelativeTime class."

    (msTime1 > msTime2) ifTrue:[
	((msTime1 - msTime2) >= 16r10000000) ifTrue:[
	    ^ false
	].
	^ true
    ].
    ((msTime2 - msTime1) > 16r10000000) ifTrue:[
	^ true
    ].
    ^ false
!

millisecondTimeAdd:msTime1 and:msTime2
    "Add two millisecond times (such as returned getMillisecondTime).
     The returned value is msTime1 + msTime2 where a wrap occurs at:16r1FFFFFFF.

     This should really be moved to some RelativeTime class."

    |sum|

    sum := msTime1 + msTime2.
    (sum > 16r1FFFFFFF) ifTrue:[^ sum - 16r20000000].
    (sum < 0) ifTrue:[^ sum + 16r20000000].
    ^ sum
!

millisecondDelay:millis
    "delay execution for millis milliseconds or until the next event
     arrives.
     All lower priority threads will also sleep for the duration, 
     interrupts (and therefore, higher prio processes) are
     still handled. 
     Better use a Delay, to only delay the calling thread.
     (however, a delay cannot be used in the event handler or scheduler)"

    |now then delta|

    now := OperatingSystem getMillisecondTime.
    then := OperatingSystem millisecondTimeAdd:now and:millis.

    [OperatingSystem millisecondTime:then isAfter:now] whileTrue:[
	delta := OperatingSystem millisecondTimeDeltaBetween:then and:now.
	self selectOnAnyReadable:nil writable:nil exception:nil withTimeOut:delta.
	now := OperatingSystem getMillisecondTime.
    ]

    "
     OperatingSystem millisecondDelay:2000
    "
!

sleep:numberOfSeconds
    "cease ANY action for some time. This suspends the whole smalltalk
     (unix-) process for some time.
     Not really useful since not even low-prio processes and interrupt
     handling will run during the sleep.
     Use either OperatingSystem>>millisecondDelay: (which makes all
     threads sleep, but handles interrupts) or use a Delay (which makes
     only the calling thread sleep)."

%{  /* NOCONTEXT */

    if (__isSmallInteger(numberOfSeconds)) {
	sleep(_intVal(numberOfSeconds));
	RETURN ( self );
    }
%}.
    "
     argument not integer
    "
    self primitiveFailed

    "
     OperatingSystem sleep:2
    "
! !

!OperatingSystem class methodsFor:'waiting for events'!

setBlocking:aBoolean on:fd
    "set/clear the blocking attribute - if set (which is the default)
     a read on the fileDescriptor will block until data is available.
     If cleared, a read operation will immediately return with a value of
     nil."

%{  /* NOCONTEXT */

    int ret, flags;
    int savInt;

#if defined(F_GETFL) && defined(F_SETFL)
# if defined(FNDELAY)
    if (__isSmallInteger(fd)) {
	flags = fcntl(_intVal(fd), F_GETFL, 0);
	if (aBoolean == true) {
	    ret = fcntl(_intVal(fd), F_SETFL, flags & ~FNDELAY);
	} else {
	    ret = fcntl(_intVal(fd), F_SETFL, flags | FNDELAY);
	}
	if (ret >= 0) ret = flags;
	RETURN ( _MKSMALLINT(ret) );
    }
# endif
#endif
%}.
    "
     fd argument not integer
    "
    self primitiveFailed
!

readCheck:fd
    "return true, if data is available on a filedescriptor (i.e. read
     is possible without blocking)"

    (self selectOnAnyReadable:(Array with:fd)
		     writable:nil
		    exception:nil
		  withTimeOut:0) == fd
	ifTrue:[^ true].
    ^ false
!

writeCheck:fd
    "return true, if filedescriptor can be written without blocking"

    (self selectOnAnyReadable:nil
		     writable:(Array with:fd)
		    exception:nil
		  withTimeOut:0) == fd
	ifTrue:[^ true].
    ^ false
!

selectOn:fd withTimeOut:millis
    "wait for aFileDesriptor to become ready; timeout after t milliseconds.
     Return true, if i/o ok, false if timed-out or interrupted.
     With 0 as timeout argument, this can be used to check for availability
     of read-data.
     Experimental."

    ^ self selectOnAnyReadable:(Array with:fd)
		      writable:(Array with:fd)
		     exception:nil
		   withTimeOut:millis
!

selectOn:fd1 and:fd2 withTimeOut:millis
    "wait for any fd to become ready; timeout after t milliseconds.
     A zero timeout-time will immediately return (i.e. poll).
     Return fd if i/o ok, nil if timed-out or interrupted.
     Obsolete:
	This is a leftover method and will vanish."

    ^ self selectOnAnyReadable:(Array with:fd1 with:fd2)
		      writable:(Array with:fd1 with:fd2)
		     exception:nil
		   withTimeOut:millis
!

selectOnAnyReadable:fdArray withTimeOut:millis
    "wait for any fd in fdArray (an Array of integers) to become ready for 
     reading. Timeout after t milliseconds. An empty set will always wait.
     A zero timeout-time will immediately return (i.e. poll).
     Return first ready fd if i/o ok, nil if timed-out or interrupted.
     Experimental."

    ^ self selectOnAnyReadable:fdArray 
		      writable:nil 
		     exception:nil
		   withTimeOut:millis
!

selectOnAny:fdArray withTimeOut:millis
    "wait for any fd in fdArray (an Array of integers) to become ready;
     timeout after t milliseconds. An empty set will always wait.
     Return first ready fd if i/o ok, nil if timed-out or interrupted.
     Experimental."

    ^ self selectOnAnyReadable:fdArray
		      writable:fdArray
		     exception:nil
		   withTimeOut:millis
! 

selectOnAnyReadable:readFdArray writable:writeFdArray exception:exceptFdArray withTimeOut:millis
    "wait for any fd in readFdArray (an Array of integers) to become ready for 
     reading, writeFdArray to become ready for writing, or exceptFdArray to 
     arrive exceptional data (i.e. out-of-band data).
     Timeout after t milliseconds or, if the timeout time is 0, immediately..
     Empty fd-sets will always wait. Zero time can be used to poll file-
     descriptors (i.e. to check if I/O possible without blocking).
     Return first ready fd if I/O ok, nil if timed-out or interrupted."

%{
    fd_set rset, wset, eset;
    int t, f, maxF, i, lX, bX;
    struct timeval wt, et;
    OBJ fd, retFd;
    int ret;
    int count;

    if (__isSmallInteger(millis)) {
	FD_ZERO(&rset);
	FD_ZERO(&wset);
	FD_ZERO(&eset);

	maxF = -1;
	if (readFdArray != nil) {
	    if (! __isArray(readFdArray)) {
		goto fail;    
	    }
	    count = __arraySize(readFdArray);

	    for (i=0; i<count;i++) {
		fd = _ArrayInstPtr(readFdArray)->a_element[i];
		if (fd != nil) {
		    f = _intVal(fd);
		    if ((f >= 0) && (f < FD_SETSIZE)) {
			FD_SET(f, &rset);
			if (f > maxF) maxF = f;
		    }
		}
	    }
	}

	if (writeFdArray != nil) {
	    if (! __isArray(writeFdArray)) {
		goto fail;    
	    }
	    count = __arraySize(writeFdArray);
	    for (i=0; i<count;i++) {
		fd = _ArrayInstPtr(writeFdArray)->a_element[i];
		if (fd != nil) {
		    f = _intVal(fd);
		    if ((f >= 0) && (f < FD_SETSIZE)) {
			FD_SET(f, &wset);       
			if (f > maxF) maxF = f;
		    }
		}
	    }
	}

	if (exceptFdArray != nil) {
	    if (! __isArray(exceptFdArray)) {
		goto fail;    
	    }
	    count = __arraySize(exceptFdArray);
	    for (i=0; i<count;i++) {
		fd = _ArrayInstPtr(exceptFdArray)->a_element[i];
		if (fd != nil) {
		    f = _intVal(fd);
		    if ((f >= 0) && (f < FD_SETSIZE)) {
			FD_SET(f, &eset);       
			if (f > maxF) maxF = f;
		    }
		}
	    }
	}

	t = _intVal(millis);
	wt.tv_sec = t / 1000;
	wt.tv_usec = (t % 1000) * 1000;

	/*
	 * make certain, that interrupt gets us out of the select
	 */
	__immediateInterrupt__ = 1;
	errno = 0;
	do {
	    ret = select(maxF+1, &rset, &wset, &eset, &wt);
	} while (0 /* (ret < 0) && (errno == EINTR) */ );
	__immediateInterrupt__ = 0;

	if (ret > 0) {
	    for (i=0; i <= maxF; i++) {
		if (FD_ISSET(i, &rset)
		 || FD_ISSET(i, &wset)
		 || FD_ISSET(i, &eset)) {
		    RETURN ( _MKSMALLINT(i) );
		}
	    }
	}

	/*
	 * return nil (means time expired)
	 */
	RETURN ( nil );
    }
fail: ;
%}.
    "
     timeout argument not integer,
     or any fd-array nonNil and not an array
    "
    self primitiveFailed
! !

!OperatingSystem class methodsFor:'executing commands'!

fork
    "fork a new (HEAVY-weight) unix process.
     Dont confuse this with Block>>fork, which creates 
     lightweight smalltalk processes. This method will return
     0 to the child process, abd a non-zero number (which is the childs
     unix-process-id) to the parent (original) process."

%{  /* NOCONTEXT */

    int pid;

    pid = fork();
    RETURN ( _MKSMALLINT(pid) );
%}
    "
     |id|

     id := OperatingSystem fork.
     id == 0 ifTrue:[
	OperatingSystem exit
     ]
    "
!

exec:aPath withArguments:argArray
    "execute the unix command specified by the argument, aPath, with
     arguments in argArray (no arguments, if nil).
     If successful, this method does not return and smalltalk is gone.
     If not sucessfull, false is returned. Normal use is with fork."

%{
    char **argv;
    int nargs, i;
    OBJ arg;

    if (__isString(aPath) && ((argArray == nil) || __isArray(argArray))) {
	nargs = argArray == nil ? 0 : _arraySize(argArray);
	argv = (char **) malloc(sizeof(char *) * (nargs + 1));
	if (argv) {
	    for (i=0; i < nargs; i++) {
		arg = _ArrayInstPtr(argArray)->a_element[i];
		if (__isString(arg)) {
		    argv[i] = (char *) _stringVal(arg);
		}
	    }
	    argv[i] = NULL;
	    execv(_stringVal(aPath), argv);
	    /* should not be reached */
	    free(argv);
	    RETURN ( false );
	}
    }
%}.
    "
     path-argument not string or
     argArray not an array/nil or
     malloc failed
    "
    self primitiveFailed

    "
     |id|

     id := OperatingSystem fork.
     id == 0 ifTrue:[
	"I am the child"
	OperatingSystem exec:'/bin/sh' withArguments:'sh -c ls -l'.
	"not reached"
     ]
    "
!

executeCommand:aCommandString
    "execute the unix command specified by the argument, aCommandString.
     Return true if successful, false otherwise. 
     Smalltalk is suspended, while the command is executing.
     The return value of the system()-call is available in the variable
     LastExecStatus (which is zero after successful execution); this value
     consists of the reason for termination (0=normal) in the upper 8bits and,
     iff it was a normal return, the value passed to exit() in the low 8bits."

%{  /* NOCONTEXT */

    int status;

    if (__isString(aCommandString)) {
	status = system((char *) _stringVal(aCommandString));
	OperatingSystem_LastExecStatus = _MKSMALLINT(status);
	if (status == 0) {
	    RETURN ( true );
	}
	RETURN ( false );
    }
%}
.
    self primitiveFailed

    "
     OperatingSystem executeCommand:'pwd'. 
     OperatingSystem lastExecStatus printNL.

     OperatingSystem executeCommand:'ls -l'. 
     OperatingSystem lastExecStatus printNL.

     OperatingSystem executeCommand:'invalidCommand'. 
     (OperatingSystem lastExecStatus printStringRadix:16) printNL.

     OperatingSystem executeCommand:'rm /tmp/foofoofoofoo'. 
     (OperatingSystem lastExecStatus printStringRadix:16) printNL.
    "
! !

!OperatingSystem class methodsFor:'file access'!

getCharacter
    "read a character from keyboard - 
     this is a blocking low-level read, provided for debugger 
     and fatal conditions. Use Stdin or (even better) the event 
     mechanisms provided."

%{  /* NOCONTEXT */

    RETURN ( _MKSMALLINT(getchar()) );
%}
!

fileSeparator
    "return the character used to separate names in a path.
     This character differs for MSDOS and other systems,
     (but those are currently not supported - so this is some
      preparation for the future)"

    ^ $/
!

parentDirectoryName
    "return the name used to refer to parent directories.
     In MSDOS, Unix and other systems this is '..', but maybe different
     for other systems.
     (but those are currently not supported - so this is some
      preparation for the future)"

    ^ '..'
!

caseSensitiveFilenames
    "return true, if the OS has caseSensitive file naming.
     On MSDOS, this will return false. Since this is for Unix, we
     return true."

    ^ true
!

baseNameOf:aPathString
    "return the baseName of the argument, aPathString
     - thats the file/directory name without leading parent-dirs
     (i.e. OperatingSystem baseNameOf:'/usr/lib/st/file' -> 'file'
       and OperatingSystem baseNameOf:'/usr/lib' -> lib).
     This method does not check if the path is valid 
     (i.e. if these directories really exist & is readable)."

    |prev index sep|

    sep := self fileSeparator.
    ((aPathString size == 1) and:[(aPathString at:1) == sep]) ifTrue:[
	^ aPathString
    ].
    prev := 1.
    [true] whileTrue:[
	index := aPathString indexOf:sep startingAt:prev.
	index == 0 ifTrue:[
	    ^ aPathString copyFrom:prev
	].
	prev := index + 1
    ]

    "
     OperatingSystem baseNameOf:'/fee/foo/bar'
     OperatingSystem baseNameOf:'foo/bar'
     OperatingSystem baseNameOf:'../../foo/bar'
     OperatingSystem baseNameOf:'hello'
    "
!

directoryNameOf:aPathString
    "return the directoryName of the argument, aPath
     - thats the name of the directory where aPath is
     (i.e. OperatingSystem directoryNameOf:'/usr/lib/st/file' -> '/usr/lib/st'
       and OperatingSystem directoryNameOf:'/usr/lib' -> /usr').
     This method does not check if the path is valid
     (i.e. if these directories really exist & are readable)."

    |last index sep sepString|

    sep := self fileSeparator.
    sepString := sep asString.
    (aPathString = sepString) ifTrue:[
	"
	 the trivial '/' case
	"
	^ aPathString
    ].
    (aPathString startsWith:sepString) ifFalse:[
	(aPathString endsWith:sepString) ifTrue:[
	    ^ aPathString copyTo:(aPathString size - 1)
	].
    ].
    last := 1.
    [true] whileTrue:[
	index := aPathString indexOf:sep startingAt:(last + 1).
	index == 0 ifTrue:[
	    (last == 1) ifTrue:[
		(aPathString startsWith:sepString) ifTrue:[
		    ^ sepString
		].
		^ '.'
	    ].
"
	    (aPathString startsWith:sepString) ifFalse:[
		(aPathString startsWith:('..' , sepString)) ifFalse:[
		    ^ './' , (aPathString copyTo:(last - 1))
		]
	    ].
"
	    ^ aPathString copyTo:(last - 1)
	].
	last := index.
    ]

    "
     OperatingSystem directoryNameOf:'/fee/foo/bar'
     OperatingSystem directoryNameOf:'foo/bar'
     OperatingSystem directoryNameOf:'../../foo/bar'
     OperatingSystem directoryNameOf:'bar'
     OperatingSystem directoryNameOf:'/bar'
    "
!

primPathNameOf:pathName
    "return the pathName of the argument, aPathString,
     - thats the full pathname of the directory, starting at '/'.
     This method here returns nil, if the OS does not provide a
     realPath library function.
     Notice: if symbolic links are involved, the result may look different
     from what you expect."

    |path|

    "some systems have a convenient function for this ..."
%{  /* STACK: 16000 */
#ifdef HAS_REALPATH
    char nameBuffer[MAXPATHLEN + 1];

    if (__isString(pathName)) {
	if (realpath(_stringVal(pathName), nameBuffer)) {
	    RETURN ( _MKSTRING(nameBuffer COMMA_CON) );
	}
    }
#endif
%}.
    ^ nil
!

compressPath:pathName
    "return the pathName compressed - that is, remove all ..-entries
     and . entries. This does not always (in case of symbolic links)
     return the true pathName and is therefore used as a fallback
     if realPath and popen failed."

    |names|

    names := pathName 
		asCollectionOfSubstringsSeparatedBy:self fileSeparator.
    names := names asOrderedCollection.
    "
     cut off initial double-slashes
    "
    [names startsWith:#('' '')] whileTrue:[
	names removeFirst.
    ].
    "
     cut off double-slashes at end
    "
    [names endsWith:#('')] whileTrue:[
	names removeLast.
    ].
    "
     cut off current-dir at beginning
    "
    [(names size >= 2) and:[names startsWith:#('.')]] whileTrue:[
	names removeFirst.
    ].

    "
     cut off parent-dirs at end
    "
    [(names size > 2) 
     and:[(names endsWith:#('..'))
     and:[((names at:(names size - 1)) startsWith:'.') not ]]] whileTrue:[
	names removeLast; removeLast
    ].

    ^ names asStringWith:self fileSeparator 
		    from:1
		    to:names size
		    compressTabs:false final:nil 

    "
     OperatingSystem compressPath:'./..'    

     OperatingSystem compressPath:'/foo/bar/baz/..'  

     OperatingSystem compressPath:'foo/bar/baz/..'  

     OperatingSystem compressPath:'foo/bar/baz/../'  

     OperatingSystem compressPath:'foo/bar/baz/..///' 

     OperatingSystem compressPath:'///foo/bar/baz/..///' 
    "
!

pathNameOf:pathName
    "return the pathName of the argument, aPathString,
     - thats the full pathname of the directory, starting at '/'.
     This method needs the path to be valid
     (i.e. all directories must exist, be readable and executable).
     Notice: if symbolic links are involved, the result may look different
     from what you expect."

    |p path command|

    "some systems have a convenient function for this ..."
    path := self primPathNameOf:pathName.

    path isNil ifTrue:[
	(SlowFork==true or:[ForkFailed]) ifFalse:[
	    PipeStream openErrorSignal handle:[:ex |
		ForkFailed := true.
		'UNIX: cannot fork/popen' errorPrintNL.
		ex return.
	    ] do:[
		"have to fall back ..."
		command := 'cd ' , pathName , '; pwd'.
		p := PipeStream readingFrom:command.
	    ].

	    (p isNil or:[p atEnd]) ifTrue:[
		('UNIX: PipeStream for <' , command , '> failed') errorPrintNL.
	    ] ifFalse:[
		path := p nextLine.
		p close.
	    ].
	].
	path isNil ifTrue:[
	    path := pathName
	].
	(SlowFork==true or:[ForkFailed]) ifTrue:[
	    path := self compressPath:path
	]
    ].
    ^ path.

    "
     OperatingSystem pathNameOf:'.'
     OperatingSystem pathNameOf:'../smalltalk/../smalltalk'
     OperatingSystem pathNameOf:'../../..'
     OperatingSystem pathNameOf:'..'
    "
!

isValidPath:aPathName
    "return true, if 'aPathName' is a valid path name
     (i.e. the file or directory exists)"

%{  /* NOCONTEXT */

    struct stat buf;
    int ret;

    if (__isString(aPathName) || __isSymbol(aPathName) ) {
	do {
	    ret = stat((char *) _stringVal(aPathName), &buf);
	} while (ret < 0 && errno == EINTR);
	RETURN ( ret ? false : true );
    }
%}
.
    self primitiveFailed
!

isDirectory:aPathName
    "return true, if 'aPathName' is a valid directory path name.
     (i.e. exists and is a directory)"

%{  /* NOCONTEXT */

    struct stat buf;
    int ret;

    if (__isString(aPathName)) {
	do {
	    ret = stat((char *) _stringVal(aPathName), &buf);
	} while (ret < 0 && errno == EINTR);
	if ((ret < 0) || ((buf.st_mode & S_IFMT) != S_IFDIR)) {
	    RETURN ( false );
	}
	RETURN ( true );
    }
%}.
    self primitiveFailed

    "an alternative implementation would be:
	^ (self infoOf:aPathName) at:#type == #directory
    "
!

isReadable:aPathName
    "return true, if the file/dir 'aPathName' is readable."

%{  /* NOCONTEXT */

    if (__isString(aPathName)) {
	if (access(_stringVal(aPathName), R_OK) == 0) {
	    RETURN ( true );
	}
	OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
	RETURN ( false );
    }
%}
.
    self primitiveFailed
!

isWritable:aPathName
    "return true, if the given file is writable"

%{  /* NOCONTEXT */

    if (__isString(aPathName)) {
	if (access(_stringVal(aPathName), W_OK) == 0) {
	    RETURN ( true );
	}
	OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
	RETURN ( false );
    }
%}
.
    self primitiveFailed
!

isExecutable:aPathName
    "return true, if the given file is executable"

%{  /* NOCONTEXT */

    if (__isString(aPathName)) {
	if (access(_stringVal(aPathName), X_OK) == 0) {
	    RETURN ( true );
	}
	OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
	RETURN ( false );
    }
%}
.
    self primitiveFailed
!

isSymbolicLink:aPathName
    "return true, if the given file is a symbolic link"

    ^ (self linkInfoOf:aPathName) notNil
!

linkInfoOf:aPathName
    "return a dictionary filled with info for the file 'aPathName',
     IFF aPathName is a symbolic link. If not, return nil.
     The contents of the dictionary gives info about the link itself,
     on contrast to #infoOf:, which returns the info of the pointed to file
     in case of a symbolic link."
     
    |info type mode uid gid size id atimeLow atimeHi mtimeLow mtimeHi ctimeLow ctimeHi
     path|

%{  /* STACK: 1200 */
    struct stat buf;
    int ret;
    char pathBuffer[1024];

    if (__isString(aPathName)) {
	do {
	    ret = lstat((char *) _stringVal(aPathName), &buf);
	} while (ret < 0 && errno == EINTR);
	if (ret < 0) {
	    OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
	    RETURN ( nil );
	}
	switch (buf.st_mode & S_IFMT) {
	    default:
		RETURN ( nil ); /* not a symbolic link */

	    case S_IFLNK:
		type = @symbol(symbolicLink);
		break;
	}

	mode = _MKSMALLINT(buf.st_mode & 0777);
	uid = _MKSMALLINT(buf.st_uid);
	gid = _MKSMALLINT(buf.st_gid);
	size = _MKSMALLINT(buf.st_size);
	id = _MKSMALLINT(buf.st_ino);
	atimeLow = _MKSMALLINT(buf.st_atime & 0xFFFF);
	atimeHi = _MKSMALLINT((buf.st_atime >> 16) & 0xFFFF);
	mtimeLow = _MKSMALLINT(buf.st_mtime & 0xFFFF);
	mtimeHi = _MKSMALLINT((buf.st_mtime >> 16) & 0xFFFF);
	ctimeLow = _MKSMALLINT(buf.st_ctime & 0xFFFF);
	ctimeHi = _MKSMALLINT((buf.st_ctime >> 16) & 0xFFFF);
	if (readlink((char *) _stringVal(aPathName), pathBuffer, sizeof(pathBuffer)) < 0) {
	    OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
	    RETURN ( nil );
	} 
	path = _MKSTRING(pathBuffer COMMA_CON);
    }
%}.
    mode notNil ifTrue:[
	info := IdentityDictionary new.
	info at:#type put:type.
	info at:#mode put:mode.
	info at:#uid put:uid.
	info at:#gid put:gid.
	info at:#size put:size.
	info at:#id put:id.
	info at:#path put:path.
	info at:#accessed      put:(AbsoluteTime fromOSTimeLow:atimeLow and:atimeHi).
	info at:#modified      put:(AbsoluteTime fromOSTimeLow:mtimeLow and:mtimeHi).
	info at:#statusChanged put:(AbsoluteTime fromOSTimeLow:ctimeLow and:ctimeHi).
	^ info
   ].
   self primitiveFailed

   "
    OperatingSystem infoOf:'Make.proto'
    OperatingSystem linkInfoOf:'Make.proto'
   "
!

infoOf:aPathName
    "return a dictionary filled with info for the file 'aPathName';
     info is: (type->t mode->n uid->u gid->g size->s id->ino).
     return nil if such a file does not exist. A dictionary is returned,
     since we might need to add more info in the future without affecting
     existing applications."

    |info type mode uid gid size id 
     atimeLow atimeHi mtimeLow mtimeHi ctimeLow ctimeHi|

%{
    struct stat buf;
    int ret;

    if (__isString(aPathName)) {
	do {
	    ret = stat((char *) _stringVal(aPathName), &buf);
	} while (ret < 0 && errno == EINTR);
	if (ret < 0) {
	    OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
	    RETURN ( nil );
	}
	switch (buf.st_mode & S_IFMT) {
	    case S_IFDIR:
		type = @symbol(directory);
		break;
	    case S_IFCHR:
		type = @symbol(characterSpecial);
		break;
	    case S_IFBLK:
		type = @symbol(blockSpecial);
		break;
	    case S_IFREG:
		type = @symbol(regular);
		break;
#ifdef S_IFLNK
	    case S_IFLNK:
		type = @symbol(symbolicLink);
		break;
#endif
#ifdef S_IFSOCK
	    case S_IFSOCK:
		type = @symbol(socket);
		break;
#endif
#ifdef S_IFIFO
	    case S_IFIFO:
		type = @symbol(fifo);
		break;
#endif
	    default:
		type = @symbol(unknown);
		break;
	}
	mode = _MKSMALLINT(buf.st_mode & 0777);
	uid = _MKSMALLINT(buf.st_uid);
	gid = _MKSMALLINT(buf.st_gid);
	size = _MKSMALLINT(buf.st_size);
	id = _MKSMALLINT(buf.st_ino);
	atimeLow = _MKSMALLINT(buf.st_atime & 0xFFFF);
	atimeHi = _MKSMALLINT((buf.st_atime >> 16) & 0xFFFF);
	mtimeLow = _MKSMALLINT(buf.st_mtime & 0xFFFF);
	mtimeHi = _MKSMALLINT((buf.st_mtime >> 16) & 0xFFFF);
	ctimeLow = _MKSMALLINT(buf.st_ctime & 0xFFFF);
	ctimeHi = _MKSMALLINT((buf.st_ctime >> 16) & 0xFFFF);
    }
%}.
    mode notNil ifTrue:[
	info := IdentityDictionary new.
	info at:#type put:type.
	info at:#mode put:mode.
	info at:#uid put:uid.
	info at:#gid put:gid.
	info at:#size put:size.
	info at:#id put:id.
	info at:#accessed      put:(AbsoluteTime fromOSTimeLow:atimeLow and:atimeHi).
	info at:#modified      put:(AbsoluteTime fromOSTimeLow:mtimeLow and:mtimeHi).
	info at:#statusChanged put:(AbsoluteTime fromOSTimeLow:ctimeLow and:ctimeHi).
	^ info
   ].
   self primitiveFailed

   "
    OperatingSystem infoOf:'/'
    (OperatingSystem infoOf:'/') at:#uid
    (OperatingSystem infoOf:'/') at:#accessed
   "
!

accessMaskFor:aSymbol
    "return the access bits mask for numbers as returned by 
     OperatingSystem>>accessModeOf:
     and expected by OperatingSystem>>changeAccessModeOf:to:.
     Since these numbers are OS dependent, always use the mask
     (never hardcode 8rxxx into your code)."

%{  /* NOCONTEXT */
#   ifndef S_IRUSR
    /* posix systems should define these ... */
#    define S_IRUSR 0400
#    define S_IWUSR 0200
#    define S_IXUSR 0100
#    define S_IRGRP 0040
#    define S_IWGRP 0020
#    define S_IXGRP 0010
#    define S_IROTH 0004
#    define S_IWOTH 0002
#    define S_IXOTH 0001

#   endif

    if (aSymbol == @symbol(readUser)) {
	return _MKSMALLINT(S_IRUSR);
    }
    if (aSymbol == @symbol(writeUser)) {
	return _MKSMALLINT(S_IWUSR);
    }
    if (aSymbol == @symbol(executeUser)) {
	return _MKSMALLINT(S_IXUSR);
    }
    if (aSymbol == @symbol(readGroup)) {
	return _MKSMALLINT(S_IRGRP);
    }
    if (aSymbol == @symbol(writeGroup)) {
	return _MKSMALLINT(S_IWGRP);
    }
    if (aSymbol == @symbol(executeGroup)) {
	return _MKSMALLINT(S_IXGRP);
    }
    if (aSymbol == @symbol(readOthers)) {
	return _MKSMALLINT(S_IROTH);
    }
    if (aSymbol == @symbol(writeOthers)) {
	return _MKSMALLINT(S_IWOTH);
    }
    if (aSymbol == @symbol(executeOthers)) {
	return _MKSMALLINT(S_IXOTH);
    }
%}.
    self primitiveFailed

    "
     OperatingSystem accessMaskFor:#readUser
    "
!

accessModeOf:aPathName
    "return a number representing access rights rwxrwxrwx for owner,
     group and others. Return nil if such a file does not exist.
     Notice that the returned number is OS dependent - use the 
     modeMasks as returned by OperatingSystem>>accessMaskFor:"

    "
     this could have been implemented as:
	(self infoOf:aPathName) at:#mode
     but for huge directory searches the code below is faster
    "

%{  /* NOCONTEXT */

    struct stat buf;
    int ret;

    if (__isString(aPathName)) {
	do {
	    ret = stat((char *) _stringVal(aPathName), &buf);
	} while (ret < 0 && errno == EINTR);
	if (ret < 0) {
	    OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
	    RETURN ( nil );
	}
	RETURN ( _MKSMALLINT(buf.st_mode & 0777) );
    }
%}.
   self primitiveFailed

   "
    (OperatingSystem accessModeOf:'/') printStringRadix:8
   "
!

changeAccessModeOf:aPathName to:modeBits
    "change the access rights of aPathName to the OS dependent modeBits.
     You should construct this mask using accessMaskFor, to be OS
     independent. Return true if changed, 
     false if such a file does not exist or change was not allowd."

%{  /* NOCONTEXT */
    int ret;

    if (__isString(aPathName) && __isSmallInteger(modeBits)) {
	do {
	    ret = chmod((char *)_stringVal(aPathName), _intVal(modeBits));
	} while (ret < 0 && errno == EINTR);
	if (ret < 0) {
	    OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
	    RETURN ( false );
	}
	RETURN ( true );
    }
%}.
    self primitiveFailed
!

timeOfLastChange:aPathName
    "return the time, when the file was last changed"

    "could be implemented as:
	(self infoOf:aPathName) at:#modified
    "

    |timeLow timeHi|
%{
    struct stat buf;
    int ret;
    time_t mtime;

    if (__isString(aPathName)) {
	do {
	    ret = stat((char *) _stringVal(aPathName), &buf);
	} while (ret < 0 && errno == EINTR);
	if (ret >= 0) {
	    timeLow = _MKSMALLINT(buf.st_mtime & 0xFFFF);
	    timeHi = _MKSMALLINT((buf.st_mtime >> 16) & 0xFFFF);
	}
	OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
    }
%}
.
    timeLow notNil ifTrue:[^ AbsoluteTime fromOSTimeLow:timeLow and:timeHi].
    self primitiveFailed

    "
     OperatingSystem timeOfLastChange:'/'
    "
!

timeOfLastAccess:aPathName
    "return the time, when the file was last accessed"

    "could be implemented as:
	(self infoOf:aPathName) at:#accessed 
    "
    |timeLow timeHi|
%{
    struct stat buf;
    time_t mtime;
    int ret;

    if (__isString(aPathName)) {
	do {
	    ret = stat((char *) _stringVal(aPathName), &buf);
	} while (ret < 0 && errno == EINTR);
	if (ret >= 0) {
	    timeLow = _MKSMALLINT(buf.st_atime & 0xFFFF);
	    timeHi = _MKSMALLINT((buf.st_atime >> 16) & 0xFFFF);
	}
	OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
    }
%}
.
    timeLow notNil ifTrue:[^ AbsoluteTime fromOSTimeLow:timeLow and:timeHi].
    self primitiveFailed

    "
     OperatingSystem timeOfLastAccess:'/'
    "
!

idOf:aPathName
    "return the fileNumber (i.e. inode number) of a file"

    "
     this could have been implemented as:
	(self infoOf:aPathName) at:#id 
     but for huge directory searches the code below is faster
    "

%{  /* NOCONTEXT */

    struct stat buf;
    int ret;

    if (__isString(aPathName)) {
	do {
	    ret = stat((char *) _stringVal(aPathName), &buf);
	} while (ret < 0 && errno == EINTR);
	if (ret >= 0) {
	    RETURN (_MKSMALLINT(buf.st_ino));
	}
	OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
    }
%}
.
    self primitiveFailed

    "OperatingSystem idOf:'/'"
!

typeOf:aPathName
    "return the type of a file as a symbol"

    "
     this could have been implemented as:
	(self infoOf:aPathName) at:#type 
     but for huge directory searches the code below is faster
    "

%{  /* NOCONTEXT */

    struct stat buf;
    int ret;

    if (__isString(aPathName)) {
	do {
	    ret = stat((char *) _stringVal(aPathName), &buf);
	} while (ret < 0 && errno == EINTR);
	if (ret < 0) {
	    OperatingSystem_LastErrorNumber = _MKSMALLINT(errno);
	    RETURN ( nil );
	}
	switch (buf.st_mode & S_IFMT) {
	    case S_IFDIR:
		RETURN ( @symbol(directory) );
	    case S_IFCHR:
		RETURN ( @symbol(characterSpecial) );
	    case S_IFBLK:
		RETURN ( @symbol(blockSpecial) );
	    case S_IFREG:
		RETURN ( @symbol(regular) );
#ifdef S_IFLNK
	    case S_IFLNK:
		RETURN ( @symbol(symbolicLink) );
#endif
#ifdef S_IFSOCK
	    case S_IFSOCK:
		RETURN ( @symbol(socket) );
#endif
#ifdef S_IFIFO
	    case S_IFIFO:
		RETURN ( @symbol(fifo) );
#endif
	    default:
		RETURN ( @symbol(unknown) );
	}
    }
%}.
    self primitiveFailed

    "
     OperatingSystem typeOf:'/' 
     OperatingSystem typeOf:'.' 
     OperatingSystem typeOf:'Make.proto' 
    "
!

createDirectory:newPathName
    "create a new directory with name 'newPathName'.
     Return true if successful, false if failed."

    "since createDirectory is not used too often,
     you'll forgive me using mkdir ..."

    ^ self executeCommand:('mkdir ' , newPathName)

    "OperatingSystem createDirectory:'foo'"
!

recursiveCreateDirectory:dirName
    "create a directory - with all parent dirs if needed.
     Return true if successful, false otherwise. If false
     is returned, a partial created tree may be left,
     which is not cleaned-up here."

    self createDirectory:dirName.
    (self isDirectory:dirName) ifFalse:[
	(self recursiveCreateDirectory:(self directoryNameOf:dirName)) ifFalse:[^ false].
	^ self createDirectory:dirName
    ].
    ^ (self isDirectory:dirName)

    "OperatingSystem recursiveCreateDirectory:'foo/bar/baz'"
!

removeFile:fullPathName
    "remove the file named 'fullPathName'; return true if successful"

%{  /* NOCONTEXT */

    if (__isString(fullPathName)) {
	RETURN ( (unlink((char *) _stringVal(fullPathName)) >= 0) ? true : false );
    }
%}
.
    self primitiveFailed
!

removeDirectory:fullPathName
    "remove the directory named 'fullPathName'.
     Return true if successful, false if directory is not empty or no permission"

%{  /* NOCONTEXT */

    if (__isString(fullPathName)) {
	RETURN ( (rmdir((char *) _stringVal(fullPathName)) >= 0) ? true : false );
    }
%}
.
    self primitiveFailed
!

recursiveRemoveDirectory:fullPathName
    "remove the directory named 'fullPathName' and all contained files/directories.
     Return true if successful."

    ^ self executeCommand:('rm -rf ' , fullPathName)
!

linkFile:oldPath to:newPath
    "link the file 'oldPath' to 'newPath'. The link will be a hard link.
     Return true if successful, false if not."

%{  /* NOCONTEXT */

    if (__isString(oldPath) && __isString(newPath)) {
	RETURN ( (link((char *) _stringVal(oldPath), (char *) _stringVal(newPath)) >= 0) ?
				true : false );
    }
%}
.
    self primitiveFailed

    "OperatingSystem linkFile:'foo' to:'bar'"
!

renameFile:oldPath to:newPath
    "rename the file 'oldPath' to 'newPath'. 
     Return true if sucessfull, false if not"

%{  /* NOCONTEXT */

    if (__isString(oldPath) && __isString(newPath)) {
#if defined(HAS_RENAME)
	if (rename((char *) _stringVal(oldPath), (char *) _stringVal(newPath)) >= 0) {
	    RETURN ( true );
	}
#else
	if (link((char *) _stringVal(oldPath), (char *) _stringVal(newPath)) >= 0) {
	    if (unlink((char *) _stringVal(oldPath)) >= 0) {
		RETURN ( true );
	    }
	    unlink((char *) _stringVal(newPath));
	}
#endif
	RETURN ( false );
    }
%}
.
    self primitiveFailed

    "OperatingSystem renameFile:'foo' to:'bar'"
! !