library/termios_console_fdhookentry.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);
}