/* -*-C-*-

$Id: ntio.c,v 1.24 2001/05/22 02:46:47 cph Exp $

Copyright (c) 1992-2001 Massachusetts Institute of Technology

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/

#include "scheme.h"
#include "prims.h"
#include "nt.h"
#include "ntio.h"
#include "osterm.h"
#include "osfile.h"
#include "outf.h"
#include "ossig.h"
#include "intrpt.h"
#include "ntscreen.h"

#undef TRACE_NTIO
#ifdef TRACE_NTIO
extern FILE * trace_file;
#endif

extern HANDLE master_tty_window;

channel_class_t * NT_channel_class_generic;
channel_class_t * NT_channel_class_file;
channel_class_t * NT_channel_class_screen;
channel_class_t * NT_channel_class_anonymous_pipe;
channel_class_t * NT_channel_class_named_pipe;

static Tchannel channel_allocate (void);
static long cooked_channel_write (Tchannel, const void *, unsigned long) ;

#ifndef NT_DEFAULT_CHANNEL_TABLE_SIZE
#define NT_DEFAULT_CHANNEL_TABLE_SIZE 1024
#endif

size_t OS_channel_table_size;
struct channel * NT_channel_table;

Tchannel
NT_make_channel (HANDLE handle, channel_class_t * class)
{
  Tchannel channel;
  transaction_begin ();
  NT_handle_close_on_abort (handle);
  channel = (channel_allocate ());
  (CHANNEL_CLASS (channel)) = class;
  (CHANNEL_HANDLE (channel)) = handle;
  (CHANNEL_INTERNAL (channel)) = 0;
  (CHANNEL_NONBLOCKING (channel)) = 0;
  (CHANNEL_BUFFERED (channel)) = 1;
  (CHANNEL_COOKED (channel)) = 0;
  transaction_commit ();
  return (channel);
}

channel_class_t *
NT_handle_channel_class (HANDLE handle)
{
  if (Screen_IsScreenHandle (handle))
    return (NT_channel_class_screen);
  /* If GetFileType returns FILE_TYPE_PIPE, assume that it is a named
     pipe.  This procedure won't be called with an anonymous-pipe
     handle.  */
  switch (GetFileType (handle))
    {
    case FILE_TYPE_DISK: return (NT_channel_class_file);
    case FILE_TYPE_CHAR: return (NT_channel_class_generic);
    case FILE_TYPE_PIPE: return (NT_channel_class_named_pipe);
    default: return (NT_channel_class_generic);
    }
}

Tchannel
NT_open_handle (HANDLE handle)
{
  Tchannel channel
    = (NT_make_channel (handle, (NT_handle_channel_class (handle))));
  /* Like Unix, all terminals initialize to cooked mode.  */
  if ((CHANNEL_TYPE (channel)) == channel_type_terminal)
    (CHANNEL_COOKED (channel)) = 1;
  return (channel);
}

long
OS_channel_read (Tchannel channel, void * buffer, size_t n_bytes)
{
  return
    ((n_bytes == 0)
     ? 0
     : ((* (CHANNEL_CLASS_OP_READ (CHANNEL_CLASS (channel))))
	(channel, buffer, n_bytes)));
}

long
OS_channel_write (Tchannel channel, const void * buffer, size_t n_bytes)
{
  return
    ((n_bytes == 0)
     ? 0
     : (CHANNEL_COOKED (channel))
     ? (cooked_channel_write (channel, buffer, n_bytes))
     : ((* (CHANNEL_CLASS_OP_WRITE (CHANNEL_CLASS (channel))))
	(channel, buffer, n_bytes)));
}

void
OS_channel_close (Tchannel channel)
{
  if (! ((CHANNEL_CLOSED_P (channel)) || (CHANNEL_INTERNAL (channel))))
    {
      (* (CHANNEL_CLASS_OP_CLOSE (CHANNEL_CLASS (channel)))) (channel, 1);
      MARK_CHANNEL_CLOSED (channel);
    }
}

void
OS_channel_close_noerror (Tchannel channel)
{
  if (! ((CHANNEL_CLOSED_P (channel)) || (CHANNEL_INTERNAL (channel))))
    {
      (* (CHANNEL_CLASS_OP_CLOSE (CHANNEL_CLASS (channel)))) (channel, 0);
      MARK_CHANNEL_CLOSED (channel);
    }
}

long
NT_channel_n_read (Tchannel channel)
{
  if (CHANNEL_CLOSED_P (channel))
    return (0);
  return ((* (CHANNEL_CLASS_OP_N_READ (CHANNEL_CLASS (channel)))) (channel));
}

static void
NT_channel_close_all (void)
{
  Tchannel channel;
  for (channel = 0; (channel < OS_channel_table_size); channel += 1)
    if (CHANNEL_OPEN_P (channel))
      OS_channel_close_noerror (channel);
}

static Tchannel
channel_allocate (void)
{
  Tchannel channel = 0;
  while (1)
  {
    if (channel == OS_channel_table_size)
      error_out_of_channels ();
    if (CHANNEL_CLOSED_P (channel))
      return (channel);
    channel += 1;
  }
}

int
OS_channel_open_p (Tchannel channel)
{
  return (CHANNEL_OPEN_P (channel));
}

static void
channel_close_on_abort_1 (void * cp)
{
  OS_channel_close (* ((Tchannel *) cp));
}

void
OS_channel_close_on_abort (Tchannel channel)
{
  Tchannel * cp = ((Tchannel *) (dstack_alloc (sizeof (Tchannel))));
  (*cp) = (channel);
  transaction_record_action (tat_abort, channel_close_on_abort_1, cp);
}

static void
NT_handle_close_on_abort_1 (void * hp)
{
  (void) CloseHandle (* ((HANDLE *) hp));
}

void
NT_handle_close_on_abort (HANDLE h)
{
  HANDLE * hp = (dstack_alloc (sizeof (HANDLE)));
  (*hp) = h;
  transaction_record_action (tat_abort, NT_handle_close_on_abort_1, hp);
}

enum channel_type
OS_channel_type (Tchannel channel)
{
  return (CHANNEL_TYPE (channel));
}

static void
generic_channel_close (Tchannel channel, int errorp)
{
  if ((!CloseHandle (CHANNEL_HANDLE (channel))) && errorp)
    NT_error_api_call ((GetLastError ()), apicall_CloseHandle);
}

static long
generic_channel_read (Tchannel channel, void * buffer, unsigned long n_bytes)
{
  DWORD bytes_read;
  if ((!ReadFile ((CHANNEL_HANDLE (channel)),
		  buffer, n_bytes, (&bytes_read), 0))
      && (bytes_read > 0))
    NT_error_api_call ((GetLastError ()), apicall_ReadFile);
  return (bytes_read);
}

static long
generic_channel_write (Tchannel channel, const void * buffer,
		       unsigned long n_bytes)
{
  DWORD n_written;
  STD_BOOL_API_CALL
    (WriteFile,
     ((CHANNEL_HANDLE (channel)), ((LPCVOID) buffer), n_bytes, (&n_written),
      0));
  return (n_written);
}

static long
generic_channel_n_read (Tchannel channel)
{
  /* This means "unknown".  */
  return (-2);
}

static void
initialize_channel_class_generic (void)
{
  channel_class_t * class = (OS_malloc (sizeof (channel_class_t)));
  (CHANNEL_CLASS_TYPE (class)) = channel_type_unknown;
  (CHANNEL_CLASS_OP_READ (class)) = generic_channel_read;
  (CHANNEL_CLASS_OP_WRITE (class)) = generic_channel_write;
  (CHANNEL_CLASS_OP_CLOSE (class)) = generic_channel_close;
  (CHANNEL_CLASS_OP_N_READ (class)) = generic_channel_n_read;
  NT_channel_class_generic = class;
}

static long
file_channel_n_read (Tchannel channel)
{
  DWORD length = (GetFileSize ((CHANNEL_HANDLE (channel)), 0));
  off_t position;
  if (length == 0xFFFFFFFF)
    return (0);
  position = (OS_file_position (channel));
  return ((position < ((off_t) length)) ? (((off_t) length) - position) : 0);
}

static void
initialize_channel_class_file (void)
{
  channel_class_t * class = (OS_malloc (sizeof (channel_class_t)));
  (*class) = (*NT_channel_class_generic);
  (CHANNEL_CLASS_TYPE (class)) = channel_type_file;
  (CHANNEL_CLASS_OP_N_READ (class)) = file_channel_n_read;
  NT_channel_class_file = class;
}

static long
screen_channel_read (Tchannel channel, void * buffer, unsigned long n_bytes)
{
  DWORD bytes_read
    = (Screen_Read ((CHANNEL_HANDLE (channel)),
		    ((BOOL) (CHANNEL_BUFFERED (channel))),
		    buffer,
		    n_bytes));
  if (bytes_read == 0xFFFFFFFF)
    {
      /* For pleasantness give up rest of this timeslice.  */
      Sleep (0);
      REQUEST_INTERRUPT (INT_Global_1);	/* windows polling */
      return (-1);
    }
  return (bytes_read);
}

static long
screen_channel_write (Tchannel channel, const void * buffer,
		      unsigned long n_bytes)
{
  HANDLE h = (CHANNEL_HANDLE (channel));
  SendMessage (h, SCREEN_WRITE, ((WPARAM) n_bytes), ((LPARAM) buffer));
  if (h == master_tty_window)
    SendMessage (h, WM_PAINT, 0, 0);
  return (n_bytes);
}

static long
screen_channel_n_read (Tchannel channel)
{
  /* This is incorrect.  However, it's a pain to do the right thing.
     Furthermore, NT_channel_n_read is only used by "select", and for
     that particular case, this is the correct value.  */
  return (-1);
}

static void
initialize_channel_class_screen (void)
{
  channel_class_t * class = (OS_malloc (sizeof (channel_class_t)));
  (CHANNEL_CLASS_TYPE (class)) = channel_type_terminal;
  (CHANNEL_CLASS_OP_READ (class)) = screen_channel_read;
  (CHANNEL_CLASS_OP_WRITE (class)) = screen_channel_write;
  (CHANNEL_CLASS_OP_CLOSE (class)) = 0;
  (CHANNEL_CLASS_OP_N_READ (class)) = screen_channel_n_read;
  NT_channel_class_screen = class;
}

void
OS_make_pipe (Tchannel * readerp, Tchannel * writerp)
{
  HANDLE hread;
  HANDLE hwrite;
  STD_BOOL_API_CALL (CreatePipe, ((&hread), (&hwrite), 0, 0));
  transaction_begin ();
  NT_handle_close_on_abort (hwrite);
  (*readerp) = (NT_make_channel (hread, NT_channel_class_anonymous_pipe));
  transaction_commit ();
  transaction_begin ();
  OS_channel_close_on_abort (*readerp);
  (*writerp) = (NT_make_channel (hwrite, NT_channel_class_anonymous_pipe));
  transaction_commit ();
}

static long
pipe_channel_read (Tchannel channel, void * buffer, unsigned long n_bytes)
{
#ifdef TRACE_NTIO
  fprintf (trace_file, "pipe_channel_read: channel=%d blocking=%s\n",
	   channel,
	   ((CHANNEL_NONBLOCKING (channel)) ? "no" : "yes"));
  fflush (trace_file);
#endif
  if (CHANNEL_NONBLOCKING (channel))
    {
      long n = (NT_channel_n_read (channel));
#ifdef TRACE_NTIO
      fprintf (trace_file, "pipe_channel_read: n=%d\n", n);
      fflush (trace_file);
#endif
      if (n <= 0)
	return (n);
    }
  return (generic_channel_read (channel, buffer, n_bytes));
}

static long
pipe_channel_n_read (Tchannel channel)
{
  DWORD n;
#ifdef TRACE_NTIO
  fprintf (trace_file, "pipe_channel_n_read: channel=%d\n", channel);
  fflush (trace_file);
#endif
  if (!PeekNamedPipe ((CHANNEL_HANDLE (channel)), 0, 0, 0, (&n), 0))
    {
      DWORD code = (GetLastError ());
      if ((code == ERROR_INVALID_HANDLE)
	  || (code == ERROR_BROKEN_PIPE))
	/* ERROR_BROKEN_PIPE means the other end of the pipe has been
	   closed, so return zero which means "end of file".  */
	return (0);
      NT_error_api_call (code, apicall_PeekNamedPipe);
    }
#ifdef TRACE_NTIO
  fprintf (trace_file, "pipe_channel_n_read: n=%d\n", n);
  fflush (trace_file);
#endif
  /* Zero bytes available means "read would block", so return -1.  */
  return ((n == 0) ? (-1) : n);
}

static void
initialize_channel_class_anonymous_pipe (void)
{
  channel_class_t * class = (OS_malloc (sizeof (channel_class_t)));
  (CHANNEL_CLASS_TYPE (class)) = channel_type_win32_anonymous_pipe;
  (CHANNEL_CLASS_OP_READ (class)) = pipe_channel_read;
  (CHANNEL_CLASS_OP_WRITE (class)) = generic_channel_write;
  (CHANNEL_CLASS_OP_CLOSE (class)) = generic_channel_close;
  (CHANNEL_CLASS_OP_N_READ (class)) = pipe_channel_n_read;
  NT_channel_class_anonymous_pipe = class;
}

static void
initialize_channel_class_named_pipe (void)
{
  channel_class_t * class = (OS_malloc (sizeof (channel_class_t)));
  (*class) = (*NT_channel_class_anonymous_pipe);
  (CHANNEL_CLASS_TYPE (class)) = channel_type_win32_named_pipe;
  NT_channel_class_named_pipe = class;
}

static long
cooked_channel_write (Tchannel channel, const void * buffer,
		      unsigned long n_bytes) 
{
  /* Map LF to CR/LF */
  static const unsigned char crlf [] = {CARRIAGE_RETURN, LINEFEED};
  const unsigned char * bstart = buffer;
  const unsigned char * start = bstart;
  const unsigned char * end = (start + n_bytes);
  while (start < end)
    {
      const unsigned char * scan = start;
      while ((scan < end) && ((*scan) != LINEFEED))
	scan += 1;
      if (scan > start)
	{
	  unsigned int n_bytes = (scan - start);
	  long n_written
	    = ((* (CHANNEL_CLASS_OP_WRITE (CHANNEL_CLASS (channel))))
	       (channel, start, n_bytes));
	  if (n_written < 0)
	    return (start - bstart);
	  if (((unsigned int) n_written) < n_bytes)
	    return ((start - bstart) + n_written);
	}
      if (scan < end)
	{
	  unsigned int n_bytes = (sizeof (crlf));
	  long n_written
	    = ((* (CHANNEL_CLASS_OP_WRITE (CHANNEL_CLASS (channel))))
	       (channel, crlf, n_bytes));
	  if (n_written < ((long) n_bytes))
	    /* This backs out incorrectly if only CR is written out.  */
	    return (scan - bstart);
	}
      start = (scan + 1);
    }
  return (n_bytes);
}

size_t
OS_channel_read_load_file (Tchannel channel, void * buffer, size_t nbytes)
{
  DWORD scr;
  return ((ReadFile (CHANNEL_HANDLE (channel), buffer, nbytes, &scr, 0))
	  ? scr : 0);
}

size_t
OS_channel_write_dump_file (Tchannel channel, const void * buffer,
			    size_t nbytes)
{
  DWORD  scr;
  return ((WriteFile (CHANNEL_HANDLE (channel), ((LPCVOID) buffer), nbytes,
		      &scr, 0))
	  ? scr : 0);
}

void
OS_channel_write_string (Tchannel channel, const char * string)
{
  long length = (strlen (string));
  if ((OS_channel_write (channel, string, length)) != length)
    error_external_return ();
}

int
OS_channel_nonblocking_p (Tchannel channel)
{
  return (CHANNEL_NONBLOCKING (channel));
}

void
OS_channel_nonblocking (Tchannel channel)
{
  (CHANNEL_NONBLOCKING (channel)) = 1;
}

void
OS_channel_blocking (Tchannel channel)
{
  (CHANNEL_NONBLOCKING (channel)) = 0;
}

int
OS_terminal_buffered_p (Tchannel channel)
{
  return (CHANNEL_BUFFERED (channel));
}

void
OS_terminal_buffered (Tchannel channel)
{
  (CHANNEL_BUFFERED (channel)) = 1;
}

void
OS_terminal_nonbuffered (Tchannel channel)
{
  (CHANNEL_BUFFERED (channel)) = 0;
}

int
OS_terminal_cooked_output_p (Tchannel channel)
{
  return (CHANNEL_COOKED (channel));
}

void
OS_terminal_cooked_output (Tchannel channel)
{
  CHANNEL_COOKED (channel) = 1;
}

void
OS_terminal_raw_output (Tchannel channel)
{
  CHANNEL_COOKED (channel) = 0;
}

void
OS_terminal_flush_input (Tchannel channel)
{
}

void
OS_terminal_flush_output (Tchannel channel)
{
}

void
OS_terminal_drain_output (Tchannel channel)
{
}

unsigned int
arg_baud_index (unsigned int argument)
{
  return (arg_index_integer (argument, 1));
}

unsigned int
OS_terminal_get_ispeed (Tchannel channel)
{
  return (0);
}

unsigned int
OS_terminal_get_ospeed (Tchannel channel)
{
  return (0);
}

void
OS_terminal_set_ispeed (Tchannel channel, unsigned int baud)
{
}

void
OS_terminal_set_ospeed (Tchannel channel, unsigned int baud)
{
}

unsigned int
OS_baud_index_to_rate (unsigned int index)
{
  return (9600);
}

int
OS_baud_rate_to_index (unsigned int rate)
{
  return ((rate == 9600) ? 0 : -1);
}

unsigned int
OS_terminal_state_size (void)
{
  return (3);
}

void
OS_terminal_get_state (Tchannel channel, void * state_ptr)
{
  unsigned char * statep = ((unsigned char *) state_ptr);
  (*statep++) = (CHANNEL_NONBLOCKING (channel));
  (*statep++) = (CHANNEL_BUFFERED (channel));
  (*statep)   = (CHANNEL_COOKED (channel));
}

void
OS_terminal_set_state (Tchannel channel, void * state_ptr)
{
  unsigned char * statep = ((unsigned char *) state_ptr);
  (CHANNEL_NONBLOCKING (channel)) = (*statep++);
  (CHANNEL_BUFFERED (channel))    = (*statep++);
  (CHANNEL_COOKED (channel))      = (*statep);
}

int
OS_job_control_p (void)
{
  return (0);
}

int
OS_have_ptys_p (void)
{
  return (0);
}

/* Initialization/Termination code. */

int OS_have_select_p = 0;

extern HANDLE master_tty_window;
extern void EXFUN (NT_initialize_channels, (void));
extern void EXFUN (NT_reset_channels, (void));
extern void EXFUN (NT_restore_channels, (void));

void
NT_reset_channels (void)
{
  OS_free (NT_channel_table);
  NT_channel_table = 0;
  OS_channel_table_size = 0;
}

void
NT_restore_channels (void)
{
  if (master_tty_window != ((HANDLE) NULL))
    Screen_Destroy (TRUE, master_tty_window);
  master_tty_window = ((HANDLE) NULL);
}

void
NT_initialize_channels (void)
{
  master_tty_window = (Screen_Create (NULL, "MIT Scheme", SW_SHOWNORMAL));
  if (win32_under_win32s_p ())
    OS_have_select_p = 0;
  else
    OS_have_select_p = 1;
  /* The following API call boosts the number of available handles to
     its maximum value.  This has no effect under NT, which does not
     place a limit on the number of handles.  */
  (void) SetHandleCount (255);
  OS_channel_table_size = NT_DEFAULT_CHANNEL_TABLE_SIZE;
  NT_channel_table
    = (OS_malloc (OS_channel_table_size * (sizeof (struct channel))));
  {
    Tchannel channel;
    for (channel = 0; (channel < OS_channel_table_size); channel += 1)
      MARK_CHANNEL_CLOSED (channel);
  }
  add_reload_cleanup (NT_channel_close_all);
  initialize_channel_class_generic ();
  initialize_channel_class_file ();
  initialize_channel_class_screen ();
  initialize_channel_class_anonymous_pipe ();
  initialize_channel_class_named_pipe ();
}


syntax highlighted by Code2HTML, v. 0.9.1