/**************************************************************************** ** ** This file is part of GAP, a system for computational discrete algebra. ** ** Copyright of GAP belongs to its developers, whose names are too numerous ** to list here. Please refer to the COPYRIGHT file for details. ** ** SPDX-License-Identifier: GPL-2.0-or-later ** ** The files "system.c" and "sysfiles.c" contain all operating system ** dependent functions. File and stream operations are implemented in this ** files, all the other system dependent functions in "system.c". There are ** various labels determine which operating system is actually used, they ** are described in "system.c". */ #include "sysfiles.h" #include "bool.h" #include "calls.h" #include "compstat.h" #include "error.h" #include "gapstate.h" #include "gaputils.h" #include "gvars.h" #include "io.h" #include "lists.h" #include "modules.h" #include "plist.h" #include "read.h" #include "records.h" #include "stats.h" #include "stringobj.h" #include "sysenv.h" #include "sysopt.h" #include "hpc/thread.h" #ifdef HAVE_LIBREADLINE #include #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_SELECT /* Only for the Hook handler calls: */ #include #endif #ifdef HAVE_SIGNAL_H /* signal handling functions */ #include typedef void sig_handler_t ( int ); #endif #ifdef HAVE_SYS_IOCTL_H #include /* for TIOCGWINSZ */ #endif #ifdef SYS_IS_CYGWIN32 #include #endif #ifdef SYS_IS_DARWIN #include #endif #include static ssize_t SyWriteandcheck(Int fid, const void * buf, size_t count); /**************************************************************************** ** *V syBuf . . . . . . . . . . . . . . buffer and other info for files, local ** ** 'syBuf' is a array used as buffers for file I/O to prevent the C I/O ** routines from allocating their buffers using 'malloc', which would ** otherwise confuse Gasman. ** ** ** Actually these days SyBuf just stores various file info. SyBuffers ** stores buffers for the relatively few files that need them. */ // The type of file stored in a 'SYS_SY_BUF' typedef enum { unused_socket, // Socket is free raw_socket, // Plain UNIX socket stored in 'fp' gzip_socket // A gzFile handled by zlib stored in 'gzfp' } GAPSocketType; GAP_STATIC_ASSERT(unused_socket == 0, "unused_socket must be zero"); typedef struct { // gzfp is used if type == gzip_socket gzFile gzfp; // file descriptor for this file (only used if type == raw_socket) int fp; // file descriptor for the echo (only used if type == raw_socket) int echo; // file is either a plain descriptor, pipe or gzipped GAPSocketType type; // set to 1 by any read operation that hits eof; reset to 0 by a // subsequent successful read UInt ateof; // records that last character read was \r for cygwin and other systems // that need end-of-line hackery UInt crlast; // if non-negative then this file has a buffer in syBuffers[bufno]; if // negative, this file may not be buffered Int bufno; // set when this fid is a *stdin* or *errin* and really is a tty Int isTTY; } SYS_SY_BUF; #define SYS_FILE_BUF_SIZE 20000 typedef struct { Char buf[SYS_FILE_BUF_SIZE]; UInt inuse; UInt bufstart; UInt buflen; } SYS_SY_BUFFER; static SYS_SY_BUF syBuf[256]; static SYS_SY_BUFFER syBuffers[32]; /* utility to check return value of 'write' */ static ssize_t echoandcheck(int fid, const char *buf, size_t count) { int ret; if (syBuf[fid].type == gzip_socket) { ret = gzwrite(syBuf[fid].gzfp, buf, count); if (ret < 0) { ErrorQuit( "Could not write to compressed file, see 'LastSystemError();'\n", 0L, 0L); } } else { ret = write(syBuf[fid].echo, buf, count); if (ret < 0) { if (syBuf[fid].fp == fileno(stdout) || syBuf[fid].fp == fileno(stderr)) { Panic("Could not write to stdout/stderr."); } else { ErrorQuit("Could not write to file descriptor %d, see " "'LastSystemError();'\n", syBuf[fid].fp, 0L); } } } return ret; } /**************************************************************************** ** *F * * * * * * * * * * * * * * dynamic loading * * * * * * * * * * * * * * * */ /**************************************************************************** ** *F SyFindOrLinkGapRootFile( , ) . . . . . . load or link ** ** 'SyFindOrLinkGapRootFile' tries to find a GAP file in the root area and ** check if there is a corresponding statically linked module. If the CRC ** matches the statically linked module is loaded, and module_info> ** is set to point to its StructInitInfo instance. ** ** The function returns: ** ** 0: no file or module was found ** 2: a statically linked module was found ** 3: only a GAP file was found; its path is stored in path> */ Int SyFindOrLinkGapRootFile(const Char * filename, TypGRF_Data * result) { // find the GAP file Int found_gap = SyFindGapRootFile(filename, result->path, sizeof(result->path)) != 0; if (SyUseModule) { // search for a statically linked module matching the given filename Char module[GAP_PATH_MAX]; strxcpy(module, "GAPROOT/", sizeof(module)); strxcat(module, filename, sizeof(module)); for (Int k = 0; CompInitFuncs[k]; k++) { StructInitInfo * info = (*(CompInitFuncs[k]))(); if (info && !strcmp(module, info->name)) { // found a matching statically linked module; if there is also // a GAP file, compare their CRC if (found_gap && info->crc != SyGAPCRC(result->path)) { Pr("#W Static module %s has CRC mismatch, ignoring\n", (Int)filename, 0); break; } result->module_info = info; return 2; } } } return found_gap ? 3 : 0; } /**************************************************************************** ** *F SyGAPCRC( ) . . . . . . . . . . . . . . . . . . crc of a GAP file ** ** This function should be clever and handle white spaces and comments but ** one has to make certain that such characters are not ignored in strings. ** ** This function *never* returns a 0 unless an error occurred. */ static const UInt4 syCcitt32[ 256 ] = { 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL }; Int4 SyGAPCRC( const Char * name ) { UInt4 crc; UInt4 old; UInt4 new; Int4 ch; Int fid; Int seen_nl; /* the CRC of a non existing file is 0 */ fid = SyFopen( name, "r" ); if ( fid == -1 ) { return 0; } /* read in the file byte by byte and compute the CRC */ crc = 0x12345678L; seen_nl = 0; while ( (ch = SyGetch(fid) )!= EOF ) { if ( ch == '\377' || ch == '\n' || ch == '\r' ) ch = '\n'; if ( ch == '\n' ) { if ( seen_nl ) continue; else seen_nl = 1; } else seen_nl = 0; old = (crc >> 8) & 0x00FFFFFFL; new = syCcitt32[ ( (UInt4)( crc ^ ch ) ) & 0xff ]; crc = old ^ new; } if ( crc == 0 ) { crc = 1; } /* and close it again */ SyFclose( fid ); /* Emulate a signed shift: */ if (crc & 0x80000000L) return (Int4) ((crc >> 4) | 0xF0000000L); else return (Int4) (crc >> 4); } /* <#GAPDoc Label="CrcString"> an integer This function computes a cyclic redundancy check number from a string str. See also . gap> CrcString("GAP example string"); -50451670 <#/GAPDoc> */ /* And here we include a variant working on a GAP string */ static Obj FuncCrcString(Obj self, Obj str) { UInt4 crc; UInt4 old; UInt4 new; UInt4 i, len; const Char *ptr; Int4 ch; Int seen_nl; // check the argument RequireStringRep("CrcString", str); ptr = CONST_CSTR_STRING(str); len = GET_LEN_STRING(str); crc = 0x12345678L; seen_nl = 0; for (i = 0; i < len; i++) { ch = (Int4)(ptr[i]); if ( ch == '\377' || ch == '\n' || ch == '\r' ) ch = '\n'; if ( ch == '\n' ) { if ( seen_nl ) continue; else seen_nl = 1; } else seen_nl = 0; old = (crc >> 8) & 0x00FFFFFFL; new = syCcitt32[ ( (UInt4)( crc ^ ch ) ) & 0xff ]; crc = old ^ new; } if ( crc == 0 ) { crc = 1; } return INTOBJ_INT(((Int4) crc) >> 4); } /**************************************************************************** ** *F * * * * * * * * * * finding location of executable * * * * * * * * * * * * */ /**************************************************************************** ** The function 'find_yourself' is based on code (C) 2015 Mark Whitis, under ** the MIT License : https://stackoverflow.com/a/34271901/928031 */ static void find_yourself(const char * argv0, char * result, size_t resultsize) { GAP_ASSERT(resultsize >= GAP_PATH_MAX); char tmpbuf[GAP_PATH_MAX]; // absolute path, like '/usr/bin/gap' if (argv0[0] == '/') { if (realpath(argv0, result) && !access(result, F_OK)) { return; // success } } // relative path, like 'bin/gap.sh' else if (strchr(argv0, '/')) { if (!getcwd(tmpbuf, sizeof(tmpbuf))) return; strlcat(tmpbuf, "/", sizeof(tmpbuf)); strlcat(tmpbuf, argv0, sizeof(tmpbuf)); if (realpath(tmpbuf, result) && !access(result, F_OK)) { return; // success } } // executable name, like 'gap' else { char pathenv[GAP_PATH_MAX], *saveptr, *pathitem; strlcpy(pathenv, getenv("PATH"), sizeof(pathenv)); pathitem = strtok_r(pathenv, ":", &saveptr); for (; pathitem; pathitem = strtok_r(NULL, ":", &saveptr)) { strlcpy(tmpbuf, pathitem, sizeof(tmpbuf)); strlcat(tmpbuf, "/", sizeof(tmpbuf)); strlcat(tmpbuf, argv0, sizeof(tmpbuf)); if (realpath(tmpbuf, result) && !access(result, F_OK)) { return; // success } } } *result = 0; // reset buffer after error } char GAPExecLocation[GAP_PATH_MAX] = ""; void SetupGAPLocation(int argc, char ** argv) { // In the code below, we keep reseting locBuf, as some of the methods we // try do not promise to leave the buffer empty on a failed return. char locBuf[GAP_PATH_MAX] = ""; Int4 length = 0; #ifdef SYS_IS_DARWIN uint32_t len = sizeof(locBuf); if (_NSGetExecutablePath(locBuf, &len) != 0) { *locBuf = 0; // reset buffer after error } #endif // try Linux procfs if (!*locBuf) { ssize_t ret = readlink("/proc/self/exe", locBuf, sizeof(locBuf)); if (ret < 0) *locBuf = 0; // reset buffer after error } // try FreeBSD / DragonFly BSD procfs if (!*locBuf) { ssize_t ret = readlink("/proc/curproc/file", locBuf, sizeof(locBuf)); if (ret < 0) *locBuf = 0; // reset buffer after error } // try NetBSD procfs if (!*locBuf) { ssize_t ret = readlink("/proc/curproc/exe", locBuf, sizeof(locBuf)); if (ret < 0) *locBuf = 0; // reset buffer after error } // if we are still failing, go and search the path if (!*locBuf) { find_yourself(argv[0], locBuf, GAP_PATH_MAX); } // resolve symlinks (if present) if (!realpath(locBuf, GAPExecLocation)) *GAPExecLocation = 0; // reset buffer after error // now strip the executable name off length = strlen(GAPExecLocation); while (length > 0 && GAPExecLocation[length] != '/') { GAPExecLocation[length] = 0; length--; } } /**************************************************************************** ** *F * * * * * * * * * * * * * * * window handler * * * * * * * * * * * * * * * */ /**************************************************************************** ** *F IS_SEP( ) . . . . . . . . . . . . . . . . . . . . is a separator */ #define IS_SEP(C) (!IsAlpha(C) && !IsDigit(C) && (C)!='_') /**************************************************************************** ** *F CTR( ) . . . . . . . . . . . . . . . . convert into control- */ #define CTR(C) ((C) & 0x1F) /* character */ /**************************************************************************** ** *F Esc( ) . . . . . . . . . . . . . . . . . convert into escape- */ #define Esc(C) ((C) | 0x100) /* character */ /**************************************************************************** ** *F CTV( ) . . . . . . . . . . . . . . . . . convert into quote */ #define CTV(C) ((C) | 0x200) /* V quotes characters */ /**************************************************************************** ** *F syWinPut( , , ) . . . . send a line to the window handler ** ** 'syWinPut' send the command and the string to the window ** handler associated with the file identifier . In the string ** '@' characters are duplicated, and control characters are converted to ** '@', e.g., is converted to '@J'. */ void syWinPut ( Int fid, const Char * cmd, const Char * str ) { Char tmp [130]; /* temporary buffer */ const Char * s; /* pointer into the string */ Char * t; /* pointer into the temporary */ /* if not running under a window handler, don't do anything */ if (!SyWindow || 4 <= fid || syBuf[fid].type == gzip_socket) return; /* print the cmd */ echoandcheck( fid, cmd, strlen(cmd) ); /* print the output line, duplicate '@' and handle - */ s = str; t = tmp; while ( *s != '\0' ) { if ( *s == '@' ) { *t++ = '@'; *t++ = *s++; } else if ( CTR('A') <= *s && *s <= CTR('Z') ) { *t++ = '@'; *t++ = *s++ - CTR('A') + 'A'; } else { *t++ = *s++; } if ( 128 <= t-tmp ) { echoandcheck( fid, tmp, t-tmp ); t = tmp; } } if ( 0 < t-tmp ) { echoandcheck( fid, tmp, t-tmp ); } } /**************************************************************************** ** *F SyWinCmd( , ) . . . . . . . . . . . . . execute a window cmd ** ** 'SyWinCmd' send the command to the window handler ( is ** ignored). In the string '@' characters are duplicated, and control ** characters are converted to '@', e.g., is converted to ** '@J'. Then 'SyWinCmd' waits for the window handlers answer and returns ** that string. */ static Char WinCmdBuffer[8000]; const Char * SyWinCmd ( const Char * str, UInt len ) { Char buf [130]; /* temporary buffer */ const Char * s; /* pointer into the string */ const Char * bb; /* pointer into the temporary */ Char * b; /* pointer into the temporary */ UInt i; /* loop variable */ #ifdef SYS_IS_CYGWIN32 UInt len1; /* temporary storage for len */ #endif /* if not running under a window handler, don't do nothing */ if ( ! SyWindow ) return "I1+S52+No Window Handler Present"; /* compute the length of the (expanded) string (and ignore argument) */ len = 0; for ( s = str; *s != '\0'; s++ ) len += 1 + (*s == '@' || (CTR('A') <= *s && *s <= CTR('Z'))); /* send the length to the window handler */ b = buf; for ( ; 0 < len; len /= 10 ) { *b++ = (len % 10) + '0'; } *b++ = '+'; *b++ = '\0'; syWinPut( 1, "@w", buf ); /* send the string to the window handler */ syWinPut( 1, "", str ); /* read the length of the answer */ b = WinCmdBuffer; i = 3; while ( 0 < i ) { len = read( 0, b, i ); i -= len; b += len; } if ( WinCmdBuffer[0] != '@' || WinCmdBuffer[1] != 'a' ) return "I1+S41+Illegal Answer"; b = WinCmdBuffer+2; for ( i=1,len=0; '0' <= *b && *b <= '9'; i *= 10 ) { len += (*b-'0')*i; while ( read( 0, b, 1 ) != 1 ) ; } /* read the arguments of the answer */ b = WinCmdBuffer; i = len; #ifdef SYS_IS_CYGWIN32 len1 = len; while ( 0 < i ) { len = read( 0, b, i ); b += len; i -= len; s += len; } len = len1; #else while ( 0 < i ) { len = read( 0, b, i ); i -= len; s += len; } #endif /* shrink '@@' into '@' */ for ( bb = b = WinCmdBuffer; 0 < len; len-- ) { if ( *bb == '@' ) { bb++; if ( *bb == '@' ) *b++ = '@'; else if ( 'A' <= *bb && *bb <= 'Z' ) *b++ = CTR(*bb); bb++; } else { *b++ = *bb++; } } *b = 0; /* return the string */ return WinCmdBuffer; } /**************************************************************************** ** *F * * * * * * * * * * * * * * * * open/close * * * * * * * * * * * * * * * * */ // Mark a member of syBuf as unused static void SyBufMarkUnused(Int i) { GAP_ASSERT(i >= 0 && i < ARRAY_SIZE(syBuf)); memset(&(syBuf[i]), 0, sizeof(syBuf[i])); syBuf[i].type = unused_socket; } // There is no explicit method to mark syBufs as used, // they are marked as used when created. // Check if a member of syBuf is in use static Int SyBufInUse(Int i) { if (i >= 0 && i < ARRAY_SIZE(syBuf)) return syBuf[i].type != unused_socket; return 0; } void SyRedirectStderrToStdOut(void) { syBuf[2].fp = syBuf[0].fp; syBuf[2].echo = syBuf[0].echo; syBuf[2].isTTY = syBuf[0].isTTY; syBuf[3].fp = syBuf[1].fp; syBuf[3].echo = syBuf[1].echo; } /**************************************************************************** ** *F SyBufFileno( ) . . . . . . . . . . . . get operating system fileno ** ** Given a 'syBuf' buffer id, return the associated file descriptor, if any. ** For gzipped files, -1 is returned. */ int SyBufFileno(Int fid) { if (fid == -1) return -1; GAP_ASSERT(0 <= fid && fid < ARRAY_SIZE(syBuf)); // fp is only valid in the raw case return (syBuf[fid].type == raw_socket) ? syBuf[fid].fp : -1; } Int SyBufIsTTY(Int fid) { GAP_ASSERT(0 <= fid && fid < ARRAY_SIZE(syBuf)); return syBuf[fid].isTTY; } void SyBufSetEOF(Int fid) { GAP_ASSERT(0 <= fid && fid < ARRAY_SIZE(syBuf)); syBuf[fid].ateof = 1; } /**************************************************************************** ** *F SyFopen( , ) . . . . . . . . open the file with name ** ** The function 'SyFopen' is called to open the file with the name . ** If is "r" it is opened for reading, in this case it must exist. ** If is "w" it is opened for writing, it is created if neccessary. ** If is "a" it is opened for appending, i.e., it is not truncated. ** ** 'SyFopen' returns an integer used by the scanner to identify the file. ** 'SyFopen' returns -1 if it cannot open the file. ** ** The following standard files names and file identifiers are guaranteed: ** 'SyFopen( "*stdin*", "r")' returns 0 identifying the standard input file. ** 'SyFopen( "*stdout*","w")' returns 1 identifying the standard outpt file. ** 'SyFopen( "*errin*", "r")' returns 2 identifying the brk loop input file. ** 'SyFopen( "*errout*","w")' returns 3 identifying the error messages file. ** ** If it is necessary to adjust the filename this should be done here, the ** filename convention used in GAP is that '/' is the directory separator. ** ** Right now GAP does not read nonascii files, but if this changes sometimes ** 'SyFopen' must adjust the mode argument to open the file in binary mode. */ Int SyFopen ( const Char * name, const Char * mode ) { Int fid; Char namegz [1024]; Char cmd [1024]; int flags = 0; /* handle standard files */ if ( strcmp( name, "*stdin*" ) == 0 ) { if ( strcmp( mode, "r" ) != 0 ) return -1; else return 0; } else if ( strcmp( name, "*stdout*" ) == 0 ) { if ( strcmp( mode, "w" ) != 0 && strcmp( mode, "a" ) != 0 ) return -1; else return 1; } else if ( strcmp( name, "*errin*" ) == 0 ) { if ( strcmp( mode, "r" ) != 0 ) return -1; else if ( !SyBufInUse( 2 ) ) return -1; else return 2; } else if ( strcmp( name, "*errout*" ) == 0 ) { if ( strcmp( mode, "w" ) != 0 && strcmp( mode, "a" ) != 0 ) return -1; else return 3; } HashLock(&syBuf); /* try to find an unused file identifier */ for ( fid = 4; fid < ARRAY_SIZE(syBuf); ++fid ) if ( !SyBufInUse(fid) ) break; if ( fid == ARRAY_SIZE(syBuf) ) { HashUnlock(&syBuf); return (Int)-1; } /* set up and for pipe command */ namegz[0] = '\0'; // Need space for "gunzip < '", ".gz'" and terminating \0. if (strlen(name) <= sizeof(cmd) - 10 - 4 - 1) { strxcpy( namegz, name, sizeof(namegz) ); strxcat( namegz, ".gz", sizeof(namegz) ); strxcpy( cmd, "gunzip < '", sizeof(cmd) ); strxcat( cmd, namegz, sizeof(cmd) ); strxcat( cmd, "'", sizeof(cmd) ); } if (strncmp( mode, "r", 1 ) == 0) flags = O_RDONLY; else if (strncmp( mode, "w",1 ) == 0) flags = O_WRONLY | O_CREAT | O_TRUNC; else if (strncmp( mode, "a",1) == 0) flags = O_WRONLY | O_APPEND | O_CREAT; else { Panic("Unknown mode %s", mode); } #ifdef SYS_IS_CYGWIN32 if(strlen(mode) >= 2 && mode[1] == 'b') flags |= O_BINARY; #endif /* try to open the file */ syBuf[fid].fp = open(name,flags, 0644); if ( 0 <= syBuf[fid].fp ) { syBuf[fid].type = raw_socket; syBuf[fid].echo = syBuf[fid].fp; syBuf[fid].bufno = -1; } else if (strncmp(mode, "r", 1) == 0 && SyIsReadableFile(namegz) == 0 && (syBuf[fid].gzfp = gzopen(namegz, mode))) { syBuf[fid].type = gzip_socket; syBuf[fid].fp = -1; syBuf[fid].bufno = -1; } else { HashUnlock(&syBuf); return (Int)-1; } HashUnlock(&syBuf); if(strncmp(mode, "r", 1) == 0) SySetBuffering(fid); /* return file identifier */ return fid; } // Lock on SyBuf for both SyBuf and SyBuffers UInt SySetBuffering( UInt fid ) { UInt bufno; if (!SyBufInUse(fid)) ErrorQuit("Can't set buffering for a closed stream", 0, 0); if (syBuf[fid].bufno >= 0) return 1; bufno = 0; HashLock(&syBuf); while (bufno < ARRAY_SIZE(syBuffers) && syBuffers[bufno].inuse != 0) bufno++; if (bufno >= ARRAY_SIZE(syBuffers)) { HashUnlock(&syBuf); return 0; } syBuf[fid].bufno = bufno; syBuffers[bufno].inuse = 1; syBuffers[bufno].bufstart = 0; syBuffers[bufno].buflen = 0; HashUnlock(&syBuf); return 1; } /**************************************************************************** ** *F SyFclose( ) . . . . . . . . . . . . . . . . . close the file ** ** 'SyFclose' closes the file with the identifier which is obtained ** from 'SyFopen'. */ Int SyFclose ( Int fid ) { /* check file identifier */ if ( ARRAY_SIZE(syBuf) <= fid || fid < 0 ) { fputs("gap: panic 'SyFclose' asked to close illegal fid!\n",stderr); return -1; } if ( !SyBufInUse(fid) ) { fputs("gap: panic 'SyFclose' asked to close closed file!\n",stderr); return -1; } /* refuse to close the standard files */ if ( fid == 0 || fid == 1 || fid == 2 || fid == 3 ) { return -1; } HashLock(&syBuf); /* try to close the file */ if (syBuf[fid].type == raw_socket && close(syBuf[fid].fp) == EOF) { fputs("gap: 'SyFclose' cannot close file, ",stderr); fputs("maybe your file system is full?\n",stderr); SyBufMarkUnused(fid); HashUnlock(&syBuf); return -1; } if (syBuf[fid].type == gzip_socket) { if (gzclose(syBuf[fid].gzfp) < 0) { fputs("gap: 'SyFclose' cannot close compressed file", stderr); } } /* mark the buffer as unused */ if (syBuf[fid].bufno >= 0) syBuffers[syBuf[fid].bufno].inuse = 0; SyBufMarkUnused(fid); HashUnlock(&syBuf); return 0; } /**************************************************************************** ** *F SyIsEndOfFile( ) . . . . . . . . . . . . . . . end of file reached */ Int SyIsEndOfFile ( Int fid ) { /* check file identifier */ if ( !SyBufInUse(fid) ) { return -1; } /* *stdin* and *errin* are never at end of file */ if ( fid < 4 ) return 0; /* How to detect end of file ?? */ return syBuf[fid].ateof; /* return feof(syBuf[fid].fp);*/ } /**************************************************************************** ** *F syStartraw( ) . . . . . . start raw mode on input file , local ** ** The following four functions are the actual system dependent part of ** 'SyFgets'. ** ** 'syStartraw' tries to put the file with the file identifier into ** raw mode. I.e., disabling echo and any buffering. It also finds a ** place to put the echoing for 'syEchoch'. If 'syStartraw' succedes it ** returns 1, otherwise, e.g., if the is not a terminal, it returns 0. ** ** 'syStopraw' stops the raw mode for the file again, switching it ** back into whatever mode the terminal had before 'syStartraw'. ** ** 'syGetch' reads one character from the file , which must have been ** turned into raw mode before, and returns it. ** ** 'syEchoch' puts the character to the file opened by 'syStartraw' for ** echoing. Note that if the user redirected 'stdout' but not 'stdin', the ** echo for 'stdin' must go to 'ttyname(fileno(stdin))' instead of 'stdout'. */ /**************************************************************************** ** For UNIX System V, input/output redirection and typeahead are supported. ** We turn off input buffering and canonical input editing and also echo. ** Because we leave the signals enabled we have to disable the characters ** for interrupt and quit, which are usually set to '-C' and '-B'. ** We also turn off the xon/xoff start and stop characters, which are ** usually set to '-S' and '-Q' so we can get those characters. ** We do not turn of signals 'ISIG' because we want to catch stop and ** continue signals if this particular version of UNIX supports them, so we ** can turn the terminal line back to cooked mode before stopping GAP. */ static struct termios syOld, syNew; /* old and new terminal state */ #ifdef SIGTSTP static Int syFid; static void syAnswerCont(int signr) { syStartraw( syFid ); signal( SIGCONT, SIG_DFL ); kill( getpid(), SIGCONT ); } static void syAnswerTstp(int signr) { syStopraw( syFid ); signal( SIGCONT, syAnswerCont ); kill( getpid(), SIGTSTP ); } #endif UInt syStartraw ( Int fid ) { /* if running under a window handler, tell it that we want to read */ if ( SyWindow ) { if ( fid == 0 ) { syWinPut( fid, "@i", "" ); return 1; } else if ( fid == 2 ) { syWinPut( fid, "@e", "" ); return 1; } else { return 0; } } /* try to get the terminal attributes, will fail if not terminal */ const int fd = SyBufFileno(fid); GAP_ASSERT(fd >= 0); if ( tcgetattr( fd, &syOld) == -1 ) return 0; /* disable interrupt, quit, start and stop output characters */ syNew = syOld; syNew.c_cc[VINTR] = 0377; syNew.c_cc[VQUIT] = 0377; /*C 27-Nov-90 martin changing 'S' and 'Q' does not work */ /*C syNew.c_iflag &= ~(IXON|INLCR|ICRNL); */ syNew.c_iflag &= ~(INLCR|ICRNL); /* disable input buffering, line editing and echo */ syNew.c_cc[VMIN] = 1; syNew.c_cc[VTIME] = 0; syNew.c_lflag &= ~(ECHO|ICANON); if ( tcsetattr( fd, TCSANOW, &syNew) == -1 ) return 0; #ifdef SIGTSTP /* install signal handler for stop */ syFid = fid; signal( SIGTSTP, syAnswerTstp ); #endif /* indicate success */ return 1; } /**************************************************************************** ** *F syStopraw( ) . . . . . . stop raw mode on input file , local */ void syStopraw ( Int fid ) { /* if running under a window handler, don't do nothing */ if ( SyWindow ) return; #ifdef SIGTSTP /* remove signal handler for stop */ signal( SIGTSTP, SIG_DFL ); #endif /* enable input buffering, line editing and echo again */ const int fd = SyBufFileno(fid); GAP_ASSERT(fd >= 0); if (tcsetattr(fd, TCSANOW, &syOld) == -1) fputs("gap: 'tcsetattr' could not turn off raw mode!\n",stderr); } /**************************************************************************** ** *F SyIsIntr() . . . . . . . . . . . . . . . . check wether user hit -C ** ** 'SyIsIntr' is called from the evaluator at regular intervals to check ** wether the user hit '-C' to interrupt a computation. ** ** 'SyIsIntr' returns 1 if the user typed '-C' and 0 otherwise. */ /**************************************************************************** ** *f SyIsIntr() ** ** For UNIX we install 'syAnswerIntr' to answer interrupt 'SIGINT'. If ** two interrupts occur within 1 second 'syAnswerIntr' exits GAP. */ #ifdef HAVE_SIGNAL static UInt syLastIntr; /* time of the last interrupt */ #ifdef HAVE_LIBREADLINE static Int doingReadline; #endif static void syAnswerIntr(int signr) { UInt nowIntr; #ifdef HAVE_LIBREADLINE /* ignore during readline */ if (doingReadline) return; #endif /* get the current wall clock time */ nowIntr = time(0); /* if the last '-C' was less than a second ago, exit GAP */ if ( syLastIntr && nowIntr-syLastIntr < 1 ) { fputs("gap: you hit '-C' twice in a second, goodbye.\n",stderr); SyExit( 1 ); } /* reinstall 'syAnswerIntr' as signal handler */ signal( SIGINT, syAnswerIntr ); siginterrupt( SIGINT, 0 ); /* remember time of this interrupt */ syLastIntr = nowIntr; #ifdef HAVE_SIGNAL /* interrupt the executor */ InterruptExecStat(); #endif } void SyInstallAnswerIntr ( void ) { if ( signal( SIGINT, SIG_IGN ) != SIG_IGN ) { signal( SIGINT, syAnswerIntr ); siginterrupt( SIGINT, 0 ); } } UInt SyIsIntr ( void ) { UInt isIntr; isIntr = (syLastIntr != 0); #ifdef HPCGAP /* The following write has to be conditional to avoid serious * performance degradation on shared memory (especially NUMA) * architectures when multiple threads all try to write to the same * location at the same time. Branch prediction can be expected to * be near perfect. */ if (isIntr) syLastIntr = 0; #else syLastIntr = 0; #endif return isIntr; } #endif /**************************************************************************** ** *F getwindowsize() . . . . . . . get screen size from termcap or TIOCGWINSZ ** ** For UNIX we install 'syWindowChangeIntr' to answer 'SIGWINCH'. */ #ifdef TIOCGWINSZ /* signal routine: window size changed */ void syWindowChangeIntr ( int signr ) { struct winsize win; if(ioctl(0, TIOCGWINSZ, (char *) &win) >= 0) { if(!SyNrRowsLocked && win.ws_row > 0) SyNrRows = win.ws_row; if(!SyNrColsLocked && win.ws_col > 0) SyNrCols = win.ws_col - 1; /* never trust last column */ if (SyNrCols < 20) SyNrCols = 20; if (SyNrCols > MAXLENOUTPUTLINE) SyNrCols = MAXLENOUTPUTLINE; } } #endif /* TIOCGWINSZ */ void getwindowsize( void ) { /* it might be that SyNrRows, SyNrCols have been set by the user with -x, -y */ /* otherwise they are zero */ /* first strategy: try to ask the operating system */ #ifdef TIOCGWINSZ if (SyNrRows <= 0 || SyNrCols <= 0) { struct winsize win; if(ioctl(0, TIOCGWINSZ, (char *) &win) >= 0) { if (SyNrRows <= 0) SyNrRows = win.ws_row; if (SyNrCols <= 0) SyNrCols = win.ws_col; } (void) signal(SIGWINCH, syWindowChangeIntr); } #endif /* TIOCGWINSZ */ #ifdef USE_TERMCAP /* note that if we define TERMCAP, this has to be linked with -ltermcap */ /* maybe that is -ltermlib on some SYSV machines */ if (SyNrRows <= 0 || SyNrCols <= 0) { /* this failed - next attempt: try to find info in TERMCAP */ char *sp; char bp[1024]; if ((sp = getenv("TERM")) != NULL && tgetent(bp,sp) == 1) { if(SyNrRows <= 0) SyNrRows = tgetnum("li"); if(SyNrCols <= 0) SyNrCols = tgetnum("co"); } } #endif /* if nothing worked, use 80x24 */ if (SyNrCols <= 0) SyNrCols = 80; if (SyNrRows <= 0) SyNrRows = 24; /* reset SyNrCols if value is strange */ if (SyNrCols < 20) SyNrCols = 20; if (SyNrCols > MAXLENOUTPUTLINE) SyNrCols = MAXLENOUTPUTLINE; } /**************************************************************************** ** *F * * * * * * * * * * * * * * * * * output * * * * * * * * * * * * * * * * * */ /**************************************************************************** ** *F syEchoch( , ) . . . . . . . . . . . echo a char to , local */ /**************************************************************************** ** *f syEchoch( , ) */ static void syEchoch(Int ch, Int fid) { Char ch2; /* write the character to the associate echo output device */ ch2 = ch; echoandcheck( fid, (char*)&ch2, 1 ); /* if running under a window handler, duplicate '@' */ if ( SyWindow && ch == '@' ) { ch2 = ch; echoandcheck( fid, (char*)&ch2, 1 ); } } /**************************************************************************** ** *F SyEchoch( , ) . . . . . . . . . . . . . echo a char from */ Int SyEchoch ( Int ch, Int fid ) { /* check file identifier */ if ( !SyBufInUse(fid) ) { return -1; } syEchoch(ch,fid); return 0; } /**************************************************************************** ** *F syEchos( , ) . . . . . . . . . . . echo a char to , local */ /**************************************************************************** ** *f syEchos( , ) */ static void syEchos(const Char * str, Int fid) { /* if running under a window handler, send the line to it */ if ( SyWindow && fid < 4 ) syWinPut( fid, (fid == 1 ? "@n" : "@f"), str ); /* otherwise, write it to the associate echo output device */ else echoandcheck(fid, str, strlen(str) ); } /**************************************************************************** ** *F SyFputs( , ) . . . . . . . . write a line to the file ** ** 'SyFputs' is called to put the to the file identified by . */ static UInt syNrchar; /* nr of chars already on the line */ static Char syPrompt[MAXLENOUTPUTLINE]; /* characters already on the line */ /**************************************************************************** ** *f SyFputs( , ) */ void SyFputs ( const Char * line, Int fid ) { UInt i; /* if outputing to the terminal compute the cursor position and length */ if ( fid == 1 || fid == 3 ) { syNrchar = 0; for ( i = 0; line[i] != '\0'; i++ ) { if ( line[i] == '\n' ) syNrchar = 0; else syPrompt[syNrchar++] = line[i]; } syPrompt[syNrchar] = '\0'; } /* otherwise compute only the length */ else { i = strlen(line); } /* if running under a window handler, send the line to it */ if ( SyWindow && fid < 4 ) syWinPut( fid, (fid == 1 ? "@n" : "@f"), line ); /* otherwise, write it to the output file */ else SyWriteandcheck(fid, line, i); } /**************************************************************************** ** *F * * * * * * * * * * * * * * * * * input * * * * * * * * * * * * * * * * * */ /**************************************************************************** ** *F SyFtell( ) . . . . . . . . . . . . . . . . . . position of stream */ Int SyFtell ( Int fid ) { /* check file identifier */ if ( !SyBufInUse(fid) ) { return -1; } Int ret; switch (syBuf[fid].type) { case raw_socket: ret = (Int)lseek(syBuf[fid].fp, 0, SEEK_CUR); break; case gzip_socket: ret = (Int)gzseek(syBuf[fid].gzfp, 0, SEEK_CUR); break; case unused_socket: default: return -1; } // Need to account for characters in buffer if (syBuf[fid].bufno >= 0) { UInt bufno = syBuf[fid].bufno; ret -= syBuffers[bufno].buflen - syBuffers[bufno].bufstart; } return ret; } /**************************************************************************** ** *F SyFseek( , ) . . . . . . . . . . . seek a position of stream */ Int SyFseek ( Int fid, Int pos ) { /* check file identifier */ if ( !SyBufInUse(fid) ) { return -1; } if (syBuf[fid].bufno >= 0) { UInt bufno = syBuf[fid].bufno; syBuffers[bufno].buflen = 0; syBuffers[bufno].bufstart = 0; } switch (syBuf[fid].type) { case raw_socket: return (Int)lseek(syBuf[fid].fp, pos, SEEK_SET); case gzip_socket: return (Int)gzseek(syBuf[fid].gzfp, pos, SEEK_SET); case unused_socket: default: return -1; } } /**************************************************************************** ** *F syGetchTerm( ) . . . . . . . . . . . . . . . . . get a char from ** ** 'SyGetchTerm' reads a character from , which is already switched ** to raw mode if it is *stdin* or *errin*. */ /**************************************************************************** ** *f syGetchTerm( ) . . . . . . . . . . . . . . . . . . . . . UNIX ** ** This version should be called if the input is stdin and command-line editing ** etc. is switched on. It handles possible messages from xgap and systems ** that return odd things rather than waiting for a key ** */ /* In the cygwin environment it is not predictable if text files get the * '\r' in their line ends filtered out *before* GAP sees them. This leads * to problem with continuation of strings or integers over several lines in * GAP input. Therefore we introduce a hack which removes such '\r's * before '\n's on such a system. Add here if there are other systems with * a similar problem. */ #ifdef SYS_IS_CYGWIN32 # define LINE_END_HACK 1 #endif Int SyRead(Int fid, void * ptr, size_t len) { /* check file identifier */ if ( !SyBufInUse(fid) ) { return -1; } if (syBuf[fid].type == gzip_socket) { return gzread(syBuf[fid].gzfp, ptr, len); } else { return read(syBuf[fid].fp, ptr, len); } } Int SyReadWithBuffer(Int fid, void * ptr, size_t len) { /* check file identifier */ if ( !SyBufInUse(fid) ) { return -1; } // first drain the buffer if (syBuf[fid].bufno >= 0) { UInt bufno = syBuf[fid].bufno; size_t avail = syBuffers[bufno].buflen - syBuffers[bufno].bufstart; if (avail > 0) { if (avail > len) avail = len; memcpy(ptr, syBuffers[bufno].buf + syBuffers[bufno].bufstart, avail); syBuffers[bufno].bufstart += avail; return avail; } } return SyRead(fid, ptr, len); } Int SyWrite(Int fid, const void * ptr, size_t len) { /* check file identifier */ if ( !SyBufInUse(fid) ) { return -1; } if (syBuf[fid].type == gzip_socket) { return gzwrite(syBuf[fid].gzfp, ptr, len); } else { return write(syBuf[fid].echo, ptr, len); } } static ssize_t SyWriteandcheck(Int fid, const void * buf, size_t count) { int ret; if (syBuf[fid].type == gzip_socket) { ret = gzwrite(syBuf[fid].gzfp, buf, count); if (ret < 0) { ErrorQuit( "Cannot write to compressed file, see 'LastSystemError();'\n", 0L, 0L); } } else { ret = write(syBuf[fid].fp, buf, count); if (ret < 0) { if (syBuf[fid].fp == fileno(stdout) || syBuf[fid].fp == fileno(stderr)) { Panic("Could not write to stdout/stderr."); } else { ErrorQuit("Cannot write to file descriptor %d, see " "'LastSystemError();'\n", syBuf[fid].fp, 0L); } } } return ret; } static Int syGetchTerm(Int fid) { UChar ch; Char str[2]; Int ret; /* retry on errors or end-of-file. Ignore 0 bytes */ #ifdef LINE_END_HACK tryagain: #endif while ( (ret = SyRead( fid, &ch, 1 )) == -1 && errno == EAGAIN ) ; if (ret <= 0) return EOF; /* if running under a window handler, handle special characters */ if ( SyWindow && ch == '@' ) { do { while ( (ret = SyRead(fid, &ch, 1)) == -1 && errno == EAGAIN ) ; if (ret <= 0) return EOF; } while ( ch < '@' || 'z' < ch ); if ( ch == 'y' ) { do { while ( (ret = SyRead(fid, &ch, 1)) == -1 && errno == EAGAIN ); if (ret <= 0) return EOF; } while ( ch < '@' || 'z' < ch ); str[0] = ch; str[1] = 0; syWinPut( syBuf[fid].echo, "@s", str ); ch = syGetchTerm(fid); } else if ( 'A' <= ch && ch <= 'Z' ) ch = CTR(ch); } #ifdef LINE_END_HACK /* A hack for non ANSI-C confirming systems which deliver \r or \r\n * line ends. These are translated to \n here. */ if (ch == '\n') { if (syBuf[fid].crlast) { syBuf[fid].crlast = 0; goto tryagain; } else return (UChar)'\n'; } if (ch == '\r') { syBuf[fid].crlast = 1; return (Int)'\n'; } // We saw a '\r' without a '\n' syBuf[fid].crlast = 0; #endif /* line end hack */ /* return the character */ return (Int)ch; } static Int syGetchNonTerm(Int fid) { UChar ch = 0; UInt bufno; int ret; /* we jump back here if the byte we just read was the \n of \r\n, in which case it doesn't count */ #ifdef LINE_END_HACK tryagain: #endif if (syBuf[fid].bufno < 0) while ((ret = SyRead(fid, &ch, 1)) == -1 && errno == EAGAIN) ; else { bufno = syBuf[fid].bufno; if (syBuffers[bufno].bufstart < syBuffers[bufno].buflen) { ch = syBuffers[bufno].buf[syBuffers[bufno].bufstart++]; ret = 1; } else { while ((ret = SyRead(fid, syBuffers[bufno].buf, SYS_FILE_BUF_SIZE)) == -1 && errno == EAGAIN) ; if (ret > 0) { ch = syBuffers[bufno].buf[0]; syBuffers[bufno].bufstart = 1; syBuffers[bufno].buflen = ret; } } } if (ret < 1) { syBuf[fid].ateof = 1; return EOF; } #ifdef LINE_END_HACK /* A hack for non ANSI-C confirming systems which deliver \r or \r\n * line ends. These are translated to \n here. */ if (ch == '\n') { if (syBuf[fid].crlast) { syBuf[fid].crlast = 0; goto tryagain; } else return (UChar)'\n'; } if (ch == '\r') { syBuf[fid].crlast = 1; return (Int)'\n'; } // We saw a '\r' without a '\n' syBuf[fid].crlast = 0; #endif /* line end hack */ /* return the character */ return (Int)ch; } /**************************************************************************** ** *f syGetch( ) */ static Int syGetch(Int fid) { if (syBuf[fid].isTTY) return syGetchTerm(fid); else return syGetchNonTerm(fid); } /**************************************************************************** ** *F SyGetch( ) . . . . . . . . . . . . . . . . . get a char from ** ** 'SyGetch' reads a character from , which is switch to raw mode if it ** is *stdin* or *errin*. */ Int SyGetch ( Int fid ) { Int ch; /* check file identifier */ if ( !SyBufInUse(fid) ) { return -1; } /* if we are reading stdin or errin use raw mode */ if ( fid == 0 || fid == 2 ) { syStartraw(fid); } ch = syGetch(fid); if ( fid == 0 || fid == 2 ) { syStopraw(fid); } return ch; } /**************************************************************************** ** *F SyFgets( , , ) . . . . . get a line from file ** ** 'SyFgets' is called to read a line from the file with identifier . ** 'SyFgets' (like 'fgets') reads characters until either -1 chars ** have been read or until a or an character is encoutered. ** It retains the '\n' (unlike 'gets'), if any, and appends '\0' to . ** 'SyFgets' returns if any char has been read, otherwise '(char*)0'. ** ** 'SyFgets' allows to edit the input line if the file refers to a ** terminal with the following commands: ** ** -A move the cursor to the beginning of the line. ** -B move the cursor to the beginning of the previous word. ** -B move the cursor backward one character. ** -F move the cursor forward one character. ** -F move the cursor to the end of the next word. ** -E move the cursor to the end of the line. ** ** -H, delete the character left of the cursor. ** -D delete the character under the cursor. ** -K delete up to the end of the line. ** -D delete forward to the end of the next word. ** - delete backward to the beginning of the last word. ** -X delete entire input line, and discard all pending input. ** -Y insert (yank) a just killed text. ** ** -T exchange (twiddle) current and previous character. ** -U uppercase next word. ** -L lowercase next word. ** -C capitalize next word. ** ** complete the identifier before the cursor. ** -L insert last input line before current character. ** -P redisplay the last input line, another -P will redisplay ** the line before that, etc. If the cursor is not in the first ** column only the lines starting with the string to the left of ** the cursor are taken. The history is limitied to ~8000 chars. ** -N Like -P but goes the other way round through the history ** -< goes to the beginning of the history. ** -> goes to the end of the history. ** -O accept this line and perform a -N. ** ** -V enter next character literally. ** -U execute the next command 4 times. ** - execute the next command times. ** --L repaint input line. ** ** Not yet implemented commands: ** ** -S search interactive for a string forward. ** -R search interactive for a string backward. ** -Y replace yanked string with previously killed text. ** -_ undo a command. ** -T exchange two words. */ static UInt syCTRO; /* number of '-O' pending */ static UInt syESCN; /* number of '-N' pending */ static UInt FreezeStdin; // When true, ignore if any new input from stdin // This is used to stop HPC-GAP from reading stdin // while forked subprocesses are running. #ifdef HAVE_SELECT static Obj OnCharReadHookActive = 0; /* if bound the hook is active */ static Obj OnCharReadHookInFds = 0; /* a list of UNIX file descriptors for reading */ static Obj OnCharReadHookInFuncs = 0; /* a list of GAP functions with 0 args */ static Obj OnCharReadHookOutFds = 0; /* a list of UNIX file descriptors for writing */ static Obj OnCharReadHookOutFuncs = 0;/* a list of GAP functions with 0 args */ static Obj OnCharReadHookExcFds = 0; /* a list of UNIX file descriptors */ static Obj OnCharReadHookExcFuncs = 0;/* a list of GAP functions with 0 args */ static Int OnCharReadHookActiveCheck(void) { return OnCharReadHookActive != 0 || FreezeStdin != 0; } static void HandleCharReadHook(int stdinfd) /* This is called directly before a character is read from stdin in the case * of an interactive session with command line editing. We have to return * as soon as stdin is ready to read! We just use `select' and care for * handlers for streams. */ { fd_set infds,outfds,excfds; int n,maxfd; Int i,j; Obj o; static int WeAreAlreadyInHere = 0; /* Just to make sure: */ if (WeAreAlreadyInHere) return; WeAreAlreadyInHere = 1; while (1) { /* breaks when fd becomes ready */ FD_ZERO(&infds); FD_ZERO(&outfds); FD_ZERO(&excfds); FD_SET(stdinfd,&infds); maxfd = stdinfd; /* Handle input file descriptors: */ if (OnCharReadHookInFds != (Obj) 0 && IS_PLIST(OnCharReadHookInFds) && OnCharReadHookInFuncs != (Obj) 0 && IS_PLIST(OnCharReadHookInFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookInFds);i++) { o = ELM_PLIST(OnCharReadHookInFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) { j = INT_INTOBJ(o); /* a UNIX file descriptor */ FD_SET(j,&infds); if (j > maxfd) maxfd = j; } } } /* Handle output file descriptors: */ if (OnCharReadHookOutFds != (Obj) 0 && IS_PLIST(OnCharReadHookOutFds) && OnCharReadHookOutFuncs != (Obj) 0 && IS_PLIST(OnCharReadHookOutFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookOutFds);i++) { o = ELM_PLIST(OnCharReadHookOutFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) { j = INT_INTOBJ(o); /* a UNIX file descriptor */ FD_SET(j,&outfds); if (j > maxfd) maxfd = j; } } } /* Handle exception file descriptors: */ if (OnCharReadHookExcFds != (Obj) 0 && IS_PLIST(OnCharReadHookExcFds) && OnCharReadHookExcFuncs != (Obj) 0 && IS_PLIST(OnCharReadHookExcFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookExcFds);i++) { o = ELM_PLIST(OnCharReadHookExcFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) { j = INT_INTOBJ(o); /* a UNIX file descriptor */ FD_SET(j,&excfds); if (j > maxfd) maxfd = j; } } } n = select(maxfd+1,&infds,&outfds,&excfds,NULL); if (n >= 0) { /* Now run through the lists and call functions if ready: */ if (OnCharReadHookInFds != (Obj) 0 && IS_PLIST(OnCharReadHookInFds) && OnCharReadHookInFuncs != (Obj) 0 && IS_PLIST(OnCharReadHookInFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookInFds);i++) { o = ELM_PLIST(OnCharReadHookInFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) { j = INT_INTOBJ(o); /* a UNIX file descriptor */ if (FD_ISSET(j,&infds)) { o = ELM_PLIST(OnCharReadHookInFuncs,i); if (o != (Obj) 0 && IS_FUNC(o)) Call1ArgsInNewReader(o,INTOBJ_INT(i)); } } } } /* Handle output file descriptors: */ if (OnCharReadHookOutFds != (Obj) 0 && IS_PLIST(OnCharReadHookOutFds) && OnCharReadHookOutFuncs != (Obj) 0 && IS_PLIST(OnCharReadHookOutFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookOutFds);i++) { o = ELM_PLIST(OnCharReadHookOutFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) { j = INT_INTOBJ(o); /* a UNIX file descriptor */ if (FD_ISSET(j,&outfds)) { o = ELM_PLIST(OnCharReadHookOutFuncs,i); if (o != (Obj) 0 && IS_FUNC(o)) Call1ArgsInNewReader(o,INTOBJ_INT(i)); } } } } /* Handle exception file descriptors: */ if (OnCharReadHookExcFds != (Obj) 0 && IS_PLIST(OnCharReadHookExcFds) && OnCharReadHookExcFuncs != (Obj) 0 && IS_PLIST(OnCharReadHookExcFuncs)) { for (i = 1;i <= LEN_PLIST(OnCharReadHookExcFds);i++) { o = ELM_PLIST(OnCharReadHookExcFds,i); if (o != (Obj) 0 && IS_INTOBJ(o)) { j = INT_INTOBJ(o); /* a UNIX file descriptor */ if (FD_ISSET(j,&excfds)) { o = ELM_PLIST(OnCharReadHookExcFuncs,i); if (o != (Obj) 0 && IS_FUNC(o)) Call1ArgsInNewReader(o,INTOBJ_INT(i)); } } } } // Return if there is input to read from stdin, // and FreezeStdin is false. if (FD_ISSET(stdinfd, &infds) && !FreezeStdin) { WeAreAlreadyInHere = 0; break; } } else break; } /* while (1) */ } #endif /* HAVE_SELECT */ /*************************************************************************** ** *F HasAvailableBytes( ) returns positive if a subsequent read to ** will read at least one byte without blocking ** */ Int HasAvailableBytes( UInt fid ) { UInt bufno; if (!SyBufInUse(fid)) return -1; if (syBuf[fid].bufno >= 0) { bufno = syBuf[fid].bufno; if (syBuffers[bufno].bufstart < syBuffers[bufno].buflen) return 1; } #ifdef HAVE_SELECT // All sockets other than raw sockets are always ready if (syBuf[fid].type == raw_socket) { fd_set set; struct timeval tv; FD_ZERO( &set); FD_SET( syBuf[fid].fp, &set ); tv.tv_sec = 0; tv.tv_usec = 0; return select( syBuf[fid].fp + 1, &set, NULL, NULL, &tv); } #endif /* best guess */ Int ret = SyIsEndOfFile(fid); return (ret != -1 && ret != 1); } static Char * syFgetsNoEdit(Char * line, UInt length, Int fid, UInt block) { UInt x = 0; int ret = 0; /* if stream is buffered, and the buffer has a full line, * grab it -- we could make more use of the buffer, but * this covers the majority of cases simply. */ #ifndef LINE_END_HACK UInt bufno; Char* newlinepos; Char* bufstart; int buflen; if(!syBuf[fid].isTTY && syBuf[fid].bufno >= 0) { bufno = syBuf[fid].bufno; if (syBuffers[bufno].bufstart < syBuffers[bufno].buflen) { bufstart = syBuffers[bufno].buf + syBuffers[bufno].bufstart; buflen = syBuffers[bufno].buflen - syBuffers[bufno].bufstart; newlinepos = memchr(bufstart, '\n', buflen); if(newlinepos && (newlinepos - bufstart) < length - 2) { newlinepos++; memcpy(line, bufstart, newlinepos - bufstart); line[newlinepos - bufstart] = '\0'; syBuffers[bufno].bufstart += (newlinepos - bufstart); return line; } } } #endif while (x < length -1) { if (!block && x && !HasAvailableBytes( fid )) { break; } ret = syGetch(fid); if (ret == EOF) break; if ((line[x++] = ret) == '\n') break; } line[x] = '\0'; syBuf[fid].ateof = (ret == EOF); if (x) return line; else return NULL; } /* will be imported from library, first is generic function which does some checks before returning result to kernel, the second is the list of handler functions which do the actual work. */ static Obj LineEditKeyHandler; static Obj LineEditKeyHandlers; static Obj GAPInfo; #ifdef HAVE_LIBREADLINE /* we import GAP level functions from GAPInfo components */ static Obj CLEFuncs; static Obj KeyHandler; static int GAPMacroNumber = 0; static int GAP_set_macro(int count, int key) { GAPMacroNumber = count; return 0; } /* a generic rl_command_func_t that delegates to GAP level */ static int GAP_rl_func(int count, int key) { Obj rldata, linestr, okey, res, obj, data, beginchange, endchange, m; Int len, n, hook, dlen, max, i; /* we shift indices 0-based on C-level and 1-based on GAP level */ linestr = MakeString(rl_line_buffer); okey = INTOBJ_INT(key + 1000*GAPMacroNumber); GAPMacroNumber = 0; rldata = NEW_PLIST(T_PLIST, 6); if (GAP_rl_func == rl_last_func) { SET_LEN_PLIST(rldata, 6); SET_ELM_PLIST(rldata, 6, True); } else SET_LEN_PLIST(rldata, 5); SET_ELM_PLIST(rldata, 1, INTOBJ_INT(count)); SET_ELM_PLIST(rldata, 2, okey); SET_ELM_PLIST(rldata, 3, linestr); SET_ELM_PLIST(rldata, 4, INTOBJ_INT(rl_point+1)); SET_ELM_PLIST(rldata, 5, INTOBJ_INT(rl_mark+1)); res = Call1ArgsInNewReader(KeyHandler, rldata); if (!res) return 0; if (!IS_LIST(res)) return 0; len = LEN_LIST(res); if (len == 0) return 0; obj = ELM_LIST(res, 1); if (IsStringConv(obj)) { /* insert txt */ rl_insert_text(CONST_CSTR_STRING(obj)); n = 1; } else if ((obj == True || obj == False) && len > 2) { /* kill or delete text */ beginchange = ELM_LIST(res, 2); if (!IS_INTOBJ(beginchange)) return 0; endchange = ELM_LIST(res, 3); if (!IS_INTOBJ(endchange)) return 0; if (obj == True) rl_kill_text(INT_INTOBJ(beginchange)-1, INT_INTOBJ(endchange)-1); else rl_delete_text(INT_INTOBJ(beginchange)-1, INT_INTOBJ(endchange)-1); n = 3; } else if (IS_INTOBJ(obj) && len > 2) { /* delete some text and insert */ beginchange = obj; endchange = ELM_LIST(res, 2); if (!IS_INTOBJ(endchange)) return 0; obj = ELM_LIST(res, 3); if (!IsStringConv(obj)) return 0; rl_begin_undo_group(); rl_delete_text(INT_INTOBJ(beginchange)-1, INT_INTOBJ(endchange)-1); rl_point = INT_INTOBJ(beginchange)-1; rl_insert_text(CONST_CSTR_STRING(obj)); rl_end_undo_group(); n = 3; } else if (IS_INTOBJ(obj) && len == 2) { /* several hooks to particular rl_ functions with data */ hook = INT_INTOBJ(obj); data = ELM_LIST(res, 2); if (hook == 1) { /* display matches */ if (!IS_LIST(data)) return 0; /* -1, because first is word to be completed */ dlen = LEN_LIST(data)-1; /* +2, must be in 'argv' format, terminated by 0 */ char **strs = (char**)calloc(dlen+2, sizeof(char*)); max = 0; for (i=0; i <= dlen; i++) { if (!IsStringConv(ELM_LIST(data, i+1))) { free(strs); return 0; } strs[i] = CSTR_STRING(ELM_LIST(data, i+1)); if (max < strlen(strs[i])) max = strlen(strs[i]); } rl_display_match_list(strs, dlen, max); free(strs); rl_on_new_line(); } else if (hook == 2) { /* put these characters into sequence of input keys */ if (!IsStringConv(data)) return 0; dlen = strlen(CSTR_STRING(data)); for (i=0; i < dlen; i++) rl_stuff_char(CSTR_STRING(data)[i]); } n = 2; } else if (IS_INTOBJ(obj) && len == 1) { /* several hooks to particular rl_ functions with no data */ hook = INT_INTOBJ(obj); /* ring bell */ if (hook == 100) rl_ding(); /* return line (execute Ctrl-m) */ else if (hook == 101) rl_execute_next(13); n = 1; } else n = 0; /* optionally we can return the new point, or new point and mark */ if (len > n) { n++; m = ELM_LIST(res, n); if (IS_INTOBJ(m)) rl_point = INT_INTOBJ(m) - 1; } if (len > n) { n++; m = ELM_LIST(res, n); if (IS_INTOBJ(m)) rl_mark = INT_INTOBJ(m) - 1; } return 0; } static Obj FuncBINDKEYSTOGAPHANDLER(Obj self, Obj keys) { Char* seq; if (!IsStringConv(keys)) return False; seq = CSTR_STRING(keys); rl_bind_keyseq(seq, GAP_rl_func); return True; } static Obj FuncBINDKEYSTOMACRO(Obj self, Obj keys, Obj macro) { Char *seq, *macr; if (!IsStringConv(keys)) return False; if (!IsStringConv(macro)) return False; seq = CSTR_STRING(keys); macr = CSTR_STRING(macro); rl_generic_bind(ISMACR, seq, macr, rl_get_keymap()); return True; } static Obj FuncREADLINEINITLINE(Obj self, Obj line) { Char *cline; if (!IsStringConv(line)) return False; cline = CSTR_STRING(line); rl_parse_and_bind(cline); return True; } /* init is needed once */ static Int ISINITREADLINE = 0; /* a hook function called regularly while waiting on input */ static Int current_rl_fid; static int charreadhook_rl(void) { #ifdef HAVE_SELECT if (OnCharReadHookActiveCheck()) HandleCharReadHook(syBuf[current_rl_fid].fp); #endif return 0; } static void initreadline(void) { /* allows users to configure GAP specific settings in their ~/.inputrc like: $if GAP .... $endif */ rl_readline_name = "GAP"; /* this should pipe signals through to GAP */ rl_already_prompted = 1 ; rl_catch_signals = 0; rl_catch_sigwinch = 1; /* hook to read from other channels */ rl_event_hook = 0; /* give GAP_rl_func a name that can be used in .inputrc */ rl_add_defun( "handled-by-GAP", GAP_rl_func, -1 ); rl_bind_keyseq("\\C-x\\C-g", GAP_set_macro); CLEFuncs = ELM_REC(GAPInfo, RNamName("CommandLineEditFunctions")); KeyHandler = ELM_REC(CLEFuncs, RNamName("KeyHandler")); ISINITREADLINE = 1; } static Char * readlineFgets(Char * line, UInt length, Int fid, UInt block) { char * rlres = (char*)NULL; current_rl_fid = fid; if (!ISINITREADLINE) initreadline(); /* read at most as much as we can buffer */ rl_num_chars_to_read = length-2; #ifdef HAVE_SELECT /* hook to read from other channels */ rl_event_hook = (OnCharReadHookActiveCheck()) ? charreadhook_rl : 0; #endif /* now do the real work */ doingReadline = 1; rlres = readline(STATE(Prompt)); doingReadline = 0; /* we get a NULL pointer on EOF, say by pressing Ctr-d */ if (!rlres) { if (!SyCTRD) { while (!rlres) rlres = readline(STATE(Prompt)); } else { printf("\n");fflush(stdout); line[0] = '\0'; return (Char*)0; } } /* maybe add to history, we use key 0 for this function */ GAP_rl_func(0, 0); strlcpy(line, rlres, length); // FIXME: handle the case where rlres contains more than length // characters better? free(rlres); strlcat(line, "\n", length); /* send the whole line (unclipped) to the window handler */ syWinPut( fid, (*line != '\0' ? "@r" : "@x"), line ); return line; } #endif #ifdef HPCGAP static GVarDescriptor GVarBeginEdit, GVarEndEdit; static Int syBeginEdit(Int fid) { Obj func = GVarFunction(&GVarBeginEdit); Obj result; if (!func) return syStartraw(fid); result = CALL_1ARGS(func, INTOBJ_INT(fid)); return result != False && result != Fail && result != INTOBJ_INT(0); } static Int syEndEdit(Int fid) { Obj func = GVarFunction(&GVarEndEdit); Obj result; if (!func) { syStopraw(fid); return 1; } result = CALL_1ARGS(func, INTOBJ_INT(fid)); return result != False && result != Fail && result != INTOBJ_INT(0); } #else #define syBeginEdit(fid) syStartraw(fid) #define syEndEdit(fid) syStopraw(fid) #endif static Char * syFgets(Char * line, UInt length, Int fid, UInt block) { Int ch, ch2, ch3, last; Char * p, * q, * r, * s, * t; static Char yank [32768]; Char old [512], new [512]; Int oldc, newc; Int rep, len; Char buffer [512]; Int rn; Int rubdel; Obj linestr, yankstr, args, res; /* check file identifier */ if ( !SyBufInUse(fid) ) { return (Char*)0; } /* no line editing if the file is not '*stdin*' or '*errin*' */ if ( fid != 0 && fid != 2 ) { p = syFgetsNoEdit(line, length, fid, block); return p; } /* no line editing if the user disabled it or we can't make it into raw mode */ if ( SyLineEdit == 0 || ! syBeginEdit(fid) ) { p = syFgetsNoEdit(line, length, fid, block ); return p; } #ifdef HAVE_LIBREADLINE if (SyUseReadline) { /* switch back to cooked mode */ if ( SyLineEdit ) syEndEdit(fid); p = readlineFgets(line, length, fid, block); if ( EndLineHook ) Call0ArgsInNewReader( EndLineHook ); if (!p) return p; else return line; } #endif /* In line editing mode 'length' is not allowed bigger than the yank buffer (= length of line buffer for input files).*/ if (length > 32768) ErrorQuit("Cannot handle lines with more than 32768 characters in line edit mode.",0,0); /* the line starts out blank */ line[0] = '\0'; p = line; for ( q = old; q < old+sizeof(old); ++q ) *q = ' '; oldc = 0; last = 0; ch = 0; rubdel=0; /* do we want to east a `del' character? */ while ( 1 ) { /* get a character, handle V, and U */ rep = 1; ch2 = 0; do { if ( syESCN > 0 ) { if (ch == Esc('N')) {ch = '\n'; syESCN--; } else {ch = Esc('N'); } } else if ( syCTRO % 2 == 1 ) { ch = CTR('N'); syCTRO = syCTRO - 1; } else if ( syCTRO != 0 ) { ch = CTR('O'); rep = syCTRO / 2; } else { #ifdef HAVE_SELECT if (OnCharReadHookActiveCheck()) HandleCharReadHook(syBuf[fid].fp); #endif ch = syGetch(fid); } if ( ch2==0 && ch==CTR('V') ) { ch2=ch; ch=0;} if ( ch2==0 && ch==CTR('[') ) { ch2=ch; ch=0;} if ( ch2==0 && ch==CTR('U') ) { ch2=ch; ch=0;} if ( ch2==CTR('[') && ch==CTR('V') ) { ch2=Esc(CTR('V')); ch=0;} if ( ch2==CTR('[') && IsDigit(ch) ) { rep=ch-'0'; ch2=ch; ch=0;} if ( ch2==CTR('[') && ch=='[' ) { ch2=ch; ch=0;} if ( ch2==CTR('U') && ch==CTR('V') ) { rep=4*rep; ch2=ch; ch=0;} if ( ch2==CTR('U') && ch==CTR('[') ) { rep=4*rep; ch2=ch; ch=0;} if ( ch2==CTR('U') && ch==CTR('U') ) { rep=4*rep; ch2=ch; ch=0;} if ( ch2==CTR('U') && IsDigit(ch) ) { rep=ch-'0'; ch2=ch; ch=0;} if ( IsDigit(ch2) && ch==CTR('V') ) { ch2=ch; ch=0;} if ( IsDigit(ch2) && ch==CTR('[') ) { ch2=ch; ch=0;} if ( IsDigit(ch2) && ch==CTR('U') ) { ch2=ch; ch=0;} if ( IsDigit(ch2) && IsDigit(ch) ) { rep=10*rep+ch-'0'; ch=0;} /* get rid of tilde in windows commands */ if (rubdel==1) { if ( ch==126 ) {ch2=0;ch=0;}; rubdel=0; } } while ( ch == 0 ); if ( ch2==CTR('V') ) ch = CTV(ch); if ( ch2==Esc(CTR('V')) ) ch = CTV(ch | 0x80); if ( ch2==CTR('[') ) ch = Esc(ch); if ( ch2==CTR('U') ) rep = 4*rep; /* windows keys */ if ( ch2=='[' && ch=='A') ch = CTR('P'); if ( ch2=='[' && ch=='B') ch = CTR('N'); if ( ch2=='[' && ch=='C') ch = CTR('F'); if ( ch2=='[' && ch=='D') ch = CTR('B'); if ( ch2=='[' && ch=='1') { ch = CTR('A');rubdel=1;} /* home */ if ( ch2=='[' && ch=='3') { ch = CTR('D');rubdel=1;} /* del */ if ( ch2=='[' && ch=='4') { ch = CTR('E');rubdel=1;} /* end */ if ( ch2=='[' && ch=='5') { ch = CTR('P');rubdel=1;} /* pgup */ if ( ch2=='[' && ch=='6') { ch = CTR('N');rubdel=1;} /* pgdwn */ /* now perform the requested action times in the input line */ while ( rep-- > 0 ) { /* check for key handler on GAP level */ Int runLineEditKeyHandler = 0; if (ch >= 0) { #ifdef HPCGAP RegionReadLock(REGION(LineEditKeyHandlers)); #endif runLineEditKeyHandler = ch < LEN_PLIST(LineEditKeyHandlers) && ELM_PLIST(LineEditKeyHandlers, ch + 1) != 0; #ifdef HPCGAP RegionUnlock(REGION(LineEditKeyHandlers)); #endif } if (runLineEditKeyHandler) { /* prepare data for GAP handler: [linestr, ch, ppos, length, yankstr] GAP handler must return new [linestr, ppos, yankstr] or an integer, interpreted as number of Esc('N') calls for the next lines. */ linestr = MakeString(line); yankstr = MakeString(yank); args = NEW_PLIST(T_PLIST, 5); SET_LEN_PLIST(args, 5); SET_ELM_PLIST(args,1,linestr); SET_ELM_PLIST(args,2,INTOBJ_INT(ch)); SET_ELM_PLIST(args,3,INTOBJ_INT((p-line)+1)); SET_ELM_PLIST(args,4,INTOBJ_INT(length)); SET_ELM_PLIST(args,5,yankstr); res = Call1ArgsInNewReader(LineEditKeyHandler, args); if (IS_INTOBJ(res)){ syESCN = INT_INTOBJ(res); ch = Esc('N'); SET_ELM_PLIST(args,2,INTOBJ_INT(ch)); res = Call1ArgsInNewReader(LineEditKeyHandler, args); } if (IS_BAG_REF(res) && IS_LIST(res) && LEN_LIST(res) == 3) { linestr = ELM_LIST(res,1); len = GET_LEN_STRING(linestr); memcpy(line,CONST_CHARS_STRING(linestr),len); line[len] = '\0'; p = line + (INT_INTOBJ(ELM_LIST(res,2)) - 1); yankstr = ELM_LIST(res,3); len = GET_LEN_STRING(yankstr); memcpy(yank,CONST_CHARS_STRING(yankstr),len); yank[len] = '\0'; } } else { switch ( ch ) { case CTR('A'): /* move cursor to the start of the line */ while ( p > line ) --p; break; case Esc('B'): /* move cursor one word to the left */ case Esc('b'): if ( p > line ) do { --p; } while ( p>line && (!IS_SEP(*(p-1)) || IS_SEP(*p))); break; case CTR('B'): /* move cursor one character to the left */ if ( p > line ) --p; break; case CTR('F'): /* move cursor one character to the right */ if ( *p != '\0' ) ++p; break; case Esc('F'): /* move cursor one word to the right */ case Esc('f'): if ( *p != '\0' ) do { ++p; } while ( *p!='\0' && (IS_SEP(*(p-1)) || !IS_SEP(*p))); break; case CTR('E'): /* move cursor to the end of the line */ while ( *p != '\0' ) ++p; break; case CTR('H'): /* delete the character left of the cursor */ case 127: if ( p == line ) break; --p; /* let '-D' do the work */ case CTR('D'): /* delete the character at the cursor */ /* on an empty line '-D' is */ if ( p == line && *p == '\0' && SyCTRD && !rubdel ) { ch = EOF; rep = 0; break; } if ( *p != '\0' ) { for ( q = p; *(q+1) != '\0'; ++q ) *q = *(q+1); *q = '\0'; } break; case CTR('X'): /* delete the line */ p = line; /* let '-K' do the work */ case CTR('K'): /* delete to end of line */ if ( last!=CTR('X') && last!=CTR('K') && last!=Esc(127) && last!=Esc('D') && last!=Esc('d') ) yank[0] = '\0'; for ( r = yank; *r != '\0'; ++r ) ; for ( s = p; *s != '\0'; ++s ) r[s-p] = *s; r[s-p] = '\0'; *p = '\0'; break; case Esc(127): /* delete the word left of the cursor */ q = p; if ( p > line ) do { --p; } while ( p>line && (!IS_SEP(*(p-1)) || IS_SEP(*p))); if ( last!=CTR('X') && last!=CTR('K') && last!=Esc(127) && last!=Esc('D') && last!=Esc('d') ) yank[0] = '\0'; for ( r = yank; *r != '\0'; ++r ) ; for ( ; yank <= r; --r ) r[q-p] = *r; for ( s = p; s < q; ++s ) yank[s-p] = *s; for ( r = p; *q != '\0'; ++q, ++r ) *r = *q; *r = '\0'; break; case Esc('D'): /* delete the word right of the cursor */ case Esc('d'): q = p; if ( *q != '\0' ) do { ++q; } while ( *q!='\0' && (IS_SEP(*(q-1)) || !IS_SEP(*q))); if ( last!=CTR('X') && last!=CTR('K') && last!=Esc(127) && last!=Esc('D') && last!=Esc('d') ) yank[0] = '\0'; for ( r = yank; *r != '\0'; ++r ) ; for ( s = p; s < q; ++s ) r[s-p] = *s; r[s-p] = '\0'; for ( r = p; *q != '\0'; ++q, ++r ) *r = *q; *r = '\0'; break; case CTR('T'): /* twiddle characters */ if ( p == line ) break; if ( *p == '\0' ) --p; if ( p == line ) break; ch2 = *(p-1); *(p-1) = *p; *p = ch2; ++p; break; case CTR('Y'): /* insert (yank) deleted text */ if (strlen(yank) + strlen(line) - 2 > length) { syEchoch(CTR('G'), fid); break; } for ( r = yank; *r != '\0' && *r != '\n'; ++r ) { ch2 = *r; for ( q = p; ch2; ++q ) { ch3 = *q; *q = ch2; ch2 = ch3; } *q = '\0'; ++p; } break; case Esc('U'): /* uppercase word */ case Esc('u'): if ( *p != '\0' ) do { if ('a' <= *p && *p <= 'z') *p = *p + 'A' - 'a'; ++p; } while ( *p!='\0' && (IS_SEP(*(p-1)) || !IS_SEP(*p))); break; case Esc('C'): /* capitalize word */ case Esc('c'): while ( *p!='\0' && IS_SEP(*p) ) ++p; if ( 'a' <= *p && *p <= 'z' ) *p = *p + 'A'-'a'; if ( *p != '\0' ) ++p; /* lowercase rest of the word */ case Esc('L'): /* lowercase word */ case Esc('l'): if ( *p != '\0' ) do { if ('A' <= *p && *p <= 'Z') *p = *p + 'a' - 'A'; ++p; } while ( *p!='\0' && (IS_SEP(*(p-1)) || !IS_SEP(*p))); break; case Esc(CTR('L')): /* repaint input line */ syEchoch('\n',fid); for ( q = syPrompt; q < syPrompt+syNrchar; ++q ) syEchoch( *q, fid ); for ( q = old; q < old+sizeof(old); ++q ) *q = ' '; oldc = 0; break; case EOF: /* end of file on input */ break; case CTR('M'): /* (same as '\r', '\n') append \n and exit */ case CTR('J'): while ( *p != '\0' ) ++p; *p++ = '\n'; *p = '\0'; rep = 0; break; case CTR('O'): /* accept line, perform '-N' next time */ while ( *p != '\0' ) ++p; *p++ = '\n'; *p = '\0'; syCTRO = 2 * rep + 1; rep = 0; break; case CTR('I'): /* try to complete the identifier before dot */ if ( p == line || IS_SEP(p[-1]) ) { /* If we don't have an identifier to complete, insert a tab */ ch2 = ch & 0xff; for ( q = p; ch2; ++q ) { ch3 = *q; *q = ch2; ch2 = ch3; } *q = '\0'; ++p; } else { /* Here is actually a bug, because it is not checked if the results leaves 'line' shorter than 'length'. But we ignore this problem assuming that interactive input lines are much shorter than 32768 characters. */ /* Locate in q the current identifier */ if ( (q = p) > line ) do { --q; } while ( q>line && (!IS_SEP(*(q-1)) || IS_SEP(*q))); /* determine if the thing immediately before the current identifier is a . */ rn = (line < q && *(q-1) == '.' && (line == q-1 || *(q-2) != '.')); /* Copy the current identifier into buffer */ r = buffer; s = q; while ( s < p ) *r++ = *s++; *r = '\0'; if ( (rn ? iscomplete_rnam( buffer, p-q ) : iscomplete_gvar( buffer, p-q )) ) { /* Complete already, just beep for single tab */ if ( last != CTR('I') ) syEchoch( CTR('G'), fid ); else { /* Double tab after a complete identifier print list of completions */ syWinPut( fid, "@c", "" ); syEchos( "\n ", fid ); syEchos( buffer, fid ); while ( (rn ? completion_rnam( buffer, p-q ) : completion_gvar( buffer, p-q )) ) { syEchos( "\n ", fid ); syEchos( buffer, fid ); } syEchos( "\n", fid ); /* Reprint the prompt and input line so far */ for ( q=syPrompt; q= new+SyNrCols-syNrchar-2 ) { if ( q >= p ) { q++; break; } new[0] = '$'; new[1] = r[-5]; new[2] = r[-4]; new[3] = r[-3]; new[4] = r[-2]; new[5] = r[-1]; r = new+6; } } if ( q == p ) newc = r-new; for ( ; r < new+sizeof(new); ++r ) *r = ' '; if ( q[0] != '\0' && q[1] != '\0' ) new[SyNrCols-syNrchar-2] = '$'; else if ( q[1] == '\0' && ' ' <= *q && *q < 0x7F ) new[SyNrCols-syNrchar-2] = *q; else if ( q[1] == '\0' && q[0] != '\0' ) new[SyNrCols-syNrchar-2] = '$'; for ( q = old, r = new; r < new+sizeof(new); ++r, ++q ) { if ( *q == *r ) continue; while (oldc<(q-old)) { syEchoch(old[oldc],fid); ++oldc; } while (oldc>(q-old)) { syEchoch('\b',fid); --oldc; } *q = *r; syEchoch( *q, fid ); ++oldc; } while ( oldc < newc ) { syEchoch(old[oldc],fid); ++oldc; } while ( oldc > newc ) { syEchoch('\b',fid); --oldc; } } if (line[1] != '\0') { /* Now we put the new string into the history, we use key handler with key 0 to update the command line history */ linestr = MakeString(line); args = NEW_PLIST(T_PLIST, 5); SET_LEN_PLIST(args, 5); SET_ELM_PLIST(args, 1, linestr); SET_ELM_PLIST(args, 2, INTOBJ_INT(0)); SET_ELM_PLIST(args, 3, INTOBJ_INT(1)); SET_ELM_PLIST(args, 4, INTOBJ_INT(length)); SET_ELM_PLIST(args, 5, linestr); Call1ArgsInNewReader(LineEditKeyHandler, args); } /* send the whole line (unclipped) to the window handler */ syWinPut( fid, (*line != '\0' ? "@r" : "@x"), line ); /* switch back to cooked mode */ if ( SyLineEdit == 1 ) syEndEdit(fid); /* return the line (or '0' at end-of-file) */ if ( *line == '\0' ) return (Char*)0; return line; } Char * SyFgets ( Char * line, UInt length, Int fid) { return syFgets( line, length, fid, 1); } Char *SyFgetsSemiBlock ( Char * line, UInt length, Int fid) { return syFgets( line, length, fid, 0); } /**************************************************************************** ** *F * * * * * * * * * * * * system error messages * * * * * * * * * * * * * * */ /**************************************************************************** ** *V SyLastErrorNo . . . . . . . . . . . . . . . . . . . . . last error number */ Int SyLastErrorNo; /**************************************************************************** ** *V SyLastErrorMessage . . . . . . . . . . . . . . . . . last error message */ Char SyLastErrorMessage [ 1024 ]; /**************************************************************************** ** *F SyClearErrorNo() . . . . . . . . . . . . . . . . . clear error messages */ void SyClearErrorNo ( void ) { errno = 0; SyLastErrorNo = 0; strxcpy( SyLastErrorMessage, "no error", sizeof(SyLastErrorMessage) ); } /**************************************************************************** ** *F SySetErrorNo() . . . . . . . . . . . . . . . . . . . . set error message */ void SySetErrorNo ( void ) { const Char * err; if ( errno != 0 ) { SyLastErrorNo = errno; err = strerror(errno); strxcpy( SyLastErrorMessage, err, sizeof(SyLastErrorMessage) ); } else { SyClearErrorNo(); } } /**************************************************************************** ** *F * * * * * * * * * * * * * file and execution * * * * * * * * * * * * * * * */ /**************************************************************************** ** *F SyExecuteProcess( , , , , ) . . . . new process ** ** Start in directory with standard input connected to , ** standard output connected to and arguments. No path search is ** performed, the return value of the process is returned if the operation ** system supports such a concept. */ /**************************************************************************** ** *f SyExecuteProcess( , , , , ) */ #if defined(HAVE_FORK) || defined(HAVE_VFORK) #ifndef WEXITSTATUS # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) #endif #ifndef WIFEXITED # define WIFEXITED(stat_val) (((stat_val) & 255) == 0) #endif #ifdef SYS_IS_CYGWIN32 UInt SyExecuteProcess ( Char * dir, Char * prg, Int in, Int out, Char * args[] ) { int savestdin, savestdout; Int tin, tout; int res; /* change the working directory */ if ( chdir(dir) == -1 ) return -1; /* if is -1 open "/dev/null" */ if ( in == -1 ) tin = open( "/dev/null", O_RDONLY ); else tin = SyBufFileno(in); if ( tin == -1 ) return -1; /* if is -1 open "/dev/null" */ if ( out == -1 ) tout = open( "/dev/null", O_WRONLY ); else tout = SyBufFileno(out); if ( tout == -1 ) { if (in == -1) close(tin); return -1; } /* set standard input to , standard output to */ savestdin = -1; /* Just to please the compiler */ if ( tin != 0 ) { savestdin = dup(0); if (savestdin == -1 || dup2(tin,0) == -1) { if (out == -1) close(tout); if (in == -1) close(tin); return -1; } fcntl( 0, F_SETFD, 0 ); } if ( tout != 1 ) { savestdout = dup(1); if (savestdout == -1 || dup2( tout, 1 ) == -1) { if (tin != 0) { close(0); dup2(savestdin,0); close(savestdin); } if (out == -1) close(tout); if (in == -1) close(tin); return -1; } fcntl( 1, F_SETFD, 0 ); } FreezeStdin = 1; /* now try to execute the program */ res = spawnve( _P_WAIT, prg, (const char * const *) args, (const char * const *) environ ); /* Now repair the open file descriptors: */ if (tout != 1) { close(1); dup2(savestdout,1); close(savestdout); } if (tin != 0) { close(0); dup2(savestdin,0); close(savestdin); } if (out == -1) close(tout); if (in == -1) close(tin); FreezeStdin = 0; /* Report result: */ if (res < 0) return -1; return WEXITSTATUS(res); } #else static void NullSignalHandler(int scratch) { } UInt SyExecuteProcess ( Char * dir, Char * prg, Int in, Int out, Char * args[] ) { pid_t pid; /* process id */ pid_t wait_pid; int status; /* do not use `Int' */ Int tin; /* temp in */ Int tout; /* temp out */ sig_handler_t *func; sig_handler_t * volatile func2; /* turn off the SIGCHLD handling, so that we can be sure to collect this child `After that, we call the old signal handler, in case any other children have died in the meantime. This resets the handler */ func2 = signal( SIGCHLD, SIG_DFL ); /* This may return SIG_DFL (0x0) or SIG_IGN (0x1) if the previous handler * was set to the default or 'ignore'. In these cases (or if SIG_ERR is * returned), just use a null signal hander - the default on most systems * is to do nothing */ if(func2 == SIG_ERR || func2 == SIG_DFL || func2 == SIG_IGN) func2 = &NullSignalHandler; /* clone the process */ pid = vfork(); if ( pid == -1 ) { return -1; } /* we are the parent */ if ( pid != 0 ) { // Stop trying to read input FreezeStdin = 1; /* ignore a CTRL-C */ func = signal( SIGINT, SIG_IGN ); /* wait for some action */ wait_pid = waitpid( pid, &status, 0 ); FreezeStdin = 0; if ( wait_pid == -1 ) { signal( SIGINT, func ); (*func2)(SIGCHLD); return -1; } if ( WIFSIGNALED(status) ) { signal( SIGINT, func ); (*func2)(SIGCHLD); return -1; } signal( SIGINT, func ); (*func2)(SIGCHLD); return WEXITSTATUS(status); } /* we are the child */ else { /* change the working directory */ if ( chdir(dir) == -1 ) { _exit(-1); } /* if is -1 open "/dev/null" */ if ( in == -1 ) { tin = open( "/dev/null", O_RDONLY ); } else { tin = SyBufFileno(in); } if ( tin == -1 ) { _exit(-1); } /* if is -1 open "/dev/null" */ if ( out == -1 ) { tout = open( "/dev/null", O_WRONLY ); } else { tout = SyBufFileno(out); } if ( tout == -1 ) { _exit(-1); } /* set standard input to , standard output to */ if ( tin != 0 ) { if ( dup2( tin, 0 ) == -1 ) { _exit(-1); } } fcntl( 0, F_SETFD, 0 ); if ( tout != 1 ) { if ( dup2( tout, 1 ) == -1 ) { _exit(-1); } } fcntl( 1, F_SETFD, 0 ); /* now try to execute the program */ execve( prg, args, environ ); _exit(-1); } /* this should not happen */ return -1; } #endif #endif /**************************************************************************** ** *F SyIsExistingFile( ) . . . . . . . . . . . does file exists ** ** 'SyIsExistingFile' returns 1 if the file exists and 0 otherwise. ** It does not check if the file is readable, writable or excuteable. ** is a system dependent description of the file. */ Int SyIsExistingFile ( const Char * name ) { Int res; SyClearErrorNo(); res = access( name, F_OK ); if ( res == -1 ) { SySetErrorNo(); } return res; } /**************************************************************************** ** *F SyIsReadableFile( ) . . . . . . . . . . . is file readable ** ** 'SyIsReadableFile' returns 0 if the file is readable and ** -1 otherwise. is a system dependent description of the file. */ Int SyIsReadableFile ( const Char * name ) { Int res; Char xname[1024]; SyClearErrorNo(); res = access( name, R_OK ); if ( res == -1 ) { /* we might be able to read the file via zlib */ /* beware of buffer overflows */ if ( strlcpy(xname, name, sizeof(xname)) < sizeof(xname) && strlcat(xname, ".gz", sizeof(xname)) < sizeof(xname) ) { res = access(xname, R_OK); } if (res == -1) SySetErrorNo(); } return res; } /**************************************************************************** ** *F SyIsWritableFile( ) . . . . . . . . . is the file writable ** ** 'SyIsWritableFile' returns 1 if the file is writable and 0 ** otherwise. is a system dependent description of the file. */ Int SyIsWritableFile ( const Char * name ) { Int res; SyClearErrorNo(); res = access( name, W_OK ); if ( res == -1 ) { SySetErrorNo(); } return res; } /**************************************************************************** ** *F SyIsExecutableFile( ) . . . . . . . . . is file executable ** ** 'SyIsExecutableFile' returns 1 if the file is executable and 0 ** otherwise. is a system dependent description of the file. */ Int SyIsExecutableFile ( const Char * name ) { Int res; SyClearErrorNo(); res = access( name, X_OK ); if ( res == -1 ) { SySetErrorNo(); } return res; } /**************************************************************************** ** *F SyIsDirectoryPath( ) . . . . . . . . . is file a directory ** ** 'SyIsDirectoryPath' returns 1 if the file is a directory and 0 ** otherwise. is a system dependent description of the file. */ Int SyIsDirectoryPath ( const Char * name ) { struct stat buf; /* buffer for `stat' */ SyClearErrorNo(); if ( stat( name, &buf ) == -1 ) { SySetErrorNo(); return -1; } return S_ISDIR(buf.st_mode) ? 0 : -1; } /**************************************************************************** ** *F SyRemoveFile( ) . . . . . . . . . . . . . . . remove file */ Int SyRemoveFile ( const Char * name ) { Int res; SyClearErrorNo(); res = unlink(name); if (res == -1) SySetErrorNo(); return res; } /**************************************************************************** ** *f SyMkdir( ) . . . . . . . . . . . . . . . . create directory ** with users umask permissions. */ Int SyMkdir ( const Char * name ) { Int res; SyClearErrorNo(); res = mkdir(name, 0777); if (res == -1) SySetErrorNo(); return res; } /**************************************************************************** ** *f SyRemoveDir( ) . . . . . . . . . . . . . . . . . using `rmdir' */ Int SyRmdir ( const Char * name ) { Int res; SyClearErrorNo(); res = rmdir(name); if (res == -1) SySetErrorNo(); return res; } /**************************************************************************** ** *F SyIsDir( ) . . . . . . . . . . . . . test if something is a dir ** ** Returns 'F' for a regular file, 'L' for a symbolic link and 'D' ** for a real directory, 'C' for a character device, 'B' for a block ** device 'P' for a FIFO (named pipe) and 'S' for a socket. */ Obj SyIsDir ( const Char * name ) { Int res; struct stat ourlstatbuf; res = lstat(name,&ourlstatbuf); if (res < 0) { SySetErrorNo(); return Fail; } if (S_ISREG(ourlstatbuf.st_mode)) return ObjsChar['F']; else if (S_ISDIR(ourlstatbuf.st_mode)) return ObjsChar['D']; else if (S_ISLNK(ourlstatbuf.st_mode)) return ObjsChar['L']; #ifdef S_ISCHR else if (S_ISCHR(ourlstatbuf.st_mode)) return ObjsChar['C']; #endif #ifdef S_ISBLK else if (S_ISBLK(ourlstatbuf.st_mode)) return ObjsChar['B']; #endif #ifdef S_ISFIFO else if (S_ISFIFO(ourlstatbuf.st_mode)) return ObjsChar['P']; #endif #ifdef S_ISSOCK else if (S_ISSOCK(ourlstatbuf.st_mode)) return ObjsChar['S']; #endif else return ObjsChar['?']; } /**************************************************************************** ** *F SyFindGapRootFile( , ) . . find file in system area */ Char * SyFindGapRootFile ( const Char * filename, Char * buffer, size_t bufferSize ) { Int k; for ( k = 0; k < ARRAY_SIZE(SyGapRootPaths); k++ ) { if ( SyGapRootPaths[k][0] ) { if (strlcpy( buffer, SyGapRootPaths[k], bufferSize ) >= bufferSize) continue; if (strlcat( buffer, filename, bufferSize ) >= bufferSize) continue; if ( SyIsReadableFile(buffer) == 0 ) { return buffer; } } } buffer[0] = '\0'; return 0; } /**************************************************************************** ** *F * * * * * * * * * * * * * * * directories * * * * * * * * * * * * * * * * */ /**************************************************************************** ** *F SyTmpname() . . . . . . . . . . . . . . . . . return a temporary filename ** ** 'SyTmpname' creates and returns a new temporary name. Subsequent calls ** to 'SyTmpname' should produce different file names *even* if no files ** were created. */ Char *SyTmpname ( void ) { static char name[1024]; strxcpy(name, "/tmp/gaptempfile.XXXXXX", sizeof(name)); close(mkstemp(name)); return name; } /**************************************************************************** ** *F SyTmpdir( ) . . . . . . . . . . . . return a temporary directory ** ** 'SyTmpdir' returns the directory for a temporary directory. This is ** guaranteed to be newly created and empty immediately after the call to ** 'SyTmpdir'. should be used by 'SyTmpdir' to construct the name of ** the directory (but 'SyTmpdir' is free to use only a part of ), and ** must be a string of at most 8 alphanumerical characters. Under UNIX this ** would usually represent '/usr/tmp/__/', e.g., ** '/usr/tmp/guava_17188_1/'. */ Char * SyTmpdir( const Char * hint ) { #ifdef SYS_IS_CYGWIN32 #define TMPDIR_BASE "/cygdrive/c/WINDOWS/Temp/" #else #define TMPDIR_BASE "/tmp/" #endif static char name[1024]; static const char *base = TMPDIR_BASE; char * env_tmpdir = getenv("TMPDIR"); if (env_tmpdir != NULL) { strxcpy(name, env_tmpdir, sizeof(name)); strxcat(name, "/", sizeof(name)); } else strxcpy(name, base, sizeof(name)); if (hint) strxcat(name, hint, sizeof(name)); else strxcat(name, "gaptempdir", sizeof(name)); strxcat(name, "XXXXXX", sizeof(name)); return mkdtemp(name); } /**************************************************************************** ** *F SyReadStringFile( ) . . . . . . . . read file content into a string ** */ Obj SyReadStringFile(Int fid) { Char buf[32769]; Int ret, len; UInt lstr; Obj str; /* read until we see eof (in 32kB pieces) */ str = NEW_STRING(0); len = 0; do { ret = SyRead(fid, buf, 32768); if (ret < 0) { SySetErrorNo(); return Fail; } len += ret; GROW_STRING( str, len ); lstr = GET_LEN_STRING(str); memcpy( CHARS_STRING(str)+lstr, buf, ret ); *(CHARS_STRING(str)+lstr+ret) = '\0'; SET_LEN_STRING(str, lstr+ret); } while(ret > 0); /* fix the length of */ len = GET_LEN_STRING(str); ResizeBag( str, SIZEBAG_STRINGLEN(len) ); syBuf[fid].ateof = 1; return str; } #if !defined(SYS_IS_CYGWIN32) /* fstat seems completely broken under CYGWIN */ /* first try to get the whole file as one chunk, this avoids garbage collections because of the GROW_STRING calls below */ static Obj SyReadStringFileStat(Int fid) { Int ret, len; Obj str; Int l; char *ptr; struct stat fstatbuf; GAP_ASSERT(syBuf[fid].type != gzip_socket); if( fstat( syBuf[fid].fp, &fstatbuf) == 0 ) { if((off_t)(Int)fstatbuf.st_size != fstatbuf.st_size) { ErrorMayQuit( "The file is too big to fit the current workspace", (Int)0, (Int)0); } len = (Int) fstatbuf.st_size; str = NEW_STRING( len ); CHARS_STRING(str)[len] = '\0'; SET_LEN_STRING(str, len); ptr = CSTR_STRING(str); while (len > 0) { l = (len > 1048576) ? 1048576 : len; ret = SyRead(fid, ptr, l); if (ret == -1) { SySetErrorNo(); return Fail; } len -= ret; ptr += ret; } syBuf[fid].ateof = 1; return str; } else { SySetErrorNo(); return Fail; } } Obj SyReadStringFid(Int fid) { if (syBuf[fid].type != raw_socket) { return SyReadStringFile(fid); } else { return SyReadStringFileStat(fid); } } #else Obj SyReadStringFid(Int fid) { return SyReadStringFile(fid); } #endif #ifdef USE_CUSTOM_MEMMOVE // The memmove in glibc on 32-bit SSE2 systems, contained in // __memmove_sse2_unaligned, is buggy in at least versions // 2.21 - 2.26 when crossing the 2GB boundary, so GAP must // include its own simple memmove implementation. void * SyMemmove(void * dst, const void * src, UInt size) { char * d = dst; const char * s = src; if ((d == s) || (size == 0)) return dst; if (d + size < s || d > s + size) { memcpy(dst, src, size); } else if (d > s) { d = d + (size - 1); s = s + (size - 1); // This is about 4x slower than glibc, but // is simple and also complicated enough that // gcc or clang seem unable to "optimise" it back // into a call to memmove. // Do size 4 jumps while (size > 4) { *d = *s; *(d - 1) = *(s - 1); *(d - 2) = *(s - 2); *(d - 3) = *(s - 3); d -= 4; s -= 4; size -= 4; } // Finish while (size > 0) { *d-- = *s--; size--; } } else { while (size > 4) { *d = *s; *(d + 1) = *(s + 1); *(d + 2) = *(s + 2); *(d + 3) = *(s + 3); d += 4; s += 4; size -= 4; } // Finish while (size > 0) { *d++ = *s++; size--; } } return dst; } #endif /**************************************************************************** ** *V GVarFuncs . . . . . . . . . . . . . . . . . . list of functions to export */ static StructGVarFunc GVarFuncs [] = { GVAR_FUNC(CrcString, 1, "string"), #ifdef HAVE_LIBREADLINE GVAR_FUNC(BINDKEYSTOGAPHANDLER, 1, "keyseq"), GVAR_FUNC(BINDKEYSTOMACRO, 2, "keyseq, macro"), GVAR_FUNC(READLINEINITLINE, 1, "line"), #endif { 0, 0, 0, 0, 0 } }; /**************************************************************************** ** *F * * * * * * * * * * * * * initialize module * * * * * * * * * * * * * * * */ // This function is called by 'InitSystem', before the usual module // initialization. void InitSysFiles(void) { memset(syBuffers, 0, sizeof(syBuf)); memset(syBuf, 0, sizeof(syBuf)); // open the standard files struct stat stat_in, stat_out, stat_err; fstat(fileno(stdin), &stat_in); fstat(fileno(stdout), &stat_out); fstat(fileno(stderr), &stat_err); // set up stdin syBuf[0].type = raw_socket; syBuf[0].fp = fileno(stdin); syBuf[0].echo = fileno(stdout); syBuf[0].bufno = -1; syBuf[0].isTTY = isatty(fileno(stdin)); if (syBuf[0].isTTY) { // if stdin is on a terminal, make sure stdout in on the same terminal if (stat_in.st_dev != stat_out.st_dev || stat_in.st_ino != stat_out.st_ino) syBuf[0].echo = open(ttyname(fileno(stdin)), O_WRONLY); } // set up stdout syBuf[1].type = raw_socket; syBuf[1].echo = syBuf[1].fp = fileno(stdout); syBuf[1].bufno = -1; syBuf[1].isTTY = isatty(fileno(stdout)); // set up errin (defaults to stdin, unless stderr is on a terminal) syBuf[2].type = raw_socket; syBuf[2].fp = fileno(stdin); syBuf[2].echo = fileno(stderr); syBuf[2].bufno = -1; syBuf[2].isTTY = isatty(fileno(stderr)); if (syBuf[2].isTTY) { // if stderr is on a terminal, make sure errin in on the same terminal if (stat_in.st_dev != stat_err.st_dev || stat_in.st_ino != stat_err.st_ino) syBuf[2].fp = open(ttyname(fileno(stderr)), O_RDONLY); } // set up errout syBuf[3].type = raw_socket; syBuf[3].echo = syBuf[3].fp = fileno(stderr); syBuf[3].bufno = -1; // turn off buffering setbuf(stdin, (char *)0); setbuf(stdout, (char *)0); setbuf(stderr, (char *)0); } /* TODO: Should probably do some checks preSave for open files etc and refuse to save if any are found */ /**************************************************************************** ** *F InitKernel( ) . . . . . . . initialise kernel data structures */ static Int InitKernel( StructInitInfo * module ) { /* init filters and functions */ InitHdlrFuncsFromTable( GVarFuncs ); /* line edit key handler from library */ ImportGVarFromLibrary("GAPInfo", &GAPInfo); ImportFuncFromLibrary("LineEditKeyHandler", &LineEditKeyHandler); ImportGVarFromLibrary("LineEditKeyHandlers", &LineEditKeyHandlers); #ifdef HPCGAP /* GAP hooks to allow library to override how we start/stop raw mode */ DeclareGVar(&GVarBeginEdit, "TERMINAL_BEGIN_EDIT"); DeclareGVar(&GVarEndEdit, "TERMINAL_END_EDIT"); #endif #ifdef HAVE_SELECT InitCopyGVar("OnCharReadHookActive",&OnCharReadHookActive); InitCopyGVar("OnCharReadHookInFds",&OnCharReadHookInFds); InitCopyGVar("OnCharReadHookInFuncs",&OnCharReadHookInFuncs); InitCopyGVar("OnCharReadHookOutFds",&OnCharReadHookOutFds); InitCopyGVar("OnCharReadHookOutFuncs",&OnCharReadHookOutFuncs); InitCopyGVar("OnCharReadHookExcFds",&OnCharReadHookExcFds); InitCopyGVar("OnCharReadHookExcFuncs",&OnCharReadHookExcFuncs); #endif /* return success */ return 0; } /**************************************************************************** ** *F InitLibrary( ) . . . . . . . initialise library data structures */ static Int InitLibrary( StructInitInfo * module ) { /* init filters and functions */ InitGVarFuncsFromTable( GVarFuncs ); return 0; } /**************************************************************************** ** *F InitInfoSysFiles() . . . . . . . . . . . . . . . table of init functions */ static StructInitInfo module = { // init struct using C99 designated initializers; for a full list of // fields, please refer to the definition of StructInitInfo .type = MODULE_BUILTIN, .name = "sysfiles", .initKernel = InitKernel, .initLibrary = InitLibrary, }; StructInitInfo * InitInfoSysFiles ( void ) { return &module; }