[MPlayer-dev-eng] [RFC] ssh sftp stream support

Nicolas Collignon tsointsoin at gmail.com
Mon Oct 30 00:24:47 CET 2006


The attached patch add support for SFTP protocol over SSH using OpenSSH program
as slave for SSH communication (the same way sftp do).

Several questions:

1) The stream is marked "config-is-in-url" but I would like to enable
more option than just
    - hostname
    - port
    - user
    Actually the OpenSSH arguments are : "ssh -2akTxs hostname sftp".
I want them to
    be settable on command line.
    How to do this if stream is marked "config-is-in-url" ?

2) STREAM_BUFFER_SIZE is 2ko in current mplayer source. This is not enough for
    SSH streams: the download rate is ~60ko/s which is too slow for playing
    remote movie (mpeg/divx/xvid encoded).
    Using a 32ko or 64ko STREAM_BUFFER_SIZE seems too be good enough: ~250ko/s
    I don't know if this happen only with SSH streams or with all
remote streams.

    I see 4 possible solutions:
      - implement data prefetching in my code
      - implement data prefetching in cache code
      - make STREAM_BUFFER_SIZE config-settable (compile time + run time)
      - increase STREAM_BUFFER_SIZE in stream.h

3) The code uses readv(2) instead of read(2) to perform zero-copy read
operations.
     So <sys/uio.h> is included, I don't know how portable it is.

4) This code has only been tested on little-endian systems.
    Big-endian implementation must be tested

The patch currently set STREAM_BUFFER_SIZE to 32ko so remote playing works ok.
Initial delay before playing video/audio is ~35 seconds. This can
certainly be optimised.

waiting for comments/answers :) ...
There are some "FIXME" in the code.

-- 
Person who say it cannot be done should not interrupt person doing it.
-------------- next part --------------
Index: stream/stream_ssh.c
===================================================================
--- stream/stream_ssh.c	(r?vision 0)
+++ stream/stream_ssh.c	(r?vision 0)
@@ -0,0 +1,922 @@
+/* 
+ * mplayer SSH+FTP stream support
+ * code is based on draft-ietf-secsh-filexfer-13.txt
+ *        -- Nicolas Collignon <tsointsoin at gmail.com>
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+
+#include "mp_msg.h"
+#include "stream.h"
+#include "help_mp.h"
+#include "m_option.h"
+#include "m_struct.h"
+#include "bswap.h"
+
+extern char **environ;
+
+struct stream_priv_s {
+	/* context ***/
+	int fd_in, fd_out; /* child ssh I/O file descriptors */
+	int killed;
+	pid_t pid;         /* child ssh pid */
+	uint32_t id;
+	char *buffer;
+	char *handle;
+	unsigned int handle_len;
+	/* config ***/
+	char *user;
+	char *host;
+	int port;
+	time_t timeout;
+	char *filename;
+	char *sshprog;
+	char *sshargs;
+	char *sshsubsys;
+};
+
+static struct stream_priv_s stream_priv_dflts = {
+	/* context ***/
+	-1, -1,
+	0,
+	-1,
+	0,
+	NULL,
+	NULL,
+	0,
+	/* config ***/
+	NULL,
+	NULL,
+	22, /* port 22 */
+	8,  /* 8 seconds timeout */
+	NULL,
+	"ssh",
+	/* OpenSSH default options:
+	 *  2   Forces protocol version 2 only
+	 *  a   Disable forwarding if auth agent
+	 *  k   Disable delegation of GSSAPI credentials
+	 *  T   Disable pseudo-tty allocation
+	 *  x   Disable X11 forwarding
+	 *  s   Use subsystem
+	 */
+	"-2akTxs",
+	"sftp"
+};
+
+#define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f)
+static m_option_t stream_opts_fields[] = {
+	{"username", ST_OFF(user), CONF_TYPE_STRING, 0, 0 , 0, NULL},
+	{"hostname", ST_OFF(host), CONF_TYPE_STRING, 0, 0 , 0, NULL},
+	{"port",     ST_OFF(port), CONF_TYPE_INT, 0, 0 , 65635, NULL},
+	{"filename", ST_OFF(filename), CONF_TYPE_STRING, 0, 0 , 0, NULL},
+	/* FIXME: handle this ? */
+	{"timeout", ST_OFF(timeout), CONF_TYPE_INT, 0, 0 , 60, NULL},
+	{"sshprog", ST_OFF(sshprog), CONF_TYPE_STRING, 0, 0 , 0, NULL},
+	{"sshargs", ST_OFF(sshargs), CONF_TYPE_STRING, 0, 0 , 0, NULL},
+	{"sshsubsys", ST_OFF(sshsubsys), CONF_TYPE_STRING, 0, 0 , 0, NULL},
+	{ NULL, NULL, 0, 0, 0, 0,  NULL }
+};
+
+static struct m_struct_st stream_opts = {
+	"ssh",
+	sizeof(struct stream_priv_s),
+	&stream_priv_dflts,
+	stream_opts_fields
+};
+
+/* SSH specs {{{ */
+
+#define SSH_FXP_OPEN  3
+#define SSH_FXP_READ  5
+#define SSH_FXP_FSTAT 8
+#define SSH_FXP_STAT  17
+
+#define SSH_FXP_STATUS 101
+#define SSH_FXP_HANDLE 102
+#define SSH_FXP_DATA   103
+#define SSH_FXP_ATTRS  105
+
+/* size (5 bytes) + type (SSH_FXP_INIT) + id (SSH_FXP_VERSION) */
+const char msg_version[] = "\0\0\0\5\1\0\0\0\3";
+/* SSH_FXF_READ + 0 attributes */
+const char msg_open[] = "\0\0\0\1\0\0\0\0";
+/* SSH_FILEXFER_ATTR_SIZE */
+const char msg_stat[] = "\0\0\0\1";
+
+/* SSH_FX_xxx error codes ***/
+static const char *sshfx_errors[32] = {
+	"Unknown error code",
+	/* 1 */
+	"End of file",
+	"File doesn't exist",
+	"Permission denied",
+	"Internal server error",
+	"Bad SFTP protocol message",
+	"", /* NO_CONNECTION not returned by server */
+	"", /* CONNECTION_LOST not returned by server */
+	"Unsupported operation",
+	"Invalid handle",
+	/* 10 */
+	"File or Path doesn't exist",
+	"", /* FILE_ALREADY_EXISTS .. we don't write */
+	"", /* WRITE_PROTECT .. we don't write */
+	"Remote media isn't mounted",
+	"", /* NO_SPACE_ON_FILESYSTEM .. we don't write */
+	"", /* QUOTA_EXCEEDED .. we don't write */
+	"", /* UNKNOWN_PRINCIPAL .. we don't write */
+	"File is locked",
+	"", /* DIR_NOT_EMPTY .. we don't write */
+	"", /* NOT_A_DIRECTORY .. we don't write */
+	/* 20 */
+	"Invalid filename",
+	"Too many symbolic links encountered",
+	"", /* CANNOT_DELETE .. we don't delete */
+	"Invalid parameter", 
+	"Target file is a directory",
+	"File range is locked",
+	"", /* RANGE_LOCK_REFUSED we don't lock */
+	"File is about to be deleted",
+	"File corrupted",
+	"", /* OWNER_INVALID .. we don't chown */
+	/* 30 */
+	"", /* GROUP_INVALID .. we don't chgrp */
+	"", /* NO_MATCHING_BYTE_RANGE_LOCK .. we don't lock */
+};
+
+/* }}} */
+
+static struct stream_priv_s *unique_ssh_instance = NULL;
+
+static void dead_ssh(int sig)
+{
+	register struct stream_priv_s *p = unique_ssh_instance;
+
+	unique_ssh_instance = NULL;
+	if (p) {
+		p->killed = 1;
+		mp_msg(MSGT_OPEN, MSGL_WARN, "[ssh] child killed\n");
+	}
+}
+
+/* unaligned buffers hell {{{ */
+
+#ifdef WORDS_ENDIAN /* big endian (ssh endian) */
+
+#define aligned_put_uint32(b, v) \
+		(*((uint32_t *) (b)) = (uint32_t)(v))
+
+#define unaligned_put_uint32(b, v) \
+	{ \
+		((uint8_t *) (b))[0] = (((uint32_t)(v)) >> 24) & 0xff; \
+		((uint8_t *) (b))[1] = (((uint32_t)(v)) >> 16) & 0xff; \
+		((uint8_t *) (b))[2] = (((uint32_t)(v)) >> 8) & 0xff; \
+		((uint8_t *) (b))[3] = ((uint32_t)(v)) & 0xff; \
+	}
+
+#define aligned_get_uint32(b) \
+		(*(uint32_t *)b)
+
+#define unaligned_get_uint32(b) \
+		(uint32_t) \
+		(((uint8_t *) (b))[0] << 24) \
+		|(((uint8_t *) (b))[1] << 16) \
+		|(((uint8_t *) (b))[2] << 8) \
+		|(((uint8_t *) (b))[3])
+
+#define aligned_put_uint64(b, v) \
+		((*((uint64_t *) (b)) = (uint64_t)(v)))
+
+#define unaligned_get_uint64(b) \
+		(uint64_t) \
+		(((uint64_t) unaligned_get_uint32(b)) \
+		|(((uint64_t) unaligned_get_uint32(b+4)) >> 32))
+
+#else /* little endian */
+
+#define aligned_put_uint32(b, v) \
+		(*((uint32_t *) (b)) = bswap_32((uint32_t)(v)))
+
+#define unaligned_put_uint32(b, v) \
+	{ \
+		((uint8_t *) (b))[0] = (((uint32_t)(v)) >> 24) & 0xff; \
+		((uint8_t *) (b))[1] = (((uint32_t)(v)) >> 16) & 0xff; \
+		((uint8_t *) (b))[2] = (((uint32_t)(v)) >> 8) & 0xff; \
+		((uint8_t *) (b))[3] = ((uint32_t)(v)) & 0xff; \
+	}
+
+#define aligned_get_uint32(b) \
+	be2me_32(*(uint32_t *)b)
+
+#define unaligned_get_uint32(b) \
+		(uint32_t) \
+		((((uint32_t)(((uint8_t *) (b))[0])) << 24) \
+		|(((uint32_t)(((uint8_t *) (b))[1])) << 16) \
+		|(((uint32_t)(((uint8_t *) (b))[2])) << 8) \
+		|(((uint32_t)(((uint8_t *) (b))[3]))))
+
+#define aligned_put_uint64(b, v) \
+		(((uint64_t *) (b))[0] = bswap_64((uint64_t)(v)))
+
+#define unaligned_get_uint64(b) \
+		(uint64_t) \
+		(((uint64_t) unaligned_get_uint32(((uint8_t *)(b))+4)) \
+		|(((uint64_t) unaligned_get_uint32(((uint8_t *)(b)))) >> 32))
+
+#endif
+
+/* }}} */
+
+/* utils {{{ */
+
+#define READ_FD    0
+#define WRITE_FD   1
+#define NO_TIMEOUT 2
+
+static int fd_wait(struct stream_priv_s *p, int flags)
+{
+   register fd_set *rfds, *wfds;
+   register int fd;
+   fd_set fds;
+   struct timeval tv;
+
+   tv.tv_sec  = p->timeout;
+   tv.tv_usec = 0;
+
+   if ((flags & WRITE_FD) == READ_FD) {
+      fd = p->fd_in;
+      rfds = &fds;
+      wfds = NULL;
+   } else {
+      fd = p->fd_out;
+      rfds = NULL;
+      wfds = &fds;
+   }
+
+   FD_ZERO(&fds);
+   FD_SET(fd, &fds);
+
+   fd =  select(fd+1, rfds, wfds, NULL, (flags & NO_TIMEOUT ? NULL : &tv));
+	if (!(flags & NO_TIMEOUT) && !fd) {
+		mp_msg(MSGT_STREAM, MSGL_ERR,
+				"[ssh] timeout exceeded\n");
+	} else if (fd < 0) {
+		mp_msg(MSGT_STREAM, MSGL_ERR,
+				"[ssh] error while waiting for file descriptor: %s\n",
+				strerror(errno));
+	}
+
+	return fd;
+}
+
+static int fd_set_async(int fd)
+{
+	register int ret = fcntl(fd, F_GETFL);
+
+	return (ret == -1 ? ret : fcntl(fd, F_SETFL, ret|O_NONBLOCK));
+}
+
+static int send_cmd(
+				struct stream_priv_s *p,
+				unsigned char code,
+				unsigned char retcode,
+				char *str,
+				unsigned int str_len,
+				void *extra,
+				unsigned int extra_len,
+				struct iovec *vecs,
+				unsigned int vec_count,
+				size_t *remaining)
+{
+	uint32_t len, id;
+	uint8_t *buf;
+	ssize_t r;
+
+	len = 5 + (str_len > 0 ? str_len+4 : 0) + extra_len; 
+	buf = p->buffer;
+	aligned_put_uint32(buf, len);
+	buf[4] = code;
+	unaligned_put_uint32(buf+5, p->id);
+	unaligned_put_uint32(buf+9, str_len);
+	memcpy(buf+13, str, str_len);
+	if (extra_len)
+		memcpy(buf+13+str_len, extra, extra_len);
+
+	/* keep sending until the whole request has been sent */
+	len += 4;
+	do {
+		if (fd_wait(p, WRITE_FD) != 1)
+			return -1;
+
+		r = write(p->fd_out, buf, len);
+		if (r <= 0) {
+			if (!p->killed) {
+				mp_msg(MSGT_STREAM, MSGL_ERR,
+						"[ssh] unable to send request: %s\n",
+						strerror(errno));
+			}
+			return -1;
+		}
+
+		len -= r;
+		buf += r;
+
+	} while (len > 0);
+
+	if (fd_wait(p, READ_FD) != 1)
+		return -1;
+
+   r = readv(p->fd_in, vecs, vec_count);
+	if (r <= 0) {
+		if (!p->killed) {
+			mp_msg(MSGT_STREAM, MSGL_ERR,
+					"[ssh] unable to read request's answer: %s\n",
+					strerror(errno));
+		}
+		return -1;
+	}
+	if (r < 13) {
+		/* as far as i know it didn't happen yet
+		 * can be fixed if needed ... */
+		mp_msg(MSGT_STREAM, MSGL_ERR,
+				"[ssh] unable to read request's answer: short read (%i)\n",
+				r);
+	}
+
+	buf = vecs[0].iov_base;
+	len = aligned_get_uint32(buf);
+
+	/* handle request id */
+	id = unaligned_get_uint32(buf+5);
+	mp_msg(MSGT_STREAM, MSGL_DBG2,
+			"[ssh] answer: type=%u, id=%lu\n", buf[4], id);
+	if (id != p->id) {
+		mp_msg(MSGT_STREAM, MSGL_WARN,
+				"[ssh] request id %lu doesn't match %lu\n",
+				id, p->id);
+		return -1;
+	}
+	++p->id;
+
+	/* handle request return code */
+	if (buf[4] == SSH_FXP_STATUS) {
+		id = unaligned_get_uint32(buf+9);
+		mp_msg(MSGT_STREAM, MSGL_WARN,
+				"[ssh] error code %lu: %s\n", id, sshfx_errors[id < 30 ? id : 0]);
+		return -1;
+		
+
+	} else if (buf[4] != retcode) {
+		mp_msg(MSGT_STREAM, MSGL_WARN,
+				"[ssh] return code not supported\n");
+		return -1;
+	}
+
+	if (len < (r - 4)) {
+		/* as far as i know it didn't happen yet
+		 * can be fixed if needed ... */
+		mp_msg(MSGT_STREAM, MSGL_ERR,
+				"[ssh] error: 2 messages bundled in 1 packet\n");
+		return -1;
+	}
+
+	/* FIXME: overflow ? */
+	*remaining = len - (r - 4);
+
+	return r;
+}
+
+static int get_size_attr(stream_t *s, int msgt)
+{
+	struct iovec iov;
+	off_t lsize;
+	size_t remaining;
+	ssize_t r;
+	/* length(4) + type (1) + id (4) + flags (4) + size (8) + [attrs] */
+   uint8_t buf[256];
+   register struct stream_priv_s *p = s->priv;
+
+	iov.iov_base = buf;
+	iov.iov_len  = sizeof(buf);
+
+	if (send_cmd(p, SSH_FXP_FSTAT, SSH_FXP_ATTRS,
+				p->handle, p->handle_len,
+				(char *)msg_stat, sizeof(msg_stat)-1,
+				&iov, 1,
+				&remaining) < 21) {
+		mp_msg(msgt, MSGL_ERR,
+				"[ssh] unable to get remote file's attributes\n");
+		return -1;
+	}
+
+	/* flags is uint32_t @ buf+9
+	 * only check for SSH_FILEXFER_ATTR_SIZE (0x00000001) */
+	if (!(buf[9+3] & 1)) {
+		mp_msg(msgt, MSGL_ERR,
+				"[ssh] file's size not reported by server\n");
+		return -1;
+	}
+	/* FIXME: is off_t signed ? */
+	s->end_pos = unaligned_get_uint64(&buf[13]);
+	mp_msg(msgt, MSGL_V,
+			"[ssh] remote file is %llu bytes length\n",
+			s->end_pos);
+
+	/* skip extra attributes */
+	while (remaining > 0) {
+		if (fd_wait(p, READ_FD) != 1)
+			return -1;
+		r = read(p->fd_in, buf, MIN(sizeof(buf), remaining));
+		if (r <= 0)
+			return -1;
+		remaining -= r;
+	}
+
+	return 0;
+}
+
+/* }}} */
+
+/* ssh_read {{{ */
+
+static int ssh_read(stream_t *s, char *buffer, int max_len)
+{
+   register struct stream_priv_s *p = s->priv;
+   ssize_t r;
+	size_t remaining;
+	uint32_t len;
+	unsigned int off;
+	int i;
+   struct iovec vecs[2];
+   char buf[13]; /* length(4) + type (1) + id (4) + size ?*/
+	struct open_arg {
+		uint64_t offset;
+		uint32_t size;
+	} oarg;
+
+	if ((max_len <= 0) || (s->pos < 0)) {
+		mp_msg(MSGT_STREAM, MSGL_WARN, "[ssh] invalid read\n");
+		return 0;
+	}
+
+   vecs[0].iov_base = buf;
+   vecs[0].iov_len  = sizeof(buf);
+   vecs[1].iov_base = buffer; /* zero copy buffer */
+   vecs[1].iov_len  = max_len;
+
+	aligned_put_uint64(&oarg.offset, s->pos);
+	aligned_put_uint32(&oarg.size, max_len);
+
+	r = send_cmd(p, SSH_FXP_READ, SSH_FXP_DATA,
+					p->handle, p->handle_len,
+					&oarg, sizeof(oarg),
+					vecs, 2,
+					&remaining);
+	if (r <= 0) {
+      return -1;
+	}
+
+	/* FIXME: check negative */
+	len = aligned_get_uint32(buf) - 9;
+	/* FIXME: wtf ?
+	len = aligned_get_uint32(buf+9);
+	printf("GOT[2] %lu bytes (%x / %x)\n", len, len, max_len); */
+	if (!len) {
+      mp_msg(MSGT_STREAM, MSGL_ERR,
+				"[ssh] server returned 0 byte when reading\n");
+		return -1;
+	} else if (len > max_len) {
+      mp_msg(MSGT_STREAM, MSGL_ERR,
+				"[ssh] server returned more bytes than we asked\n");
+		return -1;
+	}
+
+	if (remaining <= 0)
+		return len - 13;
+		
+	/* all data not received on this round */
+	off = r - 13;
+	do {
+		if (fd_wait(p, READ_FD) != 1)
+			return -1;
+		r = read(p->fd_in, buffer + off, remaining);
+		if (r <= 0) {
+			mp_msg(MSGT_STREAM, MSGL_ERR,
+					"[ssh] unable to read multi-part message\n");
+			return -1;
+		}
+
+		off += r;
+		remaining -= r;
+
+	} while (remaining > 0);
+
+	return len - 13;
+}
+
+/* }}}*/
+
+/* ssh_seek {{{ */
+
+static int ssh_seek(stream_t *s, off_t newpos)
+{
+   register struct stream_priv_s *p = s->priv;
+
+	s->pos = newpos;
+   if (newpos < s->end_pos) {
+      return 1;
+	}
+
+   /* get current size if remote file is growing */
+	if (get_size_attr(s, MSGT_STREAM) || (newpos >= s->end_pos)) {
+      s->eof = 1;
+      return 0;
+   }
+
+   return 1;
+}
+
+/* }}} */
+
+/* ssh_close {{{ */
+
+static void ssh_close(stream_t *s)
+{
+   register struct stream_priv_s *p = s->priv;
+
+   if (!p || (p != unique_ssh_instance))
+      return;
+	unique_ssh_instance = NULL;
+
+	if (p->handle)
+		free(p->handle);
+	p->handle = NULL;
+
+	/* don't send SSH_FXP_CLOSE. server will cleanup himself :p */
+
+   close(p->fd_in);
+   close(p->fd_out);
+
+   kill(p->pid, SIGTERM);
+
+   m_struct_free(&stream_opts, p);
+}
+
+/* }}} */
+
+/* ssh_open {{{ */
+
+static int kill_stream(struct stream_priv_s *p, int err)
+{
+	unique_ssh_instance = NULL;
+   m_struct_free(&stream_opts, p);
+   return err;
+}
+
+static void redirect_fd(int *pfd, int to)
+{
+	if (pfd[to] != to) {
+		close(to);
+		if (dup2(pfd[to], to) == -1)
+			_exit(-2);
+		close(pfd[to]);
+		close(pfd[!to]);
+	}
+}
+
+static unsigned int split_command_args(
+								char **argv,
+								unsigned int max_argc,
+								char *cmdline)
+{
+	unsigned int i;
+	char *ptr;
+
+	ptr = cmdline;
+	argv[0] = cmdline;
+	i = 1;
+	do {
+		ptr = strchr(ptr, ' ');
+		if (!ptr)
+			break;
+		*ptr++ = 0;
+		while (*ptr == ' ')
+			++ptr;
+		argv[i] = ptr;
+	} while (++i < max_argc);
+
+	/* !argv[i] ???? */
+	if (!argv[i] || !ptr)
+		--i;
+	return i;
+}
+
+static void show_cmdline(char **argv)
+{
+	unsigned int i, len, c;
+	char *cmdline;
+
+	len = 1;
+	for (i=0; argv[i]; ++i)
+		len += 1 + strlen(argv[i]);
+
+	cmdline = malloc(len);
+	if (!cmdline)
+		return;
+
+	/* join all args with space */
+	strcpy(cmdline, argv[0]);
+	len = strlen(cmdline);
+	for (i=1; argv[i]; ++i) {
+		cmdline[len++] = ' ';
+		c = strlen(argv[i]);
+		memcpy(cmdline+len, argv[i], c);
+		len += c;
+	}
+	cmdline[len] = 0;
+
+	mp_msg(MSGT_OPEN, MSGL_V, "[ssh] command line: %s\n", cmdline);
+	free(cmdline);
+}
+
+static int start_ssh(struct stream_priv_s *p)
+{
+	unsigned int i;
+	int st;
+	int ipfd[2], opfd[2];
+	char *argv[32];
+	pid_t dead_pid;
+
+	if (pipe(ipfd)) {
+		mp_msg(MSGT_OPEN, MSGL_ERR, "[ssh] unable to create input pipe\n");
+		return kill_stream(p, STREAM_ERROR);
+	}
+	if (pipe(opfd)) {
+		close(ipfd[0]);
+		close(ipfd[1]);
+		mp_msg(MSGT_OPEN, MSGL_ERR, "[ssh] unable to create output pipe\n");
+		return kill_stream(p, STREAM_ERROR);
+	}
+
+	argv[0] = p->sshprog;
+	i = 1;
+	if (p->sshargs && p->sshargs[0])
+		i += split_command_args(&argv[1], sizeof(argv)-8, p->sshargs) + 1;
+
+	/* ssh will use current user if not specified */
+	if (p->user && p->user[0]) {
+		argv[i++] = "-l";
+		argv[i++] = p->user;
+	}
+	argv[i++] = p->host;
+	argv[i++] = p->sshsubsys;
+	argv[i] = NULL;
+
+	if (mp_msg_test(MSGT_STREAM, MSGL_V))
+		show_cmdline(argv);
+
+	switch ((p->pid = fork())) {
+		case -1:
+			mp_msg(MSGT_OPEN, MSGL_ERR, "[ssh] unable to fork\n");
+			break;
+
+		case 0:  /* child */
+			/* starting from now we can't use mp_msg */
+			redirect_fd(ipfd, 0); /* stdin  -> pipe */
+			redirect_fd(opfd, 1); /* stdin  -> pipe */
+
+			execvp(p->sshprog, argv);
+			/* return errno to parent process */
+			_exit(errno);
+
+		default: /* parent */
+			break;
+	}
+
+	st = 0;
+	if (waitpid(p->pid, &st, WNOHANG)) {
+		if (WIFEXITED(st)) {
+			st = WEXITSTATUS(st);
+			mp_msg(MSGT_OPEN, MSGL_ERR,
+					"[ssh] child exited with code %i: %s\n",
+					st, strerror(st));
+		}
+		return -1;
+	}
+
+	close(ipfd[0]);
+	p->fd_out = ipfd[1];
+	close(opfd[1]);
+	p->fd_in = opfd[0];
+
+	if (fd_set_async(p->fd_in) || fd_set_async(p->fd_out)) {
+		mp_msg(MSGT_OPEN, MSGL_ERR,
+				"[ssh] unable to remove file descriptors blocking mode\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+#define SSH_FXP_INIT 1
+#define SSH_FXP_VERSION "\0\0\0\2"
+
+/*
+ * 1) fork + exec("ssh")
+ * 2) send SSH_FXP_INIT
+ * 3) stat remote file (get initial file size .. may be growing)
+ * 4) open remote file (get handle)
+ *
+ */
+
+static int ssh_open(
+	     stream_t *stream,
+	     int mode,
+	     struct stream_priv_s *p,
+	     int *file_format)
+{
+	char *tmp;
+	off_t len;
+	ssize_t c;
+	size_t remaining;
+	int ret;
+	unsigned int filename_len;
+	struct iovec iov;
+	char buf[512];
+	
+   /* sanity checks ***/
+
+   if (mode != STREAM_READ) {
+      mp_msg(MSGT_OPEN, MSGL_ERR, "[ssh] only read mode is supported\n");
+      return kill_stream(p, STREAM_UNSUPORTED);
+   }
+
+   if (!p->filename || !p->host) {
+      mp_msg(MSGT_OPEN, MSGL_ERR, "[ssh] Bad url\n");
+      return kill_stream(p, STREAM_ERROR);
+   }
+
+   /* signal */
+	if (unique_ssh_instance) {
+      mp_msg(MSGT_OPEN, MSGL_ERR, "[ssh] only 1 stream instance supported\n");
+      return kill_stream(p, STREAM_ERROR);
+	}
+	unique_ssh_instance = p;
+   signal(SIGCHLD, dead_ssh);
+   signal(SIGPIPE, dead_ssh);
+   signal(SIGINT, dead_ssh);
+
+   /* ***/
+   if (start_ssh(p)) {
+		mp_msg(MSGT_OPEN, MSGL_ERR, "[ssh] unable to start ssh child\n");
+      return kill_stream(p, STREAM_ERROR);
+	}
+
+   stream->fill_buffer = ssh_read;
+   stream->seek        = ssh_seek;
+   stream->close       = ssh_close;
+   stream->fd          = -1;
+	stream->type        = STREAMTYPE_FILE;
+   stream->flags       = STREAM_READ|STREAM_SEEK;
+   stream->end_pos     = 0;
+   stream->priv        = p;
+
+	if (p->killed)
+		goto failed;
+
+   /* init sftp session ***/
+	
+	if (fd_wait(p, WRITE_FD|NO_TIMEOUT) != 1)
+		goto failed;
+
+	if ((write(p->fd_out, msg_version, sizeof(msg_version)-1) != sizeof(msg_version)-1) || p->killed) {
+		mp_msg(MSGT_OPEN, MSGL_ERR,
+				"[ssh] unable to initialize session: sending version\n");
+		goto failed;
+	}
+
+	if (p->killed)
+		goto failed;
+
+	if (fd_wait(p, READ_FD|NO_TIMEOUT) != 1)
+		goto failed;
+
+	if (p->killed)
+		goto failed;
+
+	c = read(p->fd_in, buf, sizeof(buf));
+	if (c <= 0)
+		goto failed;
+
+	if (c != 9) {
+		mp_msg(MSGT_OPEN, MSGL_ERR,
+				"[ssh] error while reading server version: %s (%i/9 bytes)\n",
+				strerror(errno), c);
+		goto failed;
+	}
+
+	/* alloc a temp buffer */
+	filename_len = strlen(p->filename);
+	tmp = malloc(filename_len+64);
+	if (!tmp) {
+      mp_msg(MSGT_OPEN, MSGL_ERR,
+				"[ssh] unable to alloc temporary buffer\n");
+		ssh_close(stream);
+      return STREAM_ERROR;
+	}
+
+	if (p->killed)
+		goto failed;
+
+	p->handle = p->buffer = tmp;
+	/* initialize request id */
+	p->id = 1;
+
+   /* open remote file */
+	iov.iov_base = buf;
+	iov.iov_len  = sizeof(buf);
+
+	c = send_cmd(p, SSH_FXP_OPEN, SSH_FXP_HANDLE,
+				p->filename, strlen(p->filename),
+				(char *)msg_open, sizeof(msg_open)-1,
+				&iov, 1,
+				&remaining);
+	if (c <= 9)
+		goto failed;
+	
+	len = unaligned_get_uint32(buf+9);
+	if (!len) {
+      mp_msg(MSGT_OPEN, MSGL_ERR,
+				"[ssh] unable to open remote file: no handle\n");
+		goto failed;
+	}
+	if ((remaining > 0) || (len != (c-13))) {
+      mp_msg(MSGT_OPEN, MSGL_ERR,
+				"[ssh] unable to open remote file: handle too big\n");
+		goto failed;
+	}
+
+	/* free temp buffer, we known handle size */
+	free(p->handle);
+	p->handle = p->buffer = NULL;
+
+	/* alloc a buffer big enough for handle + read request */
+	p->handle     = malloc(32+(2*len));
+	if (!p->handle) {
+      mp_msg(MSGT_OPEN, MSGL_ERR,
+				"[ssh] unable to allocate requests buffer (%lu bytes)\n",
+				32+(2*len));
+		goto failed;
+	}
+	memcpy(p->handle, buf+13, len);
+	p->handle_len = len;
+	p->buffer     = p->handle + len;
+
+	/* get remote file size */
+	if (get_size_attr(stream, MSGT_OPEN)) {
+      mp_msg(MSGT_OPEN, MSGL_ERR, "[ssh] unable to get remote file's size\n");
+		goto failed;
+	}
+
+	if (stream->end_pos > 0) {
+		/* we don't want to catch SIGINT anymore */
+		/* FIXME: restore with sigaction ? */
+		signal(SIGINT, SIG_DFL);
+		return STREAM_OK;
+	}
+
+	mp_msg(MSGT_OPEN, MSGL_ERR, "[ssh] remote file is empty\n");
+
+failed:
+   signal(SIGINT, SIG_DFL);
+	ssh_close(stream);
+	return STREAM_ERROR;
+}
+
+/* }}} */
+
+stream_info_t stream_info_ssh = {
+  "Secure Shell File Transfer Protocol",
+  "ssh",
+  "Nicolas Collignon",
+  "SSH SFTP support using OpenSSH ssh client",
+  (int (*)(stream_t *, int, void *, int *)) ssh_open,
+  { "ssh", NULL },
+  &stream_opts,
+  1 // Urls are an option string
+};
+
+/* vim: ts=3 sw=3 fdm=marker
+ */
+
Index: stream/Makefile
===================================================================
--- stream/Makefile	(r?vision 19984)
+++ stream/Makefile	(copie de travail)
@@ -49,6 +49,9 @@
 ifeq ($(VSTREAM),yes)
 SRCS += stream_vstream.c
 endif
+ifeq ($(SSH),yes)
+SRCS += stream_ssh.c
+endif
 
 # TV in
 ifeq ($(TV),yes)
Index: stream/stream.c
===================================================================
--- stream/stream.c	(r?vision 19984)
+++ stream/stream.c	(copie de travail)
@@ -87,6 +87,9 @@
 #ifdef HAVE_DVD
 extern stream_info_t stream_info_dvd;
 #endif
+/*#ifdef HAVE_SSH*/
+extern stream_info_t stream_info_ssh;
+/*#endif*/
 
 stream_info_t* auto_open_streams[] = {
 #ifdef HAVE_VCD
@@ -137,6 +140,9 @@
 #ifdef USE_DVDNAV
   &stream_info_dvdnav,
 #endif
+#ifdef HAVE_SSH
+  &stream_info_ssh,
+#endif
 
   &stream_info_null,
   &stream_info_mf,
Index: stream/stream.h
===================================================================
--- stream/stream.h	(r?vision 19984)
+++ stream/stream.h	(copie de travail)
@@ -26,7 +26,7 @@
 #define STREAMTYPE_MF 18
 #define STREAMTYPE_RADIO 19
 
-#define STREAM_BUFFER_SIZE 2048
+#define STREAM_BUFFER_SIZE (2048*16)
 
 #define VCD_SECTOR_SIZE 2352
 #define VCD_SECTOR_OFFS 24
Index: configure
===================================================================
--- configure	(r?vision 19984)
+++ configure	(copie de travail)
@@ -247,6 +247,7 @@
   --disable-gethostbyname2  gethostbyname() function is not provided by the C
                             library [autodetect]
   --disable-ftp          Disable ftp support [enabled]
+  --disable-ssh          Disable ssh+sftp support using OpenSSH [autodetect]
   --disable-vstream      Disable tivo vstream client support [autodetect]
   --disable-pthreads     Disable Posix threads support [autodetect]
   --disable-ass          Disable internal SSA/ASS subtitles support [autodetect]
@@ -1730,6 +1731,7 @@
 _inet6=auto
 _gethostbyname2=auto
 _ftp=yes
+_ssh=auto
 _musepack=auto
 _vstream=auto
 _pthreads=auto
@@ -2039,6 +2041,8 @@
   --disable-unrarlib)	_unrarlib=no	;;
   --enable-ftp)         _ftp=yes        ;;
   --disable-ftp)        _ftp=no         ;;
+  --enable-ssh)         _ssh=yes        ;;
+  --disable-ssh)        _ssh=no         ;;
   --enable-vstream)     _vstream=yes    ;;
   --disable-vstream)    _vstream=no     ;;
   --enable-pthreads)    _pthreads=yes   ;;
@@ -6834,6 +6838,18 @@
 fi
 echores "$_ftp"
 
+echocheck "OpenSSH client"
+if ssh -v 2>&1 | grep OpenSSH >/dev/null ; then 
+  _ssh=yes
+  _def_ssh='#define HAVE_SSH 1'
+  _inputmodules="ssh $_inputmodules"
+else
+  _ssh=no
+  _def_ssh='#under HAVE_SSH'
+  _noinputmodules="ssh $_noinputmodules"
+fi
+echores "$_ssh"
+
 echocheck "vstream client"
 if test "$_vstream" = auto ; then
   _vstream=no
@@ -7399,6 +7415,7 @@
 
 MPLAYER_NETWORK = $_network
 FTP = $_ftp
+SSH = $_ssh
 STREAMING_LIVE555 = $_live
 VSTREAM = $_vstream
 MPLAYER_NETWORK_LIB = $_ld_live $_ld_vstream $_ld_network
@@ -8204,6 +8221,9 @@
 /* enable ftp support */
 $_def_ftp
 
+/* enable OpenSSH support */
+$_def_ssh
+
 /* enable vstream support */
 $_def_vstream
 


More information about the MPlayer-dev-eng mailing list