mirror of
https://github.com/upa/mscp.git
synced 2026-02-04 11:34:44 +08:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1313853d7d | ||
|
|
e621035002 | ||
|
|
ef8994d21e | ||
|
|
81d46a1cd1 | ||
|
|
07366397a8 | ||
|
|
f12d10a988 | ||
|
|
afe2182456 | ||
|
|
c4d8e64fab | ||
|
|
049600a39f | ||
|
|
fa15e9093d | ||
|
|
5bca31b75d | ||
|
|
f65cedb4de | ||
|
|
8bad10e42b | ||
|
|
8de47933bf | ||
|
|
440f25b3a5 | ||
|
|
cf9672f933 | ||
|
|
27ef4a127a | ||
|
|
1eea34ff39 | ||
|
|
11106d64fe | ||
|
|
f5f3323695 | ||
|
|
7ac34ccdde | ||
|
|
18bc88e17c | ||
|
|
f4db06b6bc | ||
|
|
504818909f | ||
|
|
763b47bb47 | ||
|
|
9ab0046b6c | ||
|
|
ecee66d03f | ||
|
|
a0153414f6 | ||
|
|
3f8b107d77 | ||
|
|
6716a71575 | ||
|
|
7fdb4a534e | ||
|
|
7095c45fc7 | ||
|
|
58d7d085b0 | ||
|
|
deda8ca74c | ||
|
|
5fad665c39 | ||
|
|
1b655b61c9 | ||
|
|
c16b981d5d | ||
|
|
1c787e562f | ||
|
|
248f932a99 | ||
|
|
1636f2a965 | ||
|
|
31e011f85c | ||
|
|
006bd30424 | ||
|
|
60f442689e | ||
|
|
404f025765 | ||
|
|
235ba41c5b | ||
|
|
675126a836 | ||
|
|
ef2dd55572 | ||
|
|
ab6649f29e | ||
|
|
7c5314ea11 | ||
|
|
01fe30efc7 | ||
|
|
61199acc7b | ||
|
|
dd99bc0ac9 | ||
|
|
a5bca0ebe0 | ||
|
|
6373e24753 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
build
|
||||
6
.github/workflows/build-freebsd.yml
vendored
6
.github/workflows/build-freebsd.yml
vendored
@@ -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
|
||||
|
||||
10
.github/workflows/build-macos.yml
vendored
10
.github/workflows/build-macos.yml
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/build-ubuntu.yml
vendored
8
.github/workflows/build-ubuntu.yml
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/codeql.yml
vendored
8
.github/workflows/codeql.yml
vendored
@@ -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
|
||||
|
||||
32
.github/workflows/release.yml
vendored
32
.github/workflows/release.yml
vendored
@@ -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: |
|
||||
|
||||
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -3,5 +3,6 @@ html
|
||||
compile_commands.json
|
||||
CMakeUserPresets.json
|
||||
.*.swp
|
||||
.cache
|
||||
|
||||
include/mscp_version.h
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
|
||||
@@ -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 \
|
||||
36
Dockerfile/arch-base.Dockerfile
Normal file
36
Dockerfile/arch-base.Dockerfile
Normal 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
|
||||
14
README.md
14
README.md
@@ -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
|
||||
|
||||
42
debian/changelog
vendored
42
debian/changelog
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
27
doc/mscp.rst
27
doc/mscp.rst
@@ -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
|
||||
|
||||
@@ -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
2
libssh
Submodule libssh updated: 6f1b1e76bb...dff6c0821e
@@ -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).
|
||||
|
||||
|
||||
@@ -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
605
patch/libssh-0.11.2.patch
Normal 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) {
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
172
src/main.c
172
src/main.c
@@ -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;
|
||||
|
||||
57
src/mscp.c
57
src/mscp.c
@@ -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)
|
||||
|
||||
60
src/path.c
60
src/path.c
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
38
src/ssh.c
38
src/ssh.c
@@ -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();
|
||||
}
|
||||
|
||||
270
test/test_e2e.py
270
test/test_e2e.py
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user