From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- erts/emulator/drivers/unix/bin_drv.c | 224 +++++ erts/emulator/drivers/unix/mem_drv.c | 145 +++ erts/emulator/drivers/unix/multi_drv.c | 105 +++ erts/emulator/drivers/unix/sig_drv.c | 81 ++ erts/emulator/drivers/unix/ttsl_drv.c | 1299 ++++++++++++++++++++++++++ erts/emulator/drivers/unix/unix_efile.c | 1505 +++++++++++++++++++++++++++++++ 6 files changed, 3359 insertions(+) create mode 100644 erts/emulator/drivers/unix/bin_drv.c create mode 100644 erts/emulator/drivers/unix/mem_drv.c create mode 100644 erts/emulator/drivers/unix/multi_drv.c create mode 100644 erts/emulator/drivers/unix/sig_drv.c create mode 100644 erts/emulator/drivers/unix/ttsl_drv.c create mode 100644 erts/emulator/drivers/unix/unix_efile.c (limited to 'erts/emulator/drivers/unix') diff --git a/erts/emulator/drivers/unix/bin_drv.c b/erts/emulator/drivers/unix/bin_drv.c new file mode 100644 index 0000000000..1827187d57 --- /dev/null +++ b/erts/emulator/drivers/unix/bin_drv.c @@ -0,0 +1,224 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* Purpose: Binary file driver interface , used for code loading */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include +#include +#include +#include "erl_driver.h" +#include + +#ifdef NO_UMASK +#define FILE_MODE 0644 +#else +#define FILE_MODE 0666 +#endif + +static int this_port; + +static long bin_start(); +static int bin_init(),bin_stop(),bin_erlang_read(); +static int read_fill(), write_fill(); + +const struct erl_drv_entry bin_driver_entry = { + bin_init, + bin_start, + bin_stop, + bin_erlang_read, + NULL, + NULL, + "binary_filer" +}; + + + +static int bin_init() +{ + this_port = -1; + return 0; +} + + +static long bin_start(port,buf) +int port; +char *buf; +{ + + if (this_port != -1) + return(-1); + this_port = port; + return(port); +} + + +static int bin_stop() +{ + this_port = -1; +} + + +static int bin_erlang_read(port,buf,count) +long port; +char *buf; +int count; + +{ + struct stat statbuf; + int i,size,fd,rval; + char *t,*rbuf; + + buf[count] = '\0'; + switch (*buf) { + case 'c' : + if (chdir(buf+1) == 0) /* sucsess */ + driver_output(this_port,"y",1); + else + driver_output(this_port,"n",1); + return 0; + case 'f': +#ifdef MSDOS + if ((fd = open(buf+1,O_RDONLY|O_BINARY)) < 0) { +#else + if ((fd = open(buf+1,O_RDONLY, 0)) < 0) { +#endif + driver_output(this_port,"n",1); + return 0; + } + if (stat(buf+1,&statbuf) < 0) { + driver_output(this_port,"n",1); + close(fd); + return 0; + } + if (S_ISREG(statbuf.st_mode)) + size = statbuf.st_size; + else + size = BUFSIZ; + + if ((rbuf = (char*) driver_alloc(1 + size)) == NULL) { + fprintf(stderr,"Out of memory\n"); + close(fd); + driver_output(this_port,"n",1); + return 0; + } + if (S_ISREG(statbuf.st_mode)) { + if (read_fill(fd,1+rbuf,size) != size) { + driver_free(rbuf); + close(fd); + driver_output(this_port,"n",1); + return 0;; + } + } + /* The idea here is that if it's a regular file read the entire + * entire file and if it's a device file or a tty try to read + * until errno != EINTR + */ + + else { + while (1) { + rval = read(fd,1+rbuf,size); + if (rval < 0 && errno == EINTR) + continue; + if (rval < 0) { + driver_free(rbuf); + close(fd); + driver_output(this_port,"n",1); + return 0; + } + size = rval; + break; + } + } + rbuf[0] = 'y'; + driver_output(this_port,rbuf,1+size); + driver_free(rbuf); + close(fd); + return 0; + case 'w': +#ifdef MSDOS + if ((fd = open(buf+1, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + FILE_MODE)) < 0) { +#else + if ((fd = open(buf+1, O_WRONLY | O_CREAT | O_TRUNC, FILE_MODE)) < 0) { +#endif + driver_output(this_port,"n",1); + return 0; + } + t = buf+1; i = 1; + while(*t && i++ < count) t++; + t++; + /* t now points to the contents of what we shall write */ + size = count - 1 - i; + + /* gotta write_fill if we are writing to a slow device + such as /dev/audio */ + + if (write_fill (fd,t,size) != size) { + driver_output(this_port,"n",1); + close(fd); + return 0; + } + driver_output(this_port,"y",1); + close(fd); + return 0; + default: + driver_failure(this_port,-1); + return 0; + } +} + + +static int write_fill(fd, buf, len) +char *buf; +{ + int i, done = 0; + + do { + if ((i = write(fd, buf+done, len-done)) < 0) { + if (errno != EINTR) + return (i); + i = 0; + } + done += i; + } while (done < len); + return (len); +} + + +static int read_fill(fd, buf, len) +char *buf; +{ + int i, got = 0; + + do { + if ((i = read(fd, buf+got, len-got)) <= 0) { + if (i == 0 || errno != EINTR) + return (i); + i = 0; + } + got += i; + } while (got < len); + return (len); +} + diff --git a/erts/emulator/drivers/unix/mem_drv.c b/erts/emulator/drivers/unix/mem_drv.c new file mode 100644 index 0000000000..1417ca1121 --- /dev/null +++ b/erts/emulator/drivers/unix/mem_drv.c @@ -0,0 +1,145 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* Purpose: Access to elib memory statistics */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include "erl_driver.h" +#include "elib_stat.h" + +#define MAP_BUF_SIZE 1000 /* Max map size */ +#define HISTO_BUF_SIZE 100 /* Max histogram buckets */ + +static ErlDrvData mem_start(ErlDrvPort); +static int mem_init(void); +static void mem_stop(ErlDrvData); +static void mem_command(ErlDrvData, char*, int); + +const struct driver_entry mem_driver_entry = { + mem_init, + mem_start, + mem_stop, + mem_command, + NULL, + NULL, + "mem_drv" +}; + +static int mem_init(void) +{ + return 0; +} + +static ErlDrvData mem_start(ErlDrvPort port, char* buf) +{ + return (ErlDrvData)port; +} + +static void mem_stop(ErlDrvData port) +{ +} + +void putint32(p, v) +byte* p; int v; +{ + p[0] = (v >> 24) & 0xff; + p[1] = (v >> 16) & 0xff; + p[2] = (v >> 8) & 0xff; + p[3] = (v) & 0xff; +} + +int getint16(p) +byte* p; +{ + return (p[0] << 8) | p[1]; +} + +/* +** Command: +** m L1 L0 -> a heap map of length L1*256 + L0 is returned +** s -> X3 X2 X1 X0 Y3 Y2 Y1 Y0 Z3 Z2 Z1 Z0 +** X == Total heap size bytes +** Y == Total free bytes +** Z == Size of largest free block in bytes +** +** h L1 L0 B0 -> Generate a logarithm historgram base B with L buckets +** l L1 L0 S0 -> Generate a linear histogram with step S with L buckets +*/ +unsigned char outbuf[HISTO_BUF_SIZE*2*4]; + +static void mem_command(ErlDrvData port, char* buf, int count) +{ + if ((count == 1) && buf[0] == 's') { + struct elib_stat info; + char v[3*4]; + + elib_stat(&info); + + putint32(v, info.mem_total*4); + putint32(v+4, info.mem_free*4); + putint32(v+8, info.max_free*4); + driver_output((ErlDrvPort)port, v, 12); + return; + } + else if ((count == 3) && buf[0] == 'm') { + char w[MAP_BUF_SIZE]; + int n = getint16(buf+1); + + if (n > MAP_BUF_SIZE) + n = MAP_BUF_SIZE; + elib_heap_map(w, n); + driver_output((ErlDrvPort)port, w, n); + return; + } + else if ((count == 4) && (buf[0] == 'h' || buf[0] == 'l')) { + unsigned long vf[HISTO_BUF_SIZE]; + unsigned long va[HISTO_BUF_SIZE]; + int n = getint16(buf+1); + int base = (unsigned char) buf[3]; + + if (n >= HISTO_BUF_SIZE) + n = HISTO_BUF_SIZE; + if (buf[0] == 'l') + base = -base; + if (elib_histo(vf, va, n, base) < 0) { + driver_failure((ErlDrvPort)port, -1); + return; + } + else { + char* p = outbuf; + int i; + + for (i = 0; i < n; i++) { + putint32(p, vf[i]); + p += 4; + } + for (i = 0; i < n; i++) { + putint32(p, va[i]); + p += 4; + } + driver_output((ErlDrvPort)port, outbuf, n*8); + } + return; + } + driver_failure((ErlDrvPort)port, -1); +} diff --git a/erts/emulator/drivers/unix/multi_drv.c b/erts/emulator/drivers/unix/multi_drv.c new file mode 100644 index 0000000000..822c96730c --- /dev/null +++ b/erts/emulator/drivers/unix/multi_drv.c @@ -0,0 +1,105 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* Purpose: Multidriver interface + This is an example of a driver which allows multiple instances of itself. + I.e have one erlang process execute open_port(multi......) and + at the same time have an other erlang process open an other port + running multi there as well. +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "erl_driver.h" +#include "sys.h" +#include +#include +#include + +#define MAXCHANNEL 20 + +static char buf[BUFSIZ]; + +static ErlDrvData multi_start(ErlDrvPort, char*); +static int multi_init(void); +static void multi_stop(ErlDrvData),multi_erlang_read(ErlDrvData, char*, int); + +struct driver_entry multi_driver_entry = { + multi_init, + multi_start, + multi_stop, + multi_erlang_read, + NULL, + NULL, + "multi" +}; + + +struct channel { + ErlDrvPort portno; + int channel; +}; + +struct channel channels[MAXCHANNEL]; /* Max MAXCHANNEL instances */ + +static int multi_init(void) +{ + memzero(channels,MAXCHANNEL * sizeof(struct channel)); + return 0; +} + +static ErlDrvData multi_start(ErlDrvPort port, char* buf) +{ + int chan; + chan = get_new_channel(); + channels[port].portno = port; + channels[port].channel = chan; + fprintf(stderr,"Opening channel %d port is %d\n",chan,port); + return (ErlDrvData)port; +} + + +static int multi_stop(ErlDrvData port) +{ + fprintf(stderr,"Closing channel %d\n",channels[port].channel); + remove_channel(channels[(int)port].channel); +} + + +static int multi_erlang_read(ErlDrvData port, char* buf, int count) +{ + fprintf(stderr,"Writing %d bytes to channel %d\n", + count, + channels[(int)port].channel); +} + + +/* These two funs are fake */ + +int get_new_channel() +{ + static int ch = 1; + return(ch++); +} + +void remove_channel(int ch) +{ +} diff --git a/erts/emulator/drivers/unix/sig_drv.c b/erts/emulator/drivers/unix/sig_drv.c new file mode 100644 index 0000000000..aab5d63a40 --- /dev/null +++ b/erts/emulator/drivers/unix/sig_drv.c @@ -0,0 +1,81 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* Purpose: demonstrate how to include interupt handlers in erlang */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include "erl_driver.h" +#include +#include + +static ErlDrvData sig_start(ErlDrvPort, char*); +static int sig_init(void); +static void sig_stop(ErlDrvData), doio(ErlDrvData, ErlDrvEvent); + +ErlDrvEntry sig_driver_entry = { + sig_init, + sig_start, + sig_stop, + NULL, + doio, + NULL, + "sig_test" +}; + +static ErlDrvPort this_port; + +static int sig_init(void) +{ + this_port = (ErlDrvPort)-1; + return 0; +} + +static sigc(int ino) +{ + driver_interrupt(this_port, ino); +} + +static ErlDrvData sig_start(ErlDrvPort port, char* buf) +{ + if (this_port != (ErlDrvPort)-1) + return ERL_DRV_ERROR_GENERAL; + this_port = port; + signal(SIGUSR1, sigc); + return (ErlDrvData)port; +} + +static void sig_stop(ErlDrvData port) +{ + this_port = (ErlDrvPort)-1; + signal(SIGUSR1, SIG_DFL); +} + +doio(ErlDrvData port, ErlDrvEvent ino) +{ + /* First go get the io, unless we already did that */ + /* In the sighandler */ + + /* Then send it to erlang */ + + driver_output(this_port, "y", 1); +} diff --git a/erts/emulator/drivers/unix/ttsl_drv.c b/erts/emulator/drivers/unix/ttsl_drv.c new file mode 100644 index 0000000000..4c2514669b --- /dev/null +++ b/erts/emulator/drivers/unix/ttsl_drv.c @@ -0,0 +1,1299 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Tty driver that reads one character at the time and provides a + * smart line for output. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "erl_driver.h" + +static int ttysl_init(void); +static ErlDrvData ttysl_start(ErlDrvPort, char*); + +#ifdef HAVE_TERMCAP /* else make an empty driver that can not be opened */ + +#include "sys.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#if !defined(HAVE_SETLOCALE) || !defined(HAVE_NL_LANGINFO) || !defined(HAVE_LANGINFO_H) +#define PRIMITIVE_UTF8_CHECK 1 +#else +#include +#endif + +#define TRUE 1 +#define FALSE 0 + +/* Termcap functions. */ +int tgetent(char* bp, char *name); +int tgetnum(char* cap); +int tgetflag(char* cap); +char *tgetstr(char* cap, char** buf); +char *tgoto(char* cm, int col, int line); +int tputs(char* cp, int affcnt, int (*outc)(int c)); + +/* Terminal capabilites in which we are interested. */ +static char *capbuf; +static char *up, *down, *left, *right; +static int cols, xn; +static volatile int cols_needs_update = FALSE; + +/* The various opcodes. */ +#define OP_PUTC 0 +#define OP_MOVE 1 +#define OP_INSC 2 +#define OP_DELC 3 +#define OP_BEEP 4 +/* Control op */ +#define CTRL_OP_GET_WINSIZE 100 +#define CTRL_OP_GET_UNICODE_STATE 101 +#define CTRL_OP_SET_UNICODE_STATE 102 + + + +static int lbuf_size = BUFSIZ; +static Uint32 *lbuf; /* The current line buffer */ +static int llen; /* The current line length */ +static int lpos; /* The current "cursor position" in the line buffer */ + +/* + * Tags used in line buffer to show that these bytes represent special characters, + * Max unicode is 0x0010ffff, so we have lots of place for meta tags... + */ +#define CONTROL_TAG 0x10000000U /* Control character, value in first position */ +#define ESCAPED_TAG 0x01000000U /* Escaped character, value in first position */ +#define TAG_MASK 0xFF000000U + +#define MAXSIZE (1 << 16) + +#define COL(_l) ((_l) % cols) +#define LINE(_l) ((_l) / cols) + +#define NL '\n' + +/* Main interface functions. */ +static void ttysl_stop(ErlDrvData); +static void ttysl_from_erlang(ErlDrvData, char*, int); +static void ttysl_from_tty(ErlDrvData, ErlDrvEvent); +static void ttysl_stop_select(ErlDrvEvent, void*); +static Sint16 get_sint16(char*); + +static ErlDrvPort ttysl_port; +static int ttysl_fd; +static FILE *ttysl_out; + +/* Functions that work on the line buffer. */ +static int start_lbuf(void); +static int stop_lbuf(void); +static int put_chars(byte*,int); +static int move_rel(int); +static int ins_chars(byte *,int); +static int del_chars(int); +static int step_over_chars(int); +static int insert_buf(byte*,int); +static int write_buf(Uint32 *,int); +static int outc(int c); +static int move_cursor(int,int); + +/* Termcap functions. */ +static int start_termcap(void); +static int stop_termcap(void); +static int move_left(int); +static int move_right(int); +static int move_up(int); +static int move_down(int); +static void update_cols(void); + +/* Terminal setting functions. */ +static int tty_init(int,int,int,int); +static int tty_set(int); +static int tty_reset(int); +static int ttysl_control(ErlDrvData, unsigned int, char *, int, char **, int); +static RETSIGTYPE suspend(int); +static RETSIGTYPE cont(int); +static RETSIGTYPE winch(int); + +/*#define LOG_DEBUG*/ + +#ifdef LOG_DEBUG +FILE *debuglog = NULL; + +#define DEBUGLOG(X) \ +do { \ + if (debuglog != NULL) { \ + my_debug_printf X; \ + } \ +} while (0) + +static void my_debug_printf(char *fmt, ...) +{ + char buffer[1024]; + va_list args; + + va_start(args, fmt); + erts_vsnprintf(buffer,1024,fmt,args); + va_end(args); + erts_fprintf(debuglog,"%s\n",buffer); + //erts_printf("Debuglog = %s\n",buffer); +} + +#else + +#define DEBUGLOG(X) + +#endif + +static int utf8_mode = 0; +static byte utf8buf[4]; /* for incomplete input */ +static int utf8buf_size; /* size of incomplete input */ + +# define IF_IMPL(x) x +#else +# define IF_IMPL(x) NULL +#endif /* HAVE_TERMCAP */ + +/* Define the driver table entry. */ +struct erl_drv_entry ttsl_driver_entry = { + ttysl_init, + ttysl_start, + IF_IMPL(ttysl_stop), + IF_IMPL(ttysl_from_erlang), + IF_IMPL(ttysl_from_tty), + NULL, + "tty_sl", + NULL, + NULL, + IF_IMPL(ttysl_control), + NULL, /* timeout */ + NULL, /* outputv */ + NULL, /* ready_async */ + NULL, /* flush */ + NULL, /* call */ + NULL, /* event */ + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + 0, /* ERL_DRV_FLAGs */ + NULL, + NULL, /* process_exit */ + IF_IMPL(ttysl_stop_select) +}; + + +static int ttysl_init(void) +{ +#ifdef HAVE_TERMCAP + ttysl_port = (ErlDrvPort)-1; + ttysl_fd = -1; + lbuf = NULL; /* For line buffer handling */ + capbuf = NULL; /* For termcap handling */ +#endif +#ifdef LOG_DEBUG + { + char *dl; + if ((dl = getenv("TTYSL_DEBUG_LOG")) != NULL && *dl) { + debuglog = fopen(dl,"w+"); + if (debuglog != NULL) + setbuf(debuglog,NULL); + } + DEBUGLOG(("Debuglog = %s(0x%ld)\n",dl,(long) debuglog)); + } +#endif + return 0; +} + +static ErlDrvData ttysl_start(ErlDrvPort port, char* buf) +{ +#ifndef HAVE_TERMCAP + return ERL_DRV_ERROR_GENERAL; +#else + char *s, *t, c, *l; + int canon, echo, sig; /* Terminal characteristics */ + int flag; + extern int using_oldshell; /* set this to let the rest of erts know */ + + utf8buf_size = 0; + if (ttysl_port != (ErlDrvPort)-1) + return ERL_DRV_ERROR_GENERAL; + + if (!isatty(0) || !isatty(1)) + return ERL_DRV_ERROR_GENERAL; + + /* Set the terminal modes to default leave as is. */ + canon = echo = sig = 0; + + /* Parse the input parameters. */ + for (s = strchr(buf, ' '); s; s = t) { + s++; + /* Find end of this argument (start of next) and insert NUL. */ + if ((t = strchr(s, ' '))) { + c = *t; + *t = '\0'; + } + if ((flag = ((*s == '+') ? 1 : ((*s == '-') ? -1 : 0)))) { + if (s[1] == 'c') canon = flag; + if (s[1] == 'e') echo = flag; + if (s[1] == 's') sig = flag; + } + else if ((ttysl_fd = open(s, O_RDWR, 0)) < 0) + return ERL_DRV_ERROR_GENERAL; + } + if (ttysl_fd < 0) + ttysl_fd = 0; + + if (tty_init(ttysl_fd, canon, echo, sig) < 0 || + tty_set(ttysl_fd) < 0) { + ttysl_port = (ErlDrvPort)-1; + tty_reset(ttysl_fd); + return ERL_DRV_ERROR_GENERAL; + } + + /* Set up smart line and termcap stuff. */ + if (!start_lbuf() || !start_termcap()) { + stop_lbuf(); /* Must free this */ + tty_reset(ttysl_fd); + return ERL_DRV_ERROR_GENERAL; + } + + /* Open the terminal and set the terminal */ + ttysl_out = fdopen(ttysl_fd, "w"); + +#ifdef PRIMITIVE_UTF8_CHECK + setlocale(LC_CTYPE, ""); /* Set international environment, + ignore result */ + if (((l = getenv("LC_ALL")) && *l) || + ((l = getenv("LC_CTYPE")) && *l) || + ((l = getenv("LANG")) && *l)) { + if (strstr(l, "UTF-8")) + utf8_mode = 1; + } + +#else + l = setlocale(LC_CTYPE, ""); /* Set international environment */ + if (l != NULL) { + utf8_mode = (strcmp(nl_langinfo(CODESET), "UTF-8") == 0); + DEBUGLOG(("setlocale: %s\n",l)); + } +#endif + DEBUGLOG(("utf8_mode is %s\n",(utf8_mode) ? "on" : "off")); + sys_sigset(SIGCONT, cont); + sys_sigset(SIGWINCH, winch); + + driver_select(port, (ErlDrvEvent)(Uint)ttysl_fd, ERL_DRV_READ|ERL_DRV_USE, 1); + ttysl_port = port; + + /* we need to know this when we enter the break handler */ + using_oldshell = 0; + + return (ErlDrvData)ttysl_port; /* Nothing important to return */ +#endif /* HAVE_TERMCAP */ +} + +#ifdef HAVE_TERMCAP + +#define DEF_HEIGHT 24 +#define DEF_WIDTH 80 +static void ttysl_get_window_size(Uint32 *width, Uint32 *height) +{ +#ifdef TIOCGWINSZ + struct winsize ws; + if (ioctl(ttysl_fd,TIOCGWINSZ,&ws) == 0) { + *width = (Uint32) ws.ws_col; + *height = (Uint32) ws.ws_row; + if (*width <= 0) + *width = DEF_WIDTH; + if (*height <= 0) + *height = DEF_HEIGHT; + return; + } +#endif + *width = DEF_WIDTH; + *height = DEF_HEIGHT; +} + +static int ttysl_control(ErlDrvData drv_data, + unsigned int command, + char *buf, int len, + char **rbuf, int rlen) +{ + char resbuff[2*sizeof(Uint32)]; + int res_size; + switch (command) { + case CTRL_OP_GET_WINSIZE: + { + Uint32 w,h; + ttysl_get_window_size(&w,&h); + memcpy(resbuff,&w,sizeof(Uint32)); + memcpy(resbuff+sizeof(Uint32),&h,sizeof(Uint32)); + res_size = 2*sizeof(Uint32); + } + break; + case CTRL_OP_GET_UNICODE_STATE: + *resbuff = (utf8_mode) ? 1 : 0; + res_size = 1; + break; + case CTRL_OP_SET_UNICODE_STATE: + if (len > 0) { + int m = (int) *buf; + *resbuff = (utf8_mode) ? 1 : 0; + res_size = 1; + utf8_mode = (m) ? 1 : 0; + } else { + return 0; + } + break; + default: + return 0; + } + if (rlen < res_size) { + *rbuf = driver_alloc(res_size); + } + memcpy(*rbuf,resbuff,res_size); + return res_size; +} + + +static void ttysl_stop(ErlDrvData ttysl_data) +{ + if (ttysl_port != (ErlDrvPort)-1) { + stop_lbuf(); + stop_termcap(); + tty_reset(ttysl_fd); + driver_select(ttysl_port, (ErlDrvEvent)(Uint)ttysl_fd, ERL_DRV_READ|ERL_DRV_USE, 0); + sys_sigset(SIGCONT, SIG_DFL); + sys_sigset(SIGWINCH, SIG_DFL); + } + ttysl_port = (ErlDrvPort)-1; + ttysl_fd = -1; + /* return TRUE; */ +} + +static int put_utf8(int ch, byte *target, int sz, int *pos) +{ + Uint x = (Uint) ch; + if (x < 0x80) { + if (*pos >= sz) { + return -1; + } + target[(*pos)++] = (byte) x; + } + else if (x < 0x800) { + if (((*pos) + 1) >= sz) { + return -1; + } + target[(*pos)++] = (((byte) (x >> 6)) | + ((byte) 0xC0)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else if (x < 0x10000) { + if ((x >= 0xD800 && x <= 0xDFFF) || + (x == 0xFFFE) || + (x == 0xFFFF)) { /* Invalid unicode range */ + return -1; + } + if (((*pos) + 2) >= sz) { + return -1; + } + + target[(*pos)++] = (((byte) (x >> 12)) | + ((byte) 0xE0)); + target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else if (x < 0x110000) { /* Standard imposed max */ + if (((*pos) + 3) >= sz) { + return -1; + } + target[(*pos)++] = (((byte) (x >> 18)) | + ((byte) 0xF0)); + target[(*pos)++] = ((((byte) (x >> 12)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) | + ((byte) 0x80)); + target[(*pos)++] = (((byte) (x & 0x3F)) | + ((byte) 0x80)); + } else { + return -1; + } + return 0; +} + + +static int pick_utf8(byte *s, int sz, int *pos) +{ + int size = sz - (*pos); + byte *source; + Uint unipoint; + + if (size > 0) { + source = s + (*pos); + if (((*source) & ((byte) 0x80)) == 0) { + unipoint = (int) *source; + ++(*pos); + return (int) unipoint; + } else if (((*source) & ((byte) 0xE0)) == 0xC0) { + if (size < 2) { + return -2; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((*source) < 0xC2) /* overlong */) { + return -1; + } + (*pos) += 2; + unipoint = + (((Uint) ((*source) & ((byte) 0x1F))) << 6) | + ((Uint) (source[1] & ((byte) 0x3F))); + return (int) unipoint; + } else if (((*source) & ((byte) 0xF0)) == 0xE0) { + if (size < 3) { + return -2; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((source[2] & ((byte) 0xC0)) != 0x80) || + (((*source) == 0xE0) && (source[1] < 0xA0)) /* overlong */ ) { + return -1; + } + if ((((*source) & ((byte) 0xF)) == 0xD) && + ((source[1] & 0x20) != 0)) { + return -1; + } + if (((*source) == 0xEF) && (source[1] == 0xBF) && + ((source[2] == 0xBE) || (source[2] == 0xBF))) { + return -1; + } + (*pos) += 3; + unipoint = + (((Uint) ((*source) & ((byte) 0xF))) << 12) | + (((Uint) (source[1] & ((byte) 0x3F))) << 6) | + ((Uint) (source[2] & ((byte) 0x3F))); + return (int) unipoint; + } else if (((*source) & ((byte) 0xF8)) == 0xF0) { + if (size < 4) { + return -2 ; + } + if (((source[1] & ((byte) 0xC0)) != 0x80) || + ((source[2] & ((byte) 0xC0)) != 0x80) || + ((source[3] & ((byte) 0xC0)) != 0x80) || + (((*source) == 0xF0) && (source[1] < 0x90)) /* overlong */) { + return -1; + } + if ((((*source) & ((byte)0x7)) > 0x4U) || + ((((*source) & ((byte)0x7)) == 0x4U) && + ((source[1] & ((byte)0x3F)) > 0xFU))) { + return -1; + } + (*pos) += 4; + unipoint = + (((Uint) ((*source) & ((byte) 0x7))) << 18) | + (((Uint) (source[1] & ((byte) 0x3F))) << 12) | + (((Uint) (source[2] & ((byte) 0x3F))) << 6) | + ((Uint) (source[3] & ((byte) 0x3F))); + return (int) unipoint; + } else { + return -1; + } + } else { + return -1; + } +} + +static int octal_or_hex_positions(Uint c) +{ + int x = 0; + Uint ch = c; + if (!ch) { + return 1; + } + while(ch) { + ++x; + ch >>= 3; + } + if (x <= 3) { + return 3; + } + /* \x{H ...} format when larger than \777 */ + x = 0; + ch = c; + while(ch) { + ++x; + ch >>= 4; + } + return x+3; +} + +static void octal_or_hex_format(Uint ch, byte *buf, int *pos) +{ + static byte hex_chars[] = { '0','1','2','3','4','5','6','7','8','9', + 'A','B','C','D','E','F'}; + int num = octal_or_hex_positions(ch); + if (num != 3) { + buf[(*pos)++] = 'x'; + buf[(*pos)++] = '{'; + num -= 3; + while(num--) { + buf[(*pos)++] = hex_chars[((ch >> (4*num)) & 0xFU)]; + } + buf[(*pos)++] = '}'; + } else { + while(num--) { + buf[(*pos)++] = ((byte) ((ch >> (3*num)) & 0x7U) + '0'); + } + } +} + +/* + * Check that there is enough room in all buffers to copy all pad chars + * and stiff we need If not, realloc lbuf. + */ +static int check_buf_size(byte *s, int n) +{ + int pos = 0; + int ch; + int size = 10; + + while(pos < n) { + /* Indata is always UTF-8 */ + if ((ch = pick_utf8(s,n,&pos)) < 0) { + /* XXX temporary allow invalid chars */ + ch = (int) s[pos]; + DEBUGLOG(("Invalid UTF8:%d",ch)); + ++pos; + } + if (utf8_mode) { /* That is, terminal is UTF8 compliant */ + if (ch >= 128 || isprint(ch)) { + DEBUGLOG(("Printable(UTF-8:%d):%d",(pos - opos),ch)); + size++; /* Buffer contains wide characters... */ + } else if (ch == '\t') { + size += 8; + } else { + DEBUGLOG(("Magic(UTF-8:%d):%d",(pos - opos),ch)); + size += 2; + } + } else { + if (ch <= 255 && isprint(ch)) { + DEBUGLOG(("Printable:%d",ch)); + size++; + } else if (ch == '\t') + size += 8; + else if (ch >= 128) { + DEBUGLOG(("Non printable:%d",ch)); + size += (octal_or_hex_positions(ch) + 1); + } + else { + DEBUGLOG(("Magic:%d",ch)); + size += 2; + } + } + } + + if (size + lpos >= lbuf_size) { + + lbuf_size = size + lpos + BUFSIZ; + if ((lbuf = driver_realloc(lbuf, lbuf_size * sizeof(Uint32))) == NULL) { + driver_failure(ttysl_port, -1); + return(0); + } + } + return(1); +} + + +static void ttysl_from_erlang(ErlDrvData ttysl_data, char* buf, int count) +{ + if (lpos > MAXSIZE) + put_chars((byte*)"\n", 1); + + switch (buf[0]) { + case OP_PUTC: + DEBUGLOG(("OP: Putc(%d)",count-1)); + if (check_buf_size((byte*)buf+1, count-1) == 0) + return; + put_chars((byte*)buf+1, count-1); + break; + case OP_MOVE: + move_rel(get_sint16(buf+1)); + break; + case OP_INSC: + if (check_buf_size((byte*)buf+1, count-1) == 0) + return; + ins_chars((byte*)buf+1, count-1); + break; + case OP_DELC: + del_chars(get_sint16(buf+1)); + break; + case OP_BEEP: + outc('\007'); + break; + default: + /* Unknown op, just ignore. */ + break; + } + fflush(ttysl_out); + return; /* TRUE; */ +} + +static void ttysl_from_tty(ErlDrvData ttysl_data, ErlDrvEvent fd) +{ + byte b[1024]; + ssize_t i; + int ch = 0, pos = 0; + int left = 1024; + byte *p = b; + byte t[1024]; + int tpos; + + if (utf8buf_size > 0) { + memcpy(b,utf8buf,utf8buf_size); + left -= utf8buf_size; + p += utf8buf_size; + utf8buf_size = 0; + } + + if ((i = read((int)(Sint)fd, (char *) p, left)) >= 0) { + if (p != b) { + i += (p - b); + } + if (utf8_mode) { /* Hopefully an UTF8 terminal */ + while(pos < i && (ch = pick_utf8(b,i,&pos)) >= 0) + ; + if (ch == -2 && i - pos <= 4) { + /* bytes left to care for */ + utf8buf_size = i -pos; + memcpy(utf8buf,b+pos,utf8buf_size); + } else if (ch == -1) { + DEBUGLOG(("Giving up on UTF8 mode, invalid character")); + utf8_mode = 0; + goto latin_terminal; + } + driver_output(ttysl_port, (char *) b, pos); + } else { + latin_terminal: + tpos = 0; + while (pos < i) { + while (tpos < 1020 && pos < i) { /* Max 4 bytes for UTF8 */ + put_utf8((int) b[pos++], t, 1024, &tpos); + } + driver_output(ttysl_port, (char *) t, tpos); + tpos = 0; + } + } + } else { + driver_failure(ttysl_port, -1); + } +} + +static void ttysl_stop_select(ErlDrvEvent e, void* _) +{ + int fd = (int)(long)e; + if (fd != 0) { + close(fd); + } +} + +/* Procedures for putting and getting integers to/from strings. */ +static Sint16 get_sint16(char *s) +{ + return ((*s << 8) | ((byte*)s)[1]); +} + +static int start_lbuf(void) +{ + if (!lbuf && !(lbuf = ( Uint32*) driver_alloc(lbuf_size * sizeof(Uint32)))) + return FALSE; + llen = 0; + lpos = 0; + return TRUE; +} + +static int stop_lbuf(void) +{ + if (lbuf) { + driver_free(lbuf); + lbuf = NULL; + } + return TRUE; +} + +/* Put l bytes (in UTF8) from s into the buffer and output them. */ +static int put_chars(byte *s, int l) +{ + int n; + + n = insert_buf(s, l); + if (n > 0) + write_buf(lbuf + lpos - n, n); + if (lpos > llen) + llen = lpos; + return TRUE; +} + +/* + * Move the current postition forwards or backwards within the current + * line. We know about padding. + */ +static int move_rel(int n) +{ + int npos; /* The new position */ + + /* Step forwards or backwards over the buffer. */ + npos = step_over_chars(n); + + /* Calculate move, updates pointers and move the cursor. */ + move_cursor(lpos, npos); + lpos = npos; + return TRUE; +} + +/* Insert characters into the buffer at the current position. */ +static int ins_chars(byte *s, int l) +{ + int n, tl; + Uint32 *tbuf = NULL; /* Suppress warning about use-before-set */ + + /* Move tail of buffer to make space. */ + if ((tl = llen - lpos) > 0) { + if ((tbuf = driver_alloc(tl * sizeof(Uint32))) == NULL) + return FALSE; + memcpy(tbuf, lbuf + lpos, tl * sizeof(Uint32)); + } + n = insert_buf(s, l); + if (tl > 0) { + memcpy(lbuf + lpos, tbuf, tl * sizeof(Uint32)); + driver_free(tbuf); + } + llen += n; + write_buf(lbuf + (lpos - n), llen - (lpos - n)); + move_cursor(llen, lpos); + return TRUE; +} + +/* + * Delete characters in the buffer. Can delete characters before (n < 0) + * and after (n > 0) the current position. Cursor left at beginning of + * deleted block. + */ +static int del_chars(int n) +{ + int i, l, r; + int pos; + + update_cols(); + + /* Step forward or backwards over n logical characters. */ + pos = step_over_chars(n); + + if (pos > lpos) { + l = pos - lpos; /* Buffer characters to delete */ + r = llen - lpos - l; /* Characters after deleted */ + /* Fix up buffer and buffer pointers. */ + if (r > 0) + memcpy(lbuf + lpos, lbuf + pos, r * sizeof(Uint32)); + llen -= l; + /* Write out characters after, blank the tail and jump back to lpos. */ + write_buf(lbuf + lpos, r); + for (i = l ; i > 0; --i) + outc(' '); + if (COL(llen+l) == 0 && xn) + { + outc(' '); + move_left(1); + } + move_cursor(llen + l, lpos); + } + else if (pos < lpos) { + l = lpos - pos; /* Buffer characters */ + r = llen - lpos; /* Characters after deleted */ + move_cursor(lpos, lpos-l); /* Move back */ + /* Fix up buffer and buffer pointers. */ + if (r > 0) + memcpy(lbuf + pos, lbuf + lpos, r * sizeof(Uint32)); + lpos -= l; + llen -= l; + /* Write out characters after, blank the tail and jump back to lpos. */ + write_buf(lbuf + lpos, r); + for (i = l ; i > 0; --i) + outc(' '); + if (COL(llen+l) == 0 && xn) + { + outc(' '); + move_left(1); + } + move_cursor(llen + l, lpos); + } + return TRUE; +} + +/* Step over n logical characters, check for overflow. */ +static int step_over_chars(int n) +{ + Uint32 *c, *beg, *end; + + beg = lbuf; + end = lbuf + llen; + c = lbuf + lpos; + for ( ; n > 0 && c < end; --n) { + c++; + while (c < end && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0)) + c++; + } + for ( ; n < 0 && c > beg; n++) { + --c; + while (c > beg && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0)) + --c; + } + return c - lbuf; +} + +/* + * Insert n characters into the buffer at lpos. + * Know about pad characters and treat \n specially. + */ + +static int insert_buf(byte *s, int n) +{ + int pos = 0; + int buffpos = lpos; + int ch; + + while (pos < n) { + if ((ch = pick_utf8(s,n,&pos)) < 0) { + /* XXX temporary allow invalid chars */ + ch = (int) s[pos]; + DEBUGLOG(("insert_buf: Invalid UTF8:%d",ch)); + ++pos; + } + if ((utf8_mode && (ch >= 128 || isprint(ch))) || (ch <= 255 && isprint(ch))) { + DEBUGLOG(("insert_buf: Printable(UTF-8):%d",ch)); + lbuf[lpos++] = (Uint32) ch; + } else if (ch >= 128) { /* not utf8 mode */ + int nc = octal_or_hex_positions(ch); + lbuf[lpos++] = ((Uint32) ch) | ESCAPED_TAG; + while (nc--) { + lbuf[lpos++] = ESCAPED_TAG; + } + } else if (ch == '\t') { + do { + lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch)); + ch = 0; + } while (lpos % 8); + } else if (ch == '\n' || ch == '\r') { + write_buf(lbuf + buffpos, lpos - buffpos); + outc('\r'); + if (ch == '\n') + outc('\n'); + if (llen > lpos) { + memcpy(lbuf, lbuf + lpos, llen - lpos); + } + llen -= lpos; + lpos = buffpos = 0; + } else { + DEBUGLOG(("insert_buf: Magic(UTF-8):%d",ch)); + lbuf[lpos++] = ch | CONTROL_TAG; + lbuf[lpos++] = CONTROL_TAG; + } + } + return lpos - buffpos; /* characters "written" into + current buffer (may be less due to newline) */ +} + + + +/* + * Write n characters in line buffer starting at s. Be smart about + * non-printables. Know about pad characters and that \n can never + * occur normally. + */ + +static int write_buf(Uint32 *s, int n) +{ + byte ubuf[4]; + int ubytes = 0, i; + byte lastput = ' '; + + update_cols(); + + while (n > 0) { + if (!(*s & TAG_MASK) ) { + if (utf8_mode) { + ubytes = 0; + if (put_utf8((int) *s, ubuf, 4, &ubytes) == 0) { + for (i = 0; i < ubytes; ++i) { + outc(ubuf[i]); + } + lastput = 0; /* Means the last written character was multibyte UTF8 */ + } + } else { + outc((byte) *s); + lastput = (byte) *s; + } + --n; + ++s; + } + else if (*s == (CONTROL_TAG | ((Uint32) '\t'))) { + outc(lastput = ' '); + --n; s++; + while (n > 0 && *s == CONTROL_TAG) { + outc(lastput = ' '); + --n; s++; + } + } else if (*s & CONTROL_TAG) { + outc('^'); + outc(lastput = ((byte) ((*s == 0177) ? '?' : *s | 0x40))); + n -= 2; + s += 2; + } else if (*s & ESCAPED_TAG) { + Uint32 ch = *s & ~(TAG_MASK); + byte *octbuff; + byte octtmp[256]; + int octbytes; + DEBUGLOG(("Escaped: %d", ch)); + octbytes = octal_or_hex_positions(ch); + if (octbytes > 256) { + octbuff = driver_alloc(octbytes); + } else { + octbuff = octtmp; + } + octbytes = 0; + octal_or_hex_format(ch, octbuff, &octbytes); + DEBUGLOG(("octbytes: %d", octbytes)); + outc('\\'); + for (i = 0; i < octbytes; ++i) { + outc(lastput = octbuff[i]); + DEBUGLOG(("outc: %d", (int) lastput)); + } + n -= octbytes+1; + s += octbytes+1; + if (octbuff != octtmp) { + driver_free(octbuff); + } + } else { + DEBUGLOG(("Very unexpected character %d",(int) *s)); + ++n; + --s; + } + } + /* Check landed in first column of new line and have 'xn' bug. */ + n = s - lbuf; + if (COL(n) == 0 && xn && n != 0) { + if (n >= llen) { + outc(' '); + } else if (lastput == 0) { /* A multibyte UTF8 character */ + for (i = 0; i < ubytes; ++i) { + outc(ubuf[i]); + } + } else { + outc(lastput); + } + move_left(1); + } + return TRUE; +} + + +/* The basic procedure for outputting one character. */ +static int outc(int c) +{ + return (int)putc(c, ttysl_out); +} + +static int move_cursor(int from, int to) +{ + int dc, dl; + + update_cols(); + + dc = COL(to) - COL(from); + dl = LINE(to) - LINE(from); + if (dl > 0) + move_down(dl); + else if (dl < 0) + move_up(-dl); + if (dc > 0) + move_right(dc); + else if (dc < 0) + move_left(-dc); + return TRUE; +} + +static int start_termcap(void) +{ + int eres; + size_t envsz = 1024; + char *env = NULL; + char *c; + + capbuf = driver_alloc(1024); + if (!capbuf) + goto false; + eres = erl_drv_getenv("TERM", capbuf, &envsz); + if (eres == 0) + env = capbuf; + else if (eres < 0) + goto false; + else /* if (eres > 1) */ { + char *envbuf = driver_alloc(envsz); + if (!envbuf) + goto false; + while (1) { + char *newenvbuf; + eres = erl_drv_getenv("TERM", envbuf, &envsz); + if (eres == 0) + break; + newenvbuf = driver_realloc(envbuf, envsz); + if (eres < 0 || !newenvbuf) { + env = newenvbuf ? newenvbuf : envbuf; + goto false; + } + envbuf = newenvbuf; + } + env = envbuf; + } + if (tgetent((char*)lbuf, env) <= 0) + goto false; + if (env != capbuf) { + env = NULL; + driver_free(env); + } + c = capbuf; + cols = tgetnum("co"); + if (cols <= 0) + cols = DEF_WIDTH; + xn = tgetflag("xn"); + up = tgetstr("up", &c); + if (!(down = tgetstr("do", &c))) + down = "\n"; + if (!(left = tgetflag("bs") ? "\b" : tgetstr("bc", &c))) + left = "\b"; /* Can't happen - but does on Solaris 2 */ + right = tgetstr("nd", &c); + if (up && down && left && right) + return TRUE; + false: + if (env && env != capbuf) + driver_free(env); + if (capbuf) + driver_free(capbuf); + capbuf = NULL; + return FALSE; +} + +static int stop_termcap(void) +{ + if (capbuf) driver_free(capbuf); + capbuf = NULL; + return TRUE; +} + +static int move_left(int n) +{ + while (n-- > 0) + tputs(left, 1, outc); + return TRUE; +} + +static int move_right(int n) +{ + while (n-- > 0) + tputs(right, 1, outc); + return TRUE; +} + +static int move_up(int n) +{ + while (n-- > 0) + tputs(up, 1, outc); + return TRUE; +} + +static int move_down(int n) +{ + while (n-- > 0) + tputs(down, 1, outc); + return TRUE; +} + + +/* + * Updates cols if terminal has resized (SIGWINCH). Should be called + * at the start of any function that uses the COL or LINE macros. If + * the terminal is resized after calling this function but before use + * of the macros, then we may write to the wrong screen location. + * + * We cannot call this from the SIGWINCH handler because it uses + * ioctl() which is not a safe function as listed in the signal(7) + * man page. + */ +static void update_cols(void) +{ + Uint32 width, height; + + if (cols_needs_update) { + cols_needs_update = FALSE; + ttysl_get_window_size(&width, &height); + cols = width; + } +} + + +/* + * Put a terminal device into non-canonical mode with ECHO off. + * Before doing so we first save the terminal's current mode, + * assuming the caller will call the tty_reset() function + * (also in this file) when it's done with raw mode. + */ + +static struct termios tty_smode, tty_rmode; + +static int tty_init(int fd, int canon, int echo, int sig) +{ + if (tcgetattr(fd, &tty_rmode) < 0) + return -1; + tty_smode = tty_rmode; + + /* Default characteristics for all usage including termcap output. */ + tty_smode.c_iflag &= ~ISTRIP; + + /* Turn canonical (line mode) on off. */ + if (canon > 0) { + tty_smode.c_iflag |= ICRNL; + tty_smode.c_lflag |= ICANON; + tty_smode.c_oflag |= OPOST; + tty_smode.c_cc[VEOF] = tty_rmode.c_cc[VEOF]; +#ifdef VDSUSP + tty_smode.c_cc[VDSUSP] = tty_rmode.c_cc[VDSUSP]; +#endif + } + if (canon < 0) { + tty_smode.c_iflag &= ~ICRNL; + tty_smode.c_lflag &= ~ICANON; + tty_smode.c_oflag &= ~OPOST; + /* Must get these really right or funny effects can occur. */ + tty_smode.c_cc[VMIN] = 1; + tty_smode.c_cc[VTIME] = 0; +#ifdef VDSUSP + tty_smode.c_cc[VDSUSP] = 0; +#endif + } + + /* Turn echo on or off. */ + if (echo > 0) + tty_smode.c_lflag |= ECHO; + if (echo < 0) + tty_smode.c_lflag &= ~ECHO; + + /* Set extra characteristics for "RAW" mode, no signals. */ + if (sig > 0) { + /* Ignore IMAXBEL as not POSIX. */ +#ifndef QNX + tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON|IXANY); +#else + tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON); +#endif + tty_smode.c_lflag |= (ISIG|IEXTEN); + } + if (sig < 0) { + /* Ignore IMAXBEL as not POSIX. */ +#ifndef QNX + tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON|IXANY); +#else + tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON); +#endif + tty_smode.c_lflag &= ~(ISIG|IEXTEN); + } + return 0; +} + +/* + * Set/restore a terminal's mode to whatever it was on the most + * recent call to the tty_init() function above. + */ + +static int tty_set(int fd) +{ + DEBUGF(("Setting tty...\n")); + + if (tcsetattr(fd, TCSANOW, &tty_smode) < 0) + return(-1); + return(0); +} + +static int tty_reset(int fd) /* of terminal device */ +{ + DEBUGF(("Resetting tty...\n")); + + if (tcsetattr(fd, TCSANOW, &tty_rmode) < 0) + return(-1); + + return(0); +} + +/* + * Signal handler to cope with signals so that we can reset the tty + * to the orignal settings + */ + +static RETSIGTYPE suspend(int sig) +{ + if (tty_reset(ttysl_fd) < 0) { + fprintf(stderr,"Can't reset tty \n"); + exit(1); + } + + sys_sigset(sig, SIG_DFL); /* Set signal handler to default */ + sys_sigrelease(sig); /* Allow 'sig' to come through */ + kill(getpid(), sig); /* Send ourselves the signal */ + sys_sigblock(sig); /* Reset to old mask */ + sys_sigset(sig, suspend); /* Reset signal handler */ + + if (tty_set(ttysl_fd) < 0) { + fprintf(stderr,"Can't set tty raw \n"); + exit(1); + } +} + +static RETSIGTYPE cont(int sig) +{ + if (tty_set(ttysl_fd) < 0) { + fprintf(stderr,"Can't set tty raw\n"); + exit(1); + } +} + +static RETSIGTYPE winch(int sig) +{ + cols_needs_update = TRUE; +} +#endif /* HAVE_TERMCAP */ diff --git a/erts/emulator/drivers/unix/unix_efile.c b/erts/emulator/drivers/unix/unix_efile.c new file mode 100644 index 0000000000..d395b68691 --- /dev/null +++ b/erts/emulator/drivers/unix/unix_efile.c @@ -0,0 +1,1505 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Purpose: Provides file and directory operations for Unix. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "sys.h" +#include "erl_driver.h" +#include "erl_efile.h" +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_SYS_UIO_H +#include +#include +#endif + +#ifdef _OSE_ +#include "efs.h" +#include +#include +#include + +#ifdef _OSE_SFK_ +#include +#endif +#endif /* _OSE_ */ + +#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__) +#define DARWIN 1 +#endif + +#ifdef DARWIN +#include +#endif /* DARWIN */ + +#ifdef VXWORKS +#include +#include +#include +#include +/* +** Not nice to include usrLib.h as MANY normal variable names get reported +** as shadowing globals, like 'i' for example. +** Instead we declare the only function we use here +*/ +/* + * #include + */ +extern STATUS copy(char *, char *); +#include +#endif + +#ifdef SUNOS4 +# define getcwd(buf, size) getwd(buf) +#endif + +/* Find a definition of MAXIOV, that is used in the code later. */ +#if defined IOV_MAX +#define MAXIOV IOV_MAX +#elif defined UIO_MAXIOV +#define MAXIOV UIO_MAXIOV +#else +#define MAXIOV 16 +#endif + + +/* + * Macros for testing file types. + */ + +#ifdef _OSE_ + +#define ISDIR(st) S_ISDIR(((st).st_mode)) +#define ISREG(st) S_ISREG(((st).st_mode)) +#define ISDEV(st) (S_ISCHR(((st).st_mode)) || S_ISBLK(((st).st_mode))) +#define ISLNK(st) S_ISLNK(((st).st_mode)) +#ifdef NO_UMASK +#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) +#define DIR_MODE (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) +#else +#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) +#define DIR_MODE (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | \ + S_IWOTH | S_IXOTH) +#endif + +#else /* !_OSE_ */ + +#define ISDIR(st) (((st).st_mode & S_IFMT) == S_IFDIR) +#define ISREG(st) (((st).st_mode & S_IFMT) == S_IFREG) +#define ISDEV(st) \ + (((st).st_mode&S_IFMT) == S_IFCHR || ((st).st_mode&S_IFMT) == S_IFBLK) +#define ISLNK(st) (((st).st_mode & S_IFLNK) == S_IFLNK) +#ifdef NO_UMASK +#define FILE_MODE 0644 +#define DIR_MODE 0755 +#else +#define FILE_MODE 0666 +#define DIR_MODE 0777 +#endif + +#endif /* _OSE_ */ + +#ifdef VXWORKS /* Currently only used on vxworks */ + +#define EF_ALLOC(S) driver_alloc((S)) +#define EF_REALLOC(P, S) driver_realloc((P), (S)) +#define EF_SAFE_ALLOC(S) ef_safe_alloc((S)) +#define EF_SAFE_REALLOC(P, S) ef_safe_realloc((P), (S)) +#define EF_FREE(P) do { if((P)) driver_free((P)); } while(0) + +extern void erl_exit(int n, char *fmt, _DOTS_); + +static void *ef_safe_alloc(Uint s) +{ + void *p = EF_ALLOC(s); + if (!p) erl_exit(1, + "unix efile drv: Can't allocate %d bytes of memory\n", + s); + return p; +} + +#if 0 /* Currently not used */ + +static void *ef_safe_realloc(void *op, Uint s) +{ + void *p = EF_REALLOC(op, s); + if (!p) erl_exit(1, + "unix efile drv: Can't reallocate %d bytes of memory\n", + s); + return p; +} + +#endif /* #if 0 */ +#endif /* #ifdef VXWORKS */ + +#define IS_DOT_OR_DOTDOT(s) \ + (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0'))) + +#ifdef VXWORKS +static FUNCTION(int, vxworks_to_posix, (int vx_errno)); +#endif + +/* +** VxWorks (not) strikes again. Too long RESULTING paths +** may give the infamous bus error. Have to check ALL +** filenames and pathnames. No wonder the emulator is slow on +** these cards... +*/ +#ifdef VXWORKS +#define CHECK_PATHLEN(Name, ErrInfo) \ + if (path_size(Name) > PATH_MAX) { \ + errno = ENAMETOOLONG; \ + return check_error(-1, ErrInfo); \ + } +#else +#define CHECK_PATHLEN(X,Y) /* Nothing */ +#endif + +static FUNCTION(int, check_error, (int result, Efile_error* errInfo)); + +static int +check_error(int result, Efile_error *errInfo) +{ + if (result < 0) { +#ifdef VXWORKS + errInfo->posix_errno = errInfo->os_errno = vxworks_to_posix(errno); +#else + errInfo->posix_errno = errInfo->os_errno = errno; +#endif + return 0; + } + return 1; +} + +#ifdef VXWORKS + +/* + * VxWorks has different error codes for different file systems. + * We map those to POSIX ones. + */ +static int +vxworks_to_posix(int vx_errno) +{ + DEBUGF(("[vxworks_to_posix] vx_errno: %08x\n", vx_errno)); + switch (vx_errno) { + /* dosFsLib mapping */ +#ifdef S_dosFsLib_FILE_ALREADY_EXISTS + case S_dosFsLib_FILE_ALREADY_EXISTS: return EEXIST; +#else + case S_dosFsLib_FILE_EXISTS: return EEXIST; +#endif +#ifdef S_dosFsLib_BAD_DISK + case S_dosFsLib_BAD_DISK: return EIO; +#endif +#ifdef S_dosFsLib_CANT_CHANGE_ROOT + case S_dosFsLib_CANT_CHANGE_ROOT: return EINVAL; +#endif +#ifdef S_dosFsLib_NO_BLOCK_DEVICE + case S_dosFsLib_NO_BLOCK_DEVICE: return ENOTBLK; +#endif +#ifdef S_dosFsLib_BAD_SEEK + case S_dosFsLib_BAD_SEEK: return ESPIPE; +#endif + case S_dosFsLib_VOLUME_NOT_AVAILABLE: return ENXIO; + case S_dosFsLib_DISK_FULL: return ENOSPC; + case S_dosFsLib_FILE_NOT_FOUND: return ENOENT; + case S_dosFsLib_NO_FREE_FILE_DESCRIPTORS: return ENFILE; + case S_dosFsLib_INVALID_NUMBER_OF_BYTES: return EINVAL; + case S_dosFsLib_ILLEGAL_NAME: return EINVAL; + case S_dosFsLib_CANT_DEL_ROOT: return EACCES; + case S_dosFsLib_NOT_FILE: return EISDIR; + case S_dosFsLib_NOT_DIRECTORY: return ENOTDIR; + case S_dosFsLib_NOT_SAME_VOLUME: return EXDEV; + case S_dosFsLib_READ_ONLY: return EACCES; + case S_dosFsLib_ROOT_DIR_FULL: return ENOSPC; + case S_dosFsLib_DIR_NOT_EMPTY: return EEXIST; + case S_dosFsLib_NO_LABEL: return ENXIO; + case S_dosFsLib_INVALID_PARAMETER: return EINVAL; + case S_dosFsLib_NO_CONTIG_SPACE: return ENOSPC; + case S_dosFsLib_FD_OBSOLETE: return EBADF; + case S_dosFsLib_DELETED: return EINVAL; + case S_dosFsLib_INTERNAL_ERROR: return EIO; + case S_dosFsLib_WRITE_ONLY: return EACCES; + /* nfsLib mapping - is needed since Windriver has used */ + /* inconsistent error codes (errno.h/nfsLib.h). */ + case S_nfsLib_NFS_OK: return 0; + case S_nfsLib_NFSERR_PERM: return EPERM; + case S_nfsLib_NFSERR_NOENT: return ENOENT; + case S_nfsLib_NFSERR_IO: return EIO; + case S_nfsLib_NFSERR_NXIO: return ENXIO; +#ifdef S_nfsLib_NFSERR_ACCES + case S_nfsLib_NFSERR_ACCES: return EACCES; +#else + case S_nfsLib_NFSERR_ACCESS: return EACCES; +#endif + case S_nfsLib_NFSERR_EXIST: return EEXIST; + case S_nfsLib_NFSERR_NODEV: return ENODEV; + case S_nfsLib_NFSERR_NOTDIR: return ENOTDIR; + case S_nfsLib_NFSERR_ISDIR: return EISDIR; + case S_nfsLib_NFSERR_FBIG: return EFBIG; + case S_nfsLib_NFSERR_NOSPC: return ENOSPC; + case S_nfsLib_NFSERR_ROFS: return EROFS; + case S_nfsLib_NFSERR_NAMETOOLONG: return ENAMETOOLONG; + case S_nfsLib_NFSERR_NOTEMPTY: return EEXIST; + case S_nfsLib_NFSERR_DQUOT: return ENOSPC; + case S_nfsLib_NFSERR_STALE: return EINVAL; + case S_nfsLib_NFSERR_WFLUSH: return ENXIO; + /* And sometimes (...) the error codes are from ioLib (as in the */ + /* case of the (for nfsLib) unimplemented rename function) */ + case S_ioLib_DISK_NOT_PRESENT: return EIO; +#if S_ioLib_DISK_NOT_PRESENT != S_ioLib_NO_DRIVER + case S_ioLib_NO_DRIVER: return ENXIO; +#endif + case S_ioLib_UNKNOWN_REQUEST: return ENOSYS; + case S_ioLib_DEVICE_TIMEOUT: return EIO; +#ifdef S_ioLib_UNFORMATED + /* Added (VxWorks 5.2 -> 5.3.1) */ + #if S_ioLib_UNFORMATED != S_ioLib_DEVICE_TIMEOUT + case S_ioLib_UNFORMATED: return EIO; + #endif +#endif +#if S_ioLib_DEVICE_TIMEOUT != S_ioLib_DEVICE_ERROR + case S_ioLib_DEVICE_ERROR: return ENXIO; +#endif + case S_ioLib_WRITE_PROTECTED: return EACCES; + case S_ioLib_NO_FILENAME: return EINVAL; + case S_ioLib_CANCELLED: return EINTR; + case S_ioLib_NO_DEVICE_NAME_IN_PATH: return EINVAL; + case S_ioLib_NAME_TOO_LONG: return ENAMETOOLONG; +#ifdef S_objLib_OBJ_UNAVAILABLE + case S_objLib_OBJ_UNAVAILABLE: return ENOENT; +#endif + + /* Temporary workaround for a weird error in passFs + (VxWorks Simsparc only). File operation fails because of + ENOENT, but errno is not set. */ +#ifdef SIMSPARCSOLARIS + case 0: return ENOENT; +#endif + + } + /* If the error code matches none of the above, assume */ + /* it is a POSIX one already. The upper bits (>=16) are */ + /* cleared since VxWorks uses those bits to indicate in */ + /* what module the error occured. */ + return vx_errno & 0xffff; +} + +static int +vxworks_enotsup(Efile_error *errInfo) +{ + errInfo->posix_errno = errInfo->os_errno = ENOTSUP; + return 0; +} + +static int +count_path_length(char *pathname, char *pathname2) +{ + static int stack[PATH_MAX / 2 + 1]; + int sp = 0; + char *tmp; + char *cpy = NULL; + int i; + int sum; + for(i = 0;i < 2;++i) { + if (!i) { + cpy = EF_SAFE_ALLOC(strlen(pathname)+1); + strcpy(cpy, pathname); + } else if (pathname2 != NULL) { + EF_FREE(cpy); + cpy = EF_SAFE_ALLOC(strlen(pathname2)+1); + strcpy(cpy, pathname2); + } else + break; + + for (tmp = strtok(cpy,"/"); tmp != NULL; tmp = strtok(NULL,"/")) { + if (!strcmp(tmp,"..") && sp > 0) + --sp; + else if (strcmp(tmp,".")) + stack[sp++] = strlen(tmp); + } + } + if (cpy != NULL) + EF_FREE(cpy); + sum = 0; + for(i = 0;i < sp; ++i) + sum += stack[i]+1; + return (sum) ? sum : 1; +} + +static int +path_size(char *pathname) +{ + static char currdir[PATH_MAX+2]; + if (*pathname == '/') + return count_path_length(pathname,NULL); + ioDefPathGet(currdir); + strcat(currdir,"/"); + return count_path_length(currdir,pathname); +} + +#endif /* VXWORKS */ + +#ifdef _OSE_ +static int +ose_enotsup(Efile_error *errInfo) +{ + errInfo->posix_errno = errInfo->os_errno = ENOTSUP; + return 0; +} +#endif /* _OSE_ */ + +int +efile_mkdir(Efile_error* errInfo, /* Where to return error codes. */ + char* name) /* Name of directory to create. */ +{ + CHECK_PATHLEN(name,errInfo); +#ifdef NO_MKDIR_MODE +#ifdef VXWORKS + /* This is a VxWorks/nfs workaround for erl_tar to create + * non-existant directories. (of some reason (...) VxWorks + * returns, the *non-module-prefixed*, 0xd code when + * trying to create a directory in a directory that doesn't exist). + * (see efile_openfile) + */ + if (mkdir(name) < 0) { + struct stat sb; + if (name[0] == '\0') { + /* Return the correct error code enoent */ + errno = S_nfsLib_NFSERR_NOENT; + } else if (stat(name, &sb) == OK) { + errno = S_nfsLib_NFSERR_EXIST; + } else if((strchr(name, '/') != NULL) && (errno == 0xd)) { + /* Return the correct error code enoent */ + errno = S_nfsLib_NFSERR_NOENT; + } + return check_error(-1, errInfo); + } else return 1; +#else + return check_error(mkdir(name), errInfo); +#endif +#else + return check_error(mkdir(name, DIR_MODE), errInfo); +#endif +} + +int +efile_rmdir(Efile_error* errInfo, /* Where to return error codes. */ + char* name) /* Name of directory to delete. */ +{ + CHECK_PATHLEN(name, errInfo); + if (rmdir(name) == 0) { + return 1; + } +#ifdef VXWORKS + if (name[0] == '\0') { + /* Return the correct error code enoent */ + errno = S_nfsLib_NFSERR_NOENT; + } +#else + if (errno == ENOTEMPTY) { + errno = EEXIST; + } + if (errno == EEXIST) { + int saved_errno = errno; + struct stat file_stat; + struct stat cwd_stat; + + /* + * The error code might be wrong if this is the current directory. + */ + + if (stat(name, &file_stat) == 0 && stat(".", &cwd_stat) == 0 && + file_stat.st_ino == cwd_stat.st_ino && + file_stat.st_dev == cwd_stat.st_dev) { + saved_errno = EINVAL; + } + errno = saved_errno; + } +#endif + return check_error(-1, errInfo); +} + +int +efile_delete_file(Efile_error* errInfo, /* Where to return error codes. */ + char* name) /* Name of file to delete. */ +{ + CHECK_PATHLEN(name,errInfo); +#ifdef _OSE_ + if (remove(name) == 0) { + return 1; + } +#else + if (unlink(name) == 0) { + return 1; + } + if (errno == EISDIR) { /* Linux sets the wrong error code. */ + errno = EPERM; + } +#endif + return check_error(-1, errInfo); +} + +/* + *--------------------------------------------------------------------------- + * + * Changes the name of an existing file or directory, from src to dst. + * If src and dst refer to the same file or directory, does nothing + * and returns success. Otherwise if dst already exists, it will be + * deleted and replaced by src subject to the following conditions: + * If src is a directory, dst may be an empty directory. + * If src is a file, dst may be a file. + * In any other situation where dst already exists, the rename will + * fail. + * + * Results: + * If the directory was successfully created, returns 1. + * Otherwise the return value is 0 and errno is set to + * indicate the error. Some possible values for errno are: + * + * EACCES: src or dst parent directory can't be read and/or written. + * EEXIST: dst is a non-empty directory. + * EINVAL: src is a root directory or dst is a subdirectory of src. + * EISDIR: dst is a directory, but src is not. + * ENOENT: src doesn't exist, or src or dst is "". + * ENOTDIR: src is a directory, but dst is not. + * EXDEV: src and dst are on different filesystems. + * + * Side effects: + * The implementation of rename may allow cross-filesystem renames, + * but the caller should be prepared to emulate it with copy and + * delete if errno is EXDEV. + * + *--------------------------------------------------------------------------- + */ + +int +efile_rename(Efile_error* errInfo, /* Where to return error codes. */ + char* src, /* Original name. */ + char* dst) /* New name. */ +{ + CHECK_PATHLEN(src,errInfo); + CHECK_PATHLEN(dst,errInfo); +#ifdef VXWORKS + + /* First check if src == dst, if so, just return. */ + /* VxWorks dos file system destroys the file otherwise, */ + /* VxWorks nfs file system rename doesn't work at all. */ + if(strcmp(src, dst) == 0) + return 1; +#endif + if (rename(src, dst) == 0) { + return 1; + } +#ifdef VXWORKS + /* nfs for VxWorks doesn't support rename. We try to emulate it */ + /* (by first copying src to dst and then deleting src). */ + if(errno == S_ioLib_UNKNOWN_REQUEST && /* error code returned + by ioLib (!) */ + copy(src, dst) == OK && + unlink(src) == OK) + return 1; +#endif + if (errno == ENOTEMPTY) { + errno = EEXIST; + } +#if defined (sparc) && !defined(VXWORKS) && !defined(_OSE_) + /* + * SunOS 4.1.4 reports overwriting a non-empty directory with a + * directory as EINVAL instead of EEXIST (first rule out the correct + * EINVAL result code for moving a directory into itself). Must be + * conditionally compiled because realpath() is only defined on SunOS. + */ + + if (errno == EINVAL) { + char srcPath[MAXPATHLEN], dstPath[MAXPATHLEN]; + DIR *dirPtr; + struct dirent *dirEntPtr; + +#ifdef PURIFY + memset(srcPath, '\0', sizeof(srcPath)); + memset(dstPath, '\0', sizeof(dstPath)); +#endif + + if ((realpath(src, srcPath) != NULL) + && (realpath(dst, dstPath) != NULL) + && (strncmp(srcPath, dstPath, strlen(srcPath)) != 0)) { + dirPtr = opendir(dst); + if (dirPtr != NULL) { + while ((dirEntPtr = readdir(dirPtr)) != NULL) { + if ((strcmp(dirEntPtr->d_name, ".") != 0) && + (strcmp(dirEntPtr->d_name, "..") != 0)) { + errno = EEXIST; + closedir(dirPtr); + return check_error(-1, errInfo); + } + } + closedir(dirPtr); + } + } + errno = EINVAL; + } +#endif /* sparc */ + + if (strcmp(src, "/") == 0) { + /* + * Alpha reports renaming / as EBUSY and Linux reports it as EACCES, + * instead of EINVAL. + */ + + errno = EINVAL; + } + + /* + * DEC Alpha OSF1 V3.0 returns EACCES when attempting to move a + * file across filesystems and the parent directory of that file is + * not writable. Most other systems return EXDEV. Does nothing to + * correct this behavior. + */ + + return check_error(-1, errInfo); +} + +int +efile_chdir(Efile_error* errInfo, /* Where to return error codes. */ + char* name) /* Name of directory to make current. */ +{ + CHECK_PATHLEN(name, errInfo); + return check_error(chdir(name), errInfo); +} + + +int +efile_getdcwd(Efile_error* errInfo, /* Where to return error codes. */ + int drive, /* 0 - current, 1 - A, 2 - B etc. */ + char* buffer, /* Where to return the current + directory. */ + size_t size) /* Size of buffer. */ +{ + if (drive == 0) { + if (getcwd(buffer, size) == NULL) + return check_error(-1, errInfo); + +#ifdef SIMSPARCSOLARIS + /* We get "host:" prepended to the dirname - remove!. */ + { + int i = 0; + int j = 0; + while ((buffer[i] != ':') && (buffer[i] != '\0')) i++; + if (buffer[i] == ':') { + i++; + while ((buffer[j++] = buffer[i++]) != '\0'); + } + } +#endif + return 1; + } + + /* + * Drives other than 0 is not supported on Unix. + */ + + errno = ENOTSUP; + return check_error(-1, errInfo); +} + +int +efile_readdir(Efile_error* errInfo, /* Where to return error codes. */ + char* name, /* Name of directory to open. */ + EFILE_DIR_HANDLE* p_dir_handle, /* Pointer to directory + handle of + open directory.*/ + char* buffer, /* Pointer to buffer for + one filename. */ + size_t size) /* Size of buffer. */ +{ + DIR *dp; /* Pointer to directory structure. */ + struct dirent* dirp; /* Pointer to directory entry. */ + + /* + * If this is the first call, we must open the directory. + */ + + CHECK_PATHLEN(name, errInfo); + + if (*p_dir_handle == NULL) { + dp = opendir(name); + if (dp == NULL) + return check_error(-1, errInfo); + *p_dir_handle = (EFILE_DIR_HANDLE) dp; + } + + /* + * Retrieve the name of the next file using the directory handle. + */ + + dp = *((DIR **)((void *)p_dir_handle)); + for (;;) { + dirp = readdir(dp); + if (dirp == NULL) { + closedir(dp); + return 0; + } + if (IS_DOT_OR_DOTDOT(dirp->d_name)) + continue; + buffer[0] = '\0'; + strncat(buffer, dirp->d_name, size-1); + return 1; + } +} + +int +efile_openfile(Efile_error* errInfo, /* Where to return error codes. */ + char* name, /* Name of directory to open. */ + int flags, /* Flags to user for opening. */ + int* pfd, /* Where to store the file + descriptor. */ + Sint64 *pSize) /* Where to store the size of the + file. */ +{ + struct stat statbuf; + int fd; + int mode; /* Open mode. */ +#ifdef VXWORKS + char pathbuff[PATH_MAX+2]; + char sbuff[PATH_MAX*2]; + char *totbuff = sbuff; + int nameneed; +#endif + + + CHECK_PATHLEN(name, errInfo); + +#ifdef VXWORKS + /* Have to check that it's not a directory. */ + if (stat(name,&statbuf) != ERROR && ISDIR(statbuf)) { + errno = EISDIR; + return check_error(-1, errInfo); + } +#endif + + if (stat(name, &statbuf) >= 0 && !ISREG(statbuf)) { +#if !defined(VXWORKS) && !defined(OSE) + /* + * For UNIX only, here is some ugly code to allow + * /dev/null to be opened as a file. + * + * Assumption: The i-node number for /dev/null cannot be zero. + */ + static ino_t dev_null_ino = 0; + + if (dev_null_ino == 0) { + struct stat nullstatbuf; + + if (stat("/dev/null", &nullstatbuf) >= 0) { + dev_null_ino = nullstatbuf.st_ino; + } + } + if (!(dev_null_ino && statbuf.st_ino == dev_null_ino)) { +#endif + errno = EISDIR; + return check_error(-1, errInfo); +#if !defined(VXWORKS) && !defined(OSE) + } +#endif + } + + switch (flags & (EFILE_MODE_READ|EFILE_MODE_WRITE)) { + case EFILE_MODE_READ: + mode = O_RDONLY; + break; + case EFILE_MODE_WRITE: + if (flags & EFILE_NO_TRUNCATE) + mode = O_WRONLY | O_CREAT; + else + mode = O_WRONLY | O_CREAT | O_TRUNC; + break; + case EFILE_MODE_READ_WRITE: + mode = O_RDWR | O_CREAT; + break; + default: + errno = EINVAL; + return check_error(-1, errInfo); + } + + + if (flags & EFILE_MODE_APPEND) { + mode &= ~O_TRUNC; +#ifndef VXWORKS + mode |= O_APPEND; /* Dont make VxWorks think things it shouldn't */ +#endif + } + + +#ifdef VXWORKS + if (*name != '/') { + /* Make sure it is an absolute pathname, because ftruncate needs it */ + ioDefPathGet(pathbuff); + strcat(pathbuff,"/"); + nameneed = strlen(pathbuff) + strlen(name) + 1; + if (nameneed > PATH_MAX*2) + totbuff = EF_SAFE_ALLOC(nameneed); + strcpy(totbuff,pathbuff); + strcat(totbuff,name); + fd = open(totbuff, mode, FILE_MODE); + if (totbuff != sbuff) + EF_FREE(totbuff); + } else { + fd = open(name, mode, FILE_MODE); + } +#else + fd = open(name, mode, FILE_MODE); +#endif + +#ifdef VXWORKS + + /* This is a VxWorks/nfs workaround for erl_tar to create + * non-existant directories. (of some reason (...) VxWorks + * returns, the *non-module-prefixed*, 0xd code when + * trying to write a file in a directory that doesn't exist). + * (see efile_mkdir) + */ + if ((fd < 0) && (strchr(name, '/') != NULL) && (errno == 0xd)) { + /* Return the correct error code enoent */ + errno = S_nfsLib_NFSERR_NOENT; + return check_error(-1, errInfo); + } +#endif + + if (!check_error(fd, errInfo)) + return 0; + + *pfd = fd; + if (pSize) { + *pSize = statbuf.st_size; + } + return 1; +} + +int +efile_may_openfile(Efile_error* errInfo, char *name) { + struct stat statbuf; /* Information about the file */ + int result; + + result = stat(name, &statbuf); + if (!check_error(result, errInfo)) + return 0; + if (!ISREG(statbuf)) { + errno = EISDIR; + return check_error(-1, errInfo); + } + return 1; +} + +void +efile_closefile(int fd) +{ + close(fd); +} + +int +efile_fsync(Efile_error *errInfo, /* Where to return error codes. */ + int fd) /* File descriptor for file to sync. */ +{ +#ifdef NO_FSYNC +#ifdef VXWORKS + return check_error(ioctl(fd, FIOSYNC, 0), errInfo); +#else + undefined fsync +#endif /* VXWORKS */ +#else +#if defined(DARWIN) && defined(F_FULLFSYNC) + return check_error(fcntl(fd, F_FULLFSYNC), errInfo); +#else + return check_error(fsync(fd), errInfo); +#endif /* DARWIN */ +#endif /* NO_FSYNC */ +} + +int +efile_fileinfo(Efile_error* errInfo, Efile_info* pInfo, + char* name, int info_for_link) +{ + struct stat statbuf; /* Information about the file */ + struct tm *timep; /* Broken-apart filetime. */ + int result; + +#ifdef VXWORKS + if (*name == '\0') { + errInfo->posix_errno = errInfo->os_errno = ENOENT; + return 0; + } +#endif + + CHECK_PATHLEN(name, errInfo); + + if (info_for_link) { +#if (defined(VXWORKS) || defined(_OSE_)) + result = stat(name, &statbuf); +#else + result = lstat(name, &statbuf); +#endif + } else { + result = stat(name, &statbuf); + } + if (!check_error(result, errInfo)) { + return 0; + } + +#if SIZEOF_OFF_T == 4 + pInfo->size_high = 0; +#else + pInfo->size_high = (Uint32)(statbuf.st_size >> 32); +#endif + pInfo->size_low = (Uint32)statbuf.st_size; + +#ifdef NO_ACCESS + /* Just look at read/write access for owner. */ +#ifdef VXWORKS + + pInfo->access = FA_NONE; + if(statbuf.st_mode & S_IRUSR) + pInfo->access |= FA_READ; + if(statbuf.st_mode & S_IWUSR) + pInfo->access |= FA_WRITE; + +#else + + pInfo->access = ((statbuf.st_mode >> 6) & 07) >> 1; + +#endif /* VXWORKS */ +#else + pInfo->access = FA_NONE; + if (access(name, R_OK) == 0) + pInfo->access |= FA_READ; + if (access(name, W_OK) == 0) + pInfo->access |= FA_WRITE; + +#endif + + if (ISDEV(statbuf)) + pInfo->type = FT_DEVICE; + else if (ISDIR(statbuf)) + pInfo->type = FT_DIRECTORY; + else if (ISREG(statbuf)) + pInfo->type = FT_REGULAR; + else if (ISLNK(statbuf)) + pInfo->type = FT_SYMLINK; + else + pInfo->type = FT_OTHER; + +#if defined(HAVE_LOCALTIME_R) || defined(VXWORKS) + { + /* Use the reentrant version of localtime() */ + static struct tm local_tm; +#define localtime(a) (localtime_r((a), &local_tm), &local_tm) +#endif + + +#define GET_TIME(dst, src) \ + timep = localtime(&statbuf.src); \ + (dst).year = timep->tm_year+1900; \ + (dst).month = timep->tm_mon+1; \ + (dst).day = timep->tm_mday; \ + (dst).hour = timep->tm_hour; \ + (dst).minute = timep->tm_min; \ + (dst).second = timep->tm_sec + + GET_TIME(pInfo->accessTime, st_atime); + GET_TIME(pInfo->modifyTime, st_mtime); + GET_TIME(pInfo->cTime, st_ctime); + +#undef GET_TIME + +#if defined(HAVE_LOCALTIME_R) || defined(VXWORKS) + } +#endif + + pInfo->mode = statbuf.st_mode; + pInfo->links = statbuf.st_nlink; + pInfo->major_device = statbuf.st_dev; +#ifdef _OSE_ + pInfo->minor_device = 0; +#else + pInfo->minor_device = statbuf.st_rdev; +#endif + pInfo->inode = statbuf.st_ino; + pInfo->uid = statbuf.st_uid; + pInfo->gid = statbuf.st_gid; + + return 1; +} + +int +efile_write_info(Efile_error *errInfo, Efile_info *pInfo, char *name) +{ + CHECK_PATHLEN(name, errInfo); + +#ifdef VXWORKS + + if (pInfo->mode != -1) { + int fd; + struct stat statbuf; + + fd = open(name, O_RDONLY, 0); + if (!check_error(fd, errInfo)) + return 0; + if (fstat(fd, &statbuf) < 0) { + close(fd); + return check_error(-1, errInfo); + } + if (pInfo->mode & S_IWUSR) { + /* clear read only bit */ + statbuf.st_attrib &= ~DOS_ATTR_RDONLY; + } else { + /* set read only bit */ + statbuf.st_attrib |= DOS_ATTR_RDONLY; + } + /* This should work for dos files but not for nfs ditos, so don't + * report errors (to avoid problems when running e.g. erl_tar) + */ + ioctl(fd, FIOATTRIBSET, statbuf.st_attrib); + close(fd); + } +#else + /* + * On some systems chown will always fail for a non-root user unless + * POSIX_CHOWN_RESTRICTED is not set. Others will succeed as long as + * you don't try to chown a file to someone besides youself. + */ + +#ifndef _OSE_ + if (chown(name, pInfo->uid, pInfo->gid) && errno != EPERM) { + return check_error(-1, errInfo); + } +#endif + + if (pInfo->mode != -1) { + mode_t newMode = pInfo->mode & (S_ISUID | S_ISGID | + S_IRWXU | S_IRWXG | S_IRWXO); + if (chmod(name, newMode)) { + newMode &= ~(S_ISUID | S_ISGID); + if (chmod(name, newMode)) { + return check_error(-1, errInfo); + } + } + } + +#endif /* !VXWORKS */ + +#ifndef _OSE_ + + if (pInfo->accessTime.year != -1 && pInfo->modifyTime.year != -1) { + struct utimbuf tval; + struct tm timebuf; + +#define MKTIME(tb, ts) \ + timebuf.tm_year = ts.year-1900; \ + timebuf.tm_mon = ts.month-1; \ + timebuf.tm_mday = ts.day; \ + timebuf.tm_hour = ts.hour; \ + timebuf.tm_min = ts.minute; \ + timebuf.tm_sec = ts.second; \ + timebuf.tm_isdst = -1; \ + if ((tb = mktime(&timebuf)) == (time_t) -1) { \ + errno = EINVAL; \ + return check_error(-1, errInfo); \ + } + + MKTIME(tval.actime, pInfo->accessTime); + MKTIME(tval.modtime, pInfo->modifyTime); +#undef MKTIME + +#ifdef VXWORKS + /* VxWorks' utime doesn't work when the file is a nfs mounted + * one, don't report error if utime fails. + */ + utime(name, &tval); + return 1; +#else + return check_error(utime(name, &tval), errInfo); +#endif + } +#endif /* !_OSE_ */ + return 1; +} + + +int +efile_write(Efile_error* errInfo, /* Where to return error codes. */ + int flags, /* Flags given when file was + opened. */ + int fd, /* File descriptor to write to. */ + char* buf, /* Buffer to write. */ + size_t count) /* Number of bytes to write. */ +{ + ssize_t written; /* Bytes written in last operation. */ + +#ifdef VXWORKS + if (flags & EFILE_MODE_APPEND) { + lseek(fd, 0, SEEK_END); /* Naive append emulation on VXWORKS */ + } +#endif + while (count > 0) { + if ((written = write(fd, buf, count)) < 0) { + if (errno != EINTR) + return check_error(-1, errInfo); + else + written = 0; + } + ASSERT(written <= count); + buf += written; + count -= written; + } + return 1; +} + +int +efile_writev(Efile_error* errInfo, /* Where to return error codes */ + int flags, /* Flags given when file was + * opened */ + int fd, /* File descriptor to write to */ + SysIOVec* iov, /* Vector of buffer structs. + * The structs are unchanged + * after the call */ + int iovcnt, /* Number of structs in vector */ + size_t size) /* Number of bytes to write */ +{ + int cnt = 0; /* Buffers so far written */ + int p = 0; /* Position in next buffer */ + + ASSERT(iovcnt >= 0); + +#ifdef VXWORKS + if (flags & EFILE_MODE_APPEND) { + lseek(fd, 0, SEEK_END); /* Naive append emulation on VXWORKS */ + } +#endif + + while (cnt < iovcnt) { +#ifdef HAVE_WRITEV + int w; /* Bytes written in this call */ + int b = iovcnt - cnt; /* Buffers to write */ + if (b > MAXIOV) + b = MAXIOV; + if (iov[cnt].iov_base && iov[cnt].iov_len > 0) { + if (b == 1) { + /* Degenerated io vector */ + do { + w = write(fd, iov[cnt].iov_base + p, iov[cnt].iov_len - p); + } while (w < 0 && errno == EINTR); + } else { + /* Non-empty vector first. + * Adjust pos in first buffer in case of + * previous incomplete writev */ + iov[cnt].iov_base += p; + iov[cnt].iov_len -= p; + do { + w = writev(fd, &iov[cnt], b); + } while (w < 0 && errno == EINTR); + iov[cnt].iov_base -= p; + iov[cnt].iov_len += p; + } + if (w < 0) + return check_error(-1, errInfo); + } else { + /* Empty vector first - skip */ + cnt++; + continue; + } + ASSERT(w >= 0); + /* Move forward to next vector to write */ + for (; cnt < iovcnt; cnt++) { + if (iov[cnt].iov_base && iov[cnt].iov_len > 0) { + if (w < iov[cnt].iov_len) + break; + else + w -= iov[cnt].iov_len; + } + } + ASSERT(w >= 0); + p = w > 0 ? w : 0; /* Skip p bytes next writev */ +#else /* #ifdef HAVE_WRITEV */ + if (iov[cnt].iov_base && iov[cnt].iov_len > 0) { + /* Non-empty vector */ + int w; /* Bytes written in this call */ + while (p < iov[cnt].iov_len) { + do { + w = write(fd, iov[cnt].iov_base + p, iov[cnt].iov_len - p); + } while (w < 0 && errno == EINTR); + if (w < 0) + return check_error(-1, errInfo); + p += w; + } + } + cnt++; + p = 0; +#endif /* #ifdef HAVE_WRITEV */ + } /* while (cnt< iovcnt) */ + size = 0; /* Avoid compiler warning */ + return 1; +} + +int +efile_read(Efile_error* errInfo, /* Where to return error codes. */ + int flags, /* Flags given when file was opened. */ + int fd, /* File descriptor to read from. */ + char* buf, /* Buffer to read into. */ + size_t count, /* Number of bytes to read. */ + size_t *pBytesRead) /* Where to return number of + bytes read. */ +{ + ssize_t n; + + for (;;) { + if ((n = read(fd, buf, count)) >= 0) + break; + else if (errno != EINTR) + return check_error(-1, errInfo); + } + *pBytesRead = (size_t) n; + return 1; +} + + +/* pread() and pwrite() */ +/* Some unix systems, notably Solaris has these syscalls */ +/* It is especially nice for i.e. the dets module to have support */ +/* for this, even if the underlying OS dosn't support it, it is */ +/* reasonably easy to work around by first calling seek, and then */ +/* calling read(). */ +/* This later strategy however changes the file pointer, which pread() */ +/* does not do. We choose to ignore this and say that the location */ +/* of the file pointer is undefined after a call to any of the p functions*/ + + +int +efile_pread(Efile_error* errInfo, /* Where to return error codes. */ + int fd, /* File descriptor to read from. */ + Sint64 offset, /* Offset in bytes from BOF. */ + char* buf, /* Buffer to read into. */ + size_t count, /* Number of bytes to read. */ + size_t *pBytesRead) /* Where to return + number of bytes read. */ +{ +#if defined(HAVE_PREAD) && defined(HAVE_PWRITE) + ssize_t n; + off_t off = (off_t) offset; + if (off != offset) { + errno = EINVAL; + return check_error(-1, errInfo); + } + for (;;) { + if ((n = pread(fd, buf, count, offset)) >= 0) + break; + else if (errno != EINTR) + return check_error(-1, errInfo); + } + *pBytesRead = (size_t) n; + return 1; +#else + { + int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL); + if (res) { + return efile_read(errInfo, 0, fd, buf, count, pBytesRead); + } else { + return res; + } + } +#endif +} + + + +int +efile_pwrite(Efile_error* errInfo, /* Where to return error codes. */ + int fd, /* File descriptor to write to. */ + char* buf, /* Buffer to write. */ + size_t count, /* Number of bytes to write. */ + Sint64 offset) /* where to write it */ +{ +#if defined(HAVE_PREAD) && defined(HAVE_PWRITE) + ssize_t written; /* Bytes written in last operation. */ + off_t off = (off_t) offset; + if (off != offset) { + errno = EINVAL; + return check_error(-1, errInfo); + } + + while (count > 0) { + if ((written = pwrite(fd, buf, count, offset)) < 0) { + if (errno != EINTR) + return check_error(-1, errInfo); + else + written = 0; + } + ASSERT(written <= count); + buf += written; + count -= written; + offset += written; + } + return 1; +#else /* For unix systems that don't support pread() and pwrite() */ + { + int res = efile_seek(errInfo, fd, offset, EFILE_SEEK_SET, NULL); + + if (res) { + return efile_write(errInfo, 0, fd, buf, count); + } else { + return res; + } + } +#endif +} + + +int +efile_seek(Efile_error* errInfo, /* Where to return error codes. */ + int fd, /* File descriptor to do the seek on. */ + Sint64 offset, /* Offset in bytes from the given + origin. */ + int origin, /* Origin of seek (SEEK_SET, SEEK_CUR, + SEEK_END). */ + Sint64 *new_location) /* Resulting new location in file. */ +{ + off_t off, result; + + switch (origin) { + case EFILE_SEEK_SET: origin = SEEK_SET; break; + case EFILE_SEEK_CUR: origin = SEEK_CUR; break; + case EFILE_SEEK_END: origin = SEEK_END; break; + default: + errno = EINVAL; + return check_error(-1, errInfo); + } + off = (off_t) offset; + if (off != offset) { + errno = EINVAL; + return check_error(-1, errInfo); + } + + errno = 0; + result = lseek(fd, off, origin); + + /* + * Note that the man page for lseek (on SunOs 5) says: + * + * "if fildes is a remote file descriptor and offset is + * negative, lseek() returns the file pointer even if it is + * negative." + */ + + if (result < 0 && errno == 0) + errno = EINVAL; + if (result < 0) + return check_error(-1, errInfo); + if (new_location) { + *new_location = result; + } + return 1; +} + + +int +efile_truncate_file(Efile_error* errInfo, int *fd, int flags) +{ +#ifdef VXWORKS + off_t offset; + char namebuf[PATH_MAX+1]; + char namebuf2[PATH_MAX+10]; + int new; + int dummy; + int i; + int left; + static char buff[1024]; + struct stat st; + Efile_error tmperr; + + if ((offset = lseek(*fd, 0, 1)) < 0) { + return check_error((int) offset,errInfo); + } + if (ftruncate(*fd, offset) < 0) { + if (vxworks_to_posix(errno) != EINVAL) { + return check_error(-1, errInfo); + } + /* + ** Kludge + */ + if(ioctl(*fd,FIOGETNAME,(int) namebuf) < 0) { + return check_error(-1, errInfo); + } + for(i=0;i<1000;++i) { + sprintf(namebuf2,"%s%d",namebuf,i); + CHECK_PATHLEN(namebuf2,errInfo); + if (stat(namebuf2,&st) < 0) { + break; + } + } + if (i > 1000) { + errno = EINVAL; + return check_error(-1, errInfo); + } + if (close(*fd) < 0) { + return check_error(-1, errInfo); + } + if (efile_rename(&tmperr,namebuf,namebuf2) < 0) { + i = check_error(-1,&tmperr); + if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE, + fd,&dummy)) { + *fd = -1; + } else { + *errInfo = tmperr; + } + return i; + } + if ((*fd = open(namebuf2, O_RDONLY, 0)) < 0) { + i = check_error(-1,errInfo); + efile_rename(&tmperr,namebuf2,namebuf); /* at least try */ + if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE, + fd,&dummy)) { + *fd = -1; + } else { + lseek(*fd,offset,SEEK_SET); + } + return i; + } + /* Point of no return... */ + + if ((new = open(namebuf,O_RDWR | O_CREAT, FILE_MODE)) < 0) { + close(*fd); + *fd = -1; + return 0; + } + left = offset; + + while (left) { + if ((i = read(*fd,buff,(left > 1024) ? 1024 : left)) < 0) { + i = check_error(-1,errInfo); + close(new); + close(*fd); + unlink(namebuf); + efile_rename(&tmperr,namebuf2,namebuf); /* at least try */ + if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE, + fd,&dummy)) { + *fd = -1; + } else { + lseek(*fd,offset,SEEK_SET); + } + return i; + } + left -= i; + if (write(new,buff,i) < 0) { + i = check_error(-1,errInfo); + close(new); + close(*fd); + unlink(namebuf); + rename(namebuf2,namebuf); /* at least try */ + if (!efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE, + fd,&dummy)) { + *fd = -1; + } else { + lseek(*fd,offset,SEEK_SET); + } + return i; + } + } + close(*fd); + unlink(namebuf2); + close(new); + i = efile_openfile(errInfo,namebuf,flags | EFILE_NO_TRUNCATE,fd, + &dummy); + if (i) { + lseek(*fd,offset,SEEK_SET); + } + return i; + } + return 1; +#else +#ifndef NO_FTRUNCATE + off_t offset; + + return check_error((offset = lseek(*fd, 0, 1)) >= 0 && + ftruncate(*fd, offset) == 0 ? 1 : -1, + errInfo); +#else + return 1; +#endif +#endif +} + +int +efile_readlink(Efile_error* errInfo, char* name, char* buffer, size_t size) +{ +#ifdef _OSE_ + return ose_enotsup(errInfo); +#else +#ifdef VXWORKS + return vxworks_enotsup(errInfo); +#else + int len; + ASSERT(size > 0); + len = readlink(name, buffer, size-1); + if (len == -1) { + return check_error(-1, errInfo); + } + buffer[len] = '\0'; + return 1; +#endif +#endif +} + +int +efile_altname(Efile_error* errInfo, char* name, char* buffer, size_t size) +{ + errno = ENOTSUP; + return check_error(-1, errInfo); +} + +int +efile_link(Efile_error* errInfo, char* old, char* new) +{ +#ifdef _OSE_ + return ose_enotsup(errInfo); +#else +#ifdef VXWORKS + return vxworks_enotsup(errInfo); +#else + return check_error(link(old, new), errInfo); +#endif +#endif +} + +int +efile_symlink(Efile_error* errInfo, char* old, char* new) +{ +#ifdef _OSE_ + return ose_enotsup(errInfo); +#else +#ifdef VXWORKS + return vxworks_enotsup(errInfo); +#else + return check_error(symlink(old, new), errInfo); +#endif +#endif +} -- cgit v1.2.3