[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