mirror of
https://github.com/adtools/clib2.git
synced 2025-12-08 14:59:05 +00:00
git-svn-id: file:///Users/olsen/Code/migration-svn-zu-git/logical-line-staging/clib2/trunk@15306 87f5fb63-7c3d-0410-a384-fd976d0f7a62
610 lines
13 KiB
C
610 lines
13 KiB
C
/*
|
|
* $Id: termios_console_fdhookentry.c,v 1.6 2010-10-20 13:12:59 obarthel Exp $
|
|
*
|
|
* :ts=4
|
|
*
|
|
* Portable ISO 'C' (1994) runtime library for the Amiga computer
|
|
* Copyright (c) 2002-2015 by Olaf Barthel <obarthel (at) gmx.net>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* - Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* - Neither the name of Olaf Barthel nor the names of contributors
|
|
* may be used to endorse or promote products derived from this
|
|
* software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef _TERMIOS_HEADERS_H
|
|
#include "termios_headers.h"
|
|
#endif /* _TERMIOS_HEADERS_H */
|
|
|
|
/****************************************************************************/
|
|
|
|
/*
|
|
* Hook for termios emulation on a console. This can probably be cleaned up a bit
|
|
* by removing things which will (should) never happen on a console.
|
|
*/
|
|
|
|
#ifndef _STDIO_HEADERS_H
|
|
#include "stdio_headers.h"
|
|
#endif /* _STDIO_HEADERS_H */
|
|
|
|
#ifndef _UNISTD_HEADERS_H
|
|
#include "unistd_headers.h"
|
|
#endif /* _UNISTD_HEADERS_H */
|
|
|
|
/****************************************************************************/
|
|
|
|
#ifndef _STDLIB_MEMORY_H
|
|
#include "stdlib_memory.h"
|
|
#endif /* _STDLIB_MEMORY_H */
|
|
|
|
/****************************************************************************/
|
|
|
|
#include <strings.h>
|
|
#include <limits.h>
|
|
|
|
/****************************************************************************/
|
|
|
|
/*
|
|
* Emulate canonical no-echo mode with a simple line-editor in raw mode.
|
|
*/
|
|
static int
|
|
LineEditor(BPTR file,char *buf,const int buflen,struct termios *tios)
|
|
{
|
|
int pos = 0,len = 0;
|
|
unsigned char z;
|
|
int do_edit = 1;
|
|
int shift_mode = 0;
|
|
|
|
SetMode(file,DOSTRUE); /* Set raw mode. */
|
|
|
|
while(do_edit && len < buflen)
|
|
{
|
|
if(WaitForChar(file,5000000) != DOSFALSE) /* 5 seconds. */
|
|
{
|
|
if(Read(file,&z,1) == ERROR)
|
|
{
|
|
len = -1;
|
|
break;
|
|
}
|
|
|
|
if(z == tios->c_cc[VQUIT])
|
|
break;
|
|
|
|
switch(z)
|
|
{
|
|
case '\n': /* NL */
|
|
case '\r': /* CR */
|
|
|
|
do_edit = 0;
|
|
|
|
buf[len++] = '\n';
|
|
continue;
|
|
|
|
case 155: /* CSI */
|
|
|
|
shift_mode = 1;
|
|
continue;
|
|
|
|
case '\b': /* Backspace */
|
|
|
|
if(pos > 0)
|
|
{
|
|
memmove(&buf[pos-1],&buf[pos],len-pos);
|
|
pos--;
|
|
len--;
|
|
}
|
|
|
|
continue;
|
|
|
|
case 127: /* Delete */
|
|
|
|
if(pos < len)
|
|
{
|
|
memmove(&buf[pos],&buf[pos+1],len-pos-1);
|
|
len--;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if(shift_mode)
|
|
{
|
|
shift_mode = 0;
|
|
|
|
switch(z)
|
|
{
|
|
case 'C': /* Right arrowkey */
|
|
|
|
if(pos < len)
|
|
pos++;
|
|
|
|
continue;
|
|
|
|
case 'D': /* Left arrowkey */
|
|
|
|
if(pos > 0)
|
|
pos--;
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if(pos != len)
|
|
memmove(&buf[pos + 1],&buf[pos],len - pos);
|
|
|
|
buf[pos]=z;
|
|
pos++;
|
|
len++;
|
|
}
|
|
}
|
|
|
|
if(len >= 0 && len < buflen) /* Does not hurt to null-terminate if we can. */
|
|
buf[len] = '\0';
|
|
|
|
SetMode(file,DOSFALSE); /* Restore mode */
|
|
|
|
return(len); /* Number of characters read. */
|
|
}
|
|
|
|
/****************************************************************************/
|
|
|
|
int
|
|
__termios_console_hook(
|
|
struct fd * fd,
|
|
struct file_action_message * fam)
|
|
{
|
|
const unsigned char CR = '\r',NL = '\n';
|
|
struct FileHandle * fh;
|
|
char * buffer = NULL;
|
|
int result = EOF;
|
|
int actual_out;
|
|
BOOL is_aliased;
|
|
BPTR file;
|
|
struct termios *tios;
|
|
|
|
ENTER();
|
|
|
|
assert( fam != NULL && fd != NULL );
|
|
assert( __is_valid_fd(fd) );
|
|
assert( FLAG_IS_SET(fd->fd_Flags,FDF_TERMIOS) );
|
|
assert( fd->fd_Aux != NULL);
|
|
|
|
tios = (struct termios *)fd->fd_Aux;
|
|
|
|
/* Careful: file_action_close has to monkey with the file descriptor
|
|
table and therefore needs to obtain the stdio lock before
|
|
it locks this particular descriptor entry. */
|
|
if(fam->fam_Action == file_action_close)
|
|
__stdio_lock();
|
|
|
|
__fd_lock(fd);
|
|
|
|
file = __resolve_fd_file(fd);
|
|
if(file == ZERO)
|
|
{
|
|
SHOWMSG("file is closed");
|
|
|
|
fam->fam_Error = EBADF;
|
|
goto out;
|
|
}
|
|
|
|
switch(fam->fam_Action)
|
|
{
|
|
case file_action_read:
|
|
|
|
SHOWMSG("file_action_read");
|
|
|
|
if(FLAG_IS_CLEAR(tios->c_cflag,CREAD))
|
|
{
|
|
SHOWMSG("Reading is not enabled for this console descriptor.");
|
|
fam->fam_Error = EIO;
|
|
goto out;
|
|
}
|
|
|
|
assert( fam->fam_Data != NULL );
|
|
assert( fam->fam_Size > 0 );
|
|
|
|
D(("read %ld bytes from position %ld to 0x%08lx",fam->fam_Size,Seek(file,0,OFFSET_CURRENT),fam->fam_Data));
|
|
|
|
PROFILE_OFF();
|
|
|
|
/* Attempt to fake everything needed in non-canonical mode. */
|
|
|
|
if(FLAG_IS_SET(tios->c_lflag,ICANON)) /* Canonical read = same as usual. Unless... */
|
|
{
|
|
if(FLAG_IS_CLEAR(tios->c_lflag,ECHO)) /* No-echo mode needs to be emulated. */
|
|
result = LineEditor(file,fam->fam_Data,fam->fam_Size,tios);
|
|
else
|
|
result = Read(file,fam->fam_Data,fam->fam_Size);
|
|
}
|
|
else if (fam->fam_Size > 0)
|
|
{
|
|
/* Non-canonical reads have timeouts and a minimum number of characters to read. */
|
|
int i = 0;
|
|
|
|
result = 0;
|
|
|
|
if(tios->c_cc[VMIN]>0)
|
|
{
|
|
i = Read(file,fam->fam_Data,1); /* Reading the first character is not affected by the timeout unless VMIN==0. */
|
|
if(i == ERROR)
|
|
{
|
|
fam->fam_Error = EIO;
|
|
goto out;
|
|
}
|
|
|
|
result = i;
|
|
|
|
while((result < tios->c_cc[VMIN]) && (result < fam->fam_Size))
|
|
{
|
|
if(tios->c_cc[VTIME] > 0)
|
|
{
|
|
if(WaitForChar(file,100000 * tios->c_cc[VTIME]) == DOSFALSE)
|
|
break; /* No more characters available within alloted time. */
|
|
}
|
|
|
|
i = Read(file,&fam->fam_Data[result],1);
|
|
if(i <= 0)
|
|
break; /* Break out of this while loop only. */
|
|
|
|
result += i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(WaitForChar(file,100000*tios->c_cc[VTIME]))
|
|
result = Read(file,fam->fam_Data,fam->fam_Size);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = 0; /* Reading zero characters will always succeed. */
|
|
}
|
|
|
|
PROFILE_ON();
|
|
|
|
if(result == ERROR)
|
|
{
|
|
D(("read failed ioerr=%ld",IoErr()));
|
|
|
|
fam->fam_Error = __translate_io_error_to_errno(IoErr());
|
|
goto out;
|
|
}
|
|
|
|
if(result > 0)
|
|
{
|
|
if(tios->c_iflag != 0) /* Input processing enabled. */
|
|
{
|
|
int i,n;
|
|
int num_bytes = result;
|
|
unsigned char byte_in;
|
|
|
|
/* XXX The input substitution could possibly be moved to the console handler with an input-map. (?) */
|
|
for(i = n = 0 ; i < num_bytes ; i++)
|
|
{
|
|
byte_in = fam->fam_Data[i];
|
|
|
|
if(FLAG_IS_SET(tios->c_iflag,ISTRIP)) /* Strip 8:th bit. Done before any other processing. */
|
|
byte_in &= 0x7f;
|
|
|
|
if(FLAG_IS_SET(tios->c_iflag,IGNCR) && byte_in == CR) /* Remove CR */
|
|
{
|
|
result--;
|
|
continue;
|
|
}
|
|
|
|
if(FLAG_IS_SET(tios->c_iflag,ICRNL) && byte_in == CR) /* Map CR->NL */
|
|
byte_in = NL;
|
|
|
|
if(FLAG_IS_SET(tios->c_iflag,INLCR) && byte_in == NL) /* Map NL->CR */
|
|
byte_in = CR;
|
|
|
|
fam->fam_Data[n++] = byte_in;
|
|
}
|
|
}
|
|
|
|
if(FLAG_IS_SET(tios->c_lflag,ECHO) && FLAG_IS_CLEAR(tios->c_lflag,ICANON) && FLAG_IS_SET(fd->fd_Flags,FDF_WRITE))
|
|
{
|
|
if(Write(file,fam->fam_Data,result) == ERROR)
|
|
{
|
|
/* "Silently" disable echoing. */
|
|
SHOWMSG("Echo failed and has been disabled.");
|
|
CLEAR_FLAG(tios->c_lflag,ECHO);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(FLAG_IS_SET(fd->fd_Flags,FDF_CACHE_POSITION))
|
|
fd->fd_Position += (ULONG)result;
|
|
|
|
break;
|
|
|
|
case file_action_write:
|
|
|
|
SHOWMSG("file_action_write");
|
|
|
|
assert( fam->fam_Data != NULL );
|
|
assert( fam->fam_Size > 0 );
|
|
|
|
if(FLAG_IS_SET(tios->c_oflag,OPOST)) /* Output processing enabled. */
|
|
{
|
|
unsigned char byte_out;
|
|
int i,n;
|
|
|
|
buffer = malloc(2 * fam->fam_Size);
|
|
if(buffer == NULL)
|
|
{
|
|
fam->fam_Error = ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
for(i = n = 0 ; i < fam->fam_Size ; i++)
|
|
{
|
|
byte_out=fam->fam_Data[i];
|
|
|
|
if(FLAG_IS_SET(tios->c_oflag,ONLRET) && byte_out == CR)
|
|
continue;
|
|
|
|
if(FLAG_IS_SET(tios->c_oflag,OCRNL) && byte_out == CR)
|
|
byte_out = NL;
|
|
|
|
if(FLAG_IS_SET(tios->c_oflag,ONOCR) && byte_out == CR)
|
|
byte_out = NL;
|
|
|
|
if(FLAG_IS_SET(tios->c_oflag,ONLCR) && byte_out == NL)
|
|
{
|
|
buffer[n++] = CR;
|
|
byte_out = NL;
|
|
}
|
|
|
|
buffer[n++] = byte_out;
|
|
}
|
|
|
|
actual_out = n;
|
|
}
|
|
else
|
|
{
|
|
buffer = fam->fam_Data;
|
|
actual_out = fam->fam_Size;
|
|
}
|
|
|
|
/* Note. When output processing is enabled, write() can return _more_ than the data length. */
|
|
D(("write %ld bytes to position %ld from 0x%08lx",actual_out,Seek(file,0,OFFSET_CURRENT),buffer));
|
|
|
|
if(actual_out > 0)
|
|
{
|
|
PROFILE_OFF();
|
|
|
|
result = Write(file,buffer,actual_out);
|
|
|
|
PROFILE_ON();
|
|
}
|
|
else
|
|
{
|
|
result = 0;
|
|
}
|
|
|
|
if(buffer == fam->fam_Data)
|
|
buffer = NULL; /* Must do this to avoid freeing the user data. */
|
|
|
|
if(result == ERROR)
|
|
{
|
|
D(("write failed ioerr=%ld",IoErr()));
|
|
|
|
fam->fam_Error = __translate_io_error_to_errno(IoErr());
|
|
goto out;
|
|
}
|
|
|
|
if(FLAG_IS_SET(fd->fd_Flags,FDF_CACHE_POSITION))
|
|
fd->fd_Position += (ULONG)result;
|
|
|
|
break;
|
|
|
|
case file_action_close:
|
|
|
|
SHOWMSG("file_action_close");
|
|
|
|
/* The following is almost guaranteed not to fail. */
|
|
result = OK;
|
|
|
|
/* If this is an alias, just remove it. */
|
|
is_aliased = __fd_is_aliased(fd);
|
|
if(is_aliased)
|
|
{
|
|
__remove_fd_alias(fd);
|
|
}
|
|
else if (FLAG_IS_CLEAR(fd->fd_Flags,FDF_STDIO))
|
|
{
|
|
/* Should we reset this file into line buffered mode? */
|
|
if(FLAG_IS_SET(fd->fd_Flags,FDF_NON_BLOCKING) && FLAG_IS_SET(fd->fd_Flags,FDF_IS_INTERACTIVE))
|
|
SetMode(fd->fd_File,DOSFALSE);
|
|
|
|
/* Are we allowed to close this file? */
|
|
if(FLAG_IS_CLEAR(fd->fd_Flags,FDF_NO_CLOSE))
|
|
{
|
|
/* Call a cleanup function, such as the one which
|
|
* releases locked records.
|
|
*/
|
|
if(fd->fd_Cleanup != NULL)
|
|
(*fd->fd_Cleanup)(fd);
|
|
|
|
PROFILE_OFF();
|
|
|
|
if(CANNOT Close(fd->fd_File))
|
|
{
|
|
fam->fam_Error = __translate_io_error_to_errno(IoErr());
|
|
|
|
result = EOF;
|
|
}
|
|
|
|
PROFILE_ON();
|
|
|
|
fd->fd_File = ZERO;
|
|
}
|
|
}
|
|
|
|
__fd_unlock(fd);
|
|
|
|
#if defined(__THREAD_SAFE)
|
|
{
|
|
/* Free the lock semaphore now. */
|
|
if(NOT is_aliased)
|
|
__delete_semaphore(fd->fd_Lock);
|
|
}
|
|
#endif /* __THREAD_SAFE */
|
|
|
|
/* And that's the last for this file descriptor. */
|
|
memset(fd,0,sizeof(*fd));
|
|
fd = NULL;
|
|
|
|
break;
|
|
|
|
case file_action_seek:
|
|
|
|
SHOWMSG("file_action_seek");
|
|
|
|
fam->fam_Error = EINVAL;
|
|
goto out;
|
|
|
|
case file_action_set_blocking:
|
|
|
|
SHOWMSG("file_action_set_blocking");
|
|
|
|
PROFILE_OFF();
|
|
|
|
if(FLAG_IS_SET(fd->fd_Flags,FDF_IS_INTERACTIVE))
|
|
{
|
|
LONG mode;
|
|
|
|
SHOWMSG("changing the mode");
|
|
|
|
if(fam->fam_Arg != 0)
|
|
mode = DOSFALSE; /* buffered mode */
|
|
else
|
|
mode = DOSTRUE; /* single character mode */
|
|
|
|
if(CANNOT SetMode(file,mode))
|
|
{
|
|
fam->fam_Error = __translate_io_error_to_errno(IoErr());
|
|
goto out;
|
|
}
|
|
|
|
/* Update tios to reflect state change. */
|
|
if(mode == DOSTRUE)
|
|
CLEAR_FLAG(tios->c_lflag,ICANON);
|
|
else
|
|
SET_FLAG(tios->c_lflag,ICANON);
|
|
|
|
result = OK;
|
|
}
|
|
else
|
|
{
|
|
SHOWMSG("can't do anything here");
|
|
|
|
fam->fam_Error = EBADF;
|
|
}
|
|
|
|
PROFILE_ON();
|
|
|
|
break;
|
|
|
|
case file_action_examine:
|
|
|
|
SHOWMSG("file_action_examine");
|
|
|
|
fh = BADDR(file);
|
|
|
|
/* Special treatment for "NIL:", for which we make
|
|
some stuff up. */
|
|
if(fh->fh_Type == NULL)
|
|
{
|
|
/* Make up some stuff for this stream. */
|
|
memset(fam->fam_FileInfo,0,sizeof(*fam->fam_FileInfo));
|
|
|
|
DateStamp(&fam->fam_FileInfo->fib_Date);
|
|
|
|
fam->fam_FileInfo->fib_DirEntryType = ST_NIL;
|
|
}
|
|
else if (CANNOT __safe_examine_file_handle(file,fam->fam_FileInfo))
|
|
{
|
|
LONG error;
|
|
|
|
/* So that didn't work. Did the file system simply fail to
|
|
respond to the request or is something more sinister
|
|
at work? */
|
|
error = IoErr();
|
|
if(error != ERROR_ACTION_NOT_KNOWN)
|
|
{
|
|
SHOWMSG("couldn't examine the file");
|
|
|
|
fam->fam_Error = __translate_io_error_to_errno(error);
|
|
goto out;
|
|
}
|
|
|
|
/* OK, let's have another look at this file. Could it be a
|
|
console stream? */
|
|
if(NOT IsInteractive(file))
|
|
{
|
|
SHOWMSG("whatever it is, we don't know");
|
|
|
|
fam->fam_Error = ENOSYS;
|
|
goto out;
|
|
}
|
|
|
|
/* Make up some stuff for this stream. */
|
|
memset(fam->fam_FileInfo,0,sizeof(*fam->fam_FileInfo));
|
|
|
|
DateStamp(&fam->fam_FileInfo->fib_Date);
|
|
|
|
fam->fam_FileInfo->fib_DirEntryType = ST_CONSOLE;
|
|
}
|
|
|
|
fam->fam_FileSystem = fh->fh_Type;
|
|
|
|
result = OK;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
SHOWVALUE(fam->fam_Action);
|
|
|
|
fam->fam_Error = EBADF;
|
|
break;
|
|
}
|
|
|
|
out:
|
|
|
|
__fd_unlock(fd);
|
|
|
|
if(fam->fam_Action == file_action_close)
|
|
__stdio_unlock();
|
|
|
|
if(buffer != NULL)
|
|
free(buffer);
|
|
|
|
SHOWVALUE(result);
|
|
|
|
RETURN(result);
|
|
return(result);
|
|
}
|