/* * $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 * 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 #include /****************************************************************************/ /* * 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); }