54 Commits
v0.2.0 ... main

Author SHA1 Message Date
Ryo Nakamura
1313853d7d adjust mscp.rst for v0.2.4 2025-11-08 19:32:30 +09:00
Ryo Nakamura
e621035002 bump version to 0.2.4 2025-11-08 19:25:08 +09:00
Ryo Nakamura
ef8994d21e cache auth_bit_mask after the first none auth attempt
This fixes #36, password auth does not occur.
2025-11-08 18:50:44 +09:00
Ryo Nakamura
81d46a1cd1 test: add test_passwordauth_without_pubkey
It reproduces #36
2025-11-08 18:50:44 +09:00
Ryo Nakamura
07366397a8 test: use fixture to cleanup copying and copied files 2025-11-08 18:50:44 +09:00
Ryo Nakamura
f12d10a988 use size_t for bitrate (for portability) 2025-11-08 18:50:44 +09:00
Ryo Nakamura
afe2182456 gitignore: add .cache 2025-11-08 18:50:44 +09:00
Ryo Nakamura
c4d8e64fab remove unnecessary code lines 2025-11-08 18:50:44 +09:00
Ryo Nakamura
049600a39f adjust the timing of verbose printing of the bw limit value 2025-11-08 18:50:44 +09:00
Ryo Nakamura
fa15e9093d Merge pull request #39 from spikeyspik/main
Fix: fallback to default terminal size on no tty
2025-11-08 18:48:58 +09:00
Ryo Nakamura
5bca31b75d Merge pull request #38 from Oink70/main
Support compiling on Debian and Devuan
2025-11-08 16:48:43 +09:00
spikeyspik
f65cedb4de Fix: fallback to default terminal size on no tty 2025-10-24 21:31:12 +03:00
Oink70
8bad10e42b Add Devuan to script 2025-10-23 10:24:11 +00:00
Oink70
8de47933bf Add Debian to script 2025-10-23 10:02:49 +00:00
Ryo Nakamura
440f25b3a5 adjust mscp.rst for v0.2.3 2025-08-12 19:34:31 +09:00
Ryo Nakamura
cf9672f933 bump version to 0.2.3 2025-08-12 19:34:31 +09:00
Ryo Nakamura
27ef4a127a try pubkey auth first instead of noauth
The libssh auth document suggets to call ssh_userauth_none() first to
obtain userauth list. However, it can lead PerSourcePenalties. Thus, try
pubkey auth first and try password and interactive auths next.
2025-08-12 19:34:31 +09:00
Ryo Nakamura
1eea34ff39 alpine: disable PerSourcePenalties on sshd 2025-08-12 19:33:51 +09:00
Ryo Nakamura
11106d64fe fix the test.yml to drop alpine 3.19 2025-08-12 17:55:41 +09:00
Ryo Nakamura
f5f3323695 remove releasing the single binary version of mscp
The single-binary version has poor performance (due to musl, maybe), as
mentioned in #22 and #25. So, we are going to stop releasing the
single-binary version.
2025-08-12 17:01:33 +09:00
Ryo Nakamura
7ac34ccdde alpine: bump version to 3.22 2025-08-12 16:58:25 +09:00
Ryo Nakamura
18bc88e17c add dockerignore 2025-08-12 16:53:32 +09:00
Ryo Nakamura
f4db06b6bc expand remote paths including '~' (partially)
The current code does not adopt expand-path@openssh.com, thus expanding
paths like `~user` is still not supported.
2025-08-12 16:42:05 +09:00
Ryo Nakamura
504818909f add test cases for remote paths including '~' 2025-08-12 16:42:05 +09:00
Ryo Nakamura
763b47bb47 if dst path has suffix '/', the dst path should be a directory
This fixes https://github.com/upa/mscp/issues/24
2025-08-12 16:42:05 +09:00
Ryo Nakamura
9ab0046b6c add test_dst_has_suffix_slash for #24 2025-08-12 16:42:05 +09:00
Ryo Nakamura
ecee66d03f cmake: add custom targets docker-run-${DOCKER_INDEX} 2025-08-12 16:42:05 +09:00
Ryo Nakamura
a0153414f6 fix: call ssh_init()
It is necessary to call ssh_init() because libssh is statically linked.
2025-08-12 16:42:05 +09:00
Ryo Nakamura
3f8b107d77 adopt new aio read api 2025-08-12 16:42:05 +09:00
Ryo Nakamura
6716a71575 use openssh proxjump due to an aio read and proxjump issue
https://gitlab.com/libssh/libssh-mirror/-/issues/319
2025-08-12 16:42:05 +09:00
Ryo Nakamura
7fdb4a534e fix github actions to obtain libssh version dynamically 2025-08-12 16:42:05 +09:00
Ryo Nakamura
7095c45fc7 bump libssh version to 0.11.2 2025-08-12 16:42:05 +09:00
Ryo Nakamura
58d7d085b0 bump version to 0.2.2 2025-04-16 17:04:19 +09:00
Ryo Nakamura
deda8ca74c set cmake_minimum_required to 3.13 for libssh (#32) 2025-04-16 12:19:13 +09:00
Ryo Nakamura
5fad665c39 add test_quiet_mode 2025-02-27 14:53:27 +09:00
Ryo Nakamura
1b655b61c9 fix -q to redirect stdout to /dev/null (#30) 2025-02-27 14:36:06 +09:00
Ryo Nakamura
c16b981d5d use openssl@3 on macos 2025-01-19 15:29:44 +09:00
Ryo Nakamura
1c787e562f parallelize test on GitHub Actions uing matrix 2025-01-07 16:46:06 +09:00
Ryo Nakamura
248f932a99 add archlinux:base support and test 2025-01-07 16:12:02 +09:00
Ryo Nakamura
1636f2a965 test: set -E log_file option for sshd for debug 2025-01-07 16:04:56 +09:00
Ryo Nakamura
31e011f85c macos: install openssl@3.0 from homebrew
because openssl@1.1 has been disabled since 2024/10/24.
2025-01-07 14:41:49 +09:00
Ryo Nakamura
006bd30424 add install instruction for MacPorts
mscp is available on MacPorts by @lasmarois #23
2024-07-04 13:45:15 +09:00
Ryo Nakamura
60f442689e adjust mscp.rst for v0.2.1 2024-05-11 21:46:18 +09:00
Ryo Nakamura
404f025765 bump version to 0.2.1 2024-05-11 15:38:16 +09:00
Ryo Nakamura
235ba41c5b default chunk size is filesize/(nr_conn*4) (Issue #20)
and clean-up chunk_sz related parts.
2024-04-29 19:36:22 +09:00
Ryo Nakamura
675126a836 manpage: improve descriptions for MaxStartups 2024-04-29 18:27:56 +09:00
Ryo Nakamura
ef2dd55572 man: add available values for -c -M and -g options 2024-04-29 18:08:15 +09:00
Ryo Nakamura
ab6649f29e add available ciphers and hmacs on help print (#20) 2024-04-29 18:03:41 +09:00
Ryo Nakamura
7c5314ea11 change default minimum chunk size to 16MB (Issue #20) 2024-04-26 23:44:46 +09:00
Ryo Nakamura
01fe30efc7 remove limitation that min/max chunk size must be a multiple of page size
Also mentioned by Issue #20.
2024-04-26 23:44:46 +09:00
Ryo Nakamura
61199acc7b support k, m, g for -s, -S, and -b options (Issue #20) 2024-04-26 23:44:46 +09:00
Ryo Nakamura
dd99bc0ac9 connect SSH before starting copy threads in resume transfer
The first ssh connection attempt intends to get ssh password/passphrase
for following ssh connections spawned by copy threads (Issue #17 and #18).
2024-04-26 23:44:17 +09:00
Ryo Nakamura
a5bca0ebe0 resume: fix increment idx only when path is added (#16) 2024-04-15 22:09:25 +09:00
Ryo Nakamura
6373e24753 adjust mscp.rst for v0.2.0 2024-04-15 11:42:56 +09:00
34 changed files with 1305 additions and 317 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
build

View File

@@ -15,13 +15,17 @@ jobs:
with:
submodules: true
- name: apply the patch to libssh
run: |
git -C libssh fetch --all --tags --prune
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
- name: Build in FreeBSD
uses: vmactions/freebsd-vm@v1
with:
prepare: |
pkg install -y git cmake
run: |
patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
build/mscp -h

View File

@@ -22,6 +22,11 @@ jobs:
with:
submodules: true
- name: apply the patch to libssh
run: |
git -C libssh fetch --all --tags --prune
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
- name: install build dependency
run: ./scripts/install-build-deps.sh
@@ -29,13 +34,10 @@ jobs:
id: brew-prefix
run: echo "HOMEBREW_PREFIX=$(brew --prefix)" >> $GITHUB_OUTPUT
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DOPENSSL_ROOT_DIR=${{steps.brew-prefix.outputs.HOMEBREW_PREFIX}}/opt/openssl@1.1
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DOPENSSL_ROOT_DIR=${{steps.brew-prefix.outputs.HOMEBREW_PREFIX}}/opt/openssl@3
- name: Build
# Build your program with the given configuration

View File

@@ -22,14 +22,16 @@ jobs:
with:
submodules: true
- name: apply the patch to libssh
run: |
git -C libssh fetch --all --tags --prune
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
- name: install build dependency
run: |
sudo apt-get update
sudo ./scripts/install-build-deps.sh
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type

View File

@@ -42,14 +42,16 @@ jobs:
with:
submodules: true
- name: apply the patch to libssh
run: |
git -C libssh fetch --all --tags --prune
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
- name: install build dependency
run: |
sudo apt-get update
sudo ./scripts/install-build-deps.sh
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3

View File

@@ -10,32 +10,6 @@ env:
BUILD_TYPE: Release
jobs:
build-and-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
# TODO: just building docker does not require packages. fix CMakeLists
- name: install build dependency
run: sudo ./scripts/install-build-deps.sh
- name: Configure Cmake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build single binary mscp
run: make -C ${{github.workspace}}/build build-single-binary
- name: Release
uses: softprops/action-gh-release@v1
with:
files: |
${{github.workspace}}/build/mscp.linux.x86_64.static
source-release:
runs-on: ubuntu-latest
steps:
@@ -43,8 +17,10 @@ jobs:
with:
submodules: true
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
- name: apply the patch to libssh
run: |
git -C libssh fetch --all --tags --prune
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
- name: Set variables
run: |

View File

@@ -12,15 +12,29 @@ env:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
index: # see DIST_IDS and DIST_VERS lists in CMakeLists.txt
- ubuntu-20.04
- ubuntu-22.04
- ubuntu-24.04
- rocky-8.9
- rocky-9.3
- almalinux-9.3
- alpine-3.22
- arch-base
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
- name: apply the patch to libssh
run: |
git -C libssh fetch --all --tags --prune
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
# TODO: just building docker does not require libssh. fix CMakeLists
# TODO: just building docker images does not require libssh. fix CMakeLists
- name: install build dependency
run: |
sudo apt-get update
@@ -30,7 +44,7 @@ jobs:
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build Containers
run: make -C ${{github.workspace}}/build docker-build-all
run: make -C ${{github.workspace}}/build docker-build-${{ matrix.index }}
- name: Run Test
run: make -C ${{github.workspace}}/build docker-test-all
run: make -C ${{github.workspace}}/build docker-test-${{ matrix.index }}

1
.gitignore vendored
View File

@@ -3,5 +3,6 @@ html
compile_commands.json
CMakeUserPresets.json
.*.swp
.cache
include/mscp_version.h

View File

@@ -165,8 +165,10 @@ enable_testing()
# Custom targets to build and test mscp in docker containers.
# foreach(IN ZIP_LISTS) (cmake >= 3.17) can shorten the following lists.
# However, ubuntu 20.04 has cmake 3.16.3. So this is a roundabout trick.
list(APPEND DIST_IDS ubuntu ubuntu ubuntu rocky rocky almalinux alpine)
list(APPEND DIST_VERS 20.04 22.04 24.04 8.9 9.3 9.3 3.19)
#
# When edit DIST_IDS and DIST_VERS, also edit .github/workflows/test.yaml
list(APPEND DIST_IDS ubuntu ubuntu ubuntu rocky rocky almalinux alpine arch)
list(APPEND DIST_VERS 20.04 22.04 24.04 8.9 9.3 9.3 3.22 base)
list(LENGTH DIST_IDS _DIST_LISTLEN)
math(EXPR DIST_LISTLEN "${_DIST_LISTLEN} - 1")
@@ -206,6 +208,16 @@ foreach(x RANGE ${DIST_LISTLEN})
--add-host=ip6-localhost:::1
${DOCKER_IMAGE} /mscp/scripts/test-in-container.sh)
add_custom_target(docker-run-${DOCKER_INDEX}
COMMENT "Start ${DOCKER_IMAGE} container"
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMAND
${CE} run --init --rm --privileged
--sysctl net.ipv6.conf.all.disable_ipv6=0
--add-host=ip6-localhost:::1
-it
${DOCKER_IMAGE} /mscp/scripts/test-in-container.sh bash)
list(APPEND DOCKER_BUILDS docker-build-${DOCKER_INDEX})
list(APPEND DOCKER_BUILDS_NO_CACHE docker-build-${DOCKER_INDEX}-no-cache)
list(APPEND DOCKER_TESTS docker-test-${DOCKER_INDEX})
@@ -276,9 +288,9 @@ add_custom_target(build-single-binary
COMMENT "Build mscp as a single binary in alpine conatiner"
WORKING_DIRECTORY ${mscp_SOURCE_DIR}
BYPRODUCTS ${CMAKE_BINARY_DIR}/${SINGLEBINARYFILE}
DEPENDS docker-build-alpine-3.19
DEPENDS docker-build-alpine-3.22
COMMAND
${CE} run --rm -v ${CMAKE_BINARY_DIR}:/out mscp-alpine:3.19
${CE} run --rm -v ${CMAKE_BINARY_DIR}:/out mscp-alpine:3.22
cp /mscp/build/mscp /out/${SINGLEBINARYFILE})

View File

@@ -1,4 +1,4 @@
FROM alpine:3.19
FROM alpine:3.22
# do not use REQUIREDPKGS build argument because
# this Dockerfile compiles mscp with conan,so we do not need
@@ -19,6 +19,10 @@ RUN ssh-keygen -A \
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
&& cat /root/.ssh/id_rsa.pub > /root/.ssh/authorized_keys
# disable PerSourcePenaltie, which would distrub test:
# https://undeadly.org/cgi?action=article;sid=20240607042157
RUN echo "PerSourcePenalties=no" > /etc/ssh/sshd_config.d/90-mscp-test.conf
# create test user
RUN addgroup -S test \
&& adduser -S test -G test \

View File

@@ -0,0 +1,36 @@
FROM archlinux:base
ARG REQUIREDPKGS
# install pyest and openssh for test
RUN set -ex && pacman -Syy && pacman --noconfirm -S ${REQUIREDPKGS} openssh python-pytest
RUN mkdir /var/run/sshd \
&& ssh-keygen -A \
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
&& cat /root/.ssh/id_rsa.pub > /root/.ssh/authorized_keys
# disable PerSourcePenaltie, which would distrub test:
# https://undeadly.org/cgi?action=article;sid=20240607042157
RUN echo "PerSourcePenalties=no" > /etc/ssh/sshd_config.d/90-mscp-test.conf
# create test user
RUN useradd -m -d /home/test test \
&& echo "test:userpassword" | chpasswd \
&& mkdir -p /home/test/.ssh \
&& ssh-keygen -f /home/test/.ssh/id_rsa_test -N "keypassphrase" \
&& cat /home/test/.ssh/id_rsa_test.pub >> /home/test/.ssh/authorized_keys \
&& chown -R test:test /home/test \
&& chown -R test:test /home/test/.ssh
ARG mscpdir="/mscp"
COPY . ${mscpdir}
# build
RUN cd ${mscpdir} \
&& rm -rf build \
&& cmake -B build \
&& cd ${mscpdir}/build \
&& make -j 2 \
&& make install

View File

@@ -42,7 +42,11 @@ Paper:
- macOS
```console
# Homebrew
brew install upa/tap/mscp
# MacPorts
sudo port install mscp
```
- Ubuntu
@@ -57,12 +61,6 @@ sudo dnf copr enable upaaa/mscp
sudo dnf install mscp
```
- Single binary `mscp` for x86_64 (not optimal performance)
```console
wget https://github.com/upa/mscp/releases/latest/download/mscp.linux.x86_64.static -O /usr/local/bin/mscp
chmod 755 /usr/local/bin/mscp
```
## Build
@@ -82,7 +80,7 @@ cd mscp
# prepare patched libssh
git submodule update --init
patch -d libssh -p1 < patch/$(git --git-dir=./libssh/.git describe).patch
patch -d libssh -p1 < patch/$(git -C libssh describe).patch
# install build dependency
bash ./scripts/install-build-deps.sh
@@ -92,7 +90,7 @@ mkdir build && cd build
cmake ..
# in macOS, you may need OPENSSL_ROOT_DIR for cmake:
# cmake .. -DOPENSSL_ROOT_DIR=$(brew --prefix)/opt/openssl@1.1
# cmake .. -DOPENSSL_ROOT_DIR=$(brew --prefix)/opt/openssl@3
# build
make

View File

@@ -1 +1 @@
0.2.0
0.2.4

42
debian/changelog vendored
View File

@@ -1,4 +1,44 @@
mscp (0.2.0) UNRELEASED; urgency=medium
mscp (0.2.4) UNRELEASED; urgency=medium
* fix password auth does not work (issue #36)
* add Debian and Devuan to the install dep script (pr #38)
* fallback to default terminal size on no tty (pr #39)
-- Ryo Nakamura <upa@haeena.net> Sat, 08 Nov 2025 19:23:07 +0900
mscp (0.2.3) unstable; urgency=medium
* bump libssh version to 0.11.2 (#25)
* adopt new AIO read API of libssh
* fix path handling when remote dst path has suffix '/' (#24)
* fix remote path handling including '~' (partially)
* try pubkey auth first to avoid PerSourcePenalties
* remove the single-binary version of mscp from release
-- Ryo Nakamura <upa@haeena.net> Tue, 12 Aug 2025 18:11:47 +0900
mscp (0.2.2) unstable; urgency=medium
* bump cmake_minimum_version on libssh (#32)
* fix quiet mode (#30)
* use openssl@3 on macOS (#29)
* add archlinux support (#28)
-- Ryo Nakamura <upa@haeena.net> Wed, 16 Apr 2025 17:01:17 +0900
mscp (0.2.1) unstable; urgency=medium
* fix broken checkpoint files when copying multiple files (#16)
* fix broken password inputs for resume transfers (#17 and #18)
* add support [kKmMgG] units for -s, -S, and -b options (#20)
* change the default min chunk size to 16MB (#20)
* change the default max chunk size to filesize / (nr_conn * 4) (#20)
* -s and -S accept integers other than multiples of page sizes (#20)
* help now shows available ciphers and HMACs (#20)
-- Ryo Nakamura <upa@haeena.net> Sat, 11 May 2024 14:49:52 +0900
mscp (0.2.0) unstable; urgency=medium
* add -J DESTINATION option for ProxyJump (#15)
* add -o SSH_OPTION option

View File

@@ -130,7 +130,8 @@ pinned to any cores.
.TP
.B \-u \fIMAX_STARTUPS\fR
Specifies the number of concurrent outgoing SSH connections.
Specifies the number of concurrent unauthenticated SSH connection
attempts.
.B sshd
limits the number of simultaneous SSH connection attempts by
.I MaxStartups
@@ -192,12 +193,16 @@ and remove the checkpoint if it returns 0.
Specifies the minimum chunk size.
.B mscp
divides a single file into chunks and copies the chunks in
parallel. The default value is 67108864 (64MB).
parallel. The default value is 16M bytes.
.TP
.B \-S \fIMAX_CHUNK_SIZE\fR
Specifies the maximum chunk size. The default is file size divided by
the number of connections.
the number of connections and devided by 4. If the calculated value
is smarller than the
.B MIN_CHUNK_SIZE
value,
MIN_CHUNK_SIZE is used.
.TP
.B \-a \fINR_AHEAD\fR
@@ -289,6 +294,10 @@ connection to the jump host described by
.TP
.B \-c \fICIPHER\fR
Selects the cipher to use for encrypting the data transfer. See
.B mscp -h
or
.B Ciphers
in
.UR https://\:www\:.libssh\:.org/\:features/
libssh features
.UE .
@@ -296,6 +305,10 @@ libssh features
.TP
.B \-M \fIHMAC\fR
Specifies MAC hash algorithms. See
.B mscp -h
or
.B MAC hashes
in
.UR https://\:www\:.libssh\:.org/\:features/
libssh features
.UE .
@@ -311,6 +324,9 @@ libssh features
.TP
.B \-g \fICONGESTION\fR
Specifies the TCP congestion control algorithm to use (Linux only).
See
.B sysctl net.ipv4.tcp_allowed_congestion_control
for available values.
.TP
.B \-p

View File

@@ -2,7 +2,7 @@
MSCP
====
:Date: v0.1.5-18-ge47d5b7
:Date: v0.2.4
NAME
====
@@ -60,10 +60,10 @@ OPTIONS
pinned to any cores.
**-u MAX_STARTUPS**
Specifies the number of concurrent outgoing SSH connections. **sshd**
limits the number of simultaneous SSH connection attempts by
*MaxStartups* in *sshd_config.* The default *MaxStartups* is 10;
thus, we set the default MAX_STARTUPS 8.
Specifies the number of concurrent unauthenticated SSH connection
attempts. **sshd** limits the number of simultaneous SSH connection
attempts by *MaxStartups* in *sshd_config.* The default *MaxStartups*
is 10; thus, we set the default MAX_STARTUPS 8.
**-I INTERVAL**
Specifies the interval (in seconds) between SSH connection attempts.
@@ -97,12 +97,14 @@ OPTIONS
**-s MIN_CHUNK_SIZE**
Specifies the minimum chunk size. **mscp** divides a single file into
chunks and copies the chunks in parallel. The default value is
67108864 (64MB).
chunks and copies the chunks in parallel. The default value is 16M
bytes.
**-S MAX_CHUNK_SIZE**
Specifies the maximum chunk size. The default is file size divided by
the number of connections.
the number of connections and devided by 4. If the calculated value
is smarller than the **MIN_CHUNK_SIZE** value, MIN_CHUNK_SIZE is
used.
**-a NR_AHEAD**
Specifies the number of inflight SFTP commands. The default value is
@@ -166,11 +168,12 @@ OPTIONS
**-c CIPHER**
Selects the cipher to use for encrypting the data transfer. See
`libssh features <https://www.libssh.org/features/>`__.
**mscp -h** or **Ciphers** in `libssh
features <https://www.libssh.org/features/>`__.
**-M HMAC**
Specifies MAC hash algorithms. See `libssh
features <https://www.libssh.org/features/>`__.
Specifies MAC hash algorithms. See **mscp -h** or **MAC hashes** in
`libssh features <https://www.libssh.org/features/>`__.
**-C COMPRESS**
Enables compression: yes, no, zlib, zlib@openssh.com. The default is
@@ -178,6 +181,8 @@ OPTIONS
**-g CONGESTION**
Specifies the TCP congestion control algorithm to use (Linux only).
See **sysctl net.ipv4.tcp_allowed_congestion_control** for available
values.
**-p**
Preserves modification times and access times (file mode bits are

View File

@@ -28,6 +28,7 @@
#include <stdbool.h>
#include <limits.h>
#include <stdlib.h>
#define MSCP_DIRECTION_L2R 1 /** Indicates local to remote copy */
#define MSCP_DIRECTION_R2L 2 /** Indicates remote to local copy */
@@ -294,4 +295,15 @@ enum {
};
/**
* @brief Return available ciphers.
*/
const char **mscp_ssh_ciphers(void);
/**
* @brief Return available hmacs.
*/
const char **mscp_ssh_hmacs(void);
#endif /* _MSCP_H_ */

2
libssh

Submodule libssh updated: 6f1b1e76bb...dff6c0821e

View File

@@ -1,5 +1,6 @@
Patches in this directory introduces `sftp_async_write()` and
`sftp_async_write_end()` to libssh. Those implementations are derived
from https://github.com/limes-datentechnik-gmbh/libssh. See [Re: SFTP
Write async](https://archive.libssh.org/libssh/2020-06/0000004.html).
Patches in this directory introduce enhancements for libssh including
`sftp_async_write()` and `sftp_async_write_end()`, derived from
https://github.com/limes-datentechnik-gmbh/libssh. See [Re: SFTP Write
async](https://archive.libssh.org/libssh/2020-06/0000004.html).

View File

@@ -1,3 +1,13 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a64b7708..c6344a5a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.3.0)
+cmake_minimum_required(VERSION 3.13.0)
cmake_policy(SET CMP0048 NEW)
# Specify search path for CMake modules to be loaded by include()
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index 9de10225..0f3d20ed 100644
--- a/ConfigureChecks.cmake
@@ -37,7 +47,7 @@ index 1fce7b76..b64d1455 100644
int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len);
diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h
index 669a0a96..da5b4099 100644
index 669a0a96..26b20f3f 100644
--- a/include/libssh/libssh.h
+++ b/include/libssh/libssh.h
@@ -368,6 +368,7 @@ enum ssh_options_e {
@@ -64,11 +74,14 @@ index 669a0a96..da5b4099 100644
LIBSSH_API void ssh_buffer_free(ssh_buffer buffer);
#define SSH_BUFFER_FREE(x) \
do { if ((x) != NULL) { ssh_buffer_free(x); x = NULL; } } while(0)
@@ -843,6 +846,8 @@ LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer);
@@ -843,6 +846,11 @@ LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer);
LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer);
LIBSSH_API int ssh_session_set_disconnect_message(ssh_session session, const char *message);
+typedef ssize_t (*ssh_add_func) (void *ptr, size_t max_bytes, void *userdata);
+
+LIBSSH_API const char **ssh_ciphers(void);
+LIBSSH_API const char **ssh_hmacs(void);
+
#ifndef LIBSSH_LEGACY_0_4
#include "libssh/legacy.h"
@@ -299,6 +312,60 @@ index 15cae644..02ef43b4 100644
errno = 0;
rc = connect(s, itr->ai_addr, itr->ai_addrlen);
if (rc == -1 && (errno != 0) && (errno != EINPROGRESS)) {
diff --git a/src/misc.c b/src/misc.c
index 7081f12a..e3879fe4 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -71,6 +71,8 @@
#include "libssh/priv.h"
#include "libssh/misc.h"
#include "libssh/session.h"
+#include "libssh/wrapper.h"
+#include "libssh/crypto.h"
#ifdef HAVE_LIBGCRYPT
#define GCRYPT_STRING "/gnutls"
@@ -2074,4 +2076,40 @@ int ssh_check_hostname_syntax(const char *hostname)
return SSH_OK;
}
+/**
+ * @brief Return supported cipher names
+ * @return The list of cipher names.
+ */
+const char **ssh_ciphers(void)
+{
+ struct ssh_cipher_struct *tab=ssh_get_ciphertab();
+ static const char *ciphers[32];
+ int n;
+
+ memset(ciphers, 0, sizeof(*ciphers));
+
+ for (n = 0; tab[n].name != NULL; n++) {
+ ciphers[n] = tab[n].name;
+ }
+ return ciphers;
+}
+
+/**
+ * @brief Return supported hmac names
+ * @return The list of hmac names.
+ */
+const char **ssh_hmacs(void)
+{
+ struct ssh_hmac_struct *tab=ssh_get_hmactab();
+ static const char *hmacs[32];
+ int n;
+
+ memset(hmacs, 0, sizeof(*hmacs));
+
+ for (n = 0; tab[n].name != NULL; n++) {
+ hmacs[n] = tab[n].name;
+ }
+ return hmacs;
+}
+
/** @} */
diff --git a/src/options.c b/src/options.c
index b3ecffe1..8de24ed6 100644
--- a/src/options.c
@@ -392,10 +459,10 @@ index 8c509699..307388e5 100644
session->opts.flags = SSH_OPT_FLAG_PASSWORD_AUTH |
SSH_OPT_FLAG_PUBKEY_AUTH |
diff --git a/src/sftp.c b/src/sftp.c
index e01012a8..3b86c3c6 100644
index e01012a8..702623a0 100644
--- a/src/sftp.c
+++ b/src/sftp.c
@@ -2228,6 +2228,135 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) {
@@ -2228,6 +2228,132 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) {
return -1; /* not reached */
}
@@ -434,8 +501,7 @@ index e01012a8..3b86c3c6 100644
+
+ buffer = ssh_buffer_new_size(buf_sz, HEADROOM);
+ if (buffer == NULL) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "ssh_buffer_new_size failed: Out of Memory");
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
@@ -449,16 +515,14 @@ index e01012a8..3b86c3c6 100644
+ count); /* len of datastring */
+
+ if (rc != SSH_OK){
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "ssh_buffer_pack failed: Out of Memory");
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return SSH_ERROR;
+ }
+
+ actual = ssh_buffer_add_func(buffer, f, count, userdata);
+ if (actual < 0){
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "ssh_buffer_add_func failed: %s", strerror(errno));
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return SSH_ERROR;
+ }

605
patch/libssh-0.11.2.patch Normal file
View File

@@ -0,0 +1,605 @@
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index 8765dc6e..766e7d16 100644
--- a/ConfigureChecks.cmake
+++ b/ConfigureChecks.cmake
@@ -209,6 +209,7 @@ if (UNIX)
check_library_exists(util forkpty "" HAVE_LIBUTIL)
check_function_exists(cfmakeraw HAVE_CFMAKERAW)
check_function_exists(__strtoull HAVE___STRTOULL)
+ check_symbol_exists(TCP_CONGESTION "netinet/tcp.h" HAVE_TCP_CONGESTION)
endif (UNIX)
set(LIBSSH_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} CACHE INTERNAL "libssh required system libraries")
diff --git a/config.h.cmake b/config.h.cmake
index 8dce5273..ef534762 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -219,6 +219,8 @@
#cmakedefine HAVE_GCC_BOUNDED_ATTRIBUTE 1
+#cmakedefine HAVE_TCP_CONGESTION 1
+
/* Define to 1 if you want to enable GSSAPI */
#cmakedefine WITH_GSSAPI 1
diff --git a/include/libssh/buffer.h b/include/libssh/buffer.h
index d22178e7..2d6aa0a7 100644
--- a/include/libssh/buffer.h
+++ b/include/libssh/buffer.h
@@ -37,6 +37,8 @@ int ssh_buffer_add_u8(ssh_buffer buffer, uint8_t data);
int ssh_buffer_add_u16(ssh_buffer buffer, uint16_t data);
int ssh_buffer_add_u32(ssh_buffer buffer, uint32_t data);
int ssh_buffer_add_u64(ssh_buffer buffer, uint64_t data);
+ssize_t ssh_buffer_add_func(ssh_buffer buffer, ssh_add_func f, size_t max_bytes,
+ void *userdata);
int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len);
diff --git a/include/libssh/libssh.h b/include/libssh/libssh.h
index 3bddb019..1d5d7761 100644
--- a/include/libssh/libssh.h
+++ b/include/libssh/libssh.h
@@ -373,6 +373,7 @@ enum ssh_options_e {
SSH_OPTIONS_HOST,
SSH_OPTIONS_PORT,
SSH_OPTIONS_PORT_STR,
+ SSH_OPTIONS_AI_FAMILY,
SSH_OPTIONS_FD,
SSH_OPTIONS_USER,
SSH_OPTIONS_SSH_DIR,
@@ -407,6 +408,7 @@ enum ssh_options_e {
SSH_OPTIONS_GSSAPI_AUTH,
SSH_OPTIONS_GLOBAL_KNOWNHOSTS,
SSH_OPTIONS_NODELAY,
+ SSH_OPTIONS_CCALGO,
SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES,
SSH_OPTIONS_PROCESS_CONFIG,
SSH_OPTIONS_REKEY_DATA,
@@ -876,6 +878,7 @@ LIBSSH_API const char* ssh_get_hmac_in(ssh_session session);
LIBSSH_API const char* ssh_get_hmac_out(ssh_session session);
LIBSSH_API ssh_buffer ssh_buffer_new(void);
+LIBSSH_API ssh_buffer ssh_buffer_new_size(uint32_t size, uint32_t headroom);
LIBSSH_API void ssh_buffer_free(ssh_buffer buffer);
#define SSH_BUFFER_FREE(x) \
do { if ((x) != NULL) { ssh_buffer_free(x); x = NULL; } } while(0)
@@ -886,6 +889,12 @@ LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer);
LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer);
LIBSSH_API int ssh_session_set_disconnect_message(ssh_session session, const char *message);
+typedef ssize_t (*ssh_add_func) (void *ptr, size_t max_bytes, void *userdata);
+
+LIBSSH_API const char **ssh_ciphers(void);
+LIBSSH_API const char **ssh_hmacs(void);
+LIBSSH_API void ssh_use_openssh_proxy_jumps(int);
+
#ifndef LIBSSH_LEGACY_0_4
#include "libssh/legacy.h"
#endif
diff --git a/include/libssh/session.h b/include/libssh/session.h
index aed94072..327cf4fe 100644
--- a/include/libssh/session.h
+++ b/include/libssh/session.h
@@ -255,6 +255,7 @@ struct ssh_session_struct {
unsigned long timeout; /* seconds */
unsigned long timeout_usec;
uint16_t port;
+ int ai_family;
socket_t fd;
int StrictHostKeyChecking;
char compressionlevel;
@@ -264,6 +265,7 @@ struct ssh_session_struct {
int flags;
int exp_flags;
int nodelay;
+ char *ccalgo;
bool config_processed;
uint8_t options_seen[SOC_MAX];
uint64_t rekey_data;
diff --git a/include/libssh/sftp.h b/include/libssh/sftp.h
index cf4458c3..1a864795 100644
--- a/include/libssh/sftp.h
+++ b/include/libssh/sftp.h
@@ -569,6 +569,10 @@ SSH_DEPRECATED LIBSSH_API int sftp_async_read(sftp_file file,
uint32_t len,
uint32_t id);
+LIBSSH_API ssize_t sftp_async_write(sftp_file file, ssh_add_func f, size_t count,
+ void *userdata, uint32_t* id);
+LIBSSH_API int sftp_async_write_end(sftp_file file, uint32_t id, int blocking);
+
/**
* @brief Write to a file using an opened sftp file handle.
*
diff --git a/src/buffer.c b/src/buffer.c
index 449fa941..f49e8af6 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -142,6 +142,40 @@ struct ssh_buffer_struct *ssh_buffer_new(void)
return buf;
}
+/**
+ * @brief Create a new SSH buffer with a specified size and headroom.
+ *
+ * @param[in] len length for newly initialized SSH buffer.
+ * @param[in] headroom length for headroom
+ * @return A newly initialized SSH buffer, NULL on error.
+ */
+struct ssh_buffer_struct *ssh_buffer_new_size(uint32_t len, uint32_t headroom)
+{
+ struct ssh_buffer_struct *buf = NULL;
+ int rc;
+
+ if (len < headroom)
+ return NULL;
+
+ buf = calloc(1, sizeof(struct ssh_buffer_struct));
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ rc = ssh_buffer_allocate_size(buf, len);
+ if (rc != 0) {
+ SAFE_FREE(buf);
+ return NULL;
+ }
+
+ buf->pos += headroom;
+ buf->used += headroom;
+
+ buffer_verify(buf);
+
+ return buf;
+}
+
/**
* @brief Deallocate a SSH buffer.
*
@@ -329,6 +363,49 @@ int ssh_buffer_add_data(struct ssh_buffer_struct *buffer, const void *data, uint
return 0;
}
+/**
+ * @brief Add data at the tail of a buffer by an external function
+ *
+ * @param[in] buffer The buffer to add data.
+ *
+ * @param[in] f function that adds data to the buffer.
+ *
+ * @param[in] max_bytes The maximum length of the data to add.
+ *
+ * @return actual bytes added on success, < 0 on error.
+ */
+ssize_t ssh_buffer_add_func(struct ssh_buffer_struct *buffer, ssh_add_func f,
+ size_t max_bytes, void *userdata)
+{
+ ssize_t actual;
+
+ if (buffer == NULL) {
+ return -1;
+ }
+
+ buffer_verify(buffer);
+
+ if (buffer->used + max_bytes < max_bytes) {
+ return -1;
+ }
+
+ if (buffer->allocated < (buffer->used + max_bytes)) {
+ if (buffer->pos > 0) {
+ buffer_shift(buffer);
+ }
+ if (realloc_buffer(buffer, buffer->used + max_bytes) < 0) {
+ return -1;
+ }
+ }
+
+ if ((actual = f(buffer->data + buffer->used, max_bytes, userdata)) < 0)
+ return -1;
+
+ buffer->used += actual;
+ buffer_verify(buffer);
+ return actual;
+}
+
/**
* @brief Ensure the buffer has at least a certain preallocated size.
*
diff --git a/src/connect.c b/src/connect.c
index 2cb64037..51f4c87e 100644
--- a/src/connect.c
+++ b/src/connect.c
@@ -109,7 +109,7 @@ static int ssh_connect_socket_close(socket_t s)
#endif
}
-static int getai(const char *host, int port, struct addrinfo **ai)
+static int getai(const char *host, int port, int ai_family, struct addrinfo **ai)
{
const char *service = NULL;
struct addrinfo hints;
@@ -118,7 +118,7 @@ static int getai(const char *host, int port, struct addrinfo **ai)
ZERO_STRUCT(hints);
hints.ai_protocol = IPPROTO_TCP;
- hints.ai_family = PF_UNSPEC;
+ hints.ai_family = ai_family > 0 ? ai_family : PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if (port == 0) {
@@ -151,6 +151,20 @@ static int set_tcp_nodelay(socket_t socket)
sizeof(opt));
}
+static int set_tcp_ccalgo(socket_t socket, const char *ccalgo)
+{
+#ifdef HAVE_TCP_CONGESTION
+ return setsockopt(socket,
+ IPPROTO_TCP,
+ TCP_CONGESTION,
+ (void *)ccalgo,
+ strlen(ccalgo));
+#else
+ errno = ENOTSUP;
+ return -1;
+#endif
+}
+
/**
* @internal
*
@@ -168,7 +182,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
struct addrinfo *ai = NULL;
struct addrinfo *itr = NULL;
- rc = getai(host, port, &ai);
+ rc = getai(host, port, session->opts.ai_family, &ai);
if (rc != 0) {
ssh_set_error(session, SSH_FATAL,
"Failed to resolve hostname %s (%s)",
@@ -194,7 +208,7 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
SSH_LOG(SSH_LOG_PACKET, "Resolving %s", bind_addr);
- rc = getai(bind_addr, 0, &bind_ai);
+ rc = getai(bind_addr, 0, session->opts.ai_family, &bind_ai);
if (rc != 0) {
ssh_set_error(session, SSH_FATAL,
"Failed to resolve bind address %s (%s)",
@@ -251,6 +265,18 @@ socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host,
}
}
+ if (session->opts.ccalgo) {
+ rc = set_tcp_ccalgo(s, session->opts.ccalgo);
+ if (rc < 0) {
+ ssh_set_error(session, SSH_FATAL,
+ "Failed to set TCP_CONGESTION on socket: %s",
+ ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
+ ssh_connect_socket_close(s);
+ s = -1;
+ continue;
+ }
+ }
+
errno = 0;
rc = connect(s, itr->ai_addr, itr->ai_addrlen);
if (rc == -1) {
diff --git a/src/misc.c b/src/misc.c
index 774211fb..74e57959 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -71,6 +71,8 @@
#include "libssh/priv.h"
#include "libssh/misc.h"
#include "libssh/session.h"
+#include "libssh/wrapper.h"
+#include "libssh/crypto.h"
#ifdef HAVE_LIBGCRYPT
#define GCRYPT_STRING "/gcrypt"
@@ -2054,6 +2056,42 @@ ssize_t ssh_readn(int fd, void *buf, size_t nbytes)
return total_bytes_read;
}
+/**
+ * @brief Return supported cipher names
+ * @return The list of cipher names.
+ */
+const char **ssh_ciphers(void)
+{
+ struct ssh_cipher_struct *tab=ssh_get_ciphertab();
+ static const char *ciphers[32];
+ int n;
+
+ memset(ciphers, 0, sizeof(*ciphers));
+
+ for (n = 0; tab[n].name != NULL; n++) {
+ ciphers[n] = tab[n].name;
+ }
+ return ciphers;
+}
+
+/**
+ * @brief Return supported hmac names
+ * @return The list of hmac names.
+ */
+const char **ssh_hmacs(void)
+{
+ struct ssh_hmac_struct *tab=ssh_get_hmactab();
+ static const char *hmacs[32];
+ int n;
+
+ memset(hmacs, 0, sizeof(*hmacs));
+
+ for (n = 0; tab[n].name != NULL; n++) {
+ hmacs[n] = tab[n].name;
+ }
+ return hmacs;
+}
+
/**
* @brief Write the requested number of bytes to a local file.
*
@@ -2227,6 +2265,17 @@ ssh_proxyjumps_free(struct ssh_list *proxy_jump_list)
}
}
+static bool force_openssh_proxy_jumps;
+
+/**
+ * @breif set use openssh proxy jumps without the OPENSSH_PROXYJUMP env var
+ */
+void
+ssh_use_openssh_proxy_jumps(int v)
+{
+ force_openssh_proxy_jumps = (v > 0);
+}
+
/**
* @brief Check if libssh proxy jumps is enabled
*
@@ -2241,7 +2290,12 @@ ssh_libssh_proxy_jumps(void)
{
const char *t = getenv("OPENSSH_PROXYJUMP");
+ if (force_openssh_proxy_jumps)
+ return false;
+
return !(t != NULL && t[0] == '1');
}
+
+
/** @} */
diff --git a/src/options.c b/src/options.c
index 785296dd..a82d4d81 100644
--- a/src/options.c
+++ b/src/options.c
@@ -251,6 +251,7 @@ int ssh_options_copy(ssh_session src, ssh_session *dest)
new->opts.gss_delegate_creds = src->opts.gss_delegate_creds;
new->opts.flags = src->opts.flags;
new->opts.nodelay = src->opts.nodelay;
+ new->opts.ccalgo = src->opts.ccalgo;
new->opts.config_processed = src->opts.config_processed;
new->opts.control_master = src->opts.control_master;
new->common.log_verbosity = src->common.log_verbosity;
@@ -326,6 +327,9 @@ int ssh_options_set_algo(ssh_session session,
* - SSH_OPTIONS_PORT_STR:
* The port to connect to (const char *).
*
+ * - SSH_OPTIONS_AI_FAMILY:
+ * The address family for connecting (int *).
+ *
* - SSH_OPTIONS_FD:
* The file descriptor to use (socket_t).\n
* \n
@@ -571,6 +575,10 @@ int ssh_options_set_algo(ssh_session session,
* Set it to disable Nagle's Algorithm (TCP_NODELAY) on the
* session socket. (int, 0=false)
*
+ * - SSH_OPTIONS_CCALGO
+ * Set it to specify TCP congestion control algorithm on the
+ * session socket (Linux only). (int, 0=false)
+ *
* - SSH_OPTIONS_PROCESS_CONFIG
* Set it to false to disable automatic processing of per-user
* and system-wide OpenSSH configuration files. LibSSH
@@ -727,6 +735,21 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
session->opts.port = i & 0xffffU;
}
break;
+ case SSH_OPTIONS_AI_FAMILY:
+ if (value == NULL) {
+ session->opts.ai_family = 0;
+ ssh_set_error_invalid(session);
+ return -1;
+ } else {
+ int *x = (int *) value;
+ if (*x < 0) {
+ session->opts.ai_family = 0;
+ ssh_set_error_invalid(session);
+ return -1;
+ }
+ session->opts.ai_family = *x;
+ }
+ break;
case SSH_OPTIONS_FD:
if (value == NULL) {
session->opts.fd = SSH_INVALID_SOCKET;
@@ -1241,6 +1264,20 @@ int ssh_options_set(ssh_session session, enum ssh_options_e type,
session->opts.nodelay = (*x & 0xff) > 0 ? 1 : 0;
}
break;
+ case SSH_OPTIONS_CCALGO:
+ v = value;
+ if (v == NULL || v[0] == '\0') {
+ ssh_set_error_invalid(session);
+ return -1;
+ } else {
+ SAFE_FREE(session->opts.ccalgo);
+ session->opts.ccalgo = strdup(v);
+ if (session->opts.ccalgo == NULL) {
+ ssh_set_error_oom(session);
+ return -1;
+ }
+ }
+ break;
case SSH_OPTIONS_PROCESS_CONFIG:
if (value == NULL) {
ssh_set_error_invalid(session);
diff --git a/src/session.c b/src/session.c
index 9fd5d946..ed9f908e 100644
--- a/src/session.c
+++ b/src/session.c
@@ -107,9 +107,11 @@ ssh_session ssh_new(void)
/* OPTIONS */
session->opts.StrictHostKeyChecking = 1;
session->opts.port = 22;
+ session->opts.ai_family = 0;
session->opts.fd = -1;
session->opts.compressionlevel = 7;
session->opts.nodelay = 0;
+ session->opts.ccalgo = NULL;
session->opts.identities_only = false;
session->opts.control_master = SSH_CONTROL_MASTER_NO;
diff --git a/src/sftp.c b/src/sftp.c
index 37b4133b..12b6d296 100644
--- a/src/sftp.c
+++ b/src/sftp.c
@@ -1488,6 +1488,132 @@ ssize_t sftp_write(sftp_file file, const void *buf, size_t count) {
return -1; /* not reached */
}
+/*
+ * sftp_async_write is based on and sftp_async_write_end is copied from
+ * https://github.com/limes-datentechnik-gmbh/libssh
+ *
+ * sftp_async_write has some optimizations:
+ * - use ssh_buffer_new_size() to reduce realoc_buffer.
+ * - use ssh_buffer_add_func() to avoid memcpy from read buffer to ssh buffer.
+ */
+ssize_t sftp_async_write(sftp_file file, ssh_add_func f, size_t count, void *userdata,
+ uint32_t* id) {
+ sftp_session sftp = file->sftp;
+ ssh_buffer buffer;
+ uint32_t buf_sz;
+ ssize_t actual;
+ int len;
+ int packetlen;
+ int rc;
+
+#define HEADROOM 16
+ /* sftp_packet_write() prepends a 5-bytes (uint32_t length and
+ * 1-byte type) header to the head of the payload by
+ * ssh_buffer_prepend_data(). Inserting headroom by
+ * ssh_buffer_new_size() eliminates memcpy for prepending the
+ * header.
+ */
+
+ buf_sz = (HEADROOM + /* for header */
+ sizeof(uint32_t) + /* id */
+ ssh_string_len(file->handle) + 4 + /* file->handle */
+ sizeof(uint64_t) + /* file->offset */
+ sizeof(uint32_t) + /* count */
+ count); /* datastring */
+
+ buffer = ssh_buffer_new_size(buf_sz, HEADROOM);
+ if (buffer == NULL) {
+ ssh_set_error_oom(sftp->session);
+ return -1;
+ }
+
+ *id = sftp_get_new_id(file->sftp);
+
+ rc = ssh_buffer_pack(buffer,
+ "dSqd",
+ *id,
+ file->handle,
+ file->offset,
+ count); /* len of datastring */
+
+ if (rc != SSH_OK){
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return SSH_ERROR;
+ }
+
+ actual = ssh_buffer_add_func(buffer, f, count, userdata);
+ if (actual < 0){
+ ssh_set_error_oom(sftp->session);
+ ssh_buffer_free(buffer);
+ return SSH_ERROR;
+ }
+
+ packetlen=ssh_buffer_get_len(buffer)+5;
+ len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer);
+ ssh_buffer_free(buffer);
+ if (len < 0) {
+ return SSH_ERROR;
+ } else if (len != packetlen) {
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Could only send %d of %d bytes to remote host!", len, packetlen);
+ SSH_LOG(SSH_LOG_PACKET,
+ "Could not write as much data as expected");
+ return SSH_ERROR;
+ }
+
+ file->offset += actual;
+
+ return actual;
+}
+
+int sftp_async_write_end(sftp_file file, uint32_t id, int blocking) {
+ sftp_session sftp = file->sftp;
+ sftp_message msg = NULL;
+ sftp_status_message status;
+
+ msg = sftp_dequeue(sftp, id);
+ while (msg == NULL) {
+ if (!blocking && ssh_channel_poll(sftp->channel, 0) == 0) {
+ /* we cannot block */
+ return SSH_AGAIN;
+ }
+ if (sftp_read_and_dispatch(sftp) < 0) {
+ /* something nasty has happened */
+ return SSH_ERROR;
+ }
+ msg = sftp_dequeue(sftp, id);
+ }
+
+ switch (msg->packet_type) {
+ case SSH_FXP_STATUS:
+ status = parse_status_msg(msg);
+ sftp_message_free(msg);
+ if (status == NULL) {
+ return SSH_ERROR;
+ }
+ sftp_set_error(sftp, status->status);
+ switch (status->status) {
+ case SSH_FX_OK:
+ status_msg_free(status);
+ return SSH_OK;
+ default:
+ break;
+ }
+ ssh_set_error(sftp->session, SSH_REQUEST_DENIED,
+ "SFTP server: %s", status->errormsg);
+ status_msg_free(status);
+ return SSH_ERROR;
+ default:
+ ssh_set_error(sftp->session, SSH_FATAL,
+ "Received message %d during write!", msg->packet_type);
+ sftp_message_free(msg);
+ return SSH_ERROR;
+ }
+
+ return SSH_ERROR; /* not reached */
+}
+
/* Seek to a specific location in a file. */
int sftp_seek(sftp_file file, uint32_t new_offset) {
if (file == NULL) {

View File

@@ -38,6 +38,18 @@ make -C build install DESTDIR=%{buildroot}
%changelog
* Sat Nov 08 2025 Ryo Nakamura <upa@haeena.net> - 0.2.4-1
- RPM release for v0.2.4
* Tue Aug 12 2025 Ryo Nakamura <upa@haeena.net> - 0.2.3-1
- RPM release for v0.2.3
* Wed Apr 16 2025 Ryo Nakamura <upa@haeena.net> - 0.2.2-1
- RPM release for v0.2.2
* Sat May 11 2024 Ryo Nakamura <upa@haeena.net> - 0.2.1-1
- RPM release for v0.2.1
* Mon Apr 15 2024 Ryo Nakamura <upa@haeena.net> - 0.2.0-1
- RPM release for v0.2.0

View File

@@ -45,9 +45,9 @@ done
case $platform in
Darwin)
cmd="brew install"
pkgs="openssl@1.1"
pkgs="openssl@3"
;;
Linux-ubuntu*)
Linux-ubuntu* | Linux-debian* | Linux-devuan*)
cmd="apt-get install --no-install-recommends -y"
pkgs="gcc make cmake zlib1g-dev libssl-dev libkrb5-dev"
;;
@@ -55,6 +55,10 @@ case $platform in
cmd="yum install -y"
pkgs="gcc make cmake zlib-devel openssl-devel rpm-build"
;;
Linux-arch*)
cmd="pacman --no-confirm -S"
pkgs="gcc make cmake"
;;
FreeBSD-freebsd)
cmd="pkg install"
pkgs="cmake"

View File

@@ -17,7 +17,7 @@ sed -i -e 's/AllowTcpForwarding no/AllowTcpForwarding yes/' /etc/ssh/sshd_config
# Run sshd
if [ ! -e /var/run/sshd.pid ]; then
/usr/sbin/sshd
/usr/sbin/sshd -E /tmp/sshd.log
sleep 1
fi
@@ -28,5 +28,11 @@ for port in 22 8022; do
ssh-keyscan -p $port ::1 >> ${HOME}/.ssh/known_hosts
done
# Run test
python3 -m pytest -v ../test
if [ $# -gt 0 ]; then
# command arguments are passed, exec them
"$@"
else
# no arguments passed, run the test
python3 -m pytest -v ../test
fi

View File

@@ -50,8 +50,6 @@ int bwlimit_init(struct bwlimit *bw, uint64_t bps, uint64_t win)
#define timespeccmp(a, b, expr) \
((a.tv_sec * 1000000000 + a.tv_nsec) expr(b.tv_sec * 1000000000 + b.tv_nsec))
#include <stdio.h>
int bwlimit_wait(struct bwlimit *bw, size_t nr_bytes)
{
struct timespec now, end, rq, rm;

View File

@@ -9,10 +9,10 @@
#include <semaphore.h>
struct bwlimit {
sem_t *sem; /* semaphore */
uint64_t bps; /* limit bit-rate (bps) */
uint64_t win; /* window size (msec) */
size_t amt; /* amount of bytes can be sent in a window */
sem_t *sem; /* semaphore */
size_t bps; /* limit bit-rate (bps) */
size_t win; /* window size (msec) */
size_t amt; /* amount of bytes can be sent in a window */
ssize_t credit; /* remaining bytes can be sent in a window */
struct timespec wstart, wend; /* window start time and end time */

View File

@@ -276,7 +276,7 @@ int checkpoint_save(const char *pathname, int dir, const char *user, const char
pool_for_each(path_pool, p, i) {
if (p->state == FILE_STATE_DONE)
continue;
if (checkpoint_write_path(fd, p, i) < 0)
if (checkpoint_write_path(fd, p, nr_paths) < 0)
return -1;
nr_paths++;
}
@@ -349,7 +349,8 @@ static int checkpoint_load_path(struct checkpoint_obj_hdr *hdr, pool *path_pool)
return -1;
}
pr_info("checkpoint:file: %s -> %s", p->path, p->dst_path);
pr_info("checkpoint:file: idx=%u %s -> %s", ntohl(path->idx),
p->path, p->dst_path);
return 0;
}
@@ -375,7 +376,8 @@ static int checkpoint_load_chunk(struct checkpoint_obj_hdr *hdr, pool *path_pool
return -1;
}
pr_debug("checkpoint:chunk: %s 0x%lx-0x%lx", p->path, c->off, c->off + c->len);
pr_debug("checkpoint:chunk: idx=%u %s 0x%lx-0x%lx", ntohl(chunk->idx),
p->path, c->off, c->off + c->len);
return 0;
}

View File

@@ -101,7 +101,6 @@ MDIR *mscp_opendir_wrapped(const char *path)
void mscp_closedir(MDIR *md)
{
int ret;
if (md->remote)
sftp_closedir(md->remote);
else

View File

@@ -2,6 +2,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <math.h>
@@ -40,14 +41,14 @@ void usage(bool print_help)
printf(" -n NR_CONNECTIONS number of connections "
"(default: floor(log(cores)*2)+1)\n"
" -m COREMASK hex value to specify cores where threads pinned\n"
" -u MAX_STARTUPS number of concurrent SSH connection attempts "
" -u MAX_STARTUPS number of concurrent unauthed SSH attempts "
"(default: 8)\n"
" -I INTERVAL interval between SSH connection attempts (default: 0)\n"
" -W CHECKPOINT write states to the checkpoint if transfer fails\n"
" -R CHECKPOINT resume transferring from the checkpoint\n"
"\n"
" -s MIN_CHUNK_SIZE min chunk size (default: 64MB)\n"
" -S MAX_CHUNK_SIZE max chunk size (default: filesize/nr_conn)\n"
" -s MIN_CHUNK_SIZE min chunk size (default: 16M bytes)\n"
" -S MAX_CHUNK_SIZE max chunk size (default: filesize/nr_conn/4)\n"
" -a NR_AHEAD number of inflight SFTP commands (default: 32)\n"
" -b BUF_SZ buffer size for i/o and transfer\n"
" -L LIMIT_BITRATE Limit the bitrate, n[KMG] (default: 0, no limit)\n"
@@ -75,6 +76,26 @@ void usage(bool print_help)
" -N enable Nagle's algorithm (default disabled)\n"
" -h print this help\n"
"\n");
const char **ciphers = mscp_ssh_ciphers();
const char **hmacs = mscp_ssh_hmacs();
int n;
printf("Available ciphers: ");
for (n = 0; ciphers[n] != NULL; n++) {
printf("%s", ciphers[n]);
if (ciphers[n + 1])
printf(", ");
}
printf("\n\n");
printf("Available hmacs: ");
for (n = 0; hmacs[n] != NULL; n++) {
printf("%s", hmacs[n]);
if (hmacs[n + 1])
printf(", ");
}
printf("\n\n");
}
char *strip_brackets(char *s)
@@ -96,8 +117,6 @@ char *split_user_host_path(const char *s, char **userp, char **hostp, char **pat
return NULL;
}
user = NULL;
host = NULL;
path = tmp;
for (cp = tmp; *cp; cp++) {
if (*cp == '@' && (cp > tmp) && *(cp - 1) != '\\' && user == NULL) {
@@ -184,13 +203,12 @@ struct target *validate_targets(char **arg, int len)
*/
struct target *t, *t0;
int n;
int n, nslash;
if ((t = calloc(len, sizeof(struct target))) == NULL) {
pr_err("calloc: %s", strerrno());
return NULL;
}
memset(t, 0, len * sizeof(struct target));
/* split remote:path into remote and path */
for (n = 0; n < len; n++) {
@@ -202,9 +220,33 @@ struct target *validate_targets(char **arg, int len)
}
}
/* check all user@host are identical. t[len - 1] is destination,
* so we need to check t[0] to t[len - 2] having the identical
* remote notation */
/* expand remote path, e.g., empty dst path and '~' */
for (n = 0; n < len; n++) {
if (!t[n].host)
continue;
/* this target is a remote path. check the path and
* expand it. this part is derived from
* openssh-portal prepare_remote_path() function.
*/
char *path = t[n].path;
if (*path == '\0' || strcmp(path, "~") == 0)
t[n].path = strdup(".");
else if (strncmp(path, "~/", 2) == 0) {
if ((nslash = strspn(path + 2, "/")) == strlen(path + 2))
t[n].path = strdup(".");
else
t[n].path = strdup(path + 2 + nslash);
}
if (!t[n].path) {
pr_err("strdup failed: %s", strerrno());
goto free_target_out;
}
}
/* check all user@host are identical. t[len - 1] is the
* destination, so we need to check t[0] to t[len - 2] having
* the identical remote notation */
t0 = &t[0];
for (n = 1; n < len - 1; n++) {
if (compare_remote(t0, &t[n]) != 0)
@@ -260,19 +302,65 @@ void print_cli(const char *fmt, ...)
void print_stat(bool final);
long atol_with_unit(char *value, bool i)
{
/* value must be "\d+[kKmMgG]?" */
char *u = value + (strlen(value) - 1);
long k = i ? 1024 : 1000;
long factor = 1;
long v;
switch (*u) {
case 'k':
case 'K':
factor = k;
*u = '\0';
break;
case 'm':
case 'M':
factor = k * k;
*u = '\0';
break;
case 'g':
case 'G':
factor = k * k * k;
*u = '\0';
break;
}
v = atol(value);
return v * factor;
}
int to_dev_null(int fd)
{
int nfd = open("/dev/null", O_WRONLY);
if (nfd < 0) {
pr_err("open /dev/null: %s", strerrno());
return -1;
}
if (dup2(nfd, fd) < 0) {
pr_err("dup2: %s", strerrno());
return -1;
}
close(nfd);
return 0;
}
int main(int argc, char **argv)
{
struct mscp_ssh_opts s;
struct mscp_opts o;
struct target *t;
int pipe_fd[2];
int ch, n, i, ret;
int direction = 0;
char *remote = NULL, *checkpoint_save = NULL, *checkpoint_load = NULL;
bool dryrun = false, resume = false;
bool quiet = false, dryrun = false, resume = false;
int nr_options = 0;
size_t factor = 1;
char *unit;
memset(&s, 0, sizeof(s));
memset(&o, 0, sizeof(o));
@@ -305,36 +393,19 @@ int main(int argc, char **argv)
resume = true;
break;
case 's':
o.min_chunk_sz = atoi(optarg);
o.min_chunk_sz = atol_with_unit(optarg, true);
break;
case 'S':
o.max_chunk_sz = atoi(optarg);
o.max_chunk_sz = atol_with_unit(optarg, true);
break;
case 'a':
o.nr_ahead = atoi(optarg);
break;
case 'b':
o.buf_sz = atoi(optarg);
o.buf_sz = atol_with_unit(optarg, true);
break;
case 'L':
factor = 1;
unit = optarg + (strlen(optarg) - 1);
if (*unit == 'k' || *unit == 'K') {
factor = 1000;
*unit = '\0';
} else if (*unit == 'm' || *unit == 'M') {
factor = 1000000;
*unit = '\0';
} else if (*unit == 'g' || *unit == 'G') {
factor = 1000000000;
*unit = '\0';
}
o.bitrate = atol(optarg);
if (o.bitrate == 0) {
pr_err("invalid bitrate: %s", optarg);
return 1;
}
o.bitrate *= factor;
o.bitrate = atol_with_unit(optarg, false);
break;
case '4':
s.ai_family = AF_INET;
@@ -346,7 +417,7 @@ int main(int argc, char **argv)
o.severity++;
break;
case 'q':
o.severity = MSCP_SEVERITY_NONE;
quiet = true;
break;
case 'D':
dryrun = true;
@@ -409,6 +480,9 @@ int main(int argc, char **argv)
}
}
if (quiet)
to_dev_null(STDOUT_FILENO);
s.password = getenv(ENV_SSH_AUTH_PASSWORD);
s.passphrase = getenv(ENV_SSH_AUTH_PASSPHRASE);
@@ -487,6 +561,18 @@ int main(int argc, char **argv)
pr_err("mscp_checkpoint_load: %s", priv_get_err());
return -1;
}
if (dryrun)
goto out;
/* create the first ssh connection to get password or
* passphrase. The sftp session over it will be not
* used for resume transfer in actuality. ToDo:
* connectin managemnet should be improved. */
if (mscp_connect(m) < 0) {
pr_err("mscp_connect: %s", priv_get_err());
return -1;
}
}
if (dryrun) {
@@ -607,8 +693,13 @@ void print_progress_bar(double percent, char *suffix)
buf[0] = '\0';
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0)
return; /* XXX */
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0 || ws.ws_col == 0) {
// fallback to default
ws.ws_col = 80;
ws.ws_row = 24;
}
bar_width = min(sizeof(buf), ws.ws_col) - strlen(suffix) - 7;
memset(buf, 0, sizeof(buf));
@@ -686,8 +777,6 @@ struct xfer_stat x;
void print_stat(bool final)
{
struct mscp_stats s;
char buf[8192];
int timeout;
gettimeofday(&x.after, NULL);
if (calculate_timedelta(&x.before, &x.after) > 1 || final) {
@@ -703,9 +792,6 @@ void print_stat(bool final)
void *print_stat_thread(void *arg)
{
struct mscp_stats s;
char buf[8192];
memset(&x, 0, sizeof(x));
gettimeofday(&x.start, NULL);
x.before = x.start;

View File

@@ -62,7 +62,7 @@ struct mscp {
struct mscp_thread scan; /* mscp_thread for mscp_scan_thread() */
};
#define DEFAULT_MIN_CHUNK_SZ (64 << 20) /* 64MB */
#define DEFAULT_MIN_CHUNK_SZ (16 << 20) /* 16MB */
#define DEFAULT_NR_AHEAD 32
#define DEFAULT_BUF_SZ 16384
/* XXX: we use 16384 byte buffer pointed by
@@ -78,7 +78,7 @@ struct mscp {
static int expand_coremask(const char *coremask, int **cores, int *nr_cores)
{
int n, *core_list, core_list_len = 0, nr_usable, nr_all;
int n, *core_list, nr_usable, nr_all;
char c[2] = { 'x', '\0' };
const char *_coremask;
long v, needle;
@@ -158,23 +158,8 @@ static int validate_and_set_defaut_params(struct mscp_opts *o)
if (o->min_chunk_sz == 0)
o->min_chunk_sz = DEFAULT_MIN_CHUNK_SZ;
else {
if (o->min_chunk_sz < getpagesize() ||
o->min_chunk_sz % getpagesize() != 0) {
priv_set_errv("min chunk size must be "
"larget than and multiple of page size %d: %lu",
getpagesize(), o->min_chunk_sz);
return -1;
}
}
if (o->max_chunk_sz) {
if (o->max_chunk_sz < getpagesize() ||
o->max_chunk_sz % getpagesize() != 0) {
priv_set_errv("min chunk size must be larget than and "
"multiple of page size %d: %lu",
getpagesize(), o->max_chunk_sz);
}
if (o->min_chunk_sz > o->max_chunk_sz) {
priv_set_errv("smaller max chunk size than "
"min chunk size: %lu < %lu",
@@ -288,7 +273,13 @@ struct mscp *mscp_init(struct mscp_opts *o, struct mscp_ssh_opts *s)
priv_set_errv("bwlimit_init: %s", strerrno());
goto free_out;
}
pr_notice("bitrate limit: %lu bps", o->bitrate);
/* workaround: set libssh using openssh proxyjump
* https://gitlab.com/libssh/libssh-mirror/-/issues/319 */
ssh_use_openssh_proxy_jumps(1);
/* call ssh_init() because libssh is statically linked */
ssh_init();
return m;
@@ -337,26 +328,20 @@ int mscp_set_dst_path(struct mscp *m, const char *dst_path)
return -1;
}
if (!non_null_string(dst_path))
strncpy(m->dst_path, ".", 1);
else
strncpy(m->dst_path, dst_path, PATH_MAX);
if (!non_null_string(dst_path)) {
priv_set_errv("empty dst path");
return -1;
}
strncpy(m->dst_path, dst_path, PATH_MAX);
return 0;
}
static int get_page_mask(void)
static size_t get_page_mask(void)
{
long page_sz = sysconf(_SC_PAGESIZE);
size_t page_mask = 0;
int n;
for (n = 0; page_sz > 0; page_sz >>= 1, n++) {
page_mask <<= 1;
page_mask |= 1;
}
return page_mask >> 1;
size_t page_sz = sysconf(_SC_PAGESIZE);
return ~(page_sz - 1);
}
static void mscp_stop_copy_thread(struct mscp *m)
@@ -416,6 +401,9 @@ void *mscp_scan_thread(void *arg)
if (pool_size(m->src_pool) > 1)
a.dst_path_should_dir = true;
if (m->dst_path[strlen(m->dst_path) - 1] == '/')
a.dst_path_should_dir = true;
if (mscp_stat(m->dst_path, &ds, dst_sftp) == 0) {
if (S_ISDIR(ds.st_mode))
a.dst_path_is_dir = true;
@@ -574,6 +562,9 @@ int mscp_start(struct mscp *m)
m->opts->nr_threads = n;
}
pr_notice("threads: %d",m->opts->nr_threads);
pr_notice("bwlimit: %ld bps", m->bw.bps);
for (n = 0; n < m->opts->nr_threads; n++) {
t = mscp_copy_thread_spawn(m, n);
if (!t)

View File

@@ -102,13 +102,10 @@ static int resolve_chunk(struct path *p, size_t size, struct path_resolve_args *
size_t chunk_sz, off, len;
size_t remaind;
if (size <= a->min_chunk_sz)
chunk_sz = size;
else if (a->max_chunk_sz)
if (a->max_chunk_sz)
chunk_sz = a->max_chunk_sz;
else {
chunk_sz = (size - (size % a->nr_conn)) / a->nr_conn;
chunk_sz &= ~a->chunk_align; /* align with page_sz */
chunk_sz = (size / (a->nr_conn * 4)) & a->chunk_align;
if (chunk_sz <= a->min_chunk_sz)
chunk_sz = a->min_chunk_sz;
}
@@ -413,46 +410,45 @@ static int copy_chunk_l2r(struct chunk *c, int fd, sftp_file sf, int nr_ahead, i
return 0;
}
static int copy_chunk_r2l(struct chunk *c, sftp_file sf, int fd, int nr_ahead, int buf_sz,
struct bwlimit *bw, size_t *counter)
static int copy_chunk_r2l(struct chunk *c, sftp_file sf, int fd,
int nr_ahead, int buf_sz,
struct bwlimit *bw, size_t *counter)
{
ssize_t read_bytes, write_bytes, remaind, thrown;
ssize_t read_bytes, write_bytes, remain, thrown, len, requested;
sftp_aio reqs[nr_ahead];
char buf[buf_sz];
int idx;
struct {
int id;
ssize_t len;
} reqs[nr_ahead];
int i;
if (c->len == 0)
return 0;
remaind = thrown = c->len;
remain = thrown = c->len;
for (idx = 0; idx < nr_ahead && thrown > 0; idx++) {
reqs[idx].len = min(thrown, sizeof(buf));
reqs[idx].id = sftp_async_read_begin(sf, reqs[idx].len);
if (reqs[idx].id < 0) {
priv_set_errv("sftp_async_read_begin: %d",
for (i = 0; i < nr_ahead && thrown > 0; i++) {
len = min(thrown, sizeof(buf));
requested = sftp_aio_begin_read(sf, len, &reqs[i]);
if (requested == SSH_ERROR) {
priv_set_errv("sftp_aio_begin_read: %d",
sftp_get_error(sf->sftp));
return -1;
}
thrown -= reqs[idx].len;
bwlimit_wait(bw, reqs[idx].len);
thrown -= requested;
bwlimit_wait(bw, requested);
}
for (idx = 0; remaind > 0; idx = (idx + 1) % nr_ahead) {
read_bytes = sftp_async_read(sf, buf, reqs[idx].len, reqs[idx].id);
for (i = 0; remain > 0; i = (i + 1) % nr_ahead) {
read_bytes = sftp_aio_wait_read(&reqs[i], buf, sizeof(buf));
if (read_bytes == SSH_ERROR) {
priv_set_errv("sftp_async_read: %d", sftp_get_error(sf->sftp));
priv_set_errv("sftp_aio_wait_read: %d",
sftp_get_error(sf->sftp));
return -1;
}
if (thrown > 0) {
reqs[idx].len = min(thrown, sizeof(buf));
reqs[idx].id = sftp_async_read_begin(sf, reqs[idx].len);
thrown -= reqs[idx].len;
bwlimit_wait(bw, reqs[idx].len);
len = min(thrown, sizeof(buf));
requested = sftp_aio_begin_read(sf, len, &reqs[i]);
thrown -= requested;
bwlimit_wait(bw, requested);
}
write_bytes = write(fd, buf, read_bytes);
@@ -467,13 +463,13 @@ static int copy_chunk_r2l(struct chunk *c, sftp_file sf, int fd, int nr_ahead, i
}
*counter += write_bytes;
remaind -= read_bytes;
remain -= write_bytes;
}
if (remaind < 0) {
priv_set_errv("invalid remaind bytes %ld. last async_read bytes %ld. "
if (remain < 0) {
priv_set_errv("invalid remain bytes %ld. last async_read bytes %ld. "
"last write bytes %ld",
remaind, read_bytes, write_bytes);
remain, read_bytes, write_bytes);
return -1;
}

View File

@@ -9,7 +9,8 @@ void set_print_severity(int serverity)
{
if (serverity < 0)
__print_severity = -1; /* no print */
__print_severity = serverity;
else
__print_severity = serverity;
}
int get_print_severity()

View File

@@ -114,27 +114,25 @@ static int ssh_set_opts(ssh_session ssh, struct mscp_ssh_opts *opts)
static int ssh_authenticate(ssh_session ssh, struct mscp_ssh_opts *opts)
{
int auth_bit_mask;
static int auth_bit_mask;
int ret;
/* none method */
ret = ssh_userauth_none(ssh, NULL);
if (ret == SSH_AUTH_SUCCESS)
return 0;
if (auth_bit_mask == 0) {
/* the first authentication attempt. try none auth to
* get available auth methods. */
if (ssh_userauth_none(ssh, NULL) == SSH_AUTH_SUCCESS)
return 0;
auth_bit_mask = ssh_userauth_list(ssh, NULL);
if (auth_bit_mask & SSH_AUTH_METHOD_NONE &&
ssh_userauth_none(ssh, NULL) == SSH_AUTH_SUCCESS)
return 0;
/* save auth_bit_mask for further authentications */
auth_bit_mask = ssh_userauth_list(ssh, NULL);
}
auth_bit_mask = ssh_userauth_list(ssh, NULL);
if (auth_bit_mask & SSH_AUTH_METHOD_PUBLICKEY) {
char *p = opts->passphrase ? opts->passphrase : NULL;
if (ssh_userauth_publickey_auto(ssh, NULL, p) == SSH_AUTH_SUCCESS)
return 0;
}
char *p = opts->passphrase ? opts->passphrase : NULL;
if (ssh_userauth_publickey_auto(ssh, NULL, p) == SSH_AUTH_SUCCESS)
return 0;
}
auth_bit_mask = ssh_userauth_list(ssh, NULL);
if (auth_bit_mask & SSH_AUTH_METHOD_PASSWORD) {
if (!opts->password) {
char buf[128] = {};
@@ -407,3 +405,13 @@ void ssh_sftp_close(sftp_session sftp)
ssh_disconnect(ssh);
ssh_free(ssh);
}
const char **mscp_ssh_ciphers(void)
{
return ssh_ciphers();
}
const char **mscp_ssh_hmacs(void)
{
return ssh_hmacs();
}

View File

@@ -9,26 +9,68 @@ import getpass
import datetime
import time
import os
import re
import shutil
from subprocess import check_call, CalledProcessError
from util import File, check_same_md5sum
def run2ok(args, env = None):
def run2ok(args, env = None, quiet = False):
cmd = list(map(str, args))
print("cmd: {}".format(" ".join(cmd)))
if not quiet:
print("cmd: {}".format(" ".join(cmd)))
check_call(cmd, env = env)
def run2ng(args, env = None, timeout = None):
def run2ng(args, env = None, timeout = None, quiet = False):
if timeout:
args = ["timeout", "-s", "INT", timeout] + args
cmd = list(map(str, args))
print("cmd: {}".format(" ".join(cmd)))
with pytest.raises(CalledProcessError) as e:
if not quiet:
print("cmd: {}".format(" ".join(cmd)))
with pytest.raises(CalledProcessError):
check_call(cmd, env = env)
@pytest.fixture(autouse=True)
def cleanup_files():
"""
Cleanup files having the following `prefixes` or matching `paths`.
"""
yield
prefixes = [
"src", "dst",
"non_existent_dstdir",
]
paths = [
"/mscp-test-src", "/tmp/mscp-test-src",
"{}/src".format(os.environ["HOME"]),
"{}/dst".format(os.environ["HOME"]),
"/tmp/mscp_test_ssh_config",
"/home/test/dst",
"/home/test/src",
"checkpoint",
]
def remove(path):
print(f"cleanup remove: {fname}")
if os.path.isdir(path):
shutil.rmtree(path)
else:
os.remove(path)
for fname in os.listdir(os.getcwd()):
for prefix in prefixes:
if fname.startswith(prefix):
remove(fname)
break
for path in paths:
if os.path.exists(path):
remove(path)
""" usage test """
@@ -70,8 +112,7 @@ def test_single_copy(mscp, src_prefix, dst_prefix, src, dst):
src.make()
run2ok([mscp, "-vvv", src_prefix + src.path, dst_prefix + dst.path])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_failed_to_copy_nonexistent_file(mscp, src_prefix, dst_prefix):
@@ -92,10 +133,6 @@ def test_double_copy(mscp, src_prefix, dst_prefix, s1, s2, d1, d2):
run2ok([mscp, "-vvv", src_prefix + s1.path, src_prefix + s2.path, dst_prefix + "dst"])
assert check_same_md5sum(s1, d1)
assert check_same_md5sum(s2, d2)
s1.cleanup()
s2.cleanup()
d1.cleanup()
d2.cleanup()
remote_v6_prefix = "[::1]:{}/".format(os.getcwd())
@@ -111,10 +148,6 @@ def test_double_copy_with_ipv6_notation(mscp, src_prefix, dst_prefix, s1, s2, d1
src_prefix + s1.path, src_prefix + s2.path, dst_prefix + "dst"])
assert check_same_md5sum(s1, d1)
assert check_same_md5sum(s2, d2)
s1.cleanup()
s2.cleanup()
d1.cleanup()
d2.cleanup()
remote_user_v6_prefix = "{}@[::1]:{}/".format(getpass.getuser(), os.getcwd())
@@ -131,11 +164,6 @@ def test_double_copy_with_user_and_ipv6_notation(mscp, src_prefix, dst_prefix,
src_prefix + s1.path, src_prefix + s2.path, dst_prefix + "dst"])
assert check_same_md5sum(s1, d1)
assert check_same_md5sum(s2, d2)
s1.cleanup()
s2.cleanup()
d1.cleanup()
d2.cleanup()
param_dir_copy = [
@@ -175,10 +203,6 @@ def test_dir_copy(mscp, src_prefix, dst_prefix, src_dir, dst_dir, src, dst, twic
for sf, df in zip(src, twice):
assert check_same_md5sum(sf, df)
for sf, df, tf in zip(src, dst, twice):
sf.cleanup()
df.cleanup()
tf.cleanup()
param_dir_copy_single = [
@@ -194,8 +218,7 @@ def test_dir_copy_single(mscp, src_prefix, dst_prefix, src_dir, dst_dir, src, ds
os.mkdir(dst_dir)
run2ok([mscp, "-vvv", src_prefix + src_dir, dst_prefix + dst_dir])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_override_single_file(mscp, src_prefix, dst_prefix):
@@ -206,8 +229,6 @@ def test_override_single_file(mscp, src_prefix, dst_prefix):
run2ok([mscp, "-vvv", src_prefix + src.path, dst_prefix + dst.path])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
absolute_remote_prefix = "localhost:"
param_absolute_remote_prefix = [
@@ -221,8 +242,55 @@ def test_copy_file_under_root_to_dir(mscp, src_prefix, dst_prefix):
run2ok([mscp, "-vvv", src_prefix + src.path,
dst_prefix + os.path.dirname(dst.path)])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup(preserve_dir = True)
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_dst_has_suffix_slash(mscp, src_prefix, dst_prefix):
"""
if dst path has suffix '/' like "dir/" and does not exist,
mscp should create dir/ and put dir/src-file-name.
"""
dstdir = "non_existent_dstdir/"
src = File("src", size = 1024 * 1024).make()
dst = File(f"{dstdir}/src")
run2ok([mscp, "-vvv", src_prefix + src.path,
dst_prefix + dstdir])
assert check_same_md5sum(src, dst)
param_tilde_paths = [
("src", "localhost:~/dst"),
("localhost:~/src", "dst"),
]
@pytest.mark.parametrize("src_path, dst_path", param_tilde_paths)
def test_remote_path_contains_tilde(mscp, src_path, dst_path):
"""
if remote path contains '~' as prefix, it should be expanded as '.'.
Note that `~user` notation is not supported yet.
"""
def extract_and_expand(path):
path = path if not ':' in path else path[path.index(':')+1:]
return path.replace('~', os.environ["HOME"])
src_f_path = extract_and_expand(src_path)
dst_f_path = extract_and_expand(dst_path)
src = File(src_f_path, size = 1024 * 1024).make()
dst = File(dst_f_path)
run2ok([mscp, "-vvv", src_path, dst_path])
assert check_same_md5sum(src, dst)
def test_remote_path_contains_tilde2(mscp):
src = File("src", size = 1024 * 1024).make()
dst = File(f"{os.environ['HOME']}/src")
run2ok([mscp, "-vvv", src.path, f"localhost:~"])
assert check_same_md5sum(src, dst)
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
@@ -233,9 +301,6 @@ def test_min_chunk(mscp, src_prefix, dst_prefix):
run2ok([mscp, "-vvv", "-s", 32768, src_prefix + src.path, dst_prefix + dst.path])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
def is_alpine():
if os.path.exists("/etc/os-release"):
@@ -270,8 +335,7 @@ def test_glob_src_path(mscp, src_prefix, dst_prefix,
run2ok([mscp, "-vvv", src_prefix + src_glob_path, dst_prefix + dst_path])
for src, dst in zip(srcs, dsts):
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_thread_affinity(mscp, src_prefix, dst_prefix):
@@ -282,8 +346,6 @@ def test_thread_affinity(mscp, src_prefix, dst_prefix):
src_prefix + src.path, dst_prefix + dst.path])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_cannot_override_file_with_dir(mscp, src_prefix, dst_prefix):
@@ -292,8 +354,6 @@ def test_cannot_override_file_with_dir(mscp, src_prefix, dst_prefix):
run2ng([mscp, "-vvv", src_prefix + src.path, dst_prefix + "dst/src"])
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_transfer_zero_bytes(mscp, src_prefix, dst_prefix):
@@ -301,8 +361,6 @@ def test_transfer_zero_bytes(mscp, src_prefix, dst_prefix):
dst = File("dst")
run2ok([mscp, "-vvv", src_prefix + src.path, dst_prefix + "dst"])
assert os.path.exists("dst")
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_override_dst_having_larger_size(mscp, src_prefix, dst_prefix):
@@ -310,8 +368,6 @@ def test_override_dst_having_larger_size(mscp, src_prefix, dst_prefix):
dst = File("dst", size = 1024 * 1024 * 2).make()
run2ok([mscp, "-vvv", src_prefix + src.path, dst_prefix + "dst"])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_dont_truncate_dst(mscp, src_prefix, dst_prefix):
@@ -320,7 +376,7 @@ def test_dont_truncate_dst(mscp, src_prefix, dst_prefix):
run2ok([mscp, "-vvv", src_prefix + f.path, dst_prefix + f.path])
md5_after = f.md5sum()
assert md5_before == md5_after
f.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_copy_readonly_file(mscp, src_prefix, dst_prefix):
@@ -334,8 +390,6 @@ def test_copy_readonly_file(mscp, src_prefix, dst_prefix):
dst = File("dst")
run2ok([mscp, "-vvv", src_prefix + src.path, dst_prefix + dst.path])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_dont_make_conns_more_than_chunks(mscp, src_prefix, dst_prefix):
@@ -352,8 +406,7 @@ def test_dont_make_conns_more_than_chunks(mscp, src_prefix, dst_prefix):
end = time.time()
for s, d in zip(srcs, dsts):
assert check_same_md5sum(s, d)
shutil.rmtree("src")
shutil.rmtree("dst")
assert((end - start) < 10)
@@ -367,8 +420,6 @@ def test_bwlimit(mscp, src_prefix, dst_prefix):
run2ok([mscp, "-vvv", "-L", "100m", src_prefix + "src", dst_prefix + "dst"])
end = datetime.datetime.now().timestamp()
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
assert end - start > 7
@@ -377,14 +428,13 @@ def test_bwlimit(mscp, src_prefix, dst_prefix):
def test_set_port_ng(mscp, src_prefix, dst_prefix, src, dst):
src.make()
run2ng([mscp, "-vvv", "-P", 21, src_prefix + src.path, dst_prefix + dst.path])
src.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
@pytest.mark.parametrize("src, dst", param_single_copy)
def test_set_port_ok(mscp, src_prefix, dst_prefix, src, dst):
src.make()
run2ok([mscp, "-vvv", "-P", 8022, src_prefix + src.path, dst_prefix + dst.path])
src.cleanup()
def test_v4only(mscp):
src = File("src", size = 1024).make()
@@ -392,8 +442,6 @@ def test_v4only(mscp):
dst_prefix = "localhost:{}/".format(os.getcwd())
run2ok([mscp, "-vvv", "-4", src.path, dst_prefix + dst.path])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
def test_v6only(mscp):
src = File("src", size = 1024).make()
@@ -401,22 +449,29 @@ def test_v6only(mscp):
dst_prefix = "ip6-localhost:{}/".format(os.getcwd())
run2ok([mscp, "-vvv", "-6", src.path, dst_prefix + dst.path])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
def test_v4_to_v6_should_fail(mscp):
src = File("src", size = 1024).make()
dst = File("dst")
dst_prefix = "[::1]:{}/".format(os.getcwd())
run2ng([mscp, "-vvv", "-4", src.path, dst_prefix + dst.path])
src.cleanup()
def test_v6_to_v4_should_fail(mscp):
src = File("src", size = 1024).make()
dst = File("dst")
dst_prefix = "127.0.0.1:{}/".format(os.getcwd())
run2ng([mscp, "-vvv", "-6", src.path, dst_prefix + dst.path])
src.cleanup()
def test_quiet_mode(capsys, mscp):
src = File("src", size = 1024).make()
dst = File("dst")
dst_prefix = "127.0.0.1:{}/".format(os.getcwd())
run2ok([mscp, "-vvv", "-q", src.path, dst_prefix + dst.path], quiet=True)
assert check_same_md5sum(src, dst)
captured = capsys.readouterr()
assert not captured.out
assert not captured.err
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_set_conn_interval(mscp, src_prefix, dst_prefix):
@@ -429,8 +484,6 @@ def test_set_conn_interval(mscp, src_prefix, dst_prefix):
for src, dst in zip(srcs, dsts):
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
compressions = ["yes", "no", "none"]
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
@@ -440,8 +493,6 @@ def test_compression(mscp, src_prefix, dst_prefix, compress):
dst = File("dst", size = 1024 * 1024 * 2).make()
run2ok([mscp, "-vvv", "-C", compress, src_prefix + src.path, dst_prefix + "dst"])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_ccalgo(mscp, src_prefix, dst_prefix):
@@ -478,8 +529,7 @@ def test_config_ok(mscp, src_prefix, dst_prefix):
os.remove(config)
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_testhost_prefix)
def test_config_ng(mscp, src_prefix, dst_prefix):
@@ -493,8 +543,6 @@ def test_config_ng(mscp, src_prefix, dst_prefix):
src_prefix + src.path, dst_prefix + "dst"])
os.remove(config)
src.cleanup()
dst.cleanup()
param_valid_option_ok = [
@@ -511,8 +559,6 @@ def test_inline_option_ok(mscp, src_prefix, dst_prefix, option):
run2ok([mscp, "-vvv"] + option +
[src_prefix + src.path, dst_prefix + dst.path])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
param_valid_option_ng = [
@@ -527,7 +573,6 @@ def test_inline_option_ng(mscp, src_prefix, dst_prefix, option):
dst = File("dst")
run2ng([mscp, "-vvv"] + option +
[src_prefix + src.path, dst_prefix + dst.path])
src.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
@@ -540,8 +585,6 @@ def test_porxyjump_ok(mscp, src_prefix, dst_prefix):
"-J", "localhost:8022",
src_prefix + src.path, dst_prefix + dst.path])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
@@ -553,7 +596,6 @@ def test_porxyjump_ng(mscp, src_prefix, dst_prefix):
run2ng([mscp, "-n", 4, "-s", 1024 * 1024, "-vvv",
"-J", "invaliduser@localhost:8022",
src_prefix + src.path, dst_prefix + dst.path])
src.cleanup()
# username test assumes that this test runs inside a container, see Dockerfiles
def test_specify_passphrase_via_env(mscp):
@@ -564,8 +606,6 @@ def test_specify_passphrase_via_env(mscp):
run2ok([mscp, "-vvv", "-l", "test", "-i", "/home/test/.ssh/id_rsa_test",
src.path, "localhost:" + dst.path], env = env)
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
def test_specify_invalid_passphrase_via_env(mscp):
src = File(os.getcwd() + "/src", size = 1024).make()
@@ -574,7 +614,6 @@ def test_specify_invalid_passphrase_via_env(mscp):
env["MSCP_SSH_AUTH_PASSPHRASE"] = "invalid-keypassphrase"
run2ng([mscp, "-vvv", "-l", "test", "-i", "/home/test/.ssh/id_rsa_test",
src.path, "localhost:" + dst.path], env = env)
src.cleanup()
def test_specify_password_via_env(mscp):
src = File(os.getcwd() + "/src", size = 1024).make()
@@ -584,8 +623,6 @@ def test_specify_password_via_env(mscp):
run2ok([mscp, "-vvv", "-l", "test",
src.path, "localhost:" + dst.path], env = env)
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
def test_specify_invalid_password_via_env(mscp):
src = File(os.getcwd() + "/src", size = 1024).make()
@@ -594,7 +631,44 @@ def test_specify_invalid_password_via_env(mscp):
env["MSCP_SSH_AUTH_PASSWORD"] = "invalid-userpassword"
run2ng([mscp, "-vvv", "-l", "test",
src.path, "localhost:" + dst.path], env = env)
src.cleanup()
@pytest.fixture
def move_pubkey_temporally():
"""
mv ~/.ssh/id_* to id_rsa.bak before test, and move it back after test.
"""
sshdir = os.path.join(os.environ["HOME"], ".ssh")
# move pubkeys to /tmp
moved = []
for fname in os.listdir(sshdir):
if re.match(r"^id_[a-z0-9]+$", fname):
moved.append(fname)
shutil.move(f"{sshdir}/{fname}", f"/tmp/{fname}")
yield
# move back the keys
for fname in moved:
shutil.move(f"/tmp/{fname}", f"{sshdir}/{fname}")
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_passwordauth_without_pubkey(move_pubkey_temporally,
mscp, src_prefix, dst_prefix):
"""
make sure password auth works (by removing public keys)
"""
src = File(os.getcwd() + "/src", size = 1024).make()
dst = File("/home/test/dst")
env = os.environ
env["MSCP_SSH_AUTH_PASSWORD"] = "userpassword"
run2ok([mscp, "-vvv", "-l", "test",
src.path, "localhost:" + dst.path], env = env)
assert check_same_md5sum(src, dst)
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_10k_files(mscp, src_prefix, dst_prefix):
@@ -606,8 +680,6 @@ def test_10k_files(mscp, src_prefix, dst_prefix):
run2ok([mscp, "-v", src_prefix + "src", dst_prefix + "dst"])
for s, d in zip(srcs, dsts):
assert check_same_md5sum(s, d)
shutil.rmtree("src")
shutil.rmtree("dst")
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_checkpoint_dump_and_resume(mscp, src_prefix, dst_prefix):
@@ -622,15 +694,11 @@ def test_checkpoint_dump_and_resume(mscp, src_prefix, dst_prefix):
run2ok([mscp, "-vvv", "-R", "checkpoint"])
assert check_same_md5sum(src1, dst1)
assert check_same_md5sum(src2, dst2)
src1.cleanup()
src2.cleanup()
dst1.cleanup()
dst2.cleanup()
os.remove("checkpoint")
@pytest.mark.parametrize("timeout", [1, 2, 3, 4, 5, 6])
@pytest.mark.parametrize("timeout", [ 1, 2, 3, 4, 5 ])
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_checkpoint_interrupt_and_resume(mscp, timeout, src_prefix, dst_prefix):
def test_checkpoint_interrupt_large_file(mscp, timeout, src_prefix, dst_prefix):
"""Copy two 100MB files with 200Mbps -> 4 sec + 4 sec """
src1 = File("src1", size = 100 * 1024 * 1024).make()
src2 = File("src2", size = 100 * 1024 * 1024).make()
@@ -644,9 +712,31 @@ def test_checkpoint_interrupt_and_resume(mscp, timeout, src_prefix, dst_prefix):
run2ok([mscp, "-vv", "-R", "checkpoint"])
assert check_same_md5sum(src1, dst1)
assert check_same_md5sum(src2, dst2)
src1.cleanup()
src2.cleanup()
dst1.cleanup()
dst2.cleanup()
os.remove("checkpoint")
@pytest.mark.parametrize("timeout", [ 1, 2, 3, 4, 5 ])
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_checkpoint_interrupt_many_files(mscp, timeout, src_prefix, dst_prefix):
"""Copy 100 1-MB files with 4 connections, and interrupt and
resume the transfer
"""
files = []
for x in range(100):
files.append((
File("src/{:03d}".format(x), size = 1024 * 1024).make(),
File("dst/{:03d}".format(x))
))
run2ng([mscp, "-vv", "-W", "checkpoint", "-L", "80m", "-n", 4,
src_prefix + "src", dst_prefix + "dst"],
timeout = timeout)
assert os.path.exists("checkpoint")
run2ok([mscp, "-vv", "-R", "checkpoint"])
for src, dst in files:
assert check_same_md5sum(src, dst)
os.remove("checkpoint")