@@ -239,7 +239,7 @@ sftp-server$(EXEEXT): $(LIBCOMPAT) libssh.a $(SFTPSERVER_OBJS)
$(LD) -o $@ $(SFTPSERVER_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)

sftp$(EXEEXT): $(LIBCOMPAT) libssh.a $(SFTP_OBJS)
$(LD) -o $@ $(SFTP_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(LIBEDIT)
$(LD) -o $@ $(SFTP_OBJS) $(LDFLAGS) -lssh -lopenbsd-compat -lm -lpthread $(LIBS) $(LIBEDIT)

# test driver for the loginrec code - not built by default
logintest: logintest.o $(LIBCOMPAT) libssh.a loginrec.o
@@ -657,7 +657,7 @@ regress-unit-binaries: regress-prep $(REGRESSLIBS) \
regress/unittests/utf8/test_utf8$(EXEEXT) \
regress/misc/kexfuzz/kexfuzz$(EXEEXT)

tests: file-tests t-exec interop-tests unit
tests: t-docker file-tests t-exec interop-tests unit
echo all tests passed

unit: regress-unit-binaries
@@ -668,6 +668,13 @@ unit: regress-unit-binaries
.CURDIR="`pwd`" \
$@ && echo $@ tests passed

t-docker:
BUILDDIR=`pwd`; \
cd $(srcdir)/regress || exit $$?; \
$(MAKE) \
.OBJDIR="$${BUILDDIR}/regress" \
$@ && echo $@ tests passed

interop-tests t-exec file-tests: regress-prep regress-binaries $(TARGETS)
BUILDDIR=`pwd`; \
TEST_SSH_SCP="$${BUILDDIR}/scp"; \
@@ -19,6 +19,12 @@ The official documentation for OpenSSH are the man pages for each tool:
* [ssh-keyscan(8)](https://man.openbsd.org/ssh-keyscan.8)
* [sftp-server(8)](https://man.openbsd.org/sftp-server.8)

This version of sftp has an additional option:

Flag | Meaning
--- | ---
``-n extra_channels`` | Sets the number of extra ssh channels used by get and put. Defaults to 0. Maximum value is 63. If set to a value > 0, get and put will be made by these extra channels in parallel. If destination resolves to multiple addresses, ssh connections are dispatched between these addresses.

## Stable Releases

Stable release tarballs are available from a number of [download mirrors](https://www.openssh.com/portable.html#downloads). We recommend the use of a stable release for most users. Please read the [release notes](https://www.openssh.com/releasenotes.html) for details of recent changes and potential incompatibilities.
@@ -36,6 +36,7 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>

#include "progressmeter.h"
#include "atomicio.h"
@@ -47,6 +48,7 @@
#define PADDING 1 /* padding between the progress indicators */
#define UPDATE_INTERVAL 1 /* update the progress meter every second */
#define STALL_TIME 5 /* we're stalled after this many seconds */
#define MAX_CHANNELS 64 /* maximum number of parallel channels */

/* determines whether we can output to the terminal */
static int can_output(void);
@@ -68,7 +70,7 @@ static const char *file; /* name of the file being transferred */
static off_t start_pos; /* initial position of transfer */
static off_t end_pos; /* ending position of transfer */
static off_t cur_pos; /* transfer position as of last refresh */
static volatile off_t *counter; /* progress counter */
static volatile off_t *counter[MAX_CHANNELS]; /* progress counter */
static long stalled; /* how long we have been stalled */
static int bytes_per_second; /* current speed in bytes per second */
static int win_size; /* terminal window size */
@@ -78,6 +80,11 @@ static volatile sig_atomic_t alarm_fired;
/* units for format_size */
static const char unit[] = " KMGT";

int progress_started = -1; /* number of progresses started in parallel */
static off_t total_done; /* sum of the size of transferred chunks */
int progress_channels[MAX_CHANNELS]; /* state of each progress channel */
pthread_mutex_t progress_mutex; /* mutex used to protect progress start/stop */

static int
can_output(void)
{
@@ -127,6 +134,7 @@ refresh_progress_meter(int force_update)
int cur_speed;
int hours, minutes, seconds;
int file_len;
int i;

if ((!force_update && !alarm_fired && !win_resized) || !can_output())
return;
@@ -137,8 +145,14 @@ refresh_progress_meter(int force_update)
win_resized = 0;
}

transferred = *counter - (cur_pos ? cur_pos : start_pos);
cur_pos = *counter;
transferred = total_done - (cur_pos ? cur_pos : start_pos);
cur_pos = total_done;
for (i = 0; i <= progress_started; i++) {
if (progress_channels[i]) {
transferred += *counter[i];
cur_pos += *counter[i];
}
}
now = monotime_double();
bytes_left = end_pos - cur_pos;

@@ -238,28 +252,52 @@ sig_alarm(int ignore)
}

void
start_progress_meter(const char *f, off_t filesize, off_t *ctr)
start_progress_meter(const char *f, off_t filesize, off_t *ctr, int channel)
{
start = last_update = monotime_double();
file = f;
start_pos = *ctr;
end_pos = filesize;
cur_pos = 0;
counter = ctr;
stalled = 0;
bytes_per_second = 0;

setscreensize();
refresh_progress_meter(1);

ssh_signal(SIGALRM, sig_alarm);
ssh_signal(SIGWINCH, sig_winch);
alarm(UPDATE_INTERVAL);
int i;

pthread_mutex_lock(&progress_mutex);
if (progress_started != -1) {
if (channel > progress_started)
progress_started = channel;
free((void *) file);
file = strdup(f);
end_pos += filesize;
counter[channel] = ctr;
progress_channels[channel] = 1;
} else {
for (i = 0; i < MAX_CHANNELS; i++) {
progress_channels[i] = 0;
}
progress_started = channel;
total_done = 0;
file = strdup(f);
start = last_update = monotime_double();
start_pos = *ctr;
end_pos = filesize;
cur_pos = 0;
counter[channel] = ctr;
progress_channels[channel] = 1;
stalled = 0;
bytes_per_second = 0;

setscreensize();
refresh_progress_meter(1);

ssh_signal(SIGALRM, sig_alarm);
ssh_signal(SIGWINCH, sig_winch);
alarm(UPDATE_INTERVAL);
}
pthread_mutex_unlock(&progress_mutex);
}

void
stop_progress_meter(void)
stop_progress_meter(int channel, int extra_channels)
{
pthread_mutex_lock(&progress_mutex);
total_done += *counter[channel];
progress_channels[channel] = 0;

alarm(0);

if (!can_output())
@@ -269,7 +307,27 @@ stop_progress_meter(void)
if (cur_pos != end_pos)
refresh_progress_meter(1);

atomicio(vwrite, STDOUT_FILENO, "\n", 1);
if (channel == 0 && extra_channels == 0) {
atomicio(vwrite, STDOUT_FILENO, "\n", 1);
progress_started = -1;
free((void *) file);
}
pthread_mutex_unlock(&progress_mutex);
}

void
real_stop_progress_meter(void)
{
if (progress_started != -1) {
alarm(0);

if (!can_output())
return;

atomicio(vwrite, STDOUT_FILENO, "\n", 1);
progress_started = -1;
free((void *) file);
}
}

/*ARGSUSED*/
@@ -23,6 +23,7 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

void start_progress_meter(const char *, off_t, off_t *);
void start_progress_meter(const char *, off_t, off_t *, int);
void refresh_progress_meter(int);
void stop_progress_meter(void);
void stop_progress_meter(int, int);
void real_stop_progress_meter(void);
@@ -259,3 +259,8 @@ unit:
$$V ${.OBJDIR}/unittests/utf8/test_utf8 ; \
fi \
fi

t-docker:
cd ${.OBJDIR}/docker || exit $$?; \
echo running docker tests ; \
bash run.sh
@@ -0,0 +1,95 @@
Functional Tests
================

How is it working?
------------------

The functional tests are run with +docker-compose+. The +run.sh+ script is a
wrapper to automatically start the tests (e.g. when using +make test+ or +make
t-docker+).

Every container is created with the same +Dockerfile+ found in the
+centos-image+ directory. All scripts started in the containers are located in
the +centos-image+ directory.

The following containers will be created:

tester::
where all tests are started. It executes two +bash+ programs. One for
compiling sftp, the other for testing sftp (cf. below).

server{1,2}::
servers with local and shared directories. They all execute an SSH
server.

The tests that run on tester can be found in the +centos-image/test.sh+ file.

How to debug the tests?
-----------------------

To debug the tests, we can modify the definition of the tester container in
+docker-compose.yaml+:

------------------------------------------------------------------------------
diff --git a/regress/docker/docker-compose.yaml b/regress/docker/docker-compose.yaml
index 65ae927..0621932 100644
--- a/regress/docker/docker-compose.yaml
+++ b/regress/docker/docker-compose.yaml
@@ -5,15 +5,15 @@ services:
- ../../../openssh-portable:/openssh-portable
networks:
- test
- command: ["/root/openssh.sh"]
+ command: ["/usr/sbin/sshd", "-De"]
server1:
container_name: server1
------------------------------------------------------------------------------

Then we start (and build if necessary) all containers:

$ docker-compose up --build

Once every container is started, we can connect to the tester container:

$ docker-compose exec tester /bin/bash
[root@tester /]#

We then use the +bash openssh.sh+ command to build sftp, and +bash test.sh+ to
run the tests:

------------------------------------------------------------------------------
[centos@tester ~]$ bash test.sh
===== Creating test tree =====
===== NFS host=server1 extra_channels=0 =====
sftp> put -r dir /share
Entering dir/
Entering dir/b
Entering dir/a
Entering dir/a/2
Entering dir/a/1
sftp> get -r /share/dir dir2
Retrieving /share/dir
Retrieving /share/dir/b
Retrieving /share/dir/a
Retrieving /share/dir/a/2
Retrieving /share/dir/a/1
Elapsed time: 20.27
===== NFS host=server1 extra_channels=2 =====
sftp> put -r dir /share
Entering dir/
Entering dir/b
Entering dir/a
Entering dir/a/2
Entering dir/a/1
sftp> get -r /share/dir dir2
Retrieving /share/dir
Retrieving /share/dir/b
Retrieving /share/dir/a
Retrieving /share/dir/a/2
Retrieving /share/dir/a/1
Elapsed time: 13.78
[...]
------------------------------------------------------------------------------

// vim:tw=78:ft=asciidoc:
@@ -0,0 +1,26 @@
FROM centos:7

# Install development environment to compile sftp
RUN set -ex \
&& yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \
&& yum -y update \
&& yum -y install autoconf gcc git make openssh-server openssl-devel pam-devel time zlib-devel

# Copy ssh keys to root
RUN set -ex && install -d -m0700 /root/.ssh
COPY ./ssh/id_ed25519.pub /root/.ssh/authorized_keys
COPY ./ssh/id_ed25519* ./ssh/known_hosts /root/.ssh/
RUN chmod 0600 /root/.ssh/authorized_keys /root/.ssh/id_ed25519

# Copy sshd keys
COPY ./ssh/ssh_config /etc/ssh/
RUN chmod 0644 /etc/ssh/ssh_config
COPY ./ssh/ssh_host_ed25519_key* /etc/ssh/
RUN chmod 0600 /etc/ssh/ssh_host_ed25519_key

# Copy sshd configurations
COPY ./ssh/sshd_config /etc/ssh/

# Copy entrypoint for tester
COPY ./openssh.sh /root/
COPY ./test.sh /root/
@@ -0,0 +1,14 @@
#!/bin/bash

set -eux

# Compile sftp
rm -rf /tmp/openssh-portable
git clone /openssh-portable /tmp/openssh-portable
cd /tmp/openssh-portable
autoreconf
./configure --prefix=/usr --sysconfdir=/etc/ssh --with-pam --with-kerberos5
make sftp

# Run tests
/root/test.sh