#include "system.h"
#include <safe-read.h>
#include <full-write.h>
#ifndef EOPNOTSUPP
# if HAVE_NET_ERRNO_H
# include <net/errno.h>
# endif
# if HAVE_SYS_INET_H
# include <sys/inet.h>
# endif
# ifndef EOPNOTSUPP
# define EOPNOTSUPP EINVAL
# endif
#endif
#include <signal.h>
#if HAVE_NETDB_H
# include <netdb.h>
#endif
#include <rmt.h>
#include <localedir.h>
#define EXIT_ON_EXEC_ERROR 128
#define COMMAND_BUFFER_SIZE 64
#ifndef RETSIGTYPE
# define RETSIGTYPE void
#endif
#define MAXUNIT 4
#define PREAD 0
#define PWRITE 1
#define READ_SIDE(Fd) (from_remote[Fd][PREAD])
#define WRITE_SIDE(Fd) (to_remote[Fd][PWRITE])
static int from_remote[MAXUNIT][2] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}};
static int to_remote[MAXUNIT][2] = {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}};
char *rmt_command = DEFAULT_RMT_COMMAND;
char *rmt_dev_name__;
bool force_local_option;
static void
_rmt_shutdown (int handle, int errno_value)
{
close (READ_SIDE (handle));
close (WRITE_SIDE (handle));
READ_SIDE (handle) = -1;
WRITE_SIDE (handle) = -1;
errno = errno_value;
}
static int
do_command (int handle, const char *buffer)
{
size_t length = strlen (buffer);
RETSIGTYPE (*pipe_handler) () = signal (SIGPIPE, SIG_IGN);
ssize_t written = full_write (WRITE_SIDE (handle), buffer, length);
signal (SIGPIPE, pipe_handler);
if (written == length)
return 0;
_rmt_shutdown (handle, EIO);
return -1;
}
static char *
get_status_string (int handle, char *command_buffer)
{
char *cursor;
int counter;
for (counter = 0, cursor = command_buffer;
counter < COMMAND_BUFFER_SIZE;
counter++, cursor++)
{
if (safe_read (READ_SIDE (handle), cursor, 1) != 1)
{
_rmt_shutdown (handle, EIO);
return 0;
}
if (*cursor == '\n')
{
*cursor = '\0';
break;
}
}
if (counter == COMMAND_BUFFER_SIZE)
{
_rmt_shutdown (handle, EIO);
return 0;
}
for (cursor = command_buffer; *cursor; cursor++)
if (*cursor != ' ')
break;
if (*cursor == 'E' || *cursor == 'F')
{
{
char character;
while (safe_read (READ_SIDE (handle), &character, 1) == 1)
if (character == '\n')
break;
}
errno = atoi (cursor + 1);
if (*cursor == 'F')
_rmt_shutdown (handle, errno);
return 0;
}
if (*cursor != 'A')
{
_rmt_shutdown (handle, EIO);
return 0;
}
return cursor + 1;
}
static long int
get_status (int handle)
{
char command_buffer[COMMAND_BUFFER_SIZE];
const char *status = get_status_string (handle, command_buffer);
if (status)
{
long int result = atol (status);
if (0 <= result)
return result;
errno = EIO;
}
return -1;
}
static off_t
get_status_off (int handle)
{
char command_buffer[COMMAND_BUFFER_SIZE];
const char *status = get_status_string (handle, command_buffer);
if (! status)
return -1;
else
{
off_t count = 0;
int negative;
for (; *status == ' ' || *status == '\t'; status++)
continue;
negative = *status == '-';
status += negative || *status == '+';
for (;;)
{
int digit = *status++ - '0';
if (9 < (unsigned) digit)
break;
else
{
off_t c10 = 10 * count;
off_t nc = negative ? c10 - digit : c10 + digit;
if (c10 / 10 != count || (negative ? c10 < nc : nc < c10))
return -1;
count = nc;
}
}
return count;
}
}
#if WITH_REXEC
static int
_rmt_rexec (char *host, char *user)
{
int saved_stdin = dup (STDIN_FILENO);
int saved_stdout = dup (STDOUT_FILENO);
struct servent *rexecserv;
int result;
if (! freopen ("/dev/tty", "r", stdin))
freopen ("/dev/null", "r", stdin);
if (! freopen ("/dev/tty", "w", stdout))
freopen ("/dev/null", "w", stdout);
if (rexecserv = getservbyname ("exec", "tcp"), !rexecserv)
error (EXIT_ON_EXEC_ERROR, 0, _("exec/tcp: Service not available"));
result = rexec (&host, rexecserv->s_port, user, 0, rmt_command, 0);
if (fclose (stdin) == EOF)
error (0, errno, _("stdin"));
fdopen (saved_stdin, "r");
if (fclose (stdout) == EOF)
error (0, errno, _("stdout"));
fdopen (saved_stdout, "w");
return result;
}
#endif
static void
encode_oflag (char *buf, int oflag)
{
sprintf (buf, "%d ", oflag);
switch (oflag & O_ACCMODE)
{
case O_RDONLY: strcat (buf, "O_RDONLY"); break;
case O_RDWR: strcat (buf, "O_RDWR"); break;
case O_WRONLY: strcat (buf, "O_WRONLY"); break;
default: abort ();
}
#ifdef O_APPEND
if (oflag & O_APPEND) strcat (buf, "|O_APPEND");
#endif
if (oflag & O_CREAT) strcat (buf, "|O_CREAT");
#ifdef O_DSYNC
if (oflag & O_DSYNC) strcat (buf, "|O_DSYNC");
#endif
if (oflag & O_EXCL) strcat (buf, "|O_EXCL");
#ifdef O_LARGEFILE
if (oflag & O_LARGEFILE) strcat (buf, "|O_LARGEFILE");
#endif
#ifdef O_NOCTTY
if (oflag & O_NOCTTY) strcat (buf, "|O_NOCTTY");
#endif
#ifdef O_NONBLOCK
if (oflag & O_NONBLOCK) strcat (buf, "|O_NONBLOCK");
#endif
#ifdef O_RSYNC
if (oflag & O_RSYNC) strcat (buf, "|O_RSYNC");
#endif
#ifdef O_SYNC
if (oflag & O_SYNC) strcat (buf, "|O_SYNC");
#endif
if (oflag & O_TRUNC) strcat (buf, "|O_TRUNC");
}
int
rmt_open__ (const char *file_name, int open_mode, int bias,
const char *remote_shell)
{
int remote_pipe_number;
char *file_name_copy;
char *remote_host;
char *remote_file;
char *remote_user;
for (remote_pipe_number = 0;
remote_pipe_number < MAXUNIT;
remote_pipe_number++)
if (READ_SIDE (remote_pipe_number) == -1
&& WRITE_SIDE (remote_pipe_number) == -1)
break;
if (remote_pipe_number == MAXUNIT)
{
errno = EMFILE;
return -1;
}
{
char *cursor;
file_name_copy = xstrdup (file_name);
remote_host = file_name_copy;
remote_user = 0;
remote_file = 0;
for (cursor = file_name_copy; *cursor; cursor++)
switch (*cursor)
{
default:
break;
case '\n':
free (file_name_copy);
errno = ENOENT;
return -1;
case '@':
if (!remote_user)
{
remote_user = remote_host;
*cursor = '\0';
remote_host = cursor + 1;
}
break;
case ':':
if (!remote_file)
{
*cursor = '\0';
remote_file = cursor + 1;
}
break;
}
}
if (remote_user && *remote_user == '\0')
remote_user = 0;
#if WITH_REXEC
READ_SIDE (remote_pipe_number) = _rmt_rexec (remote_host, remote_user);
if (READ_SIDE (remote_pipe_number) < 0)
{
int e = errno;
free (file_name_copy);
errno = e;
return -1;
}
WRITE_SIDE (remote_pipe_number) = READ_SIDE (remote_pipe_number);
#else
{
const char *remote_shell_basename;
pid_t status;
if (!remote_shell)
{
#ifdef REMOTE_SHELL
remote_shell = REMOTE_SHELL;
#else
free (file_name_copy);
errno = EIO;
return -1;
#endif
}
remote_shell_basename = base_name (remote_shell);
if (pipe (to_remote[remote_pipe_number]) == -1
|| pipe (from_remote[remote_pipe_number]) == -1)
{
int e = errno;
free (file_name_copy);
errno = e;
return -1;
}
status = fork ();
if (status == -1)
{
int e = errno;
free (file_name_copy);
errno = e;
return -1;
}
if (status == 0)
{
close (STDIN_FILENO);
dup (to_remote[remote_pipe_number][PREAD]);
close (to_remote[remote_pipe_number][PREAD]);
close (to_remote[remote_pipe_number][PWRITE]);
close (STDOUT_FILENO);
dup (from_remote[remote_pipe_number][PWRITE]);
close (from_remote[remote_pipe_number][PREAD]);
close (from_remote[remote_pipe_number][PWRITE]);
sys_reset_uid_gid ();
if (remote_user)
execl (remote_shell, remote_shell_basename, remote_host,
"-l", remote_user, rmt_command, (char *) 0);
else
execl (remote_shell, remote_shell_basename, remote_host,
rmt_command, (char *) 0);
error (EXIT_ON_EXEC_ERROR, errno, _("Cannot execute remote shell"));
}
close (from_remote[remote_pipe_number][PWRITE]);
close (to_remote[remote_pipe_number][PREAD]);
}
#endif
{
size_t remote_file_len = strlen (remote_file);
char *command_buffer = xmalloc (remote_file_len + 1000);
sprintf (command_buffer, "O%s\n", remote_file);
encode_oflag (command_buffer + remote_file_len + 2, open_mode);
strcat (command_buffer, "\n");
if (do_command (remote_pipe_number, command_buffer) == -1
|| get_status (remote_pipe_number) == -1)
{
int e = errno;
free (command_buffer);
free (file_name_copy);
_rmt_shutdown (remote_pipe_number, e);
return -1;
}
free (command_buffer);
}
free (file_name_copy);
return remote_pipe_number + bias;
}
int
rmt_close__ (int handle)
{
long int status;
if (do_command (handle, "C\n") == -1)
return -1;
status = get_status (handle);
_rmt_shutdown (handle, errno);
return status;
}
size_t
rmt_read__ (int handle, char *buffer, size_t length)
{
char command_buffer[COMMAND_BUFFER_SIZE];
size_t status;
size_t rlen;
size_t counter;
sprintf (command_buffer, "R%lu\n", (unsigned long) length);
if (do_command (handle, command_buffer) == -1
|| (status = get_status (handle)) == SAFE_READ_ERROR)
return SAFE_READ_ERROR;
for (counter = 0; counter < status; counter += rlen, buffer += rlen)
{
rlen = safe_read (READ_SIDE (handle), buffer, status - counter);
if (rlen == SAFE_READ_ERROR || rlen == 0)
{
_rmt_shutdown (handle, EIO);
return SAFE_READ_ERROR;
}
}
return status;
}
size_t
rmt_write__ (int handle, char *buffer, size_t length)
{
char command_buffer[COMMAND_BUFFER_SIZE];
RETSIGTYPE (*pipe_handler) ();
size_t written;
sprintf (command_buffer, "W%lu\n", (unsigned long) length);
if (do_command (handle, command_buffer) == -1)
return 0;
pipe_handler = signal (SIGPIPE, SIG_IGN);
written = full_write (WRITE_SIDE (handle), buffer, length);
signal (SIGPIPE, pipe_handler);
if (written == length)
{
long int r = get_status (handle);
if (r < 0)
return 0;
if (r == length)
return length;
written = r;
}
_rmt_shutdown (handle, EIO);
return written;
}
off_t
rmt_lseek__ (int handle, off_t offset, int whence)
{
char command_buffer[COMMAND_BUFFER_SIZE];
char operand_buffer[UINTMAX_STRSIZE_BOUND];
uintmax_t u = offset < 0 ? - (uintmax_t) offset : (uintmax_t) offset;
char *p = operand_buffer + sizeof operand_buffer;
*--p = 0;
do
*--p = '0' + (int) (u % 10);
while ((u /= 10) != 0);
if (offset < 0)
*--p = '-';
switch (whence)
{
case SEEK_SET: whence = 0; break;
case SEEK_CUR: whence = 1; break;
case SEEK_END: whence = 2; break;
default: abort ();
}
sprintf (command_buffer, "L%s\n%d\n", p, whence);
if (do_command (handle, command_buffer) == -1)
return -1;
return get_status_off (handle);
}
int
rmt_ioctl__ (int handle, int operation, char *argument)
{
switch (operation)
{
default:
errno = EOPNOTSUPP;
return -1;
#ifdef MTIOCTOP
case MTIOCTOP:
{
char command_buffer[COMMAND_BUFFER_SIZE];
char operand_buffer[UINTMAX_STRSIZE_BOUND];
uintmax_t u = (((struct mtop *) argument)->mt_count < 0
? - (uintmax_t) ((struct mtop *) argument)->mt_count
: (uintmax_t) ((struct mtop *) argument)->mt_count);
char *p = operand_buffer + sizeof operand_buffer;
*--p = 0;
do
*--p = '0' + (int) (u % 10);
while ((u /= 10) != 0);
if (((struct mtop *) argument)->mt_count < 0)
*--p = '-';
sprintf (command_buffer, "I%d\n%s\n",
((struct mtop *) argument)->mt_op, p);
if (do_command (handle, command_buffer) == -1)
return -1;
return get_status (handle);
}
#endif
#ifdef MTIOCGET
case MTIOCGET:
{
ssize_t status;
size_t counter;
if (do_command (handle, "S") == -1
|| (status = get_status (handle), status == -1))
return -1;
for (; status > 0; status -= counter, argument += counter)
{
counter = safe_read (READ_SIDE (handle), argument, status);
if (counter == SAFE_READ_ERROR || counter == 0)
{
_rmt_shutdown (handle, EIO);
return -1;
}
}
if (((struct mtget *) argument)->MTIO_CHECK_FIELD < 256)
return 0;
for (counter = 0; counter < status; counter += 2)
{
char copy = argument[counter];
argument[counter] = argument[counter + 1];
argument[counter + 1] = copy;
}
return 0;
}
#endif
}
}