/*
 * Simulator of microcontrollers (fio.cc)
 *
 * Copyright (C) 1997,16 Drotos Daniel, Talker Bt.
 * 
 * To contact author send email to drdani@mazsola.iit.uni-miskolc.hu
 *
 */

/* This file is part of microcontroller simulator: ucsim.

UCSIM 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.

UCSIM 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 UCSIM; see the file COPYING.  If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
/*@1@*/

#include "ddconfig.h"

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include HEADER_FD
#include <errno.h>
#include <string.h>
#if defined HAVE_SYS_SOCKET_H
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
#endif
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/stat.h>

// These two flags aren't defined on some platforms
#ifndef S_IRGRP
#define S_IRGRP 0
#endif
#ifndef S_IROTH
#define S_IROTH 0
#endif

#include "fiocl.h"

// prj
#include "utils.h"


cl_history::cl_history(char *aname):
  cl_ustrings(100, 10, aname)
{
  nr= 0;
  //actual_line= "";
}

cl_history::cl_history(const char *aname):
cl_ustrings(100, 10, aname)
{
  nr= 0;
  //actual_line= "";
}

cl_history::~cl_history(void)
{
}

char *
cl_history::up(chars line)
{
  replace(line);
  if (nr > 0)
    nr--;
  return (char*)(Items[nr]);
}

char *
cl_history::down(chars line)
{
  replace(line);
  if (nr < count)
    nr++;
  if (nr < count)
    return (char*)(Items[nr]);
  return NULL;
}

char *
cl_history::enter(chars line)
{
  if (count > 1000)
    {
      free_at(0);
      /*if (nr > count)
	nr= count;*/
    }
  if (!line.empty())
    {
      add(strdup(line));
      nr= count;
    }
  return NULL;
}

void
cl_history::replace(chars line)
{
  if (nr < count)
    {
      free(Items[nr]);
      if (line.empty())
	Items[nr]= strdup("");
      else
	Items[nr]= strdup(line);
    }
}


/****************************************************************************/

cl_f::cl_f(void)
{
  file_id= -1;
  own= false;
  tty= false;
  file_name= 0;
  file_mode= 0;
  server_port= -1;
  echo_of= NULL;
  echo_to= NULL;
  at_end= 0;
  last_used= first_free= 0;
  cooking= 0;
  line[0]= 0;
  cursor= 0;
  esc_buffer[0]= 0;
  attributes_saved= 0;
  hist= new cl_history("history");
  proc_telnet= false;
  proc_escape= false;
}

cl_f::cl_f(chars fn, chars mode):
  cl_base()
{
  file_id= -1;
  file_name= fn;
  file_mode= mode;
  tty= false;
  own= false;
  server_port= -1;
  echo_of= NULL;
  echo_to= NULL;
  at_end= 0;
  last_used= first_free= 0;
  cooking= 0;
  line[0]= 0;
  cursor= 0;
  esc_buffer[0]= 0;
  attributes_saved= 0;
  hist= new cl_history("history");
  proc_telnet= false;
}

cl_f::cl_f(int the_server_port)
{
  file_id= -1;
  own= false;
  tty= false;
  file_name= 0;
  file_mode= 0;
  server_port= the_server_port;
  echo_of= NULL;
  echo_to= NULL;
  at_end= 0;
  last_used= first_free= 0;
  cooking= 0;
  line[0]= 0;
  cursor= 0;
  esc_buffer[0]= 0;
  attributes_saved= 0;
  hist= new cl_history("history");
  proc_telnet= false;
}

class cl_f *
cl_f::copy(chars mode)
{
  class cl_f *io= mk_io(chars(""), chars(""));
  io->use_opened(file_id, mode);
  return io;
}

static int
open_flags(char *m)
{
  if (strcmp(m, "r") == 0)
    return O_RDONLY;
  else if (strcmp(m, "r+") == 0)
    return O_RDWR | O_CREAT;
  else if (strcmp(m, "w") == 0)
    return O_WRONLY | O_TRUNC | O_CREAT;
  else if (strcmp(m, "w+") == 0)
    return O_RDWR | O_CREAT;
  else if (strcmp(m, "a") == 0)
    return O_APPEND | O_WRONLY | O_CREAT;
  else if (strcmp(m, "a+") == 0)
    return O_APPEND | O_RDWR | O_CREAT;
  return O_RDWR;
}

int
cl_f::init(void)
{
  if (server_port > 0)
    {
      file_id= mk_srv_socket(server_port);
      listen(file_id, 50);
      own= true;
      tty= false;
      changed();
    }
  else if (!file_name.empty())
    {
      if (file_mode.empty())
	file_mode= cchars("r+");
      if ((file_id= ::open(file_name, open_flags(file_mode), (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))) >= 0)
	{
	  tty= isatty(file_id);
	  own= true;
	  save_attributes();
	  changed();
	}
      else
	{
	  file_id= -1;
	  own= false;
	}
    }
  return file_id;
}

int
cl_f::use_opened(int opened_file_id, char *mode)
{
  close();
  if (mode)
    file_mode= mode;
  else
    file_mode= cchars("r+");
  own= false;
  if (opened_file_id >= 0)
    {
      file_id= opened_file_id;
      tty= isatty(file_id);
      changed();
    }
  return file_id;
}

int
cl_f::own_opened(int opened_file_id, char *mode)
{
  use_opened(opened_file_id, mode);
  own= true;
  return file_id;
}

int
cl_f::use_opened(FILE *f, chars mode)
{
  close();
  if (f)
    {
      file_mode= mode;
      if ((file_id= fileno(f)) >= 0)
	{
	  tty= isatty(file_id);
	  own= false;
	  changed();
	}
      else
	file_id= -1;
    }
  return file_id;
}

int
cl_f::own_opened(FILE *f, chars mode)
{
  use_opened(f, mode);
  own= true;
  return file_id;
}

int
cl_f::open(char *fn)
{
  close();
  if (fn)
    file_name= fn;
  return init();
}

int
cl_f::open(char *fn, char *mode)
{
  close();
  if (mode)
    file_mode= mode;
  if (fn)
    file_name= fn;
  return init();
}

void
cl_f::changed(void)
{
}

int
cl_f::close(void)
{
  int i= 0;

  if (file_id >= 0)
    ::close(file_id);
  file_id= -1;
  own= false;
  file_name= 0;
  file_mode= 0;
  changed();
  return i;
}

int
cl_f::stop_use(void)
{
  //printf("cl_f stop_use fid=%d\n", file_id);
  file_id= -1;
  own= false;
  file_name= 0;
  file_mode= 0;
  attributes_saved= 0;
  changed();
  return 0;
}

cl_f::~cl_f(void)
{
  delete hist;
}

/* Buffer handling */

int
cl_f::put(int c)
{
  int n= (first_free + 1) % 1024;
  if (n == last_used)
    return -1;
  buffer[first_free]= c;
  first_free= n;
  return 0;
}

int
cl_f::get(void)
{
  if (last_used == first_free)
    {
      return -1;
    }
  int c= buffer[last_used];
  //if (c == 3 /* ^C */)
  //return -2;
  last_used= (last_used + 1) % 1024;
  return c;
}

int
cl_f::free_place(void)
{
  if (first_free >= last_used)
    return 1024 - (first_free - last_used) -1;
  return last_used - first_free -1;
}

int
cl_f::finish_esc(int k)
{
  esc_buffer[0]= 0;
  return k;
}

int
cl_f::process_telnet(unsigned char ci)
{
  int l= strlen(esc_buffer);
  esc_buffer[l]= ci;
  l++;
  esc_buffer[l]= 0;
  if ((ci == 0xff) &&
      (l == 2))
    {
      return finish_esc(0xff);
    }
  if (l == 3)
    {
      return finish_esc(0);
    }
  return 0;
}

int
cl_f::process_csi(void)
{
  int l= strlen(esc_buffer);
  if (l < 3)
    return 0;
  int f, ret= 0;
  char c= esc_buffer[l-1];
  
  switch (esc_buffer[2])
    {
    case 'M':
      if (l == 6)
	{
	  switch (esc_buffer[3])
	    {
	    case ' ': ret= TU_BTN1; break;
	    case '!': ret= TU_BTN2; break;
	    case '"': ret= TU_BTN3; break;
	    case '0': ret= TU_CBTN1; break;
	    case '1': ret= TU_CBTN2; break;
	    case '2': ret= TU_CBTN3; break;
	    case '(': ret= TU_ABTN1; break;
	    case ')': ret= TU_ABTN2; break;
	    case '*': ret= TU_ABTN3; break;
	    case '`': ret= TU_SUP; break;
	    case 'a': ret= TU_SDOWN; break;
	    case 'p': ret= TU_CSUP; break;
	    case 'q': ret= TU_CSDOWN; break;
	    }
	  f= ret;
	  ret&= ~0xffff00;
	  int x= (esc_buffer[4] - 0x20) & 0xff;
	  int y= (esc_buffer[5] - 0x20) & 0xff;
	  ret|= x << 16;
	  ret|= y << 8;
	  fprintf(stderr, "Mouse: 0x%0x (f=%d,0x%x)\n", ret, f, f);
	  return finish_esc(ret);
	}
      return 0;
      break;
    }
  // first char not recognized, check the last
  switch (c)
    {
    case 'A': return finish_esc(TU_UP);
    case 'B': return finish_esc(TU_DOWN);
    case 'C': return finish_esc(TU_RIGHT);
    case 'D': return finish_esc(TU_LEFT);
    case 'H': return finish_esc(TU_HOME);
    case 'F': return finish_esc(TU_END);
    case 'E': return finish_esc(0); // NumPad 5
    case '~':
      {
	int n;
	n= strtol(&esc_buffer[2], 0, 0);
	switch (n)
	  {
	  case 1: return finish_esc(TU_HOME);
	  case 2: return finish_esc(TU_INS);
	  case 3: return finish_esc(TU_DEL);
	  case 4: return finish_esc(TU_END);
	  case 5: return finish_esc(TU_PGUP);
	  case 6: return finish_esc(TU_PGDOWN);
	  case 11: return finish_esc(TU_F1);
	  case 12: return finish_esc(TU_F2);
	  case 13: return finish_esc(TU_F3);
	  case 14: return finish_esc(TU_F4);
	  case 15: return finish_esc(TU_F5);
	  case 17: return finish_esc(TU_F6);
	  case 18: return finish_esc(TU_F7);
	  case 19: return finish_esc(TU_F8);
	  case 20: return finish_esc(TU_F9);
	  case 21: return finish_esc(TU_F10);
	  case 23: return finish_esc(TU_F11);
	  case 24: return finish_esc(TU_F12);
	  default: return finish_esc(c);
	  }
      }
    }
  return 0;
}

int
cl_f::process_esc(char c)
{
  int l;
  //char s[100];
  unsigned int ci= c&0xff, b0= esc_buffer[0]&0xff;
  
  if (b0 == '\033')
    {
      l= strlen(esc_buffer);
      esc_buffer[l]= c;
      l++;
      esc_buffer[l]= 0;
      switch (esc_buffer[1])
	{
	case 'O':
	  if (l < 3)
	    return 0;
	  switch (c)
	    {
	    case 'P': return finish_esc(TU_F1);
	    case 'Q': return finish_esc(TU_F2);
	    case 'R': return finish_esc(TU_F3);
	    case 'S': return finish_esc(TU_F4);
	    case 'H': return finish_esc(TU_HOME);
	    case 'F': return finish_esc(TU_END);
	    default: return finish_esc(c);
	    }
	  break;
	case 'N':
	  if (l < 3)
	    return 0;
	  switch (c)
	    {
	    default: return finish_esc(c);
	    }
	  break;
	case '[':
	  return process_csi();
	  break;
	default:
	  return finish_esc(c);
	}
    }
  else if (b0 == 0xff)
    {
      return process_telnet(ci);
    }
  else // start sequence
    {
      if (ci == '\033')
	{
	  esc_buffer[0]= '\033', esc_buffer[1]= 0;
	  return 0;
	}
      if (ci == 0xff)
	{
	  esc_buffer[0]= 0xff, esc_buffer[1]= 0;
	  return 0;
	}
    }
  return c;
}

int j= 0;

int
cl_f::process(char c)
{
  int i;
  unsigned int ci= c&0xff;

  if (!cooking)
    {
      if (proc_escape)
	{
	  if ((ci == '\033') ||
	      (esc_buffer[0] != 0))
	    {
	      i= process_esc(ci);
	      if (i != 0)
		return put(i);
	      else
		return 0;
	    }
	}
      if (proc_telnet)
	{
	  if ((ci == 0xff) ||
	      (esc_buffer[0] != 0))
	    {
	      ci= process_telnet(ci);
	      if (!ci)
		{
		  return last_ln= 0;
		}
	    }
	  
	  if ((ci == '\n') ||
	      (ci == '\r') ||
	      (ci == 0) ||
	      (last_ln != 0))
	    {
	      if ((last_ln == 0) &&
		  (ci != 0))
		{
		  last_ln= ci;
		}
	      else
		{
		  if (last_ln != (int)ci)
		    {
		      return last_ln= 0;
		    }
		  if (ci == 0)
		    {
		      return last_ln= 0;
		    }		      
		}
	    }
	  
	}
      if ((ci<31) &&
	  (ci!='\n') &&
	  (ci!='\r'))
	{
	  char s[3]= "^ ";
	  s[1]= 'A'+ci-1;
	  echo_write_str(s);
	}
      else if (ci >= 127)
	{
	  char s[100];
	  sprintf(s, "\\%02x", ci);
	  echo_write_str(s);
	}
      else
	{
	  echo_write(&c, 1);
	}
      last_ln= 0;
      return put(c);
    }

  int l= strlen(line);
  int k= process_esc(c);
  int ret= 0;
  if (!k)
    return last_ln= 0;
  // CURSOR MOVEMENT
  if (k == TU_LEFT)
    {
      if (cursor > 0)
	{
	  cursor--;
	  echo_cursor_go_left(1);
	}
    }
  else if (k == TU_RIGHT)
    {
      if (line[cursor] != 0)
	{
	  cursor++;
	  echo_cursor_go_right(1);
	}
    }
  else if ((k == TU_HOME) ||
	   (k == 'A'-'A'+1))
    {
      if (cursor > 0)
	{
	  echo_cursor_go_left(cursor);
	  cursor= 0;
	}
    }
  else if ((k == TU_END) ||
	   (k == 'E'-'A'+1))
    {
      if (line[cursor] != 0)
	{
	  echo_cursor_go_right(l-cursor);
	  cursor= l;
	}
    }
  // HISTORY
  else if (k == TU_UP)
    {
      char *s= hist->up(line);
      if (cursor > 0)
	echo_cursor_go_left(cursor);
      echo_cursor_save();
      while (l--)
	echo_write_str(" ");
      echo_cursor_restore();
      line[cursor= 0]= 0;
      if (s != NULL)
	{
	  strcpy(line, s);
	  echo_write_str(s);
	  cursor= strlen(s);
	}
    }
  else if (k == TU_DOWN)
    {
      char *s= hist->down(line);
      if (cursor > 0)
	echo_cursor_go_left(cursor);
      echo_cursor_save();
      while (l--)
	echo_write_str(" ");
      echo_cursor_restore();
      line[cursor= 0]= 0;
      if (s != NULL)
	{
	  strcpy(line, s);
	  echo_write_str(s);
	  cursor= strlen(s);
	}
    }
  // FINISH EDITING
  else if ((k == '\n') ||
	   (k == '\r') ||
	   (k == 0))
    {
      if (last_ln &&
	  (last_ln != k))
	{
	  last_ln= 0;
	  return 0;
	}
      last_ln= k;
      for (i= 0; i<l; i++)
	put(line[i]);
      put('\n');
      hist->enter(line);
      //tu_cooked();
      line[cursor= 0]= 0;
      esc_buffer[0]= 0;
      ret= l+1;
      echo_write_str("\n");
    }
  // DELETING
  else if ((k == 127) || /*DEL*/
	   (k == 8 /*BS*/))
    {
      if (cursor > 0)
	{
	  for (i= cursor; line[i]; i++)
	    line[i-1]= line[i];
	  l--;
	  line[l]= 0;
	  cursor--;
	  echo_cursor_go_left(1);
	  echo_cursor_save();
	  if (line[cursor])
	    echo_write_str(&line[cursor]);
	  echo_write_str(" ");
	  echo_cursor_restore();
	}
    }
  else if (//(k == 127) || /*DEL*/
	   (k == TU_DEL))
    {
      if (line[cursor] != 0)
	{
	  for (i= cursor+1; line[i]; i++)
	    line[i-1]= line[i];
	  l--;
	  line[l]= 0;
	  echo_cursor_save();
	  if (line[cursor])
	    echo_write_str(&line[cursor]);
	  echo_write_str(" ");
	  echo_cursor_restore();
	}
    }
  else if (k == 'K'-'A'+1)
    {
      if (cursor > 0)
	echo_cursor_go_left(cursor);
      echo_cursor_save();
      while (l--)
	//write(STDOUT_FILENO, " ", 1);
	echo_write_str(" ");
      echo_cursor_restore();
      line[cursor= 0]= 0;
    }
  else if (k < 0)
    ;
  else if (isprint(k))
    {
      if (l < /*tu_buf_size*/1023)
	{
	  if (line[cursor] == 0)
	    {
	      line[cursor++]= k;
	      line[cursor]= 0;
	      echo_write(&c, 1);
	    }
	  else
	    {
	      int j;
	      for (j= l; j>=cursor; j--)
		line[j+1]= line[j];
	      line[cursor++]= k;
	      echo_cursor_save();
	      echo_write(&line[cursor-1], l-cursor+2);
	      echo_cursor_restore();
	      echo_cursor_go_right(1);
	    }
	}
    }
  return ret;
}

int
cl_f::pick(void)
{
  char b[100];
  int fp= free_place();
  if (fp < 5)
    return 0;
  int i= ::read(file_id, b, (fp>101)?99:fp-1);
  if (i > 0)
    {
      int j;
      for (j= 0; j < i; j++)
	{
	  process(b[j]);
	}
    }
  if (i == 0)
    {
      at_end= 1;
    }
  if (i < 0)
    ;
  return i;
}

int
cl_f::pick(char c)
{
  int i= /*put*/process(c);
  return i;
}

int
cl_f::pick(const char *s)
{
  int i, ret= 0;

  if (s)
    for (i= 0; s[i]; i++)
      ret= pick(s[i]);
  return ret;
}

int
cl_f::input_avail(void)
{
  int ret= check_dev();
  if (ret)
    return ret;
  return at_end;
}

int
cl_f::read(int *buf, int max)
{
  return read_dev(buf, max);
}

int
cl_f::get_c(void)
{
  int c;
  while (!check_dev())
    ;
  int i= read_dev(&c, 1);
  if (i > 0)
    return c;
  else
    return i;
}

chars
cl_f::get_s(void)
{
  chars s= "";
  char c;

  if (eof())
    return s;
  c= get_c();
  while ((c == '\n') ||
	 (c == '\r'))
    {
      if (eof())
	return s;
      c= get_c();
    }
  if (eof())
    return s;
  s+= c;
  c= get_c();
  while (!eof() &&
	 (c != '\n') &&
	 (c != '\r'))
    {
      s+= c;
      c= get_c();
    }
  return s;
}


/* IO primitives */

int
cl_f::read_dev(int *buf, int max)
{
  int i= 0, c;
  
  if (max == 0)
    return -1;

  while (i < max)
    {
      c= get();
      //if (c == -2) // ^C
	  //return i;
      if (c == -1)
	{
	  if (i>0)
	    // got something
	    return i;
	  if (at_end)
	    // no more data, and we are at the end
	    return 0;
	  // buffer is empty now, but no eof detected yet
	  return -1;
	}
      buf[i]= c;
      i++;
    }
  if (i>0)
    return i;
  if (at_end)
    return 0;
  return -2;
}


int
cl_f::write(char *buf, int count)
{
  int i;
  if (file_id >= 0)
    {
      if (type != F_SOCKET)
	{
	  return ::write(file_id, buf, count);
	}
      // on socket, assume telnet
      for (i= 0; i < count; i++)
	{
	  int j;
	  if ((buf[i] == '\r') ||
	      (buf[i] == '\n'))
	    {
	      j= ::write(file_id, "\r\n", 2);
	      if (j != 2)
		;
	    }
	  else
	    {
	      j= ::write(file_id, &buf[i], 1);
	      if (j != 1)
		;
	    }
	}
      return i;
    }
  return -1;
}


int
cl_f::write_str(char *s)
{
  if (!s ||
      !*s)
    return 0;
  return write(s, strlen(s));
}


int
cl_f::write_str(const char *s)
{
  if (!s ||
      !*s)
    return 0;
  return write((char*)s, strlen((char*)s));
}

int
cl_f::vprintf(const char *format, va_list ap)
{
  char *s= vformat_string(format, ap);
  int i= write_str(s);
  free(s);
  return i;
}

int
cl_f::prntf(const char *format, ...)
{
  va_list ap;
  int ret= 0;

  va_start(ap, format);
  ret= vprintf(format, ap);
  va_end(ap);

  return ret;
}

bool
cl_f::eof(void)
{
  if (file_id < 0)
    return true;
  return at_end && (last_used == first_free);
}


/*void
cl_f::flush(void)
{
}*/


/* Echoing */

void
cl_f::echo_cursor_save()
{
  if (echo_to)
    {
      echo_to->write(cchars("\033[s"), 3);
      //echo_to->flush();
    }
}

void
cl_f::echo_cursor_restore()
{
  if (echo_to)
    {
      echo_to->write(cchars("\033[u"), 3);
      //echo_to->flush();
    }
}

void
cl_f::echo_cursor_go_left(int n)
{
  char b[100];
  if (echo_to)
    {
      snprintf(b, 99, "\033[%dD", n);
      echo_to->write_str(b);
      //echo_to->flush();
    }
}

void
cl_f::echo_cursor_go_right(int n)
{
  char b[100];
  if (echo_to)
    {
      snprintf(b, 99, "\033[%dC", n);
      echo_to->write_str(b);
      //echo_to->flush();
    }
}

void
cl_f::echo_write(char *b, int l)
{
  if (echo_to)
    {
      echo_to->write(b, l);
      //echo_to->flush();
    }
}

void
cl_f::echo_write_str(char *s)
{
  if (echo_to)
    {
      echo_to->write_str(s);
      //echo_to->flush();
    }
}

void
cl_f::echo_write_str(const char *s)
{
  if (echo_to)
    {
      echo_to->write_str(s);
      //echo_to->flush();
    }
}

  
/* Device handling */

void
cl_f::prepare_terminal()
{
}

void
cl_f::save_attributes()
{
}

void
cl_f::restore_attributes()
{
}

int
cl_f::raw(void)
{
  if (cooking)
    {
      int l= strlen(line), i;
      for (i= 0; i<l; i++)
	put(line[i]);
    }
  cooking= 0;
  return 0;
}


int
cl_f::cooked(void)
{
  line[cursor= 0]= 0;
  esc_buffer[0]= 0;
  last_ln= 0;
  if (tty)
    {
      cooking= 1;
    }
  else if (type == F_SOCKET)
    {
      cooking= 1;
    }
  else
    {
    }
  return 0;
}

int
cl_f::echo(class cl_f *out)
{
  if (echo_to)
    {
      echo_to->echo_of= NULL;
      echo_to= NULL;
    }
  if (out != NULL)
    {
      out->echo_of= this;
      echo_to= out;
    }
  return 0;
}


void
cl_f::interactive(class cl_f *echo_out)
{
  save_attributes();
  echo(echo_out);
  cooked();
  prepare_terminal();
}


void
cl_f::set_telnet(bool val)
{
  proc_telnet= val;
}

void
cl_f::set_escape(bool val)
{
  proc_escape= val;
}


chars
fio_type_name(enum file_type t)
{
  switch (t)
    {
    case F_UNKNOWN: return "unknown";
    case F_FILE: return "file";
    case F_CHAR: return "char";
    case F_SOCKET: return "socket";
    case F_LISTENER: return "listener";
    case F_PIPE: return "pipe";
    case F_CONSOLE: return "console";
    case F_SERIAL: return "serial";
    }
  return "undef";
}


/* End of fio.cc */
