58 Commits

Author SHA1 Message Date
Ryo Nakamura
2ef4267f72 bump version to 0.1.4 2024-02-07 16:16:40 +09:00
Ryo Nakamura
1d8d853b30 fix manpage to add -g option 2024-02-07 15:14:29 +09:00
Ryo Nakamura
b2628b54fb add -4 and -6 options to use either IPv4 or IPv6 2024-02-07 15:14:27 +09:00
Ryo Nakamura
a9c59f744a update man and doc for adjusting to -p option 2024-02-07 14:32:36 +09:00
Ryo Nakamura
96084004b6 cleanup error message handling
The top-level funtion in a thread should print errors using
priv_get_err(), while lower-level functions should set error messages
using priv_set_err() except that error mesesages should be printed
immediately, e.g., under walk_src_path().
2024-02-07 13:29:45 +09:00
Ryo Nakamura
5119d5ae26 remove unused FindGSSAPI.cmake module 2024-02-06 23:24:57 +09:00
Ryo Nakamura
93f155cffe add .clang-format from Linux kernel and format sources
The exception is that ColumnLimit is 90.
2024-02-06 23:09:59 +09:00
Ryo Nakamura
4f0669f8f8 refactor error message-related functions
split message print fuctions (mpr_*), strerrno, and mscp_get/set_error
into print.c/h and strerrno.c/h.

ToDo: revise usages of priv_set_errv and pr_* functions.
2024-02-06 21:54:04 +09:00
Ryo Nakamura
76892a69f9 do not build strlcat.c if HAVE_STRLCAT is true 2024-02-06 16:25:05 +09:00
Ryo Nakamura
a7f8ad948b add -p option, preserving file timestamps 2024-02-06 16:15:43 +09:00
Ryo Nakamura
ff45d9d71b add two env vars to pass password/keyphrase (#9)
MSCP_SSH_AUTH_PASSWORD passes a password, and MSCP_SSH_AUTH_PASSPHRASE
passes a passphrase for publickey auth. They enable avoiding interactive
password input. Test cases are also added.
2024-02-06 10:35:38 +09:00
Ryo Nakamura
9908fb309d passing options via pointers in mscp_ssh_opts
We do not need static buf because we have already dropped python biding
support.
2024-02-05 23:36:59 +09:00
Ryo Nakamura
c95e6a4fff do not passing msg_fp via mscp opts
instead, mpr_* functions print messages to stdout or stderr directly.
2024-02-04 20:23:08 +09:00
Ryo Nakamura
304e71d5a0 remove (accidentally added) build-srpm.Dockerfile 2024-02-04 17:37:57 +09:00
Ryo Nakamura
ec663cc966 bump up container image versions and drop using CPack
We have already provided DEB packages in launchpad PPA and RPM packages
in COPR. Thus, we need no more deb/rpm packages in Github releases. The
single binary build of mscp is an execptio.

Updated container image versions:
- almalinux	8.8  -> 9.3
- rocky		8.8  -> 8.9, and 9.3 is added
- alpine	3.17 -> 3.19
2024-02-04 17:22:31 +09:00
Ryo Nakamura
d57ed4149d drop python binding support 2024-02-04 16:18:27 +09:00
Ryo Nakamura
7f5fcc617c add doc/DEVELOP.md instead of doc/README.md 2024-02-04 12:05:18 +09:00
Ryo Nakamura
36e45cd5f3 change build target update-mscp-rst to update-rst 2024-02-04 11:50:14 +09:00
Ryo Nakamura
25207cf865 build deb sources inside a container
A new build target `build-deb` builds mscp deb inside a docker
container. All debuild -us -uc outputs are copied to build/debbuild
directory.
2024-02-04 11:35:20 +09:00
Ryo Nakamura
306a00ced5 build source rpm inside a container
A new build target `build-srpm` builds mscp src.rpm inside a docker
container. The src.rpm can be published at COPR.
2024-02-04 10:58:11 +09:00
Ryo Nakamura
65025b9022 update README for building on FreeBSD 2024-01-20 22:09:02 +09:00
Ryo Nakamura
bbc2fbdacf fix install-build-deps.sh to run on FreeBSD 2024-01-20 22:05:39 +09:00
Ryo Nakamura
331bab6b41 run build on freebsd action in dev and main branches 2024-01-20 21:42:44 +09:00
Ryo Nakamura
1e375abb88 add github actions to build mscp in FreeBSD 2024-01-21 05:56:53 +09:00
Ryo Nakamura
7ab5cc3ce9 make it buildable at FreeBSD 2024-01-21 04:50:34 +09:00
Ryo Nakamura
60977a8e7a update doc version
and remove --dirty from git-describe-based versioning
2024-01-18 13:52:26 +09:00
Ryo Nakamura
020dadfbaf bump version to 0.1.3 2024-01-18 13:20:02 +09:00
Ryo Nakamura
16086ffdb3 import rpm pgp key before yum install in Alama
See https://cloudlinux.zendesk.com/hc/en-us/articles/12225072530204-yum-update-error-Error-GPG-check-FAILED
2024-01-18 13:17:57 +09:00
Ryo Nakamura
9bdbfe8487 add SPDX-License-Identifier to source files 2024-01-18 12:59:49 +09:00
Ryo Nakamura
bba53fab03 don't allocate char[PATH_MAX] for each file
This commit makes struct path allocation use strndup().
It reduices the memory footprint for struct path per file (issue #8).
2024-01-18 12:59:49 +09:00
Ryo Nakamura
5cbf3ad648 fix wrong dst path for source path under '/'
When a source file path is /FILE, its dest path would be dst/ILE.
This commit fixes this issue (#8).
2024-01-18 12:59:49 +09:00
Ryo Nakamura
4b34118a88 add a test case test_copy_file_under_root_to_dir
Coping a file under / (root) to a remote directory causes
corrupted remote path (Issue #8).
2024-01-18 12:59:49 +09:00
Ryo Nakamura
68a8f3724f print warn messages when stat and opendir failed
during scanning src paths due to, e.g., too many levels of
symbolic links, too long path, and permission deineid.
2024-01-18 12:59:48 +09:00
Ryo Nakamura
1479607efe add manpage for mscp
doc/mscp.rst is generate from mscp.1 by make generate-mscp-rst.
README is also updateded to reference doc/mscp.rst.
2024-01-14 18:07:17 +09:00
Ryo Nakamura
6f4038a480 bump libssh to libssh-0.10.6-2-g6f1b1e76
libssh 0.10.6 has a regression in IPv6 parsing, so we pick
stable-0.10 that includes the fixes.

https://gitlab.com/libssh/libssh-mirror/-/issues/227
2024-01-06 15:11:20 +09:00
Ryo Nakamura
71a0998e9b bump libssh to 0.10.6
libssh 0.10.6 includes some security fixes, e.g., CVE-2023-48795.
2024-01-04 21:17:16 +09:00
Ryo Nakamura
e3484dbc05 update README
install mscp from ppa for ubuntu
2023-12-10 23:56:20 +09:00
Ryo Nakamura
a107681da0 fix Description in debian/control 2023-12-10 23:18:58 +09:00
Ryo Nakamura
e0f412722b add debian directory 2023-12-10 22:14:45 +09:00
Ryo Nakamura
eb5a9e3035 dockerfile: do not run ldconfig in alpine 2023-12-10 21:17:31 +09:00
Ryo Nakamura
a6501a16bf dockerfile: do ldconfig after pymscp installation 2023-12-10 20:56:38 +09:00
Ryo Nakamura
abe94897ae cmake: add INSTALL_EXECUTABLE_ONLY option 2023-12-10 17:06:57 +09:00
Ryo Nakamura
260e39471d rename setup.py to pysetup.py
to prevent dh_auto_build from automatically detecting setup.py.
2023-12-10 17:04:55 +09:00
Ryo Nakamura
2b71c4bf8c add -P, equivalent to -p for just compatibility
and add a test case for -p and -P
2023-11-25 15:49:09 +09:00
Ryo Nakamura
0cf3acee20 add -I interval option
-I INTERVAL option inserts sleep for interval (seconds) between
SSH connection attempts (issue #7).
2023-11-25 15:17:33 +09:00
Ryo Nakamura
c292ce2b29 workflows: do apt-get update before running install-build-deps.sh 2023-11-11 16:51:00 +09:00
Ryo Nakamura
8e8e1b935d codeql.yml: do apt-get update before running install-build-deps.sh 2023-11-11 16:47:25 +09:00
Ryo Nakamura
9611b4d077 bump version to 0.1.2 2023-11-01 19:54:18 +09:00
Ryo Nakamura
2b9061f5f0 add --sysctl net.ipv6.conf.all.disable_ipv6=0 for docker run.
docker disables ipv6 on all interfaces inside containers by default,
even ::1 on lo. It causes testing mscp with IPv6 fails. Thus,
this commit disables disable_ipv6 via the --sysctl option.
2023-11-01 19:54:18 +09:00
Ryo Nakamura
8e590da322 fix parsing user@host:path.
This commit fixes issue #6. Now mscp command correctly parses
[x::x] IPv6 address notation in hostname.
2023-11-01 19:54:18 +09:00
Ryo Nakamura
b298b2ec35 main: adopt rolling average of recent eight bps values to calculate ETA 2023-11-01 19:54:18 +09:00
Ryo Nakamura
05a7e96759 main: call only mscp_stop() when receives sigint 2023-11-01 19:54:18 +09:00
Ryo Nakamura
139ba12f1a write total transferred bytes and number of files
at the end of output when serverity is notice.
2023-11-01 19:54:18 +09:00
Ryo Nakamura
cfbadebe6d change msg: thread[%d] to thread:%d 2023-11-01 19:54:18 +09:00
Ryo Nakamura
d7365683a9 print 1st decimal point in the progress bar 2023-11-01 19:54:18 +09:00
Ryo Nakamura
53a560b130 fix test_e2e for ccalgo and tiny fix on test_dir_copy_single 2023-11-01 19:54:18 +09:00
Ryo Nakamura
bf74aa095a add -g option to specify TCP cc algorithm
This commit introduce SSH_OPTIONS_CCALGO option to the libssh patch
and add -g CONGESTION option to mscp.
2023-11-01 19:54:18 +09:00
Ryo Nakamura
a88471fc43 Update README.md
add link to PEARC'23 paper
2023-09-11 19:56:33 +09:00
82 changed files with 4620 additions and 3059 deletions

745
.clang-format Normal file
View File

@@ -0,0 +1,745 @@
# SPDX-License-Identifier: GPL-2.0
#
# clang-format configuration file. Intended for clang-format >= 11.
#
# For more information, see:
#
# Documentation/process/clang-format.rst
# https://clang.llvm.org/docs/ClangFormat.html
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
#
# clang-format configuration for Linux kernel, except that ColumnLimit is 90
---
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 90
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
# Taken from:
# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ tools/ \
# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \
# | LC_ALL=C sort -u
ForEachMacros:
- '__ata_qc_for_each'
- '__bio_for_each_bvec'
- '__bio_for_each_segment'
- '__evlist__for_each_entry'
- '__evlist__for_each_entry_continue'
- '__evlist__for_each_entry_from'
- '__evlist__for_each_entry_reverse'
- '__evlist__for_each_entry_safe'
- '__for_each_mem_range'
- '__for_each_mem_range_rev'
- '__for_each_thread'
- '__hlist_for_each_rcu'
- '__map__for_each_symbol_by_name'
- '__pci_bus_for_each_res0'
- '__pci_bus_for_each_res1'
- '__pci_dev_for_each_res0'
- '__pci_dev_for_each_res1'
- '__perf_evlist__for_each_entry'
- '__perf_evlist__for_each_entry_reverse'
- '__perf_evlist__for_each_entry_safe'
- '__rq_for_each_bio'
- '__shost_for_each_device'
- '__sym_for_each'
- 'apei_estatus_for_each_section'
- 'ata_for_each_dev'
- 'ata_for_each_link'
- 'ata_qc_for_each'
- 'ata_qc_for_each_raw'
- 'ata_qc_for_each_with_internal'
- 'ax25_for_each'
- 'ax25_uid_for_each'
- 'bio_for_each_bvec'
- 'bio_for_each_bvec_all'
- 'bio_for_each_folio_all'
- 'bio_for_each_integrity_vec'
- 'bio_for_each_segment'
- 'bio_for_each_segment_all'
- 'bio_list_for_each'
- 'bip_for_each_vec'
- 'bond_for_each_slave'
- 'bond_for_each_slave_rcu'
- 'bpf_for_each'
- 'bpf_for_each_reg_in_vstate'
- 'bpf_for_each_reg_in_vstate_mask'
- 'bpf_for_each_spilled_reg'
- 'bpf_object__for_each_map'
- 'bpf_object__for_each_program'
- 'btree_for_each_safe128'
- 'btree_for_each_safe32'
- 'btree_for_each_safe64'
- 'btree_for_each_safel'
- 'card_for_each_dev'
- 'cgroup_taskset_for_each'
- 'cgroup_taskset_for_each_leader'
- 'cpu_aggr_map__for_each_idx'
- 'cpufreq_for_each_efficient_entry_idx'
- 'cpufreq_for_each_entry'
- 'cpufreq_for_each_entry_idx'
- 'cpufreq_for_each_valid_entry'
- 'cpufreq_for_each_valid_entry_idx'
- 'css_for_each_child'
- 'css_for_each_descendant_post'
- 'css_for_each_descendant_pre'
- 'damon_for_each_region'
- 'damon_for_each_region_from'
- 'damon_for_each_region_safe'
- 'damon_for_each_scheme'
- 'damon_for_each_scheme_safe'
- 'damon_for_each_target'
- 'damon_for_each_target_safe'
- 'damos_for_each_filter'
- 'damos_for_each_filter_safe'
- 'data__for_each_file'
- 'data__for_each_file_new'
- 'data__for_each_file_start'
- 'device_for_each_child_node'
- 'displayid_iter_for_each'
- 'dma_fence_array_for_each'
- 'dma_fence_chain_for_each'
- 'dma_fence_unwrap_for_each'
- 'dma_resv_for_each_fence'
- 'dma_resv_for_each_fence_unlocked'
- 'do_for_each_ftrace_op'
- 'drm_atomic_crtc_for_each_plane'
- 'drm_atomic_crtc_state_for_each_plane'
- 'drm_atomic_crtc_state_for_each_plane_state'
- 'drm_atomic_for_each_plane_damage'
- 'drm_client_for_each_connector_iter'
- 'drm_client_for_each_modeset'
- 'drm_connector_for_each_possible_encoder'
- 'drm_exec_for_each_locked_object'
- 'drm_exec_for_each_locked_object_reverse'
- 'drm_for_each_bridge_in_chain'
- 'drm_for_each_connector_iter'
- 'drm_for_each_crtc'
- 'drm_for_each_crtc_reverse'
- 'drm_for_each_encoder'
- 'drm_for_each_encoder_mask'
- 'drm_for_each_fb'
- 'drm_for_each_legacy_plane'
- 'drm_for_each_plane'
- 'drm_for_each_plane_mask'
- 'drm_for_each_privobj'
- 'drm_gem_for_each_gpuva'
- 'drm_gem_for_each_gpuva_safe'
- 'drm_gpuva_for_each_op'
- 'drm_gpuva_for_each_op_from_reverse'
- 'drm_gpuva_for_each_op_safe'
- 'drm_gpuvm_for_each_va'
- 'drm_gpuvm_for_each_va_range'
- 'drm_gpuvm_for_each_va_range_safe'
- 'drm_gpuvm_for_each_va_safe'
- 'drm_mm_for_each_hole'
- 'drm_mm_for_each_node'
- 'drm_mm_for_each_node_in_range'
- 'drm_mm_for_each_node_safe'
- 'dsa_switch_for_each_available_port'
- 'dsa_switch_for_each_cpu_port'
- 'dsa_switch_for_each_cpu_port_continue_reverse'
- 'dsa_switch_for_each_port'
- 'dsa_switch_for_each_port_continue_reverse'
- 'dsa_switch_for_each_port_safe'
- 'dsa_switch_for_each_user_port'
- 'dsa_tree_for_each_cpu_port'
- 'dsa_tree_for_each_user_port'
- 'dsa_tree_for_each_user_port_continue_reverse'
- 'dso__for_each_symbol'
- 'dsos__for_each_with_build_id'
- 'elf_hash_for_each_possible'
- 'elf_symtab__for_each_symbol'
- 'evlist__for_each_cpu'
- 'evlist__for_each_entry'
- 'evlist__for_each_entry_continue'
- 'evlist__for_each_entry_from'
- 'evlist__for_each_entry_reverse'
- 'evlist__for_each_entry_safe'
- 'flow_action_for_each'
- 'for_each_acpi_consumer_dev'
- 'for_each_acpi_dev_match'
- 'for_each_active_dev_scope'
- 'for_each_active_drhd_unit'
- 'for_each_active_iommu'
- 'for_each_active_route'
- 'for_each_aggr_pgid'
- 'for_each_and_bit'
- 'for_each_andnot_bit'
- 'for_each_available_child_of_node'
- 'for_each_bench'
- 'for_each_bio'
- 'for_each_board_func_rsrc'
- 'for_each_btf_ext_rec'
- 'for_each_btf_ext_sec'
- 'for_each_bvec'
- 'for_each_card_auxs'
- 'for_each_card_auxs_safe'
- 'for_each_card_components'
- 'for_each_card_dapms'
- 'for_each_card_pre_auxs'
- 'for_each_card_prelinks'
- 'for_each_card_rtds'
- 'for_each_card_rtds_safe'
- 'for_each_card_widgets'
- 'for_each_card_widgets_safe'
- 'for_each_cgroup_storage_type'
- 'for_each_child_of_node'
- 'for_each_clear_bit'
- 'for_each_clear_bit_from'
- 'for_each_clear_bitrange'
- 'for_each_clear_bitrange_from'
- 'for_each_cmd'
- 'for_each_cmsghdr'
- 'for_each_collection'
- 'for_each_comp_order'
- 'for_each_compatible_node'
- 'for_each_component_dais'
- 'for_each_component_dais_safe'
- 'for_each_conduit'
- 'for_each_console'
- 'for_each_console_srcu'
- 'for_each_cpu'
- 'for_each_cpu_and'
- 'for_each_cpu_andnot'
- 'for_each_cpu_or'
- 'for_each_cpu_wrap'
- 'for_each_dapm_widgets'
- 'for_each_dedup_cand'
- 'for_each_dev_addr'
- 'for_each_dev_scope'
- 'for_each_dma_cap_mask'
- 'for_each_dpcm_be'
- 'for_each_dpcm_be_rollback'
- 'for_each_dpcm_be_safe'
- 'for_each_dpcm_fe'
- 'for_each_drhd_unit'
- 'for_each_dss_dev'
- 'for_each_efi_memory_desc'
- 'for_each_efi_memory_desc_in_map'
- 'for_each_element'
- 'for_each_element_extid'
- 'for_each_element_id'
- 'for_each_endpoint_of_node'
- 'for_each_event'
- 'for_each_event_tps'
- 'for_each_evictable_lru'
- 'for_each_fib6_node_rt_rcu'
- 'for_each_fib6_walker_rt'
- 'for_each_free_mem_pfn_range_in_zone'
- 'for_each_free_mem_pfn_range_in_zone_from'
- 'for_each_free_mem_range'
- 'for_each_free_mem_range_reverse'
- 'for_each_func_rsrc'
- 'for_each_gpiochip_node'
- 'for_each_group_evsel'
- 'for_each_group_evsel_head'
- 'for_each_group_member'
- 'for_each_group_member_head'
- 'for_each_hstate'
- 'for_each_if'
- 'for_each_inject_fn'
- 'for_each_insn'
- 'for_each_insn_prefix'
- 'for_each_intid'
- 'for_each_iommu'
- 'for_each_ip_tunnel_rcu'
- 'for_each_irq_nr'
- 'for_each_lang'
- 'for_each_link_codecs'
- 'for_each_link_cpus'
- 'for_each_link_platforms'
- 'for_each_lru'
- 'for_each_matching_node'
- 'for_each_matching_node_and_match'
- 'for_each_media_entity_data_link'
- 'for_each_mem_pfn_range'
- 'for_each_mem_range'
- 'for_each_mem_range_rev'
- 'for_each_mem_region'
- 'for_each_member'
- 'for_each_memory'
- 'for_each_migratetype_order'
- 'for_each_missing_reg'
- 'for_each_mle_subelement'
- 'for_each_mod_mem_type'
- 'for_each_net'
- 'for_each_net_continue_reverse'
- 'for_each_net_rcu'
- 'for_each_netdev'
- 'for_each_netdev_continue'
- 'for_each_netdev_continue_rcu'
- 'for_each_netdev_continue_reverse'
- 'for_each_netdev_dump'
- 'for_each_netdev_feature'
- 'for_each_netdev_in_bond_rcu'
- 'for_each_netdev_rcu'
- 'for_each_netdev_reverse'
- 'for_each_netdev_safe'
- 'for_each_new_connector_in_state'
- 'for_each_new_crtc_in_state'
- 'for_each_new_mst_mgr_in_state'
- 'for_each_new_plane_in_state'
- 'for_each_new_plane_in_state_reverse'
- 'for_each_new_private_obj_in_state'
- 'for_each_new_reg'
- 'for_each_node'
- 'for_each_node_by_name'
- 'for_each_node_by_type'
- 'for_each_node_mask'
- 'for_each_node_state'
- 'for_each_node_with_cpus'
- 'for_each_node_with_property'
- 'for_each_nonreserved_multicast_dest_pgid'
- 'for_each_numa_hop_mask'
- 'for_each_of_allnodes'
- 'for_each_of_allnodes_from'
- 'for_each_of_cpu_node'
- 'for_each_of_pci_range'
- 'for_each_old_connector_in_state'
- 'for_each_old_crtc_in_state'
- 'for_each_old_mst_mgr_in_state'
- 'for_each_old_plane_in_state'
- 'for_each_old_private_obj_in_state'
- 'for_each_oldnew_connector_in_state'
- 'for_each_oldnew_crtc_in_state'
- 'for_each_oldnew_mst_mgr_in_state'
- 'for_each_oldnew_plane_in_state'
- 'for_each_oldnew_plane_in_state_reverse'
- 'for_each_oldnew_private_obj_in_state'
- 'for_each_online_cpu'
- 'for_each_online_node'
- 'for_each_online_pgdat'
- 'for_each_or_bit'
- 'for_each_path'
- 'for_each_pci_bridge'
- 'for_each_pci_dev'
- 'for_each_pcm_streams'
- 'for_each_physmem_range'
- 'for_each_populated_zone'
- 'for_each_possible_cpu'
- 'for_each_present_blessed_reg'
- 'for_each_present_cpu'
- 'for_each_prime_number'
- 'for_each_prime_number_from'
- 'for_each_probe_cache_entry'
- 'for_each_process'
- 'for_each_process_thread'
- 'for_each_prop_codec_conf'
- 'for_each_prop_dai_codec'
- 'for_each_prop_dai_cpu'
- 'for_each_prop_dlc_codecs'
- 'for_each_prop_dlc_cpus'
- 'for_each_prop_dlc_platforms'
- 'for_each_property_of_node'
- 'for_each_reg'
- 'for_each_reg_filtered'
- 'for_each_reloc'
- 'for_each_reloc_from'
- 'for_each_requested_gpio'
- 'for_each_requested_gpio_in_range'
- 'for_each_reserved_mem_range'
- 'for_each_reserved_mem_region'
- 'for_each_rtd_codec_dais'
- 'for_each_rtd_components'
- 'for_each_rtd_cpu_dais'
- 'for_each_rtd_dais'
- 'for_each_sband_iftype_data'
- 'for_each_script'
- 'for_each_sec'
- 'for_each_set_bit'
- 'for_each_set_bit_from'
- 'for_each_set_bit_wrap'
- 'for_each_set_bitrange'
- 'for_each_set_bitrange_from'
- 'for_each_set_clump8'
- 'for_each_sg'
- 'for_each_sg_dma_page'
- 'for_each_sg_page'
- 'for_each_sgtable_dma_page'
- 'for_each_sgtable_dma_sg'
- 'for_each_sgtable_page'
- 'for_each_sgtable_sg'
- 'for_each_sibling_event'
- 'for_each_sta_active_link'
- 'for_each_subelement'
- 'for_each_subelement_extid'
- 'for_each_subelement_id'
- 'for_each_sublist'
- 'for_each_subsystem'
- 'for_each_supported_activate_fn'
- 'for_each_supported_inject_fn'
- 'for_each_sym'
- 'for_each_test'
- 'for_each_thread'
- 'for_each_token'
- 'for_each_unicast_dest_pgid'
- 'for_each_valid_link'
- 'for_each_vif_active_link'
- 'for_each_vma'
- 'for_each_vma_range'
- 'for_each_vsi'
- 'for_each_wakeup_source'
- 'for_each_zone'
- 'for_each_zone_zonelist'
- 'for_each_zone_zonelist_nodemask'
- 'func_for_each_insn'
- 'fwnode_for_each_available_child_node'
- 'fwnode_for_each_child_node'
- 'fwnode_for_each_parent_node'
- 'fwnode_graph_for_each_endpoint'
- 'gadget_for_each_ep'
- 'genradix_for_each'
- 'genradix_for_each_from'
- 'genradix_for_each_reverse'
- 'hash_for_each'
- 'hash_for_each_possible'
- 'hash_for_each_possible_rcu'
- 'hash_for_each_possible_rcu_notrace'
- 'hash_for_each_possible_safe'
- 'hash_for_each_rcu'
- 'hash_for_each_safe'
- 'hashmap__for_each_entry'
- 'hashmap__for_each_entry_safe'
- 'hashmap__for_each_key_entry'
- 'hashmap__for_each_key_entry_safe'
- 'hctx_for_each_ctx'
- 'hists__for_each_format'
- 'hists__for_each_sort_list'
- 'hlist_bl_for_each_entry'
- 'hlist_bl_for_each_entry_rcu'
- 'hlist_bl_for_each_entry_safe'
- 'hlist_for_each'
- 'hlist_for_each_entry'
- 'hlist_for_each_entry_continue'
- 'hlist_for_each_entry_continue_rcu'
- 'hlist_for_each_entry_continue_rcu_bh'
- 'hlist_for_each_entry_from'
- 'hlist_for_each_entry_from_rcu'
- 'hlist_for_each_entry_rcu'
- 'hlist_for_each_entry_rcu_bh'
- 'hlist_for_each_entry_rcu_notrace'
- 'hlist_for_each_entry_safe'
- 'hlist_for_each_entry_srcu'
- 'hlist_for_each_safe'
- 'hlist_nulls_for_each_entry'
- 'hlist_nulls_for_each_entry_from'
- 'hlist_nulls_for_each_entry_rcu'
- 'hlist_nulls_for_each_entry_safe'
- 'i3c_bus_for_each_i2cdev'
- 'i3c_bus_for_each_i3cdev'
- 'idr_for_each_entry'
- 'idr_for_each_entry_continue'
- 'idr_for_each_entry_continue_ul'
- 'idr_for_each_entry_ul'
- 'in_dev_for_each_ifa_rcu'
- 'in_dev_for_each_ifa_rtnl'
- 'inet_bind_bucket_for_each'
- 'interval_tree_for_each_span'
- 'intlist__for_each_entry'
- 'intlist__for_each_entry_safe'
- 'kcore_copy__for_each_phdr'
- 'key_for_each'
- 'key_for_each_safe'
- 'klp_for_each_func'
- 'klp_for_each_func_safe'
- 'klp_for_each_func_static'
- 'klp_for_each_object'
- 'klp_for_each_object_safe'
- 'klp_for_each_object_static'
- 'kunit_suite_for_each_test_case'
- 'kvm_for_each_memslot'
- 'kvm_for_each_memslot_in_gfn_range'
- 'kvm_for_each_vcpu'
- 'libbpf_nla_for_each_attr'
- 'list_for_each'
- 'list_for_each_codec'
- 'list_for_each_codec_safe'
- 'list_for_each_continue'
- 'list_for_each_entry'
- 'list_for_each_entry_continue'
- 'list_for_each_entry_continue_rcu'
- 'list_for_each_entry_continue_reverse'
- 'list_for_each_entry_from'
- 'list_for_each_entry_from_rcu'
- 'list_for_each_entry_from_reverse'
- 'list_for_each_entry_lockless'
- 'list_for_each_entry_rcu'
- 'list_for_each_entry_reverse'
- 'list_for_each_entry_safe'
- 'list_for_each_entry_safe_continue'
- 'list_for_each_entry_safe_from'
- 'list_for_each_entry_safe_reverse'
- 'list_for_each_entry_srcu'
- 'list_for_each_from'
- 'list_for_each_prev'
- 'list_for_each_prev_safe'
- 'list_for_each_rcu'
- 'list_for_each_reverse'
- 'list_for_each_safe'
- 'llist_for_each'
- 'llist_for_each_entry'
- 'llist_for_each_entry_safe'
- 'llist_for_each_safe'
- 'lwq_for_each_safe'
- 'map__for_each_symbol'
- 'map__for_each_symbol_by_name'
- 'maps__for_each_entry'
- 'maps__for_each_entry_safe'
- 'mas_for_each'
- 'mci_for_each_dimm'
- 'media_device_for_each_entity'
- 'media_device_for_each_intf'
- 'media_device_for_each_link'
- 'media_device_for_each_pad'
- 'media_entity_for_each_pad'
- 'media_pipeline_for_each_entity'
- 'media_pipeline_for_each_pad'
- 'mlx5_lag_for_each_peer_mdev'
- 'msi_domain_for_each_desc'
- 'msi_for_each_desc'
- 'mt_for_each'
- 'nanddev_io_for_each_page'
- 'netdev_for_each_lower_dev'
- 'netdev_for_each_lower_private'
- 'netdev_for_each_lower_private_rcu'
- 'netdev_for_each_mc_addr'
- 'netdev_for_each_synced_mc_addr'
- 'netdev_for_each_synced_uc_addr'
- 'netdev_for_each_uc_addr'
- 'netdev_for_each_upper_dev_rcu'
- 'netdev_hw_addr_list_for_each'
- 'nft_rule_for_each_expr'
- 'nla_for_each_attr'
- 'nla_for_each_nested'
- 'nlmsg_for_each_attr'
- 'nlmsg_for_each_msg'
- 'nr_neigh_for_each'
- 'nr_neigh_for_each_safe'
- 'nr_node_for_each'
- 'nr_node_for_each_safe'
- 'of_for_each_phandle'
- 'of_property_for_each_string'
- 'of_property_for_each_u32'
- 'pci_bus_for_each_resource'
- 'pci_dev_for_each_resource'
- 'pcl_for_each_chunk'
- 'pcl_for_each_segment'
- 'pcm_for_each_format'
- 'perf_config_items__for_each_entry'
- 'perf_config_sections__for_each_entry'
- 'perf_config_set__for_each_entry'
- 'perf_cpu_map__for_each_cpu'
- 'perf_cpu_map__for_each_idx'
- 'perf_evlist__for_each_entry'
- 'perf_evlist__for_each_entry_reverse'
- 'perf_evlist__for_each_entry_safe'
- 'perf_evlist__for_each_evsel'
- 'perf_evlist__for_each_mmap'
- 'perf_hpp_list__for_each_format'
- 'perf_hpp_list__for_each_format_safe'
- 'perf_hpp_list__for_each_sort_list'
- 'perf_hpp_list__for_each_sort_list_safe'
- 'perf_tool_event__for_each_event'
- 'plist_for_each'
- 'plist_for_each_continue'
- 'plist_for_each_entry'
- 'plist_for_each_entry_continue'
- 'plist_for_each_entry_safe'
- 'plist_for_each_safe'
- 'pnp_for_each_card'
- 'pnp_for_each_dev'
- 'protocol_for_each_card'
- 'protocol_for_each_dev'
- 'queue_for_each_hw_ctx'
- 'radix_tree_for_each_slot'
- 'radix_tree_for_each_tagged'
- 'rb_for_each'
- 'rbtree_postorder_for_each_entry_safe'
- 'rdma_for_each_block'
- 'rdma_for_each_port'
- 'rdma_umem_for_each_dma_block'
- 'resort_rb__for_each_entry'
- 'resource_list_for_each_entry'
- 'resource_list_for_each_entry_safe'
- 'rhl_for_each_entry_rcu'
- 'rhl_for_each_rcu'
- 'rht_for_each'
- 'rht_for_each_entry'
- 'rht_for_each_entry_from'
- 'rht_for_each_entry_rcu'
- 'rht_for_each_entry_rcu_from'
- 'rht_for_each_entry_safe'
- 'rht_for_each_from'
- 'rht_for_each_rcu'
- 'rht_for_each_rcu_from'
- 'rq_for_each_bvec'
- 'rq_for_each_segment'
- 'rq_list_for_each'
- 'rq_list_for_each_safe'
- 'sample_read_group__for_each'
- 'scsi_for_each_prot_sg'
- 'scsi_for_each_sg'
- 'sctp_for_each_hentry'
- 'sctp_skb_for_each'
- 'sec_for_each_insn'
- 'sec_for_each_insn_continue'
- 'sec_for_each_insn_from'
- 'sec_for_each_sym'
- 'shdma_for_each_chan'
- 'shost_for_each_device'
- 'sk_for_each'
- 'sk_for_each_bound'
- 'sk_for_each_bound_bhash2'
- 'sk_for_each_entry_offset_rcu'
- 'sk_for_each_from'
- 'sk_for_each_rcu'
- 'sk_for_each_safe'
- 'sk_nulls_for_each'
- 'sk_nulls_for_each_from'
- 'sk_nulls_for_each_rcu'
- 'snd_array_for_each'
- 'snd_pcm_group_for_each_entry'
- 'snd_soc_dapm_widget_for_each_path'
- 'snd_soc_dapm_widget_for_each_path_safe'
- 'snd_soc_dapm_widget_for_each_sink_path'
- 'snd_soc_dapm_widget_for_each_source_path'
- 'strlist__for_each_entry'
- 'strlist__for_each_entry_safe'
- 'sym_for_each_insn'
- 'sym_for_each_insn_continue_reverse'
- 'symbols__for_each_entry'
- 'tb_property_for_each'
- 'tcf_act_for_each_action'
- 'tcf_exts_for_each_action'
- 'ttm_resource_manager_for_each_res'
- 'twsk_for_each_bound_bhash2'
- 'udp_portaddr_for_each_entry'
- 'udp_portaddr_for_each_entry_rcu'
- 'usb_hub_for_each_child'
- 'v4l2_device_for_each_subdev'
- 'v4l2_m2m_for_each_dst_buf'
- 'v4l2_m2m_for_each_dst_buf_safe'
- 'v4l2_m2m_for_each_src_buf'
- 'v4l2_m2m_for_each_src_buf_safe'
- 'virtio_device_for_each_vq'
- 'while_for_each_ftrace_op'
- 'xa_for_each'
- 'xa_for_each_marked'
- 'xa_for_each_range'
- 'xa_for_each_start'
- 'xas_for_each'
- 'xas_for_each_conflict'
- 'xas_for_each_marked'
- 'xbc_array_for_each_value'
- 'xbc_for_each_key_value'
- 'xbc_node_for_each_array_value'
- 'xbc_node_for_each_child'
- 'xbc_node_for_each_key_value'
- 'xbc_node_for_each_subkey'
- 'zorro_for_each_dev'
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentGotoLabels: false
IndentPPDirectives: None
IndentWidth: 8
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 8
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
# Taken from git's rules
PenaltyBreakAssignment: 10
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 10
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatementsExceptForEachMacros
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp03
TabWidth: 8
UseTab: Always
...

27
.github/workflows/build-freebsd.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: build on FreeBSD
on:
push:
branches: [ "main", "dev" ]
pull_request:
branches: [ "main", "dev" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- 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

@@ -30,7 +30,7 @@ jobs:
run: echo "HOMEBREW_PREFIX=$(brew --prefix)" >> $GITHUB_OUTPUT
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.4.patch
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.

View File

@@ -23,10 +23,12 @@ jobs:
submodules: true
- name: install build dependency
run: sudo ./scripts/install-build-deps.sh
run: |
sudo apt-get update
sudo ./scripts/install-build-deps.sh
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.4.patch
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.

View File

@@ -43,10 +43,12 @@ jobs:
submodules: true
- name: install build dependency
run: sudo ./scripts/install-build-deps.sh
run: |
sudo apt-get update
sudo ./scripts/install-build-deps.sh
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.4.patch
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -18,7 +18,7 @@ jobs:
submodules: true
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.4.patch
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
@@ -33,22 +33,14 @@ jobs:
- name: Test
run: make -C ${{github.workspace}}/build docker-test-all
- name: Retrieve packages from containers
run: make -C ${{github.workspace}}/build docker-pkg-all
- name: Copy mscp single binary built inside Alpine
run: cp ${{github.workspace}}/build/mscp_alpine-3.17-x86_64.static ${{github.workspace}}/build/mscp.linux.x86.static
- 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_ubuntu-20.04-x86_64.deb
${{github.workspace}}/build/mscp_ubuntu-22.04-x86_64.deb
${{github.workspace}}/build/mscp_rocky-8.8-x86_64.rpm
${{github.workspace}}/build/mscp_almalinux-8.8-x86_64.rpm
${{github.workspace}}/build/mscp_alpine-3.17-x86_64.static
${{github.workspace}}/build/mscp.linux.x86.static
${{github.workspace}}/build/mscp.linux.x86_64.static
source-release:
runs-on: ubuntu-latest
@@ -58,7 +50,7 @@ jobs:
submodules: true
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.4.patch
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
- name: Set variables
run: |

View File

@@ -18,11 +18,13 @@ jobs:
submodules: true
- name: patch to libssh
run: patch -d libssh -p1 < patch/libssh-0.10.4.patch
run: patch -d libssh -p1 < patch/libssh-0.10.6-2-g6f1b1e76.patch
# TODO: just building docker does not require libssh. fix CMakeLists
- name: install build dependency
run: sudo ./scripts/install-build-deps.sh
run: |
sudo apt-get update
sudo ./scripts/install-build-deps.sh
- name: configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}

4
.gitignore vendored
View File

@@ -5,7 +5,3 @@ CMakeUserPresets.json
.*.swp
include/mscp_version.h
dist
*.egg-info
__pycache__

View File

@@ -11,7 +11,7 @@ find_package(Git)
if (Git_FOUND)
# based on https://github.com/nocnokneo/cmake-git-versioning-example
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags --dirty --match "v*"
COMMAND ${GIT_EXECUTABLE} describe --tags --match "v*"
OUTPUT_VARIABLE GIT_DESCRIBE_VERSION
RESULT_VARIABLE GIT_DESCRIBE_ERROR_CODE
OUTPUT_STRIP_TRAILING_WHITESPACE)
@@ -69,12 +69,12 @@ add_subdirectory(libssh EXCLUDE_FROM_ALL)
# setup mscp compile options
set(MSCP_COMPILE_OPTS -iquote ${CMAKE_CURRENT_BINARY_DIR}/libssh/include)
set(MSCP_BUILD_INCLUDE_DIRS
list(APPEND MSCP_COMPILE_OPTS -iquote ${CMAKE_CURRENT_BINARY_DIR}/libssh/include)
list(APPEND MSCP_BUILD_INCLUDE_DIRS
${mscp_SOURCE_DIR}/src
${CMAKE_CURRENT_BINARY_DIR}/libssh/include)
set(MSCP_LINK_LIBS ssh-static)
list(APPEND MSCP_LINK_LIBS ssh-static)
if(BUILD_CONAN)
find_package(ZLIB REQUIRED)
find_package(OpenSSL REQUIRED)
@@ -83,31 +83,24 @@ if(BUILD_CONAN)
endif()
# generate version header file
# Symbol check
check_symbol_exists(strlcat string.h HAVE_STRLCAT)
if (NOT HAVE_STRLCAT)
list(APPEND OPENBSD_COMPAT_SRC src/openbsd-compat/strlcat.c)
endif()
# generate config.h in build dir
configure_file(
${mscp_SOURCE_DIR}/include/mscp_version.h.in
${mscp_SOURCE_DIR}/include/mscp_version.h)
# libmscp.so
set(LIBMSCP_SRC
src/mscp.c src/ssh.c src/fileops.c src/path.c src/platform.c src/message.c)
add_library(mscp-shared SHARED ${LIBMSCP_SRC})
target_include_directories(mscp-shared
PUBLIC $<BUILD_INTERFACE:${mscp_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE ${MSCP_BUILD_INCLUDE_DIRS})
target_compile_options(mscp-shared PRIVATE ${MSCP_COMPILE_OPTS})
target_link_libraries(mscp-shared PRIVATE ${MSCP_LINK_LIBS})
set_target_properties(mscp-shared
PROPERTIES
OUTPUT_NAME mscp
PUBLIC_HEADER ${mscp_SOURCE_DIR}/include/mscp.h)
install(TARGETS mscp-shared)
${mscp_SOURCE_DIR}/include/config.h.in
${CMAKE_CURRENT_BINARY_DIR}/include/config.h)
list(APPEND MSCP_BUILD_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/include)
# libmscp.a
set(LIBMSCP_SRC
src/mscp.c src/ssh.c src/fileops.c src/path.c src/platform.c
src/print.c src/strerrno.c
${OPENBSD_COMPAT_SRC})
add_library(mscp-static STATIC ${LIBMSCP_SRC})
target_include_directories(mscp-static
PRIVATE ${MSCP_BUILD_INCLUDE_DIRS} ${mscp_SOURCE_DIR}/include)
@@ -117,9 +110,6 @@ set_target_properties(mscp-static
PROPERTIES
OUTPUT_NAME mscp)
install(TARGETS mscp-static)
# mscp executable
list(APPEND MSCP_LINK_LIBS m pthread)
@@ -136,6 +126,21 @@ target_compile_options(mscp PRIVATE ${MSCP_COMPILE_OPTS})
install(TARGETS mscp RUNTIME DESTINATION bin)
# mscp manpage and document
configure_file(
${mscp_SOURCE_DIR}/doc/mscp.1.in
${PROJECT_BINARY_DIR}/mscp.1)
add_custom_target(update-rst
COMMENT "Update doc/mscp.rst from mscp.1.in"
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
COMMAND
pandoc -s -f man mscp.1 -t rst -o ${PROJECT_SOURCE_DIR}/doc/mscp.rst)
install(FILES ${PROJECT_BINARY_DIR}/mscp.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
# Test
add_test(NAME pytest
COMMAND python3 -m pytest -v
@@ -146,51 +151,13 @@ enable_testing()
# CPACK Rules
#set(CPACK_SET_DESTDIR true)
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_CONTACT "Ryo Nakamura <upa@haeena.net>")
set(CPACK_PACKAGE_DESCRIPTION
"mscp, copy files over multiple ssh connections")
execute_process(COMMAND uname -m
OUTPUT_VARIABLE ARCH OUTPUT_STRIP_TRAILING_WHITESPACE)
if(UNIX AND NOT APPLE) # on linux
execute_process(COMMAND
bash "-c" "cat /etc/os-release|grep '^ID='|cut -d '=' -f 2|tr -d '\"'"
OUTPUT_VARIABLE DIST_NAME OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND
bash "-c" "cat /etc/os-release|grep '^VERSION_ID='|cut -d '=' -f 2|tr -d '\"'"
OUTPUT_VARIABLE DIST_VER OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND
bash "-c" "${mscp_SOURCE_DIR}/scripts/print-install-deps.sh ${DIST_NAME}-${DIST_VER}"
OUTPUT_VARIABLE DIST_DEP OUTPUT_STRIP_TRAILING_WHITESPACE)
set(PACKAGE_FILE_NAME
${PROJECT_NAME}_${DIST_NAME}-${DIST_VER}-${ARCH})
set(CPACK_DEBIAN_FILE_NAME ${PACKAGE_FILE_NAME}.deb)
set(CPACK_DEBIAN_PACKAGE_DEPENDS ${DIST_DEP})
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/upa/mscp")
set(CPACK_RPM_FILE_NAME ${PACKAGE_FILE_NAME}.rpm)
set(CPACK_RPM_PACKAGE_REQUIRES ${DIST_DEP})
set(CPACK_RPM_PACKAGE_HOMEPAGE "https://github.com/upa/mscp")
set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION})
set(CPACK_RPM_PACKAGE_LICENSE "GPLv3")
endif() # on linux
include(CPack)
# 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_NAMES ubuntu ubuntu rocky almalinux alpine)
list(APPEND DIST_VERS 20.04 22.04 8.8 8.8 3.17)
list(APPEND DIST_PKGS deb deb rpm rpm static)
list(APPEND DIST_NAMES ubuntu ubuntu rocky rocky almalinux alpine)
list(APPEND DIST_VERS 20.04 22.04 8.9 9.3 9.3 3.19)
list(APPEND DIST_PKGS deb deb rpm rpm rpm static)
list(LENGTH DIST_NAMES _DIST_LISTLEN)
math(EXPR DIST_LISTLEN "${_DIST_LISTLEN} - 1")
@@ -209,34 +176,83 @@ foreach(x RANGE ${DIST_LISTLEN})
COMMENT "Build mscp in ${DOCKER_IMAGE} container"
WORKING_DIRECTORY ${mscp_SOURCE_DIR}
COMMAND
docker build -t ${DOCKER_IMAGE} -f docker/${DOCKER_INDEX}.Dockerfile .)
docker build -t ${DOCKER_IMAGE} -f Dockerfile/${DOCKER_INDEX}.Dockerfile .)
add_custom_target(docker-build-${DOCKER_INDEX}-no-cache
COMMENT "Build mscp in ${DOCKER_IMAGE} container"
WORKING_DIRECTORY ${mscp_SOURCE_DIR}
COMMAND
docker build --no-cache -t ${DOCKER_IMAGE} -f docker/${DOCKER_INDEX}.Dockerfile .)
docker build --no-cache -t ${DOCKER_IMAGE} -f Dockerfile/${DOCKER_INDEX}.Dockerfile .)
add_custom_target(docker-test-${DOCKER_INDEX}
COMMENT "Test mscp in ${DOCKER_IMAGE} container"
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMAND
docker run --init --rm ${DOCKER_IMAGE} /mscp/scripts/test-in-container.sh)
add_custom_target(docker-pkg-${DOCKER_INDEX}
COMMENT "Retrieve mscp package from ${DOCKER_IMAGE} container"
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMAND
docker run --rm -v ${CMAKE_BINARY_DIR}:/out ${DOCKER_IMAGE}
cp /mscp/build/${PKG_FILE_NAME} /out/)
docker run --init --rm --sysctl net.ipv6.conf.all.disable_ipv6=0
${DOCKER_IMAGE} /mscp/scripts/test-in-container.sh)
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})
list(APPEND DOCKER_PKGS docker-pkg-${DOCKER_INDEX})
endforeach()
add_custom_target(docker-build-all DEPENDS ${DOCKER_BUILDS})
add_custom_target(docker-build-all-no-cache DEPENDS ${DOCKER_BUILDS_NO_CACHE})
add_custom_target(docker-test-all DEPENDS ${DOCKER_TESTS})
add_custom_target(docker-pkg-all DEPENDS ${DOCKER_PKGS})
### debuild-related definitions
set(DEBBUILDCONTAINER mscp-build-deb)
add_custom_target(build-deb
COMMENT "build mscp deb files inside a container"
WORKING_DIRECTORY ${mscp_SOURCE_DIR}
BYPRODUCTS ${CMAKE_BINARY_DIR}/debbuild
COMMAND
docker build -t ${DEBBUILDCONTAINER} -f Dockerfile/build-deb.Dockerfile .
COMMAND
docker run --rm -v ${CMAKE_BINARY_DIR}:/out ${DEBBUILDCONTAINER}
cp -r /debbuild /out/)
### rpmbuild-related definitions
# generate files for rpmbuild
configure_file(
${mscp_SOURCE_DIR}/rpm/mscp.spec.in
${mscp_SOURCE_DIR}/rpm/mscp.spec
@ONLY)
configure_file(
${mscp_SOURCE_DIR}/Dockerfile/build-srpm.Dockerfile.in
${mscp_SOURCE_DIR}/Dockerfile/build-srpm.Dockerfile
@ONLY)
# Custom target to build mscp as a src.rpm in docker.
set(RPMBUILDCONTAINER mscp-build-srpm)
set(SRPMFILE mscp-${MSCP_VERSION}-1.el9.src.rpm)
add_custom_target(build-srpm
COMMENT "Build mscp src.rpm inside a container"
WORKING_DIRECTORY ${mscp_SOURCE_DIR}
BYPRODUCTS ${CMAKE_BINARY_DIR}/${SRPMFILE}
COMMAND
docker build -t ${RPMBUILDCONTAINER} -f Dockerfile/build-srpm.Dockerfile .
COMMAND
docker run --rm -v ${CMAKE_BINARY_DIR}:/out ${RPMBUILDCONTAINER}
cp /root/rpmbuild/SRPMS/${SRPMFILE} /out/)
### single-binary-build-related definitions
# Custom target to get single binary mscp
set(SINGLEBINARYFILE mscp.linux.${CMAKE_SYSTEM_PROCESSOR}.static)
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
COMMAND
docker run --rm -v ${CMAKE_BINARY_DIR}:/out mscp-alpine:3.19
cp /mscp/build/mscp /out/${SINGLEBINARYFILE})
add_custom_target(build-pkg-all
DEPENDS build-deb build-srpm build-single-binary)

3
Dockerfile/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# generated by cmake
build-srpm.Dockerfile

19
Dockerfile/README.md Normal file
View File

@@ -0,0 +1,19 @@
Dockerfiles for building and testing mscp.
Build container:
```
docker build -t mscp-DIST:VER -f docker/DIST-VER.Dockerfile .
```
Run test:
```
docker run --init --rm mscp-DST:VER /mscp/scripts/test-in-container.sh
```
Custom targets to build and test mscp in the containers are provided
via `cmake`. See `make docker-*` targets. `make docker-build-all`
builds all container images, and `make docker-test-all` runs the test
in all container images.

View File

@@ -0,0 +1,40 @@
FROM almalinux:9.3
# install pytest, sshd for test, and rpm-build
RUN set -ex && yum -y install \
python3 python3-pip python3-devel openssh openssh-server openssh-clients rpm-build
RUN python3 -m pip install pytest
# preparation for sshd
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
# 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
RUN rm -rf /run/nologin
ARG mscpdir="/mscp"
COPY . ${mscpdir}
# install build dependency
RUN ${mscpdir}/scripts/install-build-deps.sh
# build
RUN cd ${mscpdir} \
&& rm -rf build \
&& cmake -B build \
&& cd ${mscpdir}/build \
&& make -j 2 \
&& make install

View File

@@ -0,0 +1,45 @@
FROM alpine:3.19
# Build mscp with conan to create single binary mscp
RUN apk add --no-cache \
gcc make cmake python3 py3-pip perl linux-headers libc-dev \
openssh bash python3-dev py3-pytest g++
RUN pip3 install --break-system-packages conan
# preparation for sshd
RUN ssh-keygen -A \
&& mkdir /var/run/sshd \
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
&& cat /root/.ssh/id_rsa.pub > /root/.ssh/authorized_keys
# create test user
RUN addgroup -S test \
&& adduser -S test -G 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
# Build mscp as a single binary
RUN conan profile detect --force
ARG mscpdir="/mscp"
COPY . ${mscpdir}
RUN cd ${mscpdir} \
&& rm -rf build \
&& conan install . --output-folder=build --build=missing \
&& cd ${mscpdir}/build \
&& cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake \
-DBUILD_CONAN=ON -DBUILD_STATIC=ON \
&& make -j 2 \
&& make install

View File

@@ -0,0 +1,20 @@
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive
RUN set -ex && apt-get update && apt-get install -y --no-install-recommends \
ca-certificates build-essential devscripts debhelper gcc make cmake
ARG mscpdir="/debbuild/mscp"
COPY . ${mscpdir}
# install build dependency
RUN ${mscpdir}/scripts/install-build-deps.sh
# build
RUN cd ${mscpdir} \
&& debuild -us -uc -S \
&& mv ${mscpdir} /
# Then all debuild output files exsit at /debbuild

View File

@@ -0,0 +1,22 @@
FROM rockylinux:9
# install pytest, sshd for test, and rpm-build
RUN set -ex && yum -y install rpm-build rpmdevtools
ARG mscpdir="/mscp-@MSCP_VERSION@"
ARG mscptgz="mscp-@MSCP_VERSION@.tar.gz"
COPY . ${mscpdir}
# install build dependency
RUN ${mscpdir}/scripts/install-build-deps.sh
# prepare rpmbuild
RUN rpmdev-setuptree \
&& rm -rf ${mscpdir}/build \
&& tar zcvf /${mscptgz} --exclude-vcs ${mscpdir} \
&& cp /${mscptgz} ~/rpmbuild/SOURCES/ \
&& cp ${mscpdir}/rpm/mscp.spec ~/rpmbuild/SPECS/
# build rpm and src.rpm
RUN rpmbuild -ba ~/rpmbuild/SPECS/mscp.spec

View File

@@ -0,0 +1,41 @@
FROM rockylinux:8.9
# install pytest, sshd for test, and rpm-build
RUN set -ex && yum -y install \
python3 python3-pip python3-devel openssh openssh-server openssh-clients rpm-build
RUN python3 -m pip install pytest
# preparation for sshd
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
# 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
RUN rm -rf /run/nologin
ARG mscpdir="/mscp"
COPY . ${mscpdir}
# install build dependency
RUN ${mscpdir}/scripts/install-build-deps.sh
# build
RUN cd ${mscpdir} \
&& rm -rf build \
&& cmake -B build \
&& cd ${mscpdir}/build \
&& make -j 2 \
&& make install

View File

@@ -0,0 +1,41 @@
FROM rockylinux:9.3
# install pytest, sshd for test, and rpm-build
RUN set -ex && yum -y install \
python3 python3-pip python3-devel openssh openssh-server openssh-clients rpm-build
RUN python3 -m pip install pytest
# preparation for sshd
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
# 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
RUN rm -rf /run/nologin
ARG mscpdir="/mscp"
COPY . ${mscpdir}
# install build dependency
RUN ${mscpdir}/scripts/install-build-deps.sh
# build
RUN cd ${mscpdir} \
&& rm -rf build \
&& cmake -B build \
&& cd ${mscpdir}/build \
&& make -j 2 \
&& make install

View File

@@ -12,9 +12,19 @@ RUN python3 -m pip install pytest
# preparation for sshd
RUN mkdir /var/run/sshd \
RUN mkdir /var/run/sshd \
&& ssh-keygen -A \
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
&& mv /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
&& cat /root/.ssh/id_rsa.pub > /root/.ssh/authorized_keys
# 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"
@@ -30,10 +40,6 @@ RUN cd ${mscpdir} \
&& rm -rf build \
&& cmake -B build \
&& cd ${mscpdir}/build \
&& make \
&& cpack -G DEB CPackConfig.cmake \
&& dpkg -i *.deb
&& make -j 2 \
&& make install
# install mscp python module
RUN cd ${mscpdir} \
&& python3 setup.py install --user

View File

@@ -12,9 +12,20 @@ RUN python3 -m pip install pytest
# preparation for sshd
RUN mkdir /var/run/sshd \
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
&& mv /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
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
# 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"
@@ -29,11 +40,5 @@ RUN cd ${mscpdir} \
&& rm -rf build \
&& cmake -B build \
&& cd ${mscpdir}/build \
&& make \
&& cpack -G DEB CPackConfig.cmake \
&& dpkg -i *.deb
# install mscp python module
RUN cd ${mscpdir} \
&& python3 setup.py install --user
&& make -j 2 \
&& make install

View File

@@ -9,5 +9,5 @@ GENERATE_MAN = NO
SOURCE_BROWSER = YES
INPUT = src include mscp
INPUT = src include

163
README.md
View File

@@ -1,19 +1,26 @@
# mscp: multi-threaded scp
[![build on ubuntu](https://github.com/upa/mscp/actions/workflows/build-ubuntu.yml/badge.svg)](https://github.com/upa/mscp/actions/workflows/build-ubuntu.yml) [![build on macOS](https://github.com/upa/mscp/actions/workflows/build-macos.yml/badge.svg)](https://github.com/upa/mscp/actions/workflows/build-macos.yml) [![test](https://github.com/upa/mscp/actions/workflows/test.yml/badge.svg)](https://github.com/upa/mscp/actions/workflows/test.yml)
[![build on ubuntu](https://github.com/upa/mscp/actions/workflows/build-ubuntu.yml/badge.svg)](https://github.com/upa/mscp/actions/workflows/build-ubuntu.yml)
[![build on macOS](https://github.com/upa/mscp/actions/workflows/build-macos.yml/badge.svg)](https://github.com/upa/mscp/actions/workflows/build-macos.yml)
[![build on FreeBSD](https://github.com/upa/mscp/actions/workflows/build-freebsd.yml/badge.svg)](https://github.com/upa/mscp/actions/workflows/build-freebsd.yml)
[![test](https://github.com/upa/mscp/actions/workflows/test.yml/badge.svg)](https://github.com/upa/mscp/actions/workflows/test.yml)
`mscp`, a variant of `scp`, copies files over multiple ssh (SFTP)
connections. Multiple threads and connections in mscp transfer (1)
multiple files simultaneously and (2) a large file in parallel. It
would shorten the waiting time for transferring a lot of/large files
over networks.
You can use `mscp` like `scp`, for example, `mscp
user@example.com:srcfile /tmp/dstfile`. Remote hosts only need to run
standard `sshd` supporting the SFTP subsystem (e.g. openssh-server),
and you need to be able to ssh to the hosts as usual. `mscp` does not
require anything else.
`mscp`, a variant of `scp`, copies files over multiple SSH (SFTP)
connections by multiple threads. It enables transferring (1) multiple
files simultaneously and (2) a large file in parallel, reducing the
transfer time for a lot of/large files over networks.
You can use `mscp` like `scp`, for example:
```shell-session
$ mscp user@example.com:srcfile /tmp/dstfile
```
Remote hosts only need to run standard `sshd` supporting the SFTP
subsystem (e.g. openssh-server), and you need to be able to ssh to the
hosts as usual. `mscp` does not require anything else.
https://github.com/upa/mscp/assets/184632/19230f57-be7f-4ef0-98dd-cb4c460f570d
@@ -26,7 +33,8 @@ Differences from `scp` on usage:
- `-r` option is not needed to transfer directories.
- and any other differences I have not implemented and noticed.
Paper:
- Ryo Nakamura and Yohei Kuga. 2023. Multi-threaded scp: Easy and Fast File Transfer over SSH. In Practice and Experience in Advanced Research Computing (PEARC '23). Association for Computing Machinery, New York, NY, USA, 320323. https://doi.org/10.1145/3569951.3597582
## Install
@@ -36,29 +44,19 @@ Differences from `scp` on usage:
brew install upa/tap/mscp
```
- Ubuntu 22.04
- Ubuntu
```console
wget https://github.com/upa/mscp/releases/latest/download/mscp_ubuntu-22.04-x86_64.deb
apt-get install -f ./mscp_ubuntu-22.04-x86_64.deb
sudo add-apt-repository ppa:upaa/mscp
sudo apt-get install mscp
```
- Ubuntu 20.04
- RHEL-based distributions
```console
wget https://github.com/upa/mscp/releases/latest/download/mscp_ubuntu-20.04-x86_64.deb
apt-get install -f ./mscp_ubuntu-20.04-x86_64.deb
sudo dnf copr enable upaaa/mscp
sudo dnf install mscp
```
- Rocky 8.8
```console
yum install https://github.com/upa/mscp/releases/latest/download/mscp_rocky-8.8-x86_64.rpm
```
- Alma 8.8
```console
yum install https://github.com/upa/mscp/releases/latest/download/mscp_almalinux-8.8-x86_64.rpm
```
- Linux with single binary `mscp` (x86_64 only)
- Single binary `mscp` for x86_64 (not optimal performance)
```console
wget https://github.com/upa/mscp/releases/latest/download/mscp.linux.x86.static -O /usr/local/bin/mscp
chmod 755 /usr/local/bin/mscp
@@ -67,12 +65,14 @@ chmod 755 /usr/local/bin/mscp
## Build
mscp depends on a patched [libssh](https://www.libssh.org/). The
mscp depends on a patched [libssh](https://www.libssh.org/). The
patch introduces asynchronous SFTP Write, which is derived from
https://github.com/limes-datentechnik-gmbh/libssh (see [Re: SFTP Write
async](https://archive.libssh.org/libssh/2020-06/0000004.html)).
Currently macOS and Linux (Ubuntu, Rocky and Alma) are supported.
We test building mscp on Linux (Ubuntu, Rocky, Alma, and Alpine),
macOS, and FreeBSD.
```console
# clone this repository
@@ -81,7 +81,7 @@ cd mscp
# prepare patched libssh
git submodule update --init
patch -d libssh -p1 < patch/libssh-0.10.4.patch
patch -d libssh -p1 < patch/$(git --git-dir=./libssh/.git describe).patch
# install build dependency
bash ./scripts/install-build-deps.sh
@@ -99,105 +99,12 @@ make
# install the mscp binary to CMAKE_INSTALL_PREFIX/bin (usually /usr/local/bin)
make install
```
Source tar balls (`mscp-X.X.X.tar.gz`, not `Source code`) in
[Releases page](https://github.com/upa/mscp/releases) contains the patched version
of libssh. So you can start from cmake with it.
## Run
- Usage
## Documentation
```console
$ mscp
mscp v0.0.8: copy files over multiple ssh connections
Usage: mscp [vqDHdNh] [-n nr_conns] [-m coremask] [-u max_startups]
[-s min_chunk_sz] [-S max_chunk_sz] [-a nr_ahead] [-b buf_sz]
[-l login_name] [-p port] [-i identity_file]
[-c cipher_spec] [-M hmac_spec] [-C compress] source ... target
```
- Example: copy a 15GB file on memory over a 100Gbps link
- Two Intel Xeon Gold 6130 machines directly connected with Intel E810 100Gbps NICs.
- Default `openssh-server` runs on the remote host.
```console
$ mscp /var/ram/test.img 10.0.0.1:/var/ram/
[======================================] 100% 15GB/15GB 1.7GB/s 00:00 ETA
```
```console
# with some optimizations. top speed reaches 3.0GB/s.
$ mscp -n 5 -m 0x1f -c aes128-gcm@openssh.com /var/ram/test.img 10.0.0.1:/var/ram/
[======================================] 100% 15GB/15GB 2.4GB/s 00:00 ETA
```
- `-v` option increments verbose output level.
```console
$ mscp test 10.0.0.1:
[=======================================] 100% 49B /49B 198.8B/s 00:00 ETA
```
```console
$ mscp -vv test 10.0.0.1:
file: test/test1 -> ./test/test1
file: test/testdir/asdf -> ./test/testdir/asdf
file: test/testdir/qwer -> ./test/testdir/qwer
file: test/test2 -> ./test/test2
we have only 4 chunk(s). set number of connections to 4
connecting to localhost for a copy thread...
connecting to localhost for a copy thread...
connecting to localhost for a copy thread...
copy start: test/test1
copy start: test/test2
copy start: test/testdir/asdf
copy start: test/testdir/qwer
copy done: test/test1
copy done: test/test2
copy done: test/testdir/qwer
copy done: test/testdir/asdf
[=======================================] 100% 49B /49B 198.1B/s 00:00 ETA
```
- Full usage
```console
$ mscp -h
mscp v0.0.9-11-g5802679: copy files over multiple ssh connections
Usage: mscp [vqDHdNh] [-n nr_conns] [-m coremask] [-u max_startups]
[-s min_chunk_sz] [-S max_chunk_sz] [-a nr_ahead] [-b buf_sz]
[-l login_name] [-p port] [-F ssh_config] [-i identity_file]
[-c cipher_spec] [-M hmac_spec] [-C compress] source ... target
-n NR_CONNECTIONS number of connections (default: floor(log(cores)*2)+1)
-m COREMASK hex value to specify cores where threads pinned
-u MAX_STARTUPS number of concurrent outgoing connections (default: 8)
-s MIN_CHUNK_SIZE min chunk size (default: 64MB)
-S MAX_CHUNK_SIZE max chunk size (default: filesize/nr_conn)
-a NR_AHEAD number of inflight SFTP commands (default: 32)
-b BUF_SZ buffer size for i/o and transfer
-v increment verbose output level
-q disable output
-D dry run. check copy destinations with -vvv
-r no effect
-l LOGIN_NAME login name
-p PORT port number
-F CONFIG path to user ssh config (default ~/.ssh/config)
-i IDENTITY identity file for public key authentication
-c CIPHER cipher spec
-M HMAC hmac spec
-C COMPRESS enable compression: yes, no, zlib, zlib@openssh.com
-H disable hostkey check
-d increment ssh debug output level
-N enable Nagle's algorithm (default disabled)
-h print this help
```
Note: mscp is still under development, and the author is not
responsible for any accidents due to mscp.
[manpage](/doc/mscp.rst) is available.

View File

@@ -1 +1 @@
0.1.1
0.1.4

View File

@@ -1,325 +0,0 @@
# - Try to find GSSAPI
# Once done this will define
#
# KRB5_CONFIG - Path to krb5-config
# GSSAPI_ROOT_DIR - Set this variable to the root installation of GSSAPI
#
# Read-Only variables:
# GSSAPI_FLAVOR_MIT - set to TURE if MIT Kerberos has been found
# GSSAPI_FLAVOR_HEIMDAL - set to TRUE if Heimdal Keberos has been found
# GSSAPI_FOUND - system has GSSAPI
# GSSAPI_INCLUDE_DIR - the GSSAPI include directory
# GSSAPI_LIBRARIES - Link these to use GSSAPI
# GSSAPI_DEFINITIONS - Compiler switches required for using GSSAPI
#
#=============================================================================
# Copyright (c) 2013 Andreas Schneider <asn@cryptomilk.org>
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
#
find_path(GSSAPI_ROOT_DIR
NAMES
include/gssapi.h
include/gssapi/gssapi.h
HINTS
${_GSSAPI_ROOT_HINTS}
PATHS
${_GSSAPI_ROOT_PATHS}
)
mark_as_advanced(GSSAPI_ROOT_DIR)
if (UNIX)
find_program(KRB5_CONFIG
NAMES
krb5-config
PATHS
${GSSAPI_ROOT_DIR}/bin
/opt/local/bin)
mark_as_advanced(KRB5_CONFIG)
if (KRB5_CONFIG)
# Check if we have MIT KRB5
execute_process(
COMMAND
${KRB5_CONFIG} --vendor
RESULT_VARIABLE
_GSSAPI_VENDOR_RESULT
OUTPUT_VARIABLE
_GSSAPI_VENDOR_STRING)
if ((_GSSAPI_VENDOR_STRING MATCHES ".*Massachusetts.*") OR (_GSSAPI_VENDOR_STRING
MATCHES ".*MITKerberosShim.*"))
set(GSSAPI_FLAVOR_MIT TRUE)
else()
execute_process(
COMMAND
${KRB5_CONFIG} --libs gssapi
RESULT_VARIABLE
_GSSAPI_LIBS_RESULT
OUTPUT_VARIABLE
_GSSAPI_LIBS_STRING)
if (_GSSAPI_LIBS_STRING MATCHES ".*roken.*")
set(GSSAPI_FLAVOR_HEIMDAL TRUE)
endif()
endif()
# Get the include dir
execute_process(
COMMAND
${KRB5_CONFIG} --cflags gssapi
RESULT_VARIABLE
_GSSAPI_INCLUDE_RESULT
OUTPUT_VARIABLE
_GSSAPI_INCLUDE_STRING)
string(REGEX REPLACE "(\r?\n)+$" "" _GSSAPI_INCLUDE_STRING "${_GSSAPI_INCLUDE_STRING}")
string(REGEX REPLACE " *-I" "" _GSSAPI_INCLUDEDIR "${_GSSAPI_INCLUDE_STRING}")
endif()
if (NOT GSSAPI_FLAVOR_MIT AND NOT GSSAPI_FLAVOR_HEIMDAL)
# Check for HEIMDAL
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_check_modules(_GSSAPI heimdal-gssapi)
endif (PKG_CONFIG_FOUND)
if (_GSSAPI_FOUND)
set(GSSAPI_FLAVOR_HEIMDAL TRUE)
else()
find_path(_GSSAPI_ROKEN
NAMES
roken.h
PATHS
${GSSAPI_ROOT_DIR}/include
${_GSSAPI_INCLUDEDIR})
if (_GSSAPI_ROKEN)
set(GSSAPI_FLAVOR_HEIMDAL TRUE)
endif()
endif ()
endif()
endif (UNIX)
find_path(GSSAPI_INCLUDE_DIR
NAMES
gssapi.h
gssapi/gssapi.h
PATHS
${GSSAPI_ROOT_DIR}/include
${_GSSAPI_INCLUDEDIR}
)
if (GSSAPI_FLAVOR_MIT)
find_library(GSSAPI_LIBRARY
NAMES
gssapi_krb5
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(KRB5_LIBRARY
NAMES
krb5
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(K5CRYPTO_LIBRARY
NAMES
k5crypto
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(COM_ERR_LIBRARY
NAMES
com_err
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
if (GSSAPI_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${GSSAPI_LIBRARY}
)
endif (GSSAPI_LIBRARY)
if (KRB5_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${KRB5_LIBRARY}
)
endif (KRB5_LIBRARY)
if (K5CRYPTO_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${K5CRYPTO_LIBRARY}
)
endif (K5CRYPTO_LIBRARY)
if (COM_ERR_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${COM_ERR_LIBRARY}
)
endif (COM_ERR_LIBRARY)
endif (GSSAPI_FLAVOR_MIT)
if (GSSAPI_FLAVOR_HEIMDAL)
find_library(GSSAPI_LIBRARY
NAMES
gssapi
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(KRB5_LIBRARY
NAMES
krb5
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(HCRYPTO_LIBRARY
NAMES
hcrypto
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(COM_ERR_LIBRARY
NAMES
com_err
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(HEIMNTLM_LIBRARY
NAMES
heimntlm
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(HX509_LIBRARY
NAMES
hx509
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(ASN1_LIBRARY
NAMES
asn1
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(WIND_LIBRARY
NAMES
wind
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
find_library(ROKEN_LIBRARY
NAMES
roken
PATHS
${GSSAPI_ROOT_DIR}/lib
${_GSSAPI_LIBDIR}
)
if (GSSAPI_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${GSSAPI_LIBRARY}
)
endif (GSSAPI_LIBRARY)
if (KRB5_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${KRB5_LIBRARY}
)
endif (KRB5_LIBRARY)
if (HCRYPTO_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${HCRYPTO_LIBRARY}
)
endif (HCRYPTO_LIBRARY)
if (COM_ERR_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${COM_ERR_LIBRARY}
)
endif (COM_ERR_LIBRARY)
if (HEIMNTLM_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${HEIMNTLM_LIBRARY}
)
endif (HEIMNTLM_LIBRARY)
if (HX509_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${HX509_LIBRARY}
)
endif (HX509_LIBRARY)
if (ASN1_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${ASN1_LIBRARY}
)
endif (ASN1_LIBRARY)
if (WIND_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${WIND_LIBRARY}
)
endif (WIND_LIBRARY)
if (ROKEN_LIBRARY)
set(GSSAPI_LIBRARIES
${GSSAPI_LIBRARIES}
${WIND_LIBRARY}
)
endif (ROKEN_LIBRARY)
endif (GSSAPI_FLAVOR_HEIMDAL)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(GSSAPI DEFAULT_MSG GSSAPI_LIBRARIES GSSAPI_INCLUDE_DIR)
if (GSSAPI_INCLUDE_DIRS AND GSSAPI_LIBRARIES)
set(GSSAPI_FOUND TRUE)
endif (GSSAPI_INCLUDE_DIRS AND GSSAPI_LIBRARIES)
# show the GSSAPI_INCLUDE_DIRS and GSSAPI_LIBRARIES variables only in the advanced view
mark_as_advanced(GSSAPI_INCLUDE_DIRS GSSAPI_LIBRARIES)

11
debian/.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
/.debhelper/
/*/
!/tests/
!/source
/*.log
/*.substvars
/*.debhelper
/files
/mscp

33
debian/changelog vendored Normal file
View File

@@ -0,0 +1,33 @@
mscp (0.1.4) UNRELEASED; urgency=medium
* add a test for builds on FreeBSD
* updat container images for tests
* dropp support for Python bindings
* clean up error message handling
* add MSCP_SSH_AUTH_PASSWORD/PASSPHRASE environment variables
to pass passwords to MSCP without interactive input (issue #9)
* add a -p option to preserve timestamps
* add -4 and -6 options to use IPv4 or IPv6, respectively
* introduc .clang-format, which is derived from the Linux kernel
* fix the manpage
-- Ryo Nakamura <upa@haeena.net> Wed, 07 Feb 2024 15:56:58 +0900
mscp (0.1.3) unstable; urgency=medium
* add -I option for inserting intervals between SSH attempts (issue #7)
* add -P option, equivalent to -p (just for compatibility)
* update libssh to 0.10.6-2-g6f1b1e76 for security fixes
* cleanup warning messages when scanning source files fails
* fix wrong destination paths for source paths under '/' (issue #8)
* reduce memory footprint for coping many (e.g., over 100k) files (issue #8)
* add SPDX-License-Identifer to the source files
* add manpage
-- Ryo Nakamura <upa@haeena.net> Fri, 12 Jan 2024 22:20:24 +0900
mscp (0.1.2) unstable; urgency=medium
* Initial release for debian packaging
-- Ryo Nakamura <upa@haeena.net> Sun, 10 Dec 2023 21:51:49 +0900

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
10

17
debian/control vendored Normal file
View File

@@ -0,0 +1,17 @@
Source: mscp
Section: net
Priority: optional
Maintainer: Ryo Nakamura <upa@haeena.net>
Build-Depends: debhelper (>= 10~), cmake, zlib1g-dev, libssl-dev, libkrb5-dev
Homepage: https://github.com/upa/mscp
Standards-Version: 4.5.0.3
Package: mscp
Architecture: linux-any
Depends: ${misc:Depends}, ${shlibs:Depends}
Description: mscp, fast file transfer over multiple SSH connections
mscp transfers files over multiple SSH connections. Multiple threads
and connections in mscp transfer (1) multiple files simultaneously
and (2) a large file in parallel. It would shorten the waiting time
for transferring a lot of/large files over networks.

12
debian/copyright vendored Normal file
View File

@@ -0,0 +1,12 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Source: https://github.com/upa/mscp
Upstream-Name: mscp
Upstream-Contact: Ryo Nakamura <upa@haeena.net>
Files: *
Copyright: Ryo Nakamura
License: GPL-3
Files: src/list.h
Copyright: kazutomo@mcs.anl.gov
License: GPL

9
debian/rules vendored Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/make -f
%:
dh $@
override_dh_auto_test:

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
3.0 (native)

71
doc/RELEASE.md Normal file
View File

@@ -0,0 +1,71 @@
## Build mscp as deb package
`make build-deb` produces a mscp deb package and related files. This
target builds mscp with `debuild` inside a docker container
(Dockerfile is `docker/build-deb.Docerfile`).
```console
mkdir build && cd build && cmake ..
make build-deb
```
After that:
```console
$ ls debbuild
mscp_0.1.4.dsc mscp_0.1.4_source.buildinfo mscp_0.1.4.tar.xz
mscp_0.1.4_source.build mscp_0.1.4_source.changes
```
### To publush mscp in launchpad PPA:
1. write changes in `debian/changelog` at main branch (the date command needed here is `date -R`)
2. switch to `ppa-focal` or `ppa-jammy` branch
3. reblase to the `main` branch and modify `debian/changes`:
* change `UNRELEASED` to the release name (`focal` or `jammy`).
4. run `make build-deb` at the build directory and `cd debbuild`
5. sign the files with `debsign -k [GPGKEYID] mscp_X.X.X~X_source.changes`
5. upload the files with `dput ppa:upaa/mscp mscp_X.X.X~X_source.changes`
## Build mscp as (source) rpm package
`make build-srpm` produces a mscp src.rpm package. This target builts
mscp with `rpmbuild` inside a docker container (Dockerfile is
`docker/build-srpm.Dockerfile`, generated from
`build-srpm.Dockerfile.in` by cmake).
```console
mkdir build && cd build && cmake ..
make build-srpm
```
After that:
```console
$ ls *.rpm
mscp-0.1.3-1.el9.src.rpm
```
### To publish mscp in COPR:
1. update `changelog` in `rpm/mscp.spec` (the date command needed here is `date "+%a %b %d %Y"`)
2. run `make build-srpm`
3. download `mscp-X.X.X-1.yyy.src.rpm`
4. upload the src.rpm to Build page at COPR.
## Update Document
The docuemnt is `doc/mscp.rst` (at present). When `mscp.1.in` is
modified, run `make update-rst` to make it up to date.
```console
mkdir build cd build && cmake ..
make update-rst
```

353
doc/mscp.1.in Normal file
View File

@@ -0,0 +1,353 @@
.TH MSCP 1 "@MSCP_BUILD_VERSION@" "mscp" "User Commands"
.SH NAME
mscp \- copy files over multiple SSH connections
.SH SYNOPSIS
.B mscp
.RB [ \-46vqDpHdNh ]
[\c
.BI \-n \ NR_CONNECTIONS\c
]
[\c
.BI \-m \ COREMASK\c
]
[\c
.BI \-u \ MAX_STARTUPS\c
]
[\c
.BI \-I \ INTERVAL\c
]
[\c
.BI \-s \ MIN_CHUNK_SIZE\c
]
[\c
.BI \-S \ MAX_CHUNK_SIZE\c
]
[\c
.BI \-a \ NR_AHEAD\c
]
[\c
.BI \-b \ BUF_SIZE\c
]
[\c
.BI \-l \ LOGIN_NAME\c
]
[\c
.BI \-P \ PORT\c
]
[\c
.BI \-F \ CONFIG\c
]
[\c
.BI \-i \ IDENTITY\c
]
[\c
.BI \-c \ CIPHER\c
]
[\c
.BI \-M \ HMAC\c
]
[\c
.BI \-C \ COMPRESS\c
]
[\c
.BI \-g \ CONGESTION\c
]
.I source ... target
.SH DESCRIPTION
.PP
.B mscp
copies files over multiple SSH (SFTP) connections by multiple
threads. It enables transferring (1) multiple files simultaneously and
(2) a large file in parallel, reducing the transfer time for a lot
of/large files over networks.
.PP
The usage of
.B mscp
imitates the
.B scp
command of
.I OpenSSH,
for example:
.nf
$ mscp srcfile user@example.com:dstfile
.fi
Remote hosts only need to run standard
.B sshd
supporting the SFTP subsystem, and users need to be able to
.B ssh
to the hosts as usual.
.B mscp
does not require anything else.
.PP
.B mscp
uses
.UR https://\:www\:.libssh\:.org
libssh
.UE
as its SSH implementation. Thus, supported SSH features, for example,
authentication, encryption, and various options in ssh_config, follow
what
.I libssh
supports.
.SH OPTIONS
.TP
.B \-n \fINR_CONNECTIONS\fR
Specifies the number of SSH connections. The default value is
calculated from the number of CPU cores on the host with the following
formula: floor(log(nr_cores)*2)+1.
.TP
.B \-m \fICOREMASK\fR
Configures CPU cores to be used by the hexadecimal bitmask. All CPU
cores are used by default.
.TP
.B \-u \fIMAX_STARTUPS\fR
Specifies the number of concurrent outgoing SSH connections.
.B sshd
limits the number of simultaneous SSH connection attempts by
.I MaxStartups
in
.I sshd_config.
The default
.I MaxStartups
is 10; thus, we set the default MAX_STARTUPS 8.
.TP
.B \-I \fIINTERVAL\fR
Specifies the interval (in seconds) between SSH connection
attempts. Some firewall products treat SSH connection attempts from a
single source IP address for a short period as a brute force attack.
This option inserts intervals between the attempts to avoid being
determined as an attack. The default value is 0.
.TP
.B \-s \fIMIN_CHUNK_SIZE\fR
Specifies the minimum chunk size.
.B mscp
divides a file into chunks and copies the chunks in parallel.
.TP
.B \-S \fIMAX_CHUNK_SIZE\fR
Specifies the maximum chunk size. The default is file size divided by
the number of connections.
.TP
.B \-a \fINR_AHEAD\fR
Specifies the number of inflight SFTP commands. The default value is
32.
.TP
.B \-b \fIBUF_SIZE\fR
Specifies the buffer size for I/O and transfer over SFTP. The default
value is 16384. Note that the SSH specification restricts buffer size
delivered over SSH. Changing this value is not recommended at present.
.TP
.B \-4
Uses IPv4 addresses only.
.TP
.B \-6
Uses IPv6 addresses only.
.TP
.B \-v
Increments the verbose output level.
.TP
.B \-q
Quiet mode: turns off all outputs.
.TP
.B \-D
Dry-run mode: it scans source files to be copied, calculates chunks,
and resolves destination file paths. Dry-run mode with
.B -vv
option enables confirming files to be copied and their destination
paths.
.TP
.B \-r
No effect.
.B mscp
copies recursively if a source path is a directory. This option exists
for just compatibility.
.TP
.B \-l \fILOGIN_NAME\fR
Specifies the username to log in on the remote machine as with
.I ssh(1).
.TP
.B \-P \fIPORT\fR
Specifies the port number to connect to on the remote machine as with
ssh(1) and scp(1).
.TP
.B \-F \fICONFIG\fR
Specifies an alternative per-user ssh configuration file. Note that
acceptable options in the configuration file are what
.I libssh
supports.
.TP
.B \-i \fIIDENTITY\fR
Specifies the identity file for public key authentication.
.TP
.B \-c \fICIPHER\fR
Selects the cipher to use for encrypting the data transfer. See
.UR https://\:www\:.libssh\:.org/\:features/
libssh features
.UE .
.TP
.B \-M \fIHMAC\fR
Specifies MAC hash algorithms. See
.UR https://\:www\:.libssh\:.org/\:features/
libssh features
.UE .
.TP
.B \-C \fICOMPRESS\fR
Enables compression: yes, no, zlib, zlib@openssh.com. The default is
none. See
.UR https://\:www\:.libssh\:.org/\:features/
libssh features
.UE .
.TP
.B \-g \fICONGESTION\fR
Specifies the TCP congestion control algorithm to use (Linux only).
.TP
.B \-p
Preserves modification times and access times (file mode bits are
preserved by default).
.TP
.B \-H
Disables hostkey checking.
.TP
.B \-d
Increments the ssh debug output level.
.TP
.B \-N
Enables Nagle's algorithm. It is disabled by default.
.TP
.B \-h
Prints help.
.SH EXIT STATUS
Exit status is 0 on success, and >0 if an error occurs.
.SH ENVIRONMENT
.PP
.B mscp
recognizes the following environment variables.
.TP
.B MSCP_SSH_AUTH_PASSWORD
This environment variable passes a password for password
authentication to establish SSH connections.
.TP
.B MSCP_SSH_AUTH_PASSPHRASE
This environment variable passes a passphrase for public-key
authentication for establishing SSH connections.
.SH NOTES
.PP
.B mscp
uses glob(3) for globbing pathnames, including matching patterns for
local and remote paths. However, globbing on the
.I remote
side does not work with musl libc (used in Alpine Linux and the
single-binary version of mscp) because musl libc does not support
GLOB_ALTDIRFUNC.
.PP
.B mscp
does not support remote-to-remote copy, which
.B scp
supports.
.SH EXAMPLES
.PP
Copy a local file to a remote host with different name:
.nf
$ mscp ~/src-file 10.0.0.1:copied-file
.fi
.PP
Copy a local file and a directory to /tmp at a remote host:
.nf
$ mscp ~/src-file dir1 10.0.0.1:/tmp
.fi
.PP
In a long fat network, following options might improve performance:
.nf
$ mscp -n 64 -m 0xffff -a 64 -c aes128-gcm@openssh.com src 10.0.0.1:
.fi
.B -n
increases the number of SSH connections than default,
.B -m
pins threads to specific CPU cores,
.B -a
increases asynchronous inflight SFTP WRITE/READ commands, and
.B -c aes128-gcm@openssh.com
will be faster than the default chacha20-poly1305 cipher, particularly
on hosts that support AES-NI.
.SH "SEE ALSO"
.BR scp (1),
.BR ssh (1),
.BR sshd (8).
.SH "PAPER REFERENCE"
Ryo Nakamura and Yohei Kuga. 2023. Multi-threaded scp: Easy and Fast
File Transfer over SSH. In Practice and Experience in Advanced
Research Computing (PEARC '23). Association for Computing Machinery,
New York, NY, USA, 320323.
.UR https://\:doi\:.org/\:10.1145/\:3569951.3597582
DOI
.UE .
.SH CONTACT INFROMATION
.PP
For pathces, bug reports, or feature requests, please open an issue on
.UR https://\:github\:.com/\:upa/\:mscp
GitHub
.UE .
.SH AUTHORS
Ryo Nakamura <upa@haeena.net>

233
doc/mscp.rst Normal file
View File

@@ -0,0 +1,233 @@
====
MSCP
====
:Date: v0.1.3-23-ga9c59f7
NAME
====
mscp - copy files over multiple SSH connections
SYNOPSIS
========
**mscp** [**-46vqDpHdNh**] [ **-n**\ *NR_CONNECTIONS* ] [
**-m**\ *COREMASK* ] [ **-u**\ *MAX_STARTUPS* ] [ **-I**\ *INTERVAL* ] [
**-s**\ *MIN_CHUNK_SIZE* ] [ **-S**\ *MAX_CHUNK_SIZE* ] [
**-a**\ *NR_AHEAD* ] [ **-b**\ *BUF_SIZE* ] [ **-l**\ *LOGIN_NAME* ] [
**-P**\ *PORT* ] [ **-F**\ *CONFIG* ] [ **-i**\ *IDENTITY* ] [
**-c**\ *CIPHER* ] [ **-M**\ *HMAC* ] [ **-C**\ *COMPRESS* ] *source ...
target*
DESCRIPTION
===========
**mscp** copies files over multiple SSH (SFTP) connections by multiple
threads. It enables transferring (1) multiple files simultaneously and
(2) a large file in parallel, reducing the transfer time for a lot
of/large files over networks.
The usage of **mscp** imitates the **scp** command of *OpenSSH,* for
example:
::
$ mscp srcfile user@example.com:dstfile
Remote hosts only need to run standard **sshd** supporting the SFTP
subsystem, and users need to be able to **ssh** to the hosts as usual.
**mscp** does not require anything else.
**mscp** uses `libssh <https://www.libssh.org>`__ as its SSH
implementation. Thus, supported SSH features, for example,
authentication, encryption, and various options in ssh_config, follow
what *libssh* supports.
OPTIONS
=======
**-n NR_CONNECTIONS**
Specifies the number of SSH connections. The default value is
calculated from the number of CPU cores on the host with the
following formula: floor(log(nr_cores)*2)+1.
**-m COREMASK**
Configures CPU cores to be used by the hexadecimal bitmask. All CPU
cores are used by default.
**-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.
**-I INTERVAL**
Specifies the interval (in seconds) between SSH connection attempts.
Some firewall products treat SSH connection attempts from a single
source IP address for a short period as a brute force attack. This
option inserts intervals between the attempts to avoid being
determined as an attack. The default value is 0.
**-s MIN_CHUNK_SIZE**
Specifies the minimum chunk size. **mscp** divides a file into chunks
and copies the chunks in parallel.
**-S MAX_CHUNK_SIZE**
Specifies the maximum chunk size. The default is file size divided by
the number of connections.
**-a NR_AHEAD**
Specifies the number of inflight SFTP commands. The default value is
32.
**-b BUF_SIZE**
Specifies the buffer size for I/O and transfer over SFTP. The default
value is 16384. Note that the SSH specification restricts buffer size
delivered over SSH. Changing this value is not recommended at
present.
**-4**
Uses IPv4 addresses only.
**-6**
Uses IPv6 addresses only.
**-v**
Increments the verbose output level.
**-q**
Quiet mode: turns off all outputs.
**-D**
Dry-run mode: it scans source files to be copied, calculates chunks,
and resolves destination file paths. Dry-run mode with **-vv** option
enables confirming files to be copied and their destination paths.
**-r**
No effect. **mscp** copies recursively if a source path is a
directory. This option exists for just compatibility.
**-l LOGIN_NAME**
Specifies the username to log in on the remote machine as with
*ssh(1).*
**-P PORT**
Specifies the port number to connect to on the remote machine as with
ssh(1) and scp(1).
**-F CONFIG**
Specifies an alternative per-user ssh configuration file. Note that
acceptable options in the configuration file are what *libssh*
supports.
**-i IDENTITY**
Specifies the identity file for public key authentication.
**-c CIPHER**
Selects the cipher to use for encrypting the data transfer. See
`libssh features <https://www.libssh.org/features/>`__.
**-M HMAC**
Specifies MAC hash algorithms. See `libssh
features <https://www.libssh.org/features/>`__.
**-C COMPRESS**
Enables compression: yes, no, zlib, zlib@openssh.com. The default is
none. See `libssh features <https://www.libssh.org/features/>`__.
**-p**
Preserves modification times and access times (file mode bits are
preserved by default).
**-H**
Disables hostkey checking.
**-d**
Increments the ssh debug output level.
**-N**
Enables Nagle's algorithm. It is disabled by default.
**-h**
Prints help.
EXIT STATUS
===========
Exit status is 0 on success, and >0 if an error occurs.
ENVIRONMENT
===========
**mscp** recognizes the following environment variables.
**MSCP_SSH_AUTH_PASSWORD**
This environment variable passes a password for password
authentication to establish SSH connections.
**MSCP_SSH_AUTH_PASSPHRASE**
This environment variable passes a passphrase for public-key
authentication for establishing SSH connections.
NOTES
=====
**mscp** uses glob(3) for globbing pathnames, including matching
patterns for local and remote paths. However, globbing on the *remote*
side does not work with musl libc (used in Alpine Linux and the
single-binary version of mscp) because musl libc does not support
GLOB_ALTDIRFUNC.
**mscp** does not support remote-to-remote copy, which **scp** supports.
EXAMPLES
========
Copy a local file to a remote host with different name:
::
$ mscp ~/src-file 10.0.0.1:copied-file
Copy a local file and a directory to /tmp at a remote host:
::
$ mscp ~/src-file dir1 10.0.0.1:/tmp
In a long fat network, following options might improve performance:
::
$ mscp -n 64 -m 0xffff -a 64 -c aes128-gcm@openssh.com src 10.0.0.1:
**-n** increases the number of SSH connections than default, **-m** pins
threads to specific CPU cores, **-a** increases asynchronous inflight
SFTP WRITE/READ commands, and **-c aes128-gcm@openssh.com** will be
faster than the default chacha20-poly1305 cipher, particularly on hosts
that support AES-NI.
SEE ALSO
========
**scp**\ (1), **ssh**\ (1), **sshd**\ (8).
PAPER REFERENCE
===============
Ryo Nakamura and Yohei Kuga. 2023. Multi-threaded scp: Easy and Fast
File Transfer over SSH. In Practice and Experience in Advanced Research
Computing (PEARC '23). Association for Computing Machinery, New York,
NY, USA, 320323. `DOI <https://doi.org/10.1145/3569951.3597582>`__.
CONTACT INFROMATION
===================
For pathces, bug reports, or feature requests, please open an issue on
`GitHub <https://github.com/upa/mscp>`__.
AUTHORS
=======
Ryo Nakamura <upa@haeena.net>

View File

@@ -1,37 +0,0 @@
Build docker containers.
```console
cd ..
docker build -t mscp-ubuntu:20.04 -f docker/ubuntu-20.04.Dockerfile .
docker build -t mscp-ubuntu:22.04 -f docker/ubuntu-22.04.Dockerfile .
docker build -t mscp-rocky:8.8 -f docker/rocky-8.Dockerfile .
```
Test `mscp` in the containers.
```console
docker run --init --rm mscp-ubuntu:20.04 /mscp/scripts/test-in-container.sh
docker run --init --rm mscp-ubuntu:22.04 /mscp/scripts/test-in-container.sh
docker run --init --rm mscp-rocky:8.9 /mscp/scripts/test-in-container.sh
```
Retrieve deb/rpm packages.
```console
docker run --rm -v (pwd):/out mscp-ubuntu:20.04 \
cp /mscp/build/mscp_ubuntu-20.04-x86_64.deb /out/
docker run --rm -v (pwd):/out mscp-ubuntu:22.04 \
cp /mscp/build/mscp_ubuntu-22.04-x86_64.deb /out/
docker run --rm -v (pwd):/out mscp-rocky:8.8 \
cp /mscp/build/mscp_rocky-8.8-x86_64.rpm /out/
```
I don't know whether these are good way.

View File

@@ -1,36 +0,0 @@
FROM almalinux:8.8
# install pytest, sshd for test, and rpm-build
RUN set -ex && yum -y install \
python3 python3-pip python3-devel openssh openssh-server openssh-clients rpm-build
RUN python3 -m pip install pytest
# preparation for sshd
RUN mkdir /var/run/sshd \
&& ssh-keygen -A \
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
&& mv /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
ARG mscpdir="/mscp"
COPY . ${mscpdir}
# install build dependency
RUN ${mscpdir}/scripts/install-build-deps.sh
# build
RUN cd ${mscpdir} \
&& rm -rf build \
&& cmake -B build \
&& cd ${mscpdir}/build \
&& make \
&& cpack -G RPM CPackConfig.cmake \
&& rpm -iv *.rpm
# install mscp python module
RUN cd ${mscpdir} \
&& python3 setup.py install --user

View File

@@ -1,41 +0,0 @@
FROM alpine:3.17
# Build mscp with conan to create single binary mscp
RUN apk add --no-cache \
gcc make cmake python3 py3-pip perl linux-headers libc-dev \
openssh bash python3-dev g++
RUN pip3 install conan pytest
# preparation for sshd
RUN ssh-keygen -A
RUN mkdir /var/run/sshd \
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
&& mv /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
# Build mscp as a single binary
RUN conan profile detect --force
ARG mscpdir="/mscp"
COPY . ${mscpdir}
RUN cd ${mscpdir} \
&& rm -rf build \
&& conan install . --output-folder=build --build=missing \
&& cd ${mscpdir}/build \
&& cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake \
-DBUILD_CONAN=ON -DBUILD_STATIC=ON \
&& make \
&& cp mscp /usr/bin/ \
&& cp mscp /mscp/build/mscp_alpine-3.17-x86_64.static
# copy mscp to PKG FILE NAME because this build doesn't use CPACK
# install mscp python module
RUN cd ${mscpdir} \
&& python3 setup.py install --user

View File

@@ -1,35 +0,0 @@
FROM rockylinux:8.8
# install pytest, sshd for test, and rpm-build
RUN set -ex && yum -y install \
python3 python3-pip python3-devel openssh openssh-server openssh-clients rpm-build
RUN python3 -m pip install pytest
# preparation for sshd
RUN mkdir /var/run/sshd \
&& ssh-keygen -A \
&& ssh-keygen -f /root/.ssh/id_rsa -N "" \
&& mv /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
ARG mscpdir="/mscp"
COPY . ${mscpdir}
# install build dependency
RUN ${mscpdir}/scripts/install-build-deps.sh
# build
RUN cd ${mscpdir} \
&& rm -rf build \
&& cmake -B build \
&& cd ${mscpdir}/build \
&& make \
&& cpack -G RPM CPackConfig.cmake \
&& rpm -iv *.rpm
# install mscp python module
RUN cd ${mscpdir} \
&& python3 setup.py install --user

3
examples/.gitignore vendored
View File

@@ -1,3 +0,0 @@
simple-copy-dest
*.img
.ipynb_checkpoints

View File

@@ -1,226 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ccda9e3a-35de-43fc-9b6e-02475c763f6b",
"metadata": {},
"source": [
"# mscp python binding example"
]
},
{
"cell_type": "code",
"execution_count": 60,
"id": "df04d655-a082-47eb-9a1e-154ebc2a5655",
"metadata": {},
"outputs": [],
"source": [
"import glob\n",
"import time\n",
"import os\n",
"\n",
"import mscp"
]
},
{
"cell_type": "code",
"execution_count": 53,
"id": "e9ed4519-c3fd-4639-89a5-1c1cdffd9519",
"metadata": {},
"outputs": [],
"source": [
"this_dir = os.getcwd()"
]
},
{
"cell_type": "markdown",
"id": "fee75bf8-df40-45f4-81d1-113069c34f13",
"metadata": {},
"source": [
"## Simple copy"
]
},
{
"cell_type": "code",
"execution_count": 54,
"id": "2b06e6d3-30cc-47be-bd4f-af27eb141c8c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['../src/ssh.c',\n",
" '../src/mscp.c',\n",
" '../src/platform.c',\n",
" '../src/pymscp.c',\n",
" '../src/main.c',\n",
" '../src/path.c',\n",
" '../src/message.c',\n",
" '../src/fileops.c']"
]
},
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# preparing files to be transferred\n",
"c_sources = glob.glob(\"../src/*.c\")\n",
"c_sources"
]
},
{
"cell_type": "code",
"execution_count": 55,
"id": "89bb4558-9472-4d26-9af3-24f426b15edc",
"metadata": {},
"outputs": [],
"source": [
"# copy files using mscp\n",
"dst_dir = this_dir + \"/simple-copy-dest\"\n",
"m = mscp.mscp(\"localhost\", mscp.LOCAL2REMOTE)\n",
"m.copy(c_sources, dst_dir)"
]
},
{
"cell_type": "code",
"execution_count": 56,
"id": "6daf2c98-8905-4039-b82a-a593df3107fe",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['ssh.c',\n",
" 'mscp.c',\n",
" 'platform.c',\n",
" 'pymscp.c',\n",
" 'main.c',\n",
" 'path.c',\n",
" 'message.c',\n",
" 'fileops.c']"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"os.listdir(\"simple-copy-dest\")"
]
},
{
"cell_type": "markdown",
"id": "f4a3869a-878e-43b0-9758-a049eaf8b5bd",
"metadata": {},
"source": [
"## Simple Copy with Python Rich ProgressBar"
]
},
{
"cell_type": "code",
"execution_count": 64,
"id": "e7cb7cd6-b845-4d26-93ed-aee8ed3983ab",
"metadata": {},
"outputs": [],
"source": [
"# make a 256MB file\n",
"src = \"example-256MB-src.img\"\n",
"with open(src, \"wb\") as f:\n",
" f.seek(128 * 1024 * 1024 -1, 0)\n",
" f.write(b'1')"
]
},
{
"cell_type": "code",
"execution_count": 69,
"id": "878607ed-5c06-4b15-81ac-9845dad0c9c6",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "b700e9fc00464969a22a26300404dc35",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Output()"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
],
"text/plain": []
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">\n",
"</pre>\n"
],
"text/plain": [
"\n"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# copy the 256MB file while ploting progress bar using python rich\n",
"dst = this_dir + \"/example-256MB-dst.img\"\n",
"\n",
"kw = {\"nr_threads\": 1, \"nr_ahead\": 1} # slow mscp to watch the progress bar\n",
"\n",
"m = mscp.mscp(\"localhost\", mscp.LOCAL2REMOTE, **kw)\n",
"m.copy(src, dst, nonblock = True)\n",
"\n",
"# m.stats() returns total bytes to be transferred, bytes transferred (done), and finished (bool).\n",
"total, done, finished = m.stats()\n",
"with Progress() as progress:\n",
"\n",
" task = progress.add_task(f\"[green]Copying {src}\", total = total)\n",
"\n",
" while not progress.finished:\n",
" total, done, finished = m.stats()\n",
" progress.update(task, completed = done)\n",
" time.sleep(0.5)\n",
"\n",
"m.join()\n",
"m.cleanup()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env python3
"""mscp.py
An example python script running mscp
"""
import argparse
import time
import sys
from rich.progress import Progress
import mscp
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--from", dest = "fr",
metavar = "REMOTE", default = None,
help = "copy a file from this remote host")
parser.add_argument("-t", "--to", metavar = "REMOTE", default = None,
help = "copy a file to this remote host")
parser.add_argument("source", help = "path to source file to be copied")
parser.add_argument("destination", help = "path of copy destination")
args = parser.parse_args()
if args.fr and args.to:
print("-f and -t are exclusive", file = sys.stderr)
sys.exit(1)
elif args.fr:
d = mscp.REMOTE2LOCAL
remote = args.fr
elif args.to:
d = mscp.LOCAL2REMOTE
remote = args.to
else:
print("-f or -t must be specified", file = sys.stderr)
sys.exit(1)
m = mscp.mscp(remote, d)
m.connect()
m.add_src_path(args.source)
m.set_dst_path(args.destination)
m.scan()
m.start()
total, done, finished = m.stats()
with Progress() as progress:
task = progress.add_task("[green]Copying...", total = total)
while not progress.finished:
total, done, finished = m.stats()
progress.update(task, completed = done)
time.sleep(0.5)
m.join()
m.cleanup()
if __name__ == "__main__":
main()

12
include/config.h.in Normal file
View File

@@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#ifndef _CONFIG_H_
#define _CONFIG_H_
#define MSCP_VERSION "@MSCP_VERSION@"
#define MSCP_BUILD_VERSION "@MSCP_BUILD_VERSION@"
/* Define to 1 if you have the strlcat function. */
#cmakedefine HAVE_STRLCAT 1
#endif /* _CONFIG_H_ */

View File

@@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#ifndef _MSCP_H_
#define _MSCP_H_
@@ -30,8 +31,6 @@
#define MSCP_DIRECTION_L2R 1 /** Indicates local to remote copy */
#define MSCP_DIRECTION_R2L 2 /** Indicates remote to local copy */
#define MSCP_MAX_COREMASK_STR 64
/**
* @struct mscp_opts
* @brief Structure configuring mscp.
@@ -42,22 +41,14 @@ struct mscp_opts {
size_t min_chunk_sz; /** minimum chunk size (default 64MB) */
size_t max_chunk_sz; /** maximum chunk size (default file size/nr_threads) */
size_t buf_sz; /** buffer size, default 16k. */
char coremask[MSCP_MAX_COREMASK_STR]; /** hex to specifiy usable cpu cores */
int max_startups; /* sshd MaxStartups concurrent connections */
char *coremask; /** hex to specifiy usable cpu cores */
int max_startups; /** sshd MaxStartups concurrent connections */
int interval; /** interval between SSH connection attempts */
bool preserve_ts; /** preserve file timestamps */
int severity; /** messaging severity. set MSCP_SERVERITY_* */
int msg_fd; /** fd to output message. default STDOUT (0),
* and -1 disables output */
};
#define MSCP_SSH_MAX_LOGIN_NAME 64
#define MSCP_SSH_MAX_PORT_STR 32
#define MSCP_SSH_MAX_IDENTITY_PATH PATH_MAX
#define MSCP_SSH_MAX_CIPHER_STR 32
#define MSCP_SSH_MAX_HMAC_STR 32
#define MSCP_SSH_MAX_COMP_STR 32 /* yes, no, zlib, zlib@openssh.com, none */
#define MSCP_SSH_MAX_PASSWORD 128
#define MSCP_SSH_MAX_PASSPHRASE 128
/**
* @struct mscp_ssh_opts
@@ -65,22 +56,35 @@ struct mscp_opts {
*/
struct mscp_ssh_opts {
/* ssh options */
char login_name[MSCP_SSH_MAX_LOGIN_NAME]; /** ssh username */
char port[MSCP_SSH_MAX_PORT_STR]; /** ssh port */
char config[PATH_MAX]; /** path to ssh_config, default ~/.ssh/config*/
char identity[MSCP_SSH_MAX_IDENTITY_PATH]; /** path to private key */
char cipher[MSCP_SSH_MAX_CIPHER_STR]; /** cipher spec */
char hmac[MSCP_SSH_MAX_HMAC_STR]; /** hmacp spec */
char compress[MSCP_SSH_MAX_COMP_STR]; /** yes, no, zlib@openssh.com */
char *login_name; /** ssh username */
char *port; /** ssh port */
int ai_family; /** address family */
char *config; /** path to ssh_config, default ~/.ssh/config*/
char *identity; /** path to private key */
char *cipher; /** cipher spec */
char *hmac; /** hmacp spec */
char *compress; /** yes, no, zlib@openssh.com */
char *ccalgo; /** TCP cc algorithm */
char password[MSCP_SSH_MAX_PASSWORD]; /** password auth passowrd */
char passphrase[MSCP_SSH_MAX_PASSPHRASE]; /** passphrase for private key */
char *password; /** password auth passowrd */
char *passphrase; /** passphrase for private key */
int debug_level; /** inclirement libssh debug output level */
bool no_hostkey_check; /** do not check host keys */
bool enable_nagle; /** enable Nagle's algorithm if true */
};
/** @def
* Environment variable that passes password for ssh password auth
*/
#define ENV_SSH_AUTH_PASSWORD "MSCP_SSH_AUTH_PASSWORD"
/** @def
* Environment vraible that passes passphrase for private key
*/
#define ENV_SSH_AUTH_PASSPHRASE "MSCP_SSH_AUTH_PASSPHRASE"
/**
* @struct mscp_stats
* @brief Structure to get mscp statistics
@@ -117,7 +121,6 @@ struct mscp *mscp_init(const char *remote_host, int direction,
* @param m mscp instance.
*
* @return 0 on success, < 0 if an error occured.
* mscp_get_error() can be used to retrieve error message.
*/
int mscp_connect(struct mscp *m);
@@ -133,7 +136,6 @@ int mscp_connect(struct mscp *m);
* @param src_path source file path to be copied.
*
* @return 0 on success, < 0 if an error occured.
* mscp_get_error() can be used to retrieve error message.
*/
int mscp_add_src_path(struct mscp *m, const char *src_path);
@@ -147,7 +149,6 @@ int mscp_add_src_path(struct mscp *m, const char *src_path);
* @param dst_path destination path to which source files copied.
*
* @return 0 on success, < 0 if an error occured.
* mscp_get_error() can be used to retrieve error message.
*/
int mscp_set_dst_path(struct mscp *m, const char *dst_path);
@@ -162,7 +163,6 @@ int mscp_set_dst_path(struct mscp *m, const char *dst_path);
* @param m mscp instance.
*
* @return 0 on success, < 0 if an error occured.
* mscp_get_error() can be used to retrieve error message.
*/
int mscp_scan(struct mscp *m);
@@ -173,7 +173,6 @@ int mscp_scan(struct mscp *m);
*
* @param m mscp instance.
* @return 0 on success, < 0 if an error occured.
* mscp_get_error() can be used to retrieve error message.
*/
int mscp_scan_join(struct mscp *m);
@@ -186,7 +185,6 @@ int mscp_scan_join(struct mscp *m);
* @param m mscp instance.
*
* @return number of threads on success, < 0 if an error occured.
* mscp_get_error() can be used to retrieve error message.
*
* @see mscp_join()
*/
@@ -208,7 +206,6 @@ void mscp_stop(struct mscp *m);
* @param m mscp instance.
*
* @return 0 on success, < 0 if an error occured.
* mscp_get_error() can be used to retrieve error message.
*/
int mscp_join(struct mscp *m);
@@ -258,15 +255,4 @@ enum {
};
/**
* @brief Get the recent error message from libmscp. Note that this
* function is not thread-safe.
*
* @return pointer to the message.
*/
const char *mscp_get_error(void);
#endif /* _MSCP_H_ */

View File

@@ -1,7 +0,0 @@
#ifndef _MSCP_VERSION_H_
#define _MSCP_VERSION_H_
#define MSCP_VERSION "@MSCP_VERSION@"
#define MSCP_BUILD_VERSION "@MSCP_BUILD_VERSION@"
#endif /* _MSCP_VERSION_H_ */

2
libssh

Submodule libssh updated: e8322817a9...6f1b1e76bb

View File

@@ -1 +0,0 @@
from mscp.mscp import *

View File

@@ -1,187 +0,0 @@
_retry_import_pymscp = False
try:
import pymscp
except ImportError:
_retry_import_pymscp = True
if _retry_import_pymscp:
""" libmscp.so is not installed on system library paths. So retry
to import libmscp.so installed on the mscp python module
directory.
"""
import os
import sys
import ctypes
if sys.platform == "linux":
libmscp = "libmscp.so"
elif sys.platform == "darwin":
libmscp = "libmscp.dylib"
mscp_dir = os.path.dirname(__file__)
ctypes.cdll.LoadLibrary("{}/{}".format(mscp_dir, libmscp))
import pymscp
# inherit static values from pymscp
LOCAL2REMOTE = pymscp.LOCAL2REMOTE
REMOTE2LOCAL = pymscp.REMOTE2LOCAL
SEVERITY_NONE = pymscp.SEVERITY_NONE
SEVERITY_ERR = pymscp.SEVERITY_ERR
SEVERITY_WARN = pymscp.SEVERITY_WARN
SEVERITY_NOTICE = pymscp.SEVERITY_NOTICE
SEVERITY_INFO = pymscp.SEVERITY_INFO
SEVERITY_DEBUG = pymscp.SEVERITY_DEBUG
STATE_INIT = 0
STATE_CONNECTED = 1
STATE_SCANNED = 2
STATE_RUNNING = 3
STATE_STOPPED = 4
STATE_JOINED = 5
STATE_CLEANED = 6
STATE_RELEASED = 7
_state_str = {
STATE_INIT: "init",
STATE_CONNECTED: "connected",
STATE_SCANNED: "scanned",
STATE_RUNNING: "running",
STATE_STOPPED: "stopped",
STATE_JOINED: "joined",
STATE_CLEANED: "cleaned",
STATE_RELEASED: "released",
}
class mscp:
def __init__(self, remote: str, direction: int, **kwargs):
self.remote = remote
self.direction = direction
kwargs["remote"] = remote
kwargs["direction"] = direction
self.m = pymscp.mscp_init(**kwargs)
self.src_paths = []
self.dst_path = None
self.state = STATE_INIT
def __str__(self):
if not hasattr(self, "state"):
# this instance failed on mscp_init
return "mscp:{}:init-failed"
return "mscp:{}:{}".format(self.remote, self.__state2str())
def __repr__(self):
return "<{}>".format(str(self))
def __del__(self):
if not hasattr(self, "state"):
return # this instance failed on mscp_init
if self.state == STATE_RUNNING:
self.stop()
if self.state == STATE_STOPPED:
self.join()
self.cleanup()
self.release()
def __state2str(self):
return _state_str[self.state]
def connect(self):
if not (self.state == STATE_INIT or state.state == STATE_CLEANED):
raise RuntimeError("invalid mscp state: {}".format(self.__state2str()))
pymscp.mscp_connect(m = self.m)
self.state = STATE_CONNECTED
def add_src_path(self, src_path: str):
if type(src_path) != str:
raise ValueError("src_path must be str: {}".format(src_path))
self.src_paths.append(src_path)
pymscp.mscp_add_src_path(m = self.m, src_path = src_path)
def set_dst_path(self, dst_path: str):
if type(dst_path) != str:
raise ValueError("dst_path must be str: {}".format(dst_path))
self.dst_path = dst_path
pymscp.mscp_set_dst_path(m = self.m, dst_path = dst_path);
def scan(self):
if self.state == STATE_SCANNED:
return
if self.state != STATE_CONNECTED:
raise RuntimeError("invalid mscp state: {}".format(self.__state2str()))
if not self.src_paths:
raise RuntimeError("src path list is empty")
if self.dst_path == None:
raise RuntimeError("dst path is not set")
pymscp.mscp_scan(m = self.m)
self.state = STATE_SCANNED
def start(self):
if self.state != STATE_SCANNED:
raise RuntimeError("invalid mscp state: {}".format(self.__state2str()))
pymscp.mscp_start(m = self.m)
self.state = STATE_RUNNING
def stop(self):
if self.state != STATE_RUNNING:
raise RuntimeError("invalid mscp state: {}".format(self.__state2str()))
pymscp.mscp_stop(m = self.m)
self.state = STATE_STOPPED
def join(self):
if self.state == STATE_JOINED:
return
if not (self.state == STATE_RUNNING or self.state == STATE_STOPPED):
raise RuntimeError("invalid mscp state: {}".format(self.__state2str()))
pymscp.mscp_join(m = self.m)
self.state = STATE_JOINED
def stats(self):
return pymscp.mscp_get_stats(m = self.m)
def cleanup(self):
if self.state == STATE_RUNNING:
raise RuntimeError("invalid mscp state: {}".format(self.__state2str()))
pymscp.mscp_cleanup(m = self.m)
self.state = STATE_CLEANED
def release(self):
if self.state != STATE_CLEANED:
raise RuntimeError("invalid mscp state: {}".format(self.__state2str()))
pymscp.mscp_free(m = self.m)
self.state = STATE_RELEASED
# Simple interface: mscp.copy(src, dst)
def copy(self, src, dst, nonblock = False):
if self.state < STATE_CONNECTED:
self.connect()
if type(src) == list:
for path in src:
self.add_src_path(path)
elif type(src) == str:
self.add_src_path(src)
else:
raise ValueError("src must be str of list: '{}'".format(src))
self.set_dst_path(dst)
self.scan()
self.start()
if nonblock:
return
self.join()
self.cleanup()

View File

@@ -1,13 +1,5 @@
Patch(es) in this directory introduces `sftp_async_write()` and
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).
```console
git clone https://git.libssh.org/projects/libssh.git/ --depth=1 -b libssh-0.10.4
cd libssh
git apply ../pathc/libssh-0.10.4.patch
# then build libssh
```

View File

@@ -1,3 +1,28 @@
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index 7103f303..c64eb39d 100644
--- a/ConfigureChecks.cmake
+++ b/ConfigureChecks.cmake
@@ -258,6 +258,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 1357615b..1e915ead 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -237,6 +237,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 a55a1b40..e34e075c 100644
--- a/include/libssh/buffer.h
@@ -12,10 +37,18 @@ index a55a1b40..e34e075c 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 7857a77b..3eef7a16 100644
index 7857a77b..6b4d481c 100644
--- a/include/libssh/libssh.h
+++ b/include/libssh/libssh.h
@@ -833,6 +833,7 @@ LIBSSH_API const char* ssh_get_hmac_in(ssh_session session);
@@ -402,6 +402,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,
@@ -833,6 +834,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);
@@ -23,7 +56,7 @@ index 7857a77b..3eef7a16 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 +844,8 @@ LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer);
@@ -843,6 +845,8 @@ 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);
@@ -32,6 +65,18 @@ index 7857a77b..3eef7a16 100644
#ifndef LIBSSH_LEGACY_0_4
#include "libssh/legacy.h"
#endif
diff --git a/include/libssh/session.h b/include/libssh/session.h
index d3e5787c..15183d1b 100644
--- a/include/libssh/session.h
+++ b/include/libssh/session.h
@@ -232,6 +232,7 @@ struct ssh_session_struct {
int gss_delegate_creds;
int 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 c855df8a..0fcdb9b8 100644
--- a/include/libssh/sftp.h
@@ -158,6 +203,106 @@ index e0068015..cc0caf35 100644
/**
* @brief Ensure the buffer has at least a certain preallocated size.
*
diff --git a/src/connect.c b/src/connect.c
index 57e37e63..c02397d5 100644
--- a/src/connect.c
+++ b/src/connect.c
@@ -156,6 +156,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
*
@@ -256,6 +270,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 && (errno != 0) && (errno != EINPROGRESS)) {
diff --git a/src/options.c b/src/options.c
index 49aaefa2..9f7360c3 100644
--- a/src/options.c
+++ b/src/options.c
@@ -210,6 +210,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->common.log_verbosity = src->common.log_verbosity;
new->common.callbacks = src->common.callbacks;
@@ -450,6 +451,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
@@ -1013,6 +1018,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 6025c133..6b197526 100644
--- a/src/session.c
+++ b/src/session.c
@@ -108,6 +108,7 @@ ssh_session ssh_new(void)
session->opts.fd = -1;
session->opts.compressionlevel = 7;
session->opts.nodelay = 0;
+ session->opts.ccalgo = NULL;
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..702623a0 100644
--- a/src/sftp.c

View File

@@ -0,0 +1,530 @@
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index 9de10225..0f3d20ed 100644
--- a/ConfigureChecks.cmake
+++ b/ConfigureChecks.cmake
@@ -258,6 +258,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 cc83734d..f74cd03b 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -237,6 +237,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 1fce7b76..b64d1455 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 669a0a96..da5b4099 100644
--- a/include/libssh/libssh.h
+++ b/include/libssh/libssh.h
@@ -368,6 +368,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,
@@ -402,6 +403,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,
@@ -833,6 +835,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)
@@ -843,6 +846,8 @@ 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);
+
#ifndef LIBSSH_LEGACY_0_4
#include "libssh/legacy.h"
#endif
diff --git a/include/libssh/session.h b/include/libssh/session.h
index 97936195..e4fc4fce 100644
--- a/include/libssh/session.h
+++ b/include/libssh/session.h
@@ -249,6 +249,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;
@@ -258,6 +259,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 c713466e..e27fe326 100644
--- a/include/libssh/sftp.h
+++ b/include/libssh/sftp.h
@@ -565,6 +565,10 @@ LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_
*/
LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count);
+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 Seek to a specific location in a file.
*
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 807313b5..86487087 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -448,6 +448,11 @@ if (BUILD_STATIC_LIB)
if (WIN32)
target_compile_definitions(ssh-static PUBLIC "LIBSSH_STATIC")
endif (WIN32)
+
+ install(TARGETS ssh-static
+ EXPORT libssh-config
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ COMPONENT libraries)
endif (BUILD_STATIC_LIB)
message(STATUS "Threads_FOUND=${Threads_FOUND}")
diff --git a/src/buffer.c b/src/buffer.c
index 8991e006..e0414801 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 15cae644..02ef43b4 100644
--- a/src/connect.c
+++ b/src/connect.c
@@ -114,7 +114,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;
@@ -123,7 +123,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) {
@@ -156,6 +156,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
*
@@ -173,7 +187,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)",
@@ -199,7 +213,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)",
@@ -256,6 +270,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 && (errno != 0) && (errno != EINPROGRESS)) {
diff --git a/src/options.c b/src/options.c
index b3ecffe1..8de24ed6 100644
--- a/src/options.c
+++ b/src/options.c
@@ -217,6 +217,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->common.log_verbosity = src->common.log_verbosity;
new->common.callbacks = src->common.callbacks;
@@ -268,6 +269,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
@@ -458,6 +462,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
@@ -571,6 +579,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;
@@ -1017,6 +1040,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 8c509699..307388e5 100644
--- a/src/session.c
+++ b/src/session.c
@@ -105,9 +105,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.flags = SSH_OPT_FLAG_PASSWORD_AUTH |
SSH_OPT_FLAG_PUBKEY_AUTH |
diff --git a/src/sftp.c b/src/sftp.c
index e01012a8..702623a0 100644
--- a/src/sftp.c
+++ b/src/sftp.c
@@ -2228,6 +2228,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) {

442
patch/libssh-0.10.6.patch Normal file
View File

@@ -0,0 +1,442 @@
diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake
index 9de10225..0f3d20ed 100644
--- a/ConfigureChecks.cmake
+++ b/ConfigureChecks.cmake
@@ -258,6 +258,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 cc83734d..f74cd03b 100644
--- a/config.h.cmake
+++ b/config.h.cmake
@@ -237,6 +237,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 1fce7b76..b64d1455 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 669a0a96..b6a93ac7 100644
--- a/include/libssh/libssh.h
+++ b/include/libssh/libssh.h
@@ -402,6 +402,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,
@@ -833,6 +834,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)
@@ -843,6 +845,8 @@ 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);
+
#ifndef LIBSSH_LEGACY_0_4
#include "libssh/legacy.h"
#endif
diff --git a/include/libssh/session.h b/include/libssh/session.h
index 97936195..e4a7f80c 100644
--- a/include/libssh/session.h
+++ b/include/libssh/session.h
@@ -258,6 +258,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 c713466e..e27fe326 100644
--- a/include/libssh/sftp.h
+++ b/include/libssh/sftp.h
@@ -565,6 +565,10 @@ LIBSSH_API int sftp_async_read(sftp_file file, void *data, uint32_t len, uint32_
*/
LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count);
+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 Seek to a specific location in a file.
*
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 807313b5..86487087 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -448,6 +448,11 @@ if (BUILD_STATIC_LIB)
if (WIN32)
target_compile_definitions(ssh-static PUBLIC "LIBSSH_STATIC")
endif (WIN32)
+
+ install(TARGETS ssh-static
+ EXPORT libssh-config
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ COMPONENT libraries)
endif (BUILD_STATIC_LIB)
message(STATUS "Threads_FOUND=${Threads_FOUND}")
diff --git a/src/buffer.c b/src/buffer.c
index 8991e006..e0414801 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 15cae644..e7520f40 100644
--- a/src/connect.c
+++ b/src/connect.c
@@ -156,6 +156,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
*
@@ -256,6 +270,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 && (errno != 0) && (errno != EINPROGRESS)) {
diff --git a/src/options.c b/src/options.c
index 38511455..a183605d 100644
--- a/src/options.c
+++ b/src/options.c
@@ -217,6 +217,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->common.log_verbosity = src->common.log_verbosity;
new->common.callbacks = src->common.callbacks;
@@ -458,6 +459,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
@@ -1023,6 +1028,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 8c509699..88602b6a 100644
--- a/src/session.c
+++ b/src/session.c
@@ -108,6 +108,7 @@ ssh_session ssh_new(void)
session->opts.fd = -1;
session->opts.compressionlevel = 7;
session->opts.nodelay = 0;
+ session->opts.ccalgo = NULL;
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..702623a0 100644
--- a/src/sftp.c
+++ b/src/sftp.c
@@ -2228,6 +2228,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) {

3
rpm/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# generated by cmake
mscp.spec

45
rpm/mscp.spec.in Normal file
View File

@@ -0,0 +1,45 @@
Name: mscp
Version: @MSCP_VERSION@
Release: 1%{?dist}
Summary: mscp, fast file transfer over multiple SSH connections
Group: Applications/Internet
License: GPLv3
URL: https://github.com/upa/mscp
Source0: %{name}-%{version}.tar.gz
BuildRequires: gcc make cmake zlib-devel openssl-devel
Requires: glibc crypto-policies krb5-libs openssl-libs libcom_err
%description
mscp transfers files over multiple SSH connections. Multiple threads
and connections in mscp transfer (1) multiple files simultaneously
and (2) a large file in parallel. It would shorten the waiting time
for transferring a lot of/large files over networks.
%global debug_package %{nil}
%prep
%setup -q
%build
cmake -S . -B build -DINSTALL_EXECUTABLE_ONLY=ON
make -C build %{?_smp_mflags}
%install
make -C build install DESTDIR=%{buildroot}
%files
/usr/local/bin/mscp
/usr/local/share/man/man1/mscp.1
%changelog
* Wed Feb 07 2024 Ryo nakamura <upa@haeena.net> - 0.1.4-0
- RPM release for v0.1.4
* Sat Feb 03 2024 Ryo nakamura <upa@haeena.net> - 0.1.3-0
- Initial release for rpm packaging

View File

@@ -1,7 +1,10 @@
#!/bin/bash -eu
#!/usr/bin/env bash
#
# Install build dpenedencies.
set -e
set -u
platform=$(uname -s)
if [ -e /etc/os-release ]; then
@@ -16,13 +19,16 @@ case $platform in
brew install openssl@1.1
;;
Linux-ubuntu*)
apt-get install -y \
apt-get install --no-install-recommends -y \
gcc make cmake zlib1g-dev libssl-dev libkrb5-dev
;;
Linux-centos* | Linux-rhel* | Linux-rocky* | Linux-almalinux)
yum install -y \
gcc make cmake zlib-devel openssl-devel rpm-build
;;
FreeBSD-freebsd)
pkg install cmake
;;
*)
echo "unsupported platform: $platform"
exit 1

View File

@@ -1,23 +0,0 @@
#!/bin/bash -e
#
# Print install dpenedencies on Linux. CMake runs this script to obtain deps for CPACK.
# mscp dependes on packages on which libssh depends.
source /etc/os-release
release=$1
case $release in
ubuntu-20.04*)
echo "libc6 (>= 2.27), libgssapi-krb5-2 (>= 1.17), libssl1.1 (>= 1.1.1), zlib1g (>= 1:1.1.4)"
;;
ubuntu-22.04*)
echo "libc6 (>= 2.33), libgssapi-krb5-2 (>= 1.17), libssl3 (>= 3.0.0~~alpha1), zlib1g (>= 1:1.1.4)"
;;
centos* | rhel* | rocky* | almalinux*)
echo "glibc crypto-policies krb5-libs openssl-libs libcom_err"
;;
*)
echo "$(basename $0): unsupported install dependency: $release"
exit 1
esac

View File

@@ -15,6 +15,8 @@ if [ ! -e /var/run/sshd.pid ]; then
fi
ssh-keyscan localhost >> ${HOME}/.ssh/known_hosts
ssh-keyscan 127.0.0.1 >> ${HOME}/.ssh/known_hosts
ssh-keyscan ::1 >> ${HOME}/.ssh/known_hosts
# Run test
python3 -m pytest ../test -v

View File

@@ -1,37 +0,0 @@
from setuptools import setup, Extension, find_packages
import sys
import os
mypackage_root_dir = os.path.dirname(__file__)
with open(os.path.join(mypackage_root_dir, 'VERSION')) as version_file:
version = version_file.read().strip()
if sys.platform == "linux":
libmscp = "libmscp.so"
elif sys.platform == "darwin":
libmscp = "libmscp.dylib"
data_dir = sys.prefix + "/lib"
libmscp = "build/" + libmscp
setup(
name='mscp',
version = version,
description = "libmscp python binding",
author = "Ryo Nakamura",
author_email = "upa@haeena.net",
url = "https://github.com/upa/mscp",
packages = find_packages("mscp"),
package_dir = {"": "mscp"},
data_files = [ (data_dir, [libmscp])],
py_modules = [ "mscp" ],
ext_modules = [
Extension(
'pymscp',
['src/pymscp.c'],
library_dirs = ['build'],
libraries = ['mscp'],
include_dirs = ['include']
)
]
)

View File

@@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#ifndef _ATOMIC_H_
#define _ATOMIC_H_
@@ -5,8 +6,6 @@
#include <assert.h>
#include <pthread.h>
#include <message.h>
typedef int refcnt;
static inline void refcnt_inc(refcnt *cnt)
@@ -19,7 +18,6 @@ static inline refcnt refcnt_dec(refcnt *cnt)
return __sync_sub_and_fetch(cnt, 1);
}
/* mutex */
typedef pthread_mutex_t lock;
@@ -46,14 +44,11 @@ static inline void lock_release_via_cleanup(void *l)
lock_release(l);
}
#define LOCK_ACQUIRE(l) \
lock_acquire(l); \
#define LOCK_ACQUIRE(l) \
lock_acquire(l); \
pthread_cleanup_push(lock_release_via_cleanup, l)
#define LOCK_RELEASE() \
pthread_cleanup_pop(1)
#define LOCK_RELEASE() pthread_cleanup_pop(1)
/* read/write lock */
typedef pthread_rwlock_t rwlock;
@@ -86,18 +81,14 @@ static inline void rwlock_release_via_cleanup(void *rw)
rwlock_release(rw);
}
#define RWLOCK_READ_ACQUIRE(rw) \
rwlock_read_acquire(rw); \
#define RWLOCK_READ_ACQUIRE(rw) \
rwlock_read_acquire(rw); \
pthread_cleanup_push(rwlock_release_via_cleanup, rw)
#define RWLOCK_WRITE_ACQUIRE(rw) \
rwlock_write_acquire(rw); \
#define RWLOCK_WRITE_ACQUIRE(rw) \
rwlock_write_acquire(rw); \
pthread_cleanup_push(rwlock_release_via_cleanup, rw)
#define RWLOCK_RELEASE() \
pthread_cleanup_pop(1)
#define RWLOCK_RELEASE() pthread_cleanup_pop(1)
#endif /* _ATOMIC_H_ */

View File

@@ -1,13 +1,16 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/times.h>
#include <utime.h>
#include <fileops.h>
#include <ssh.h>
#include <message.h>
#include <print.h>
#include <platform.h>
sftp_session __thread tls_sftp;
/* tls_sftp is used *_wrapped() functions */
@@ -21,7 +24,7 @@ static void sftp_err_to_errno(sftp_session sftp)
{
int sftperr = sftp_get_error(sftp);
switch (sftperr){
switch (sftperr) {
case SSH_FX_OK:
case SSH_FX_EOF:
errno = 0;
@@ -59,11 +62,10 @@ static void sftp_err_to_errno(sftp_session sftp)
errno = ENODEV;
break;
default:
mpr_warn(stderr, "unkown SSH_FX response %d", sftperr);
pr_warn("unkown SSH_FX response %d", sftperr);
}
}
MDIR *mscp_opendir(const char *path, sftp_session sftp)
{
MDIR *md;
@@ -108,7 +110,6 @@ void mscp_closedir(MDIR *md)
free(md);
}
struct dirent __thread tls_dirent;
/* tls_dirent contains dirent converted from sftp_attributes returned
* from sftp_readdir(). This trick is derived from openssh's
@@ -157,14 +158,25 @@ int mscp_mkdir(const char *path, mode_t mode, sftp_session sftp)
return ret;
}
static void sftp_attr_to_stat(sftp_attributes attr, struct stat *st)
{
memset(st, 0, sizeof(*st));
st->st_size = attr->size;
st->st_uid = attr->uid;
st->st_gid = attr->gid;
st->st_mode = attr->permissions;
memset(st, 0, sizeof(*st));
st->st_size = attr->size;
st->st_uid = attr->uid;
st->st_gid = attr->gid;
st->st_mode = attr->permissions;
#if defined(__APPLE__)
#define st_atim st_atimespec
#define st_mtim st_mtimespec
#define st_ctim st_ctimespec
#endif
st->st_atim.tv_sec = attr->atime;
st->st_atim.tv_nsec = attr->atime_nseconds;
st->st_mtim.tv_sec = attr->mtime;
st->st_mtim.tv_nsec = attr->mtime_nseconds;
st->st_ctim.tv_sec = attr->createtime;
st->st_ctim.tv_nsec = attr->createtime_nseconds;
switch (attr->type) {
case SSH_FILEXFER_TYPE_REGULAR:
@@ -183,18 +195,17 @@ static void sftp_attr_to_stat(sftp_attributes attr, struct stat *st)
st->st_mode |= S_IFIFO; /* really? */
break;
default:
mpr_warn(stderr, "unkown SSH_FILEXFER_TYPE %d", attr->type);
pr_warn("unkown SSH_FILEXFER_TYPE %d", attr->type);
}
/* ToDo: convert atime, ctime, and mtime */
}
int mscp_stat(const char *path, struct stat *st, sftp_session sftp)
{
sftp_attributes attr;
int ret = 0;
memset(st, 0, sizeof(*st));
if (sftp) {
attr = sftp_stat(sftp, path);
sftp_err_to_errno(sftp);
@@ -240,7 +251,6 @@ int mscp_lstat_wrapped(const char *path, struct stat *st)
return mscp_lstat(path, st, tls_sftp);
}
mf *mscp_open(const char *path, int flags, mode_t mode, sftp_session sftp)
{
mf *f;
@@ -291,23 +301,34 @@ off_t mscp_lseek(mf *f, off_t off)
return ret;
}
int mscp_setstat(const char *path, mode_t mode, size_t size, sftp_session sftp)
int mscp_setstat(const char *path, struct stat *st, bool preserve_ts, sftp_session sftp)
{
int ret;
if (sftp) {
struct sftp_attributes_struct attr;
memset(&attr, 0, sizeof(attr));
attr.permissions = mode;
attr.size = size;
attr.flags = (SSH_FILEXFER_ATTR_PERMISSIONS|SSH_FILEXFER_ATTR_SIZE);
attr.permissions = st->st_mode;
attr.size = st->st_size;
attr.flags = (SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_SIZE);
if (preserve_ts) {
attr.atime = st->st_atim.tv_sec;
attr.atime_nseconds = st->st_atim.tv_nsec;
attr.mtime = st->st_mtim.tv_sec;
attr.mtime_nseconds = st->st_mtim.tv_nsec;
attr.flags |= (SSH_FILEXFER_ATTR_ACCESSTIME |
SSH_FILEXFER_ATTR_MODIFYTIME |
SSH_FILEXFER_ATTR_SUBSECOND_TIMES);
}
ret = sftp_setstat(sftp, path, &attr);
sftp_err_to_errno(sftp);
} else {
if ((ret = chmod(path, mode)) < 0)
if ((ret = chmod(path, st->st_mode)) < 0)
return ret;
if ((ret = truncate(path, size)) < 0)
if ((ret = truncate(path, st->st_size)) < 0)
return ret;
if (preserve_ts)
ret = setutimes(path, st->st_atim, st->st_mtim);
}
return ret;
@@ -328,9 +349,9 @@ int mscp_glob(const char *pattern, int flags, glob_t *pglob, sftp_session sftp)
#else
flags |= GLOB_ALTDIRFUNC;
set_tls_sftp_session(sftp);
#ifdef __APPLE__
#if defined(__APPLE__) || defined(__FreeBSD__)
pglob->gl_opendir = (void *(*)(const char *))mscp_opendir_wrapped;
pglob->gl_readdir = (struct dirent *(*)(void *))mscp_readdir;
pglob->gl_readdir = (struct dirent * (*)(void *)) mscp_readdir;
pglob->gl_closedir = (void (*)(void *))mscp_closedir;
pglob->gl_lstat = mscp_lstat_wrapped;
pglob->gl_stat = mscp_stat_wrapped;

View File

@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#include <dirent.h>
#include <sys/stat.h>
#include <glob.h>
@@ -19,7 +19,6 @@ struct mdir_struct {
};
typedef struct mdir_struct MDIR;
MDIR *mscp_opendir(const char *path, sftp_session sftp);
MDIR *mscp_opendir_wrapped(const char *path);
void mscp_closedir(MDIR *md);
@@ -34,7 +33,6 @@ int mscp_stat_wrapped(const char *path, struct stat *st);
int mscp_lstat(const char *path, struct stat *st, sftp_session sftp);
int mscp_lstat_wrapped(const char *path, struct stat *st);
/* file operations */
struct mf_struct {
@@ -50,7 +48,7 @@ off_t mscp_lseek(mf *f, off_t off);
/* mscp_setstat() involves chmod and truncate. It executes both at
* once via a single SFTP command (sftp_setstat()).
*/
int mscp_setstat(const char *path, mode_t mode, size_t size, sftp_session sftp);
int mscp_setstat(const char *path, struct stat *st, bool preserve_ts, sftp_session sftp);
/* remote glob */
int mscp_glob(const char *pattern, int flags, glob_t *pglob, sftp_session sftp);

View File

@@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
@@ -8,20 +9,27 @@
#include <sys/time.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <pthread.h>
#include <mscp.h>
#include <mscp_version.h>
#include <util.h>
#include <minmax.h>
#include <strerrno.h>
#include <print.h>
#include "config.h"
void usage(bool print_help) {
printf("mscp " MSCP_BUILD_VERSION ": copy files over multiple ssh connections\n"
void usage(bool print_help)
{
printf("mscp " MSCP_BUILD_VERSION ": copy files over multiple SSH connections\n"
"\n"
"Usage: mscp [vqDHdNh] [-n nr_conns] [-m coremask] [-u max_startups]\n"
"Usage: mscp [-46vqDpHdNh] [-n nr_conns] [-m coremask]\n"
" [-u max_startups] [-I interval]\n"
" [-s min_chunk_sz] [-S max_chunk_sz] [-a nr_ahead] [-b buf_sz]\n"
" [-l login_name] [-p port] [-F ssh_config] [-i identity_file]\n"
" [-c cipher_spec] [-M hmac_spec] [-C compress] source ... target\n"
" [-l login_name] [-P port] [-F ssh_config] [-i identity_file]\n"
" [-c cipher_spec] [-M hmac_spec] [-C compress] [-g congestion]\n"
" source ... target\n"
"\n");
if (!print_help)
@@ -30,27 +38,32 @@ 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 outgoing connections "
" -u MAX_STARTUPS number of concurrent SSH connection attempts "
"(default: 8)\n"
" -I INTERVAL interval between SSH connection attempts (default: 0)\n"
"\n"
" -s MIN_CHUNK_SIZE min chunk size (default: 64MB)\n"
" -S MAX_CHUNK_SIZE max chunk size (default: filesize/nr_conn)\n"
"\n"
" -a NR_AHEAD number of inflight SFTP commands (default: 32)\n"
" -b BUF_SZ buffer size for i/o and transfer\n"
"\n"
" -4 use IPv4\n"
" -6 use IPv6\n"
" -v increment verbose output level\n"
" -q disable output\n"
" -D dry run. check copy destinations with -vvv\n"
" -r no effect\n"
"\n"
" -l LOGIN_NAME login name\n"
" -p PORT port number\n"
" -P PORT port number\n"
" -F CONFIG path to user ssh config (default ~/.ssh/config)\n"
" -i IDENTITY identity file for public key authentication\n"
" -c CIPHER cipher spec\n"
" -M HMAC hmac spec\n"
" -C COMPRESS enable compression: "
"yes, no, zlib, zlib@openssh.com\n"
" -g CONGESTION specify TCP congestion control algorithm\n"
" -p preserve timestamps of files\n"
" -H disable hostkey check\n"
" -d increment ssh debug output level\n"
" -N enable Nagle's algorithm (default disabled)\n"
@@ -58,107 +71,160 @@ void usage(bool print_help) {
"\n");
}
char *split_remote_and_path(const char *string, char **remote, char **path)
char *strip_brackets(char *s)
{
char *s, *p;
/* split user@host:path into user@host, and path.
* return value is strdup()ed memory (for free()).
*/
if (!(s = strdup(string))) {
fprintf(stderr, "strdup: %s\n", strerror(errno));
return NULL;
if (s[0] == '[' && s[strlen(s) - 1] == ']') {
s[strlen(s) - 1] = '\0';
return s + 1;
}
if ((p = strchr(s, ':'))) {
if (p == s || ((p > s) && *(p - 1) == '\\')) {
/* first byte is colon, or escaped colon. no user@host here */
goto no_remote;
} else {
/* we found ':', so this is remote:path notation. split it */
*p = '\0';
*remote = s;
*path = p + 1;
return s;
}
}
no_remote:
*remote = NULL;
*path = s;
return s;
}
char *split_user_host_path(const char *s, char **userp, char **hostp, char **pathp)
{
char *tmp, *cp, *user = NULL, *host = NULL, *path = NULL;
bool inbrackets = false;
if (!(tmp = strdup(s))) {
pr_err("stdrup: %s", strerror(errno));
return NULL;
}
user = NULL;
host = NULL;
path = tmp;
for (cp = tmp; *cp; cp++) {
if (*cp == '@' && (cp > tmp) && *(cp - 1) != '\\' && user == NULL) {
/* cp is non-escaped '@', so this '@' is the
* delimitater between username and host. */
*cp = '\0';
user = tmp;
host = cp + 1;
}
if (*cp == '[')
inbrackets = true;
if (*cp == ']')
inbrackets = false;
if (*cp == ':' && (cp > tmp) && *(cp - 1) != '\\') {
if (!inbrackets) {
/* cp is non-escaped ':' and not in
* brackets for IPv6 address
* notation. So, this ':' is the
* delimitater between host and
* path. */
*cp = '\0';
host = host == NULL ? tmp : host;
path = cp + 1;
break;
}
}
}
*userp = user;
*hostp = host ? strip_brackets(host) : NULL;
*pathp = path;
return tmp;
}
struct target {
char *remote;
char *copy;
char *user;
char *host;
char *path;
};
int compare_remote(struct target *a, struct target *b)
{
/* return 0 if a and b have the identical user@host, otherwise 1 */
int alen, blen;
if (a->user) {
if (!b->user)
return 1;
alen = strlen(a->user);
blen = strlen(b->user);
if (alen != blen)
return 1;
if (strncmp(a->user, b->user, alen) != 0)
return 1;
} else if (b->user)
return 1;
if (a->host) {
if (!b->host)
return 1;
alen = strlen(a->host);
blen = strlen(b->host);
if (alen != blen)
return 1;
if (strncmp(a->host, b->host, alen) != 0)
return 1;
} else if (b->host)
return 1;
return 0;
}
struct target *validate_targets(char **arg, int len)
{
/* arg is array of source ... destination.
* There are two cases:
*
* 1. remote:path remote:path ... path, remote to local copy
* 2. path path ... remote:path, local to remote copy.
* 1. user@host:path host:path ... path, remote to local copy
* 2. path path ... host:path, local to remote copy.
*
* This function split (remote:)path args into struct target,
* This function split user@remote:path args into struct target,
* and validate all remotes are identical (mscp does not support
* remote to remote copy).
*/
struct target *t;
char *r;
struct target *t, *t0;
int n;
if ((t = calloc(len, sizeof(struct target))) == NULL) {
fprintf(stderr, "calloc: %s\n", strerror(errno));
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++) {
if (split_remote_and_path(arg[n], &t[n].remote, &t[n].path) == NULL)
t[n].copy =
split_user_host_path(arg[n], &t[n].user, &t[n].host, &t[n].path);
if (!t[n].copy) {
pr_err("failed to parse '%s'", arg[n]);
goto free_target_out;
}
}
/* check all remote are identical. t[len - 1] is destination,
/* 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 */
r = t[0].remote;
* remote notation */
t0 = &t[0];
for (n = 1; n < len - 1; n++) {
if (!r && t[n].remote) {
if (compare_remote(t0, &t[n]) != 0)
goto invalid_remotes;
}
if (r) {
if (!t[n].remote ||
strlen(r) != strlen(t[n].remote) ||
strcmp(r, t[n].remote) != 0)
goto invalid_remotes;
}
}
/* check inconsistent remote position in args */
if (t[0].remote == NULL && t[len - 1].remote == NULL) {
fprintf(stderr, "no remote host given\n");
if (t[0].host == NULL && t[len - 1].host == NULL) {
pr_err("no remote host given");
goto free_split_out;
}
if (t[0].remote != NULL && t[len - 1].remote != NULL) {
fprintf(stderr, "no local path given\n");
if (t[0].host != NULL && t[len - 1].host != NULL) {
pr_err("no local path given");
goto free_split_out;
}
return t;
invalid_remotes:
fprintf(stderr, "specified remote host invalid\n");
pr_err("invalid remote host notation");
free_split_out:
for (n = 0; n < len; n++)
t[n].remote ? free(t[n].remote) : free(t[n].path);
if (t[n].copy)
free(t[n].copy);
free_target_out:
free(t);
@@ -166,21 +232,20 @@ free_target_out:
}
struct mscp *m = NULL;
int msg_fd = 0;
pthread_t tid_stat = 0;
void sigint_handler(int sig)
{
if (tid_stat)
pthread_cancel(tid_stat);
mscp_stop(m);
if (tid_stat > 0)
pthread_cancel(tid_stat);
}
void *print_stat_thread(void *arg);
void print_cli(const char *fmt, ...)
{
va_list va;
va_list va;
va_start(va, fmt);
vfprintf(stdout, fmt, va);
fflush(stdout);
@@ -202,22 +267,25 @@ int main(int argc, char **argv)
memset(&o, 0, sizeof(o));
o.severity = MSCP_SEVERITY_WARN;
while ((ch = getopt(argc, argv, "n:m:u:s:S:a:b:vqDrl:p:i:F:c:M:C:HdNh")) != -1) {
#define mscpopts "n:m:u:I:s:S:a:b:46vqDrl:P:i:F:c:M:C:g:pHdNh"
while ((ch = getopt(argc, argv, mscpopts)) != -1) {
switch (ch) {
case 'n':
o.nr_threads = atoi(optarg);
if (o.nr_threads < 1) {
fprintf(stderr, "invalid number of connections: %s\n",
optarg);
pr_err("invalid number of connections: %s", optarg);
return 1;
}
break;
case 'm':
strncpy(o.coremask, optarg, sizeof(o.coremask));
o.coremask = optarg;
break;
case 'u':
o.max_startups = atoi(optarg);
break;
case 'I':
o.interval = atoi(optarg);
break;
case 's':
o.min_chunk_sz = atoi(optarg);
break;
@@ -230,6 +298,12 @@ int main(int argc, char **argv)
case 'b':
o.buf_sz = atoi(optarg);
break;
case '4':
s.ai_family = AF_INET;
break;
case '6':
s.ai_family = AF_INET6;
break;
case 'v':
o.severity++;
break;
@@ -243,49 +317,31 @@ int main(int argc, char **argv)
/* for compatibility with scp */
break;
case 'l':
if (strlen(optarg) > MSCP_SSH_MAX_LOGIN_NAME - 1) {
fprintf(stderr, "long login name: %s\n", optarg);
return -1;
}
strncpy(s.login_name, optarg, MSCP_SSH_MAX_LOGIN_NAME - 1);
s.login_name = optarg;
break;
case 'p':
if (strlen(optarg) > MSCP_SSH_MAX_PORT_STR - 1) {
fprintf(stderr, "long port string: %s\n", optarg);
return -1;
}
strncpy(s.port, optarg, MSCP_SSH_MAX_PORT_STR);
case 'P':
s.port = optarg;
break;
case 'F':
strncpy(s.config, optarg, PATH_MAX - 1);
s.config = optarg;
break;
case 'i':
if (strlen(optarg) > MSCP_SSH_MAX_IDENTITY_PATH - 1) {
fprintf(stderr, "long identity path: %s\n", optarg);
return -1;
}
strncpy(s.identity, optarg, MSCP_SSH_MAX_IDENTITY_PATH);
s.identity = optarg;
break;
case 'c':
if (strlen(optarg) > MSCP_SSH_MAX_CIPHER_STR - 1) {
fprintf(stderr, "long cipher string: %s\n", optarg);
return -1;
}
strncpy(s.cipher, optarg, MSCP_SSH_MAX_CIPHER_STR);
s.cipher = optarg;
break;
case 'M':
if (strlen(optarg) > MSCP_SSH_MAX_HMAC_STR - 1) {
fprintf(stderr, "long hmac string: %s\n", optarg);
return -1;
}
strncpy(s.hmac, optarg, MSCP_SSH_MAX_HMAC_STR);
s.hmac = optarg;
break;
case 'C':
if (strlen(optarg) > MSCP_SSH_MAX_COMP_STR - 1) {
fprintf(stderr, "long compress string: %s\n", optarg);
return -1;
}
strncpy(s.compress, optarg, MSCP_SSH_MAX_COMP_STR);
s.compress = optarg;
break;
case 'g':
s.ccalgo = optarg;
break;
case 'p':
o.preserve_ts = true;
break;
case 'H':
s.no_hostkey_check = true;
@@ -305,6 +361,9 @@ int main(int argc, char **argv)
}
}
s.password = getenv(ENV_SSH_AUTH_PASSWORD);
s.passphrase = getenv(ENV_SSH_AUTH_PASSPHRASE);
if (argc - optind < 2) {
/* mscp needs at lease 2 (src and target) argument */
usage(false);
@@ -315,49 +374,42 @@ int main(int argc, char **argv)
if ((t = validate_targets(argv + optind, i)) == NULL)
return -1;
if (t[0].remote) {
if (t[0].host) {
/* copy remote to local */
direction = MSCP_DIRECTION_R2L;
remote = t[0].remote;
remote = t[0].host;
s.login_name = s.login_name ? s.login_name : t[0].user;
} else {
/* copy local to remote */
direction = MSCP_DIRECTION_L2R;
remote = t[i - 1].remote;
}
if (!dryrun) {
if (pipe(pipe_fd) < 0) {
fprintf(stderr, "pipe: %s\n", strerror(errno));
return -1;
}
msg_fd = pipe_fd[0];
o.msg_fd = pipe_fd[1];
remote = t[i - 1].host;
s.login_name = s.login_name ? s.login_name : t[i - 1].user;
}
if ((m = mscp_init(remote, direction, &o, &s)) == NULL) {
fprintf(stderr, "mscp_init: %s\n", mscp_get_error());
pr_err("mscp_init: %s", priv_get_err());
return -1;
}
if (mscp_connect(m) < 0) {
fprintf(stderr, "mscp_connect: %s\n", mscp_get_error());
pr_err("mscp_connect: %s", priv_get_err());
return -1;
}
for (n = 0; n < i - 1; n++) {
if (mscp_add_src_path(m, t[n].path) < 0) {
fprintf(stderr, "mscp_add_src_path: %s\n", mscp_get_error());
pr_err("mscp_add_src_path: %s", priv_get_err());
return -1;
}
}
}
if (mscp_set_dst_path(m, t[i - 1].path) < 0) {
fprintf(stderr, "mscp_set_dst_path: %s\n", mscp_get_error());
pr_err("mscp_set_dst_path: %s", priv_get_err());
return -1;
}
if (mscp_scan(m) < 0) {
fprintf(stderr, "mscp_scan: %s\n", mscp_get_error());
pr_err("mscp_scan: %s", priv_get_err());
return -1;
}
@@ -367,22 +419,20 @@ int main(int argc, char **argv)
}
if (pthread_create(&tid_stat, NULL, print_stat_thread, NULL) < 0) {
fprintf(stderr, "pthread_create: %s\n", strerror(errno));
pr_err("pthread_create: %s", strerror(errno));
return -1;
}
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
fprintf(stderr, "signal: %s\n", strerror(errno));
pr_err("signal: %s", strerror(errno));
return -1;
}
ret = mscp_start(m);
if (ret < 0)
fprintf(stderr, "mscp_start: %s\n", mscp_get_error());
pr_err("mscp_start: %s", priv_get_err());
ret = mscp_join(m);
if (ret != 0)
fprintf(stderr, "mscp_join: %s\n", mscp_get_error());
pthread_cancel(tid_stat);
pthread_join(tid_stat, NULL);
@@ -394,192 +444,196 @@ out:
return ret;
}
/* progress bar-related functions */
double calculate_timedelta(struct timeval *b, struct timeval *a)
{
double sec, usec;
double sec, usec;
if (a->tv_usec < b->tv_usec) {
a->tv_usec += 1000000;
a->tv_sec--;
}
if (a->tv_usec < b->tv_usec) {
a->tv_usec += 1000000;
a->tv_sec--;
}
sec = a->tv_sec - b->tv_sec;
usec = a->tv_usec - b->tv_usec;
sec += usec / 1000000;
sec = a->tv_sec - b->tv_sec;
usec = a->tv_usec - b->tv_usec;
sec += usec / 1000000;
return sec;
return sec;
}
double calculate_bps(size_t diff, struct timeval *b, struct timeval *a)
{
return (double)diff / calculate_timedelta(b, a);
return (double)diff / calculate_timedelta(b, a);
}
char *calculate_eta(size_t remain, size_t diff, struct timeval *b, struct timeval *a,
bool final)
{
static char buf[16];
double elapsed = calculate_timedelta(b, a);
double eta;
static char buf[16];
if (diff == 0)
snprintf(buf, sizeof(buf), "--:-- ETA");
else if (final) {
snprintf(buf, sizeof(buf), "%02d:%02d ",
(int)(floor(elapsed / 60)), (int)round(elapsed) % 60);
} else {
eta = remain / (diff / elapsed);
snprintf(buf, sizeof(buf), "%02d:%02d ETA",
(int)floor(eta / 60), (int)round(eta) % 60);
}
return buf;
#define bps_window_size 16
static double bps_window[bps_window_size];
static size_t sum, idx, count;
double elapsed = calculate_timedelta(b, a);
double bps = diff / elapsed;
double avg, eta;
/* early return when diff == 0 (stalled) or final output */
if (diff == 0) {
snprintf(buf, sizeof(buf), "--:-- ETA");
return buf;
}
if (final) {
snprintf(buf, sizeof(buf), "%02d:%02d ", (int)(floor(elapsed / 60)),
(int)round(elapsed) % 60);
return buf;
}
/* drop the old bps value and add the recent one */
sum -= bps_window[idx];
bps_window[idx] = bps;
sum += bps_window[idx];
idx = (idx + 1) % bps_window_size;
count++;
/* calcuate ETA from avg of recent bps values */
avg = sum / min(count, bps_window_size);
eta = remain / avg;
snprintf(buf, sizeof(buf), "%02d:%02d ETA", (int)floor(eta / 60),
(int)round(eta) % 60);
return buf;
}
void print_progress_bar(double percent, char *suffix)
{
int n, thresh, bar_width;
struct winsize ws;
char buf[128];
int n, thresh, bar_width;
struct winsize ws;
char buf[128];
/*
/*
* [=======> ] XX% SUFFIX
*/
buf[0] = '\0';
buf[0] = '\0';
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0)
return; /* XXX */
bar_width = min(sizeof(buf), ws.ws_col) - strlen(suffix) - 7;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0)
return; /* XXX */
bar_width = min(sizeof(buf), ws.ws_col) - strlen(suffix) - 7;
memset(buf, 0, sizeof(buf));
if (bar_width > 8) {
thresh = floor(bar_width * (percent / 100)) - 1;
memset(buf, 0, sizeof(buf));
if (bar_width > 8) {
thresh = floor(bar_width * (percent / 100)) - 1;
for (n = 1; n < bar_width - 1; n++) {
if (n <= thresh)
buf[n] = '=';
else
buf[n] = ' ';
}
buf[thresh] = '>';
buf[0] = '[';
buf[bar_width - 1] = ']';
snprintf(buf + bar_width, sizeof(buf) - bar_width,
" %3d%% ", (int)floor(percent));
}
for (n = 1; n < bar_width - 1; n++) {
if (n <= thresh)
buf[n] = '=';
else
buf[n] = ' ';
}
buf[thresh] = '>';
buf[0] = '[';
buf[bar_width - 1] = ']';
snprintf(buf + bar_width, sizeof(buf) - bar_width, " %3d%% ",
(int)floor(percent));
}
print_cli("\r\033[K" "%s%s", buf, suffix);
print_cli("\r\033[K"
"%s%s",
buf, suffix);
}
void print_progress(struct timeval *b, struct timeval *a,
size_t total, size_t last, size_t done, bool final)
void print_progress(struct timeval *b, struct timeval *a, size_t total, size_t last,
size_t done, bool final)
{
char *bps_units[] = { "B/s ", "KB/s", "MB/s", "GB/s" };
char *byte_units[] = { "B ", "KB", "MB", "GB", "TB", "PB" };
char suffix[128];
int bps_u, byte_tu, byte_du;
size_t total_round, done_round;
int percent;
double bps;
char *bps_units[] = { "B/s ", "KB/s", "MB/s", "GB/s" };
char *byte_units[] = { "B ", "KB", "MB", "GB", "TB", "PB" };
char suffix[128];
int bps_u, byte_tu, byte_du;
double total_round, done_round;
int percent;
double bps;
#define array_size(a) (sizeof(a) / sizeof(a[0]))
if (total <= 0) {
print_cli("\r\033[K" "total 0 byte transferred");
return; /* copy 0-byte file(s) */
}
if (total <= 0) {
print_cli("\r\033[K"
"total 0 byte transferred");
return; /* copy 0-byte file(s) */
}
total_round = total;
for (byte_tu = 0; total_round > 1000 && byte_tu < array_size(byte_units) - 1;
byte_tu++)
total_round /= 1024;
total_round = total;
for (byte_tu = 0; total_round > 1000 && byte_tu < array_size(byte_units) - 1;
byte_tu++)
total_round /= 1024;
bps = calculate_bps(done - last, b, a);
for (bps_u = 0; bps > 1000 && bps_u < array_size(bps_units); bps_u++)
bps /= 1000;
bps = calculate_bps(done - last, b, a);
for (bps_u = 0; bps > 1000 && bps_u < array_size(bps_units); bps_u++)
bps /= 1000;
percent = floor(((double)(done) / (double)total) * 100);
percent = floor(((double)(done) / (double)total) * 100);
done_round = done;
for (byte_du = 0; done_round > 1000 && byte_du < array_size(byte_units) - 1;
byte_du++)
done_round /= 1024;
done_round = done;
for (byte_du = 0; done_round > 1024 && byte_du < array_size(byte_units) - 1;
byte_du++)
done_round /= 1024;
snprintf(suffix, sizeof(suffix), "%4lu%s/%lu%s %6.1f%s %s",
done_round, byte_units[byte_du], total_round, byte_units[byte_tu],
bps, bps_units[bps_u],
calculate_eta(total - done, done - last, b, a, final));
snprintf(suffix, sizeof(suffix), "%4.1lf%s/%.1lf%s %6.1f%s %s", done_round,
byte_units[byte_du], total_round, byte_units[byte_tu], bps,
bps_units[bps_u], calculate_eta(total - done, done - last, b, a, final));
print_progress_bar(percent, suffix);
print_progress_bar(percent, suffix);
}
struct xfer_stat {
struct timeval start, before, after;
size_t total;
size_t last;
size_t done;
struct timeval start, before, after;
size_t total;
size_t last;
size_t done;
};
struct xfer_stat x;
void print_stat_thread_cleanup(void *arg)
void print_stat(bool final)
{
struct mscp_stats s;
char buf[8192];
int timeout;
gettimeofday(&x.after, NULL);
mscp_get_stats(m, &s);
x.total = s.total;
x.done = s.done;
if (calculate_timedelta(&x.before, &x.after) > 1 || final) {
mscp_get_stats(m, &s);
x.total = s.total;
x.done = s.done;
print_progress(!final ? &x.before : &x.start, &x.after, x.total,
!final ? x.last : 0, x.done, final);
x.before = x.after;
x.last = x.done;
}
}
/* print progress from the beginning */
print_progress(&x.start, &x.after, x.total, 0, x.done, true);
void print_stat_thread_cleanup(void *arg)
{
print_stat(true);
print_cli("\n"); /* final output */
}
void *print_stat_thread(void *arg)
{
struct pollfd pfd = { .fd = msg_fd, .events = POLLIN };
struct mscp_stats s;
char buf[8192];
memset(&x, 0, sizeof(x));
gettimeofday(&x.start, NULL);
x.before = x.start;
gettimeofday(&x.start, NULL);
x.before = x.start;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
pthread_cleanup_push(print_stat_thread_cleanup, NULL);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
pthread_cleanup_push(print_stat_thread_cleanup, NULL);
while (true) {
if (poll(&pfd, 1, 100) < 0) {
fprintf(stderr, "poll: %s\n", strerror(errno));
return NULL;
}
if (pfd.revents & POLLIN) {
memset(buf, 0, sizeof(buf));
if (read(msg_fd, buf, sizeof(buf)) < 0) {
fprintf(stderr, "read: %s\n", strerror(errno));
return NULL;
}
print_cli("\r\033[K" "%s", buf);
}
gettimeofday(&x.after, NULL);
if (calculate_timedelta(&x.before, &x.after) > 1) {
mscp_get_stats(m, &s);
x.total = s.total;
x.done = s.done;
print_progress(&x.before, &x.after, x.total, x.last, x.done,
false);
x.before = x.after;
x.last = x.done;
}
print_stat(false);
sleep(1);
}
pthread_cleanup_pop(1);

View File

@@ -1,49 +0,0 @@
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#include <pthread.h>
#include <util.h>
#include <message.h>
/* strerror_r wrapper */
__thread char thread_strerror[128];
/* mscp error message buffer */
#define MSCP_ERRMSG_SIZE (PATH_MAX * 2)
static char errmsg[MSCP_ERRMSG_SIZE];
void _mscp_set_error(const char *fmt, ...)
{
va_list va;
memset(errmsg, 0, sizeof(errmsg));
va_start(va, fmt);
vsnprintf(errmsg, sizeof(errmsg) - 1, fmt, va);
va_end(va);
}
const char *mscp_get_error()
{
return errmsg;
}
/* message print functions */
static int mprint_severity = MSCP_SEVERITY_WARN;
void mprint_set_severity(int serverity)
{
if (serverity < 0)
mprint_severity = -1; /* no print */
mprint_severity = serverity;
}
int mprint_get_severity()
{
return mprint_severity;
}

View File

@@ -1,56 +0,0 @@
#ifndef _MESSAGE_H_
#define _MESSAGE_H_
#include <libgen.h>
#include <stdio.h>
#include <mscp.h>
/* message print. printed messages are passed to application via msg_fd */
void mprint_set_severity(int severity);
int mprint_get_severity();
#define mprint(fp, severity, fmt, ...) \
do { \
if (fp && severity <= mprint_get_severity()) { \
fprintf(fp, fmt, ##__VA_ARGS__); \
fflush(fp); \
} \
} while (0)
#define mpr_err(fp, fmt, ...) \
mprint(fp, MSCP_SEVERITY_ERR, fmt, ##__VA_ARGS__)
#define mpr_warn(fp, fmt, ...) \
mprint(fp, MSCP_SEVERITY_WARN, fmt, ##__VA_ARGS__)
#define mpr_notice(fp, fmt, ...) \
mprint(fp, MSCP_SEVERITY_NOTICE, fmt, ##__VA_ARGS__)
#define mpr_info(fp, fmt, ...) \
mprint(fp, MSCP_SEVERITY_INFO, fmt, ##__VA_ARGS__)
#define mpr_debug(fp, fmt, ...) \
mprint(fp, MSCP_SEVERITY_DEBUG, fmt, ##__VA_ARGS__)
/* errorno wrapper */
extern __thread char thread_strerror[128];
#ifdef _GNU_SOURCE
/* GNU strerror_r */
#define strerrno() \
strerror_r(errno, thread_strerror, sizeof(thread_strerror))
#else
/* this macro assumes that strerror_r never fails. any good way? */
#define strerrno() \
(strerror_r(errno, thread_strerror, sizeof(thread_strerror)) \
? thread_strerror : thread_strerror)
#endif
/* error message buffer */
#define mscp_set_error(fmt, ...) \
_mscp_set_error("%s:%d:%s: " fmt "\0", \
basename(__FILE__), __LINE__, __func__, ##__VA_ARGS__)
void _mscp_set_error(const char *fmt, ...);
#endif /* _MESSAGE_H_ */

8
src/minmax.h Normal file
View File

@@ -0,0 +1,8 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#ifndef _MINMAX_H_
#define _MINMAX_H_
#define min(a, b) (((a) > (b)) ? (b) : (a))
#define max(a, b) (((a) > (b)) ? (a) : (b))
#endif /* _MINMAX_H_ */

View File

@@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#include <stdbool.h>
#include <unistd.h>
#include <math.h>
@@ -5,69 +6,68 @@
#include <semaphore.h>
#include <sys/time.h>
#include <list.h>
#include <util.h>
#include <ssh.h>
#include <list.h>
#include <minmax.h>
#include <ssh.h>
#include <path.h>
#include <fileops.h>
#include <atomic.h>
#include <atomic.h>
#include <platform.h>
#include <message.h>
#include <print.h>
#include <strerrno.h>
#include <mscp.h>
#include <openbsd-compat/openbsd-compat.h>
struct mscp {
char *remote; /* remote host (and uername) */
int direction; /* copy direction */
struct mscp_opts *opts;
struct mscp_ssh_opts *ssh_opts;
char *remote; /* remote host (and uername) */
int direction; /* copy direction */
struct mscp_opts *opts;
struct mscp_ssh_opts *ssh_opts;
FILE *msg_fp; /* writer fd for message pipe */
int *cores; /* usable cpu cores by COREMASK */
int nr_cores; /* length of array of cores */
int *cores; /* usable cpu cores by COREMASK */
int nr_cores; /* length of array of cores */
sem_t *sem; /* semaphore for concurrent
sem_t *sem; /* semaphore for concurrent
* connecting ssh sessions */
sftp_session first; /* first sftp session */
sftp_session first; /* first sftp session */
char dst_path[PATH_MAX];
struct list_head src_list;
struct list_head path_list;
struct chunk_pool cp;
char dst_path[PATH_MAX];
struct list_head src_list;
struct list_head path_list;
struct chunk_pool cp;
pthread_t tid_scan; /* tid for scan thread */
int ret_scan; /* return code from scan thread */
pthread_t tid_scan; /* tid for scan thread */
int ret_scan; /* return code from scan thread */
size_t total_bytes; /* total bytes to be transferred */
size_t total_bytes; /* total bytes to be transferred */
struct list_head thread_list;
rwlock thread_rwlock;
struct list_head thread_list;
rwlock thread_rwlock;
};
struct mscp_thread {
struct list_head list; /* mscp->thread_list */
struct list_head list; /* mscp->thread_list */
struct mscp *m;
int id;
sftp_session sftp;
pthread_t tid;
int cpu;
size_t done;
bool finished;
int ret;
struct mscp *m;
int id;
sftp_session sftp;
pthread_t tid;
int cpu;
size_t done;
bool finished;
int ret;
};
struct src {
struct list_head list; /* mscp->src_list */
struct list_head list; /* mscp->src_list */
char *path;
};
#define DEFAULT_MIN_CHUNK_SZ (64 << 20) /* 64MB */
#define DEFAULT_NR_AHEAD 32
#define DEFAULT_BUF_SZ 16384
#define DEFAULT_MIN_CHUNK_SZ (64 << 20) /* 64MB */
#define DEFAULT_NR_AHEAD 32
#define DEFAULT_BUF_SZ 16384
/* XXX: we use 16384 byte buffer pointed by
* https://api.libssh.org/stable/libssh_tutor_sftp.html. The larget
* read length from sftp_async_read is 65536 byte. Read sizes larger
@@ -75,89 +75,86 @@ struct src {
* sftp_async_read returns 0.
*/
#define DEFAULT_MAX_STARTUPS 8
#define DEFAULT_MAX_STARTUPS 8
#define non_null_string(s) (s[0] != '\0')
static int expand_coremask(const char *coremask, int **cores, int *nr_cores)
{
int n, *core_list, core_list_len = 0, nr_usable, nr_all;
char c[2] = { 'x', '\0' };
const char *_coremask;
long v, needle;
int ncores = nr_cpus();
int n, *core_list, core_list_len = 0, nr_usable, nr_all;
char c[2] = { 'x', '\0' };
const char *_coremask;
long v, needle;
int ncores = nr_cpus();
/*
/*
* This function returns array of usable cores in `cores` and
* returns the number of usable cores (array length) through
* nr_cores.
*/
if (strncmp(coremask, "0x", 2) == 0)
_coremask = coremask + 2;
else
_coremask = coremask;
if (strncmp(coremask, "0x", 2) == 0)
_coremask = coremask + 2;
else
_coremask = coremask;
core_list = realloc(NULL, sizeof(int) * 64);
if (!core_list) {
mscp_set_error("failed to realloc: %s", strerrno());
return -1;
}
core_list = realloc(NULL, sizeof(int) * 64);
if (!core_list) {
priv_set_errv("realloc: %s", strerrno());
return -1;
}
nr_usable = 0;
nr_all = 0;
for (n = strlen(_coremask) - 1; n >=0; n--) {
c[0] = _coremask[n];
v = strtol(c, NULL, 16);
if (v == LONG_MIN || v == LONG_MAX) {
mscp_set_error("invalid coremask: %s", coremask);
return -1;
}
nr_usable = 0;
nr_all = 0;
for (n = strlen(_coremask) - 1; n >= 0; n--) {
c[0] = _coremask[n];
v = strtol(c, NULL, 16);
if (v == LONG_MIN || v == LONG_MAX) {
priv_set_errv("invalid coremask: %s", coremask);
return -1;
}
for (needle = 0x01; needle < 0x10; needle <<= 1) {
nr_all++;
if (nr_all > ncores)
break; /* too long coremask */
if (v & needle) {
nr_usable++;
core_list = realloc(core_list, sizeof(int) * nr_usable);
if (!core_list) {
mscp_set_error("realloc: %s", strerrno());
return -1;
}
core_list[nr_usable - 1] = nr_all - 1;
}
}
}
for (needle = 0x01; needle < 0x10; needle <<= 1) {
nr_all++;
if (nr_all > ncores)
break; /* too long coremask */
if (v & needle) {
nr_usable++;
core_list = realloc(core_list, sizeof(int) * nr_usable);
if (!core_list) {
priv_set_errv("realloc: %s", strerrno());
return -1;
}
core_list[nr_usable - 1] = nr_all - 1;
}
}
}
if (nr_usable < 1) {
mscp_set_error("invalid core mask: %s", coremask);
return -1;
}
if (nr_usable < 1) {
priv_set_errv("invalid core mask: %s", coremask);
return -1;
}
*cores = core_list;
*nr_cores = nr_usable;
return 0;
*cores = core_list;
*nr_cores = nr_usable;
return 0;
}
static int default_nr_threads()
{
return (int)(floor(log(nr_cpus()) * 2) + 1);
return (int)(floor(log(nr_cpus()) * 2) + 1);
}
static int validate_and_set_defaut_params(struct mscp_opts *o)
{
if (o->nr_threads < 0) {
mscp_set_error("invalid nr_threads: %d", o->nr_threads);
priv_set_errv("invalid nr_threads: %d", o->nr_threads);
return -1;
} else if (o->nr_threads == 0)
o->nr_threads = default_nr_threads();
if (o->nr_ahead < 0) {
mscp_set_error("invalid nr_ahead: %d", o->nr_ahead);
priv_set_errv("invalid nr_ahead: %d", o->nr_ahead);
return -1;
} else if (o->nr_ahead == 0)
o->nr_ahead = DEFAULT_NR_AHEAD;
@@ -167,9 +164,9 @@ static int validate_and_set_defaut_params(struct mscp_opts *o)
else {
if (o->min_chunk_sz < getpagesize() ||
o->min_chunk_sz % getpagesize() != 0) {
mscp_set_error("min chunk size must be "
"larget than and multiple of page size %d: %lu",
getpagesize(), o->min_chunk_sz);
priv_set_errv("min chunk size must be "
"larget than and multiple of page size %d: %lu",
getpagesize(), o->min_chunk_sz);
return -1;
}
}
@@ -177,14 +174,14 @@ static int validate_and_set_defaut_params(struct mscp_opts *o)
if (o->max_chunk_sz) {
if (o->max_chunk_sz < getpagesize() ||
o->max_chunk_sz % getpagesize() != 0) {
mscp_set_error("min chunk size must be larget than and "
"multiple of page size %d: %lu",
getpagesize(), o->max_chunk_sz);
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) {
mscp_set_error("smaller max chunk size than "
"min chunk size: %lu < %lu",
o->max_chunk_sz, o->min_chunk_sz);
priv_set_errv("smaller max chunk size than "
"min chunk size: %lu < %lu",
o->max_chunk_sz, o->min_chunk_sz);
return -1;
}
}
@@ -192,41 +189,42 @@ static int validate_and_set_defaut_params(struct mscp_opts *o)
if (o->buf_sz == 0)
o->buf_sz = DEFAULT_BUF_SZ;
else if (o->buf_sz == 0) {
mscp_set_error("invalid buf size: %lu", o->buf_sz);
priv_set_errv("invalid buf size: %lu", o->buf_sz);
return -1;
}
if (o->max_startups == 0)
o->max_startups = DEFAULT_MAX_STARTUPS;
else if (o->max_startups < 0) {
mscp_set_error("invalid max_startups: %d", o->max_startups);
priv_set_errv("invalid max_startups: %d", o->max_startups);
return -1;
}
if (o->msg_fd == 0)
o->msg_fd = STDOUT_FILENO;
if (o->interval > 0) {
/* when the interval is set, establish SSH connections sequentially. */
o->max_startups = 1;
}
return 0;
}
struct mscp *mscp_init(const char *remote_host, int direction,
struct mscp_opts *o, struct mscp_ssh_opts *s)
struct mscp *mscp_init(const char *remote_host, int direction, struct mscp_opts *o,
struct mscp_ssh_opts *s)
{
struct mscp *m;
int n;
if (!remote_host) {
mscp_set_error("empty remote host");
priv_set_errv("empty remote host");
return NULL;
}
if (!(direction == MSCP_DIRECTION_L2R ||
direction == MSCP_DIRECTION_R2L)) {
mscp_set_error("invalid copy direction: %d", direction);
if (!(direction == MSCP_DIRECTION_L2R || direction == MSCP_DIRECTION_R2L)) {
priv_set_errv("invalid copy direction: %d", direction);
return NULL;
}
mprint_set_severity(o->severity);
set_print_severity(o->severity);
if (validate_and_set_defaut_params(o) < 0) {
return NULL;
@@ -234,7 +232,7 @@ struct mscp *mscp_init(const char *remote_host, int direction,
m = malloc(sizeof(*m));
if (!m) {
mscp_set_error("failed to allocate memory: %s", strerrno());
priv_set_errv("malloc: %s", strerrno());
return NULL;
}
@@ -247,32 +245,28 @@ struct mscp *mscp_init(const char *remote_host, int direction,
rwlock_init(&m->thread_rwlock);
if ((m->sem = sem_create(o->max_startups)) == NULL) {
mscp_set_error("sem_create: %s", strerrno());
priv_set_errv("sem_create: %s", strerrno());
goto free_out;
}
m->remote = strdup(remote_host);
if (!m->remote) {
mscp_set_error("failed to allocate memory: %s", strerrno());
priv_set_errv("strdup: %s", strerrno());
goto free_out;
}
m->direction = direction;
if (o->msg_fd > -1) {
m->msg_fp = fdopen(o->msg_fd, "a");
if (!m->msg_fp) {
mscp_set_error("fdopen failed: %s", strerrno());
goto free_out;
}
} else
m->msg_fp = NULL;
if (strlen(o->coremask) > 0) {
if (o->coremask) {
if (expand_coremask(o->coremask, &m->cores, &m->nr_cores) < 0)
goto free_out;
mpr_notice(m->msg_fp, "usable cpu cores:");
for (n = 0; n < m->nr_cores; n++)
mpr_notice(m->msg_fp, " %d", m->cores[n]);
mpr_notice(m->msg_fp, "\n");
char b[512], c[8];
memset(b, 0, sizeof(b));
for (n = 0; n < m->nr_cores; n++) {
memset(c, 0, sizeof(c));
snprintf(c, sizeof(c) - 1, " %d", m->cores[n]);
strlcat(b, c, sizeof(b));
}
pr_notice("usable cpu cores:%s", b);
}
m->opts = o;
@@ -300,14 +294,14 @@ int mscp_add_src_path(struct mscp *m, const char *src_path)
s = malloc(sizeof(*s));
if (!s) {
mscp_set_error("failed to allocate memory: %s", strerrno());
priv_set_errv("malloc: %s", strerrno());
return -1;
}
memset(s, 0, sizeof(*s));
s->path = strdup(src_path);
if (!s->path) {
mscp_set_error("failed to allocate memory: %s", strerrno());
priv_set_errv("malloc: %s", strerrno());
free(s);
return -1;
}
@@ -319,7 +313,7 @@ int mscp_add_src_path(struct mscp *m, const char *src_path)
int mscp_set_dst_path(struct mscp *m, const char *dst_path)
{
if (strlen(dst_path) + 1 >= PATH_MAX) {
mscp_set_error("too long dst path: %s", dst_path);
priv_set_errv("too long dst path: %s", dst_path);
return -1;
}
@@ -333,16 +327,16 @@ int mscp_set_dst_path(struct mscp *m, const char *dst_path)
static int get_page_mask(void)
{
long page_sz = sysconf(_SC_PAGESIZE);
size_t page_mask = 0;
int n;
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;
}
for (n = 0; page_sz > 0; page_sz >>= 1, n++) {
page_mask <<= 1;
page_mask |= 1;
}
return page_mask >> 1;
return page_mask >> 1;
}
static void mscp_stop_copy_thread(struct mscp *m)
@@ -380,7 +374,7 @@ void *mscp_scan_thread(void *arg)
struct stat ss, ds;
glob_t pglob;
int n;
m->ret_scan = 0;
switch (m->direction) {
@@ -393,13 +387,12 @@ void *mscp_scan_thread(void *arg)
dst_sftp = NULL;
break;
default:
mscp_set_error("invalid copy direction: %d", m->direction);
pr_err("invalid copy direction: %d", m->direction);
goto err_out;
}
/* initialize path_resolve_args */
memset(&a, 0, sizeof(a));
a.msg_fp = m->msg_fp;
a.total_bytes = &m->total_bytes;
if (list_count(&m->src_list) > 1)
@@ -416,19 +409,19 @@ void *mscp_scan_thread(void *arg)
a.max_chunk_sz = m->opts->max_chunk_sz;
a.chunk_align = get_page_mask();
mpr_info(m->msg_fp, "start to walk source path(s)\n");
pr_info("start to walk source path(s)");
/* walk a src_path recusively, and resolve path->dst_path for each src */
list_for_each_entry(s, &m->src_list, list) {
memset(&pglob, 0, sizeof(pglob));
if (mscp_glob(s->path, GLOB_NOCHECK, &pglob, src_sftp) < 0) {
mscp_set_error("mscp_glob: %s", strerrno());
pr_err("mscp_glob: %s", strerrno());
goto err_out;
}
for (n = 0; n < pglob.gl_pathc; n++) {
if (mscp_stat(pglob.gl_pathv[n], &ss, src_sftp) < 0) {
mscp_set_error("stat: %s %s", s->path, strerrno());
pr_err("stat: %s %s", s->path, strerrno());
goto err_out;
}
@@ -449,7 +442,7 @@ void *mscp_scan_thread(void *arg)
mscp_globfree(&pglob);
}
mpr_info(m->msg_fp, "walk source path(s) done\n");
pr_info("walk source path(s) done");
chunk_pool_set_filled(&m->cp);
m->ret_scan = 0;
return NULL;
@@ -464,7 +457,7 @@ int mscp_scan(struct mscp *m)
{
int ret = pthread_create(&m->tid_scan, NULL, mscp_scan_thread, m);
if (ret < 0) {
mscp_set_error("pthread_create_error: %d", ret);
priv_set_err("pthread_create: %d", ret);
m->tid_scan = 0;
mscp_stop(m);
return -1;
@@ -492,8 +485,6 @@ int mscp_scan_join(struct mscp *m)
return 0;
}
static void *mscp_copy_thread(void *arg);
static struct mscp_thread *mscp_copy_thread_spawn(struct mscp *m, int id)
@@ -502,8 +493,8 @@ static struct mscp_thread *mscp_copy_thread_spawn(struct mscp *m, int id)
int ret;
t = malloc(sizeof(*t));
if (!t){
mscp_set_error("malloc: %s,", strerrno());
if (!t) {
priv_set_errv("malloc: %s,", strerrno());
return NULL;
}
@@ -511,13 +502,13 @@ static struct mscp_thread *mscp_copy_thread_spawn(struct mscp *m, int id)
t->m = m;
t->id = id;
if (m->cores == NULL)
t->cpu = -1; /* not pinned to cpu */
t->cpu = -1; /* not pinned to cpu */
else
t->cpu = m->cores[id % m->nr_cores];
ret = pthread_create(&t->tid, NULL, mscp_copy_thread, t);
if (ret < 0) {
mscp_set_error("pthread_create error: %d", ret);
priv_set_errv("pthread_create: %d", ret);
free(t);
return NULL;
}
@@ -525,28 +516,25 @@ static struct mscp_thread *mscp_copy_thread_spawn(struct mscp *m, int id)
return t;
}
int mscp_start(struct mscp *m)
{
struct mscp_thread *t;
int n, ret = 0;
if ((n = chunk_pool_size(&m->cp)) < m->opts->nr_threads) {
mpr_notice(m->msg_fp, "we have only %d chunk(s). "
"set number of connections to %d\n", n, n);
pr_notice("we have %d chunk(s), set number of connections to %d", n, n);
m->opts->nr_threads = n;
}
for (n = 0; n < m->opts->nr_threads; n++) {
t = mscp_copy_thread_spawn(m, n);
if (!t) {
mpr_err(m->msg_fp, "failed to spawn copy thread\n");
if (!t)
break;
}
RWLOCK_WRITE_ACQUIRE(&m->thread_rwlock);
list_add_tail(&t->list, &m->thread_list);
RWLOCK_RELEASE();
}
}
return n;
}
@@ -554,16 +542,19 @@ int mscp_start(struct mscp *m)
int mscp_join(struct mscp *m)
{
struct mscp_thread *t;
struct path *p;
size_t done = 0, nr_copied = 0, nr_tobe_copied = 0;
int n, ret = 0;
/* waiting for scan thread joins... */
ret = mscp_scan_join(m);
/* waiting for copy threads join... */
/* waiting for copy threads join... */
RWLOCK_READ_ACQUIRE(&m->thread_rwlock);
list_for_each_entry(t, &m->thread_list, list) {
pthread_join(t->tid, NULL);
if (t->ret < 0)
done += t->done;
if (t->ret != 0)
ret = t->ret;
if (t->sftp) {
ssh_sftp_close(t->sftp);
@@ -572,108 +563,149 @@ int mscp_join(struct mscp *m)
}
RWLOCK_RELEASE();
if (m->first) {
ssh_sftp_close(m->first);
if (m->first) {
ssh_sftp_close(m->first);
m->first = NULL;
}
/* count up number of transferred files */
list_for_each_entry(p, &m->path_list, list) {
nr_tobe_copied++;
if (p->state == FILE_STATE_DONE) {
nr_copied++;
}
}
pr_notice("%lu/%lu bytes copied for %lu/%lu files", done, m->total_bytes,
nr_copied, nr_tobe_copied);
return ret;
}
/* copy thread related functions */
/* copy thread-related functions */
static void wait_for_interval(int interval)
{
_Atomic static long next;
struct timeval t;
long now;
gettimeofday(&t, NULL);
now = t.tv_sec * 1000000 + t.tv_usec;
if (next - now > 0)
usleep(next - now);
next = now + interval * 1000000;
}
static void mscp_copy_thread_cleanup(void *arg)
{
struct mscp_thread *t = arg;
t->finished = true;
struct mscp_thread *t = arg;
t->finished = true;
}
void *mscp_copy_thread(void *arg)
{
sftp_session src_sftp, dst_sftp;
struct mscp_thread *t = arg;
sftp_session src_sftp, dst_sftp;
struct mscp_thread *t = arg;
struct mscp *m = t->m;
struct chunk *c;
struct chunk *c;
bool nomore;
if (t->cpu > -1) {
if (set_thread_affinity(pthread_self(), t->cpu) < 0) {
t->ret = -1;
return NULL;
/* when error occurs, each thread prints error messages
* immediately with pr_* functions. */
pthread_cleanup_push(mscp_copy_thread_cleanup, t);
if (t->cpu > -1) {
if (set_thread_affinity(pthread_self(), t->cpu) < 0) {
pr_err("set_thread_affinity: %s", priv_get_err());
goto err_out;
}
}
pr_notice("thread[%d]: pin to cpu core %d", t->id, t->cpu);
}
if (sem_wait(m->sem) < 0) {
mscp_set_error("sem_wait: %s", strerrno());
mpr_err(m->msg_fp, "%s", mscp_get_error());
pr_err("sem_wait: %s", strerrno());
goto err_out;
}
mpr_notice(m->msg_fp, "connecting to %s for a copy thread[%d]...\n",
m->remote, t->id);
t->sftp = ssh_init_sftp_session(m->remote, m->ssh_opts);
if (!(nomore = chunk_pool_is_empty(&m->cp))) {
if (m->opts->interval > 0)
wait_for_interval(m->opts->interval);
pr_notice("thread[%d]: connecting to %s", t->id, m->remote);
t->sftp = ssh_init_sftp_session(m->remote, m->ssh_opts);
}
if (sem_post(m->sem) < 0) {
mscp_set_error("sem_post: %s", strerrno());
mpr_err(m->msg_fp, "%s", mscp_get_error());
pr_err("sem_post: %s", strerrno());
goto err_out;
}
if (nomore) {
pr_notice("thread[%d]: no more connections needed", t->id);
goto out;
}
if (!t->sftp) {
mpr_err(m->msg_fp, "copy thread[%d]: %s\n", t->id, mscp_get_error());
pr_err("thread[%d]: %s", t->id, priv_get_err());
goto err_out;
}
switch (m->direction) {
case MSCP_DIRECTION_L2R:
src_sftp = NULL;
dst_sftp = t->sftp;
break;
case MSCP_DIRECTION_R2L:
src_sftp = t->sftp;
dst_sftp = NULL;
break;
default:
return NULL; /* not reached */
}
switch (m->direction) {
case MSCP_DIRECTION_L2R:
src_sftp = NULL;
dst_sftp = t->sftp;
break;
case MSCP_DIRECTION_R2L:
src_sftp = t->sftp;
dst_sftp = NULL;
break;
default:
assert(false);
goto err_out; /* not reached */
}
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
pthread_cleanup_push(mscp_copy_thread_cleanup, t);
while (1) {
c = chunk_pool_pop(&m->cp);
while (1) {
c = chunk_pool_pop(&m->cp);
if (c == CHUNK_POP_WAIT) {
usleep(100); /* XXX: hard code */
continue;
}
if (!c)
break; /* no more chunks */
if (!c)
break; /* no more chunks */
if ((t->ret = copy_chunk(m->msg_fp,
c, src_sftp, dst_sftp, m->opts->nr_ahead,
m->opts->buf_sz, &t->done)) < 0)
if ((t->ret = copy_chunk(c, src_sftp, dst_sftp, m->opts->nr_ahead,
m->opts->buf_sz, m->opts->preserve_ts,
&t->done)) < 0)
break;
}
}
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
if (t->ret < 0)
mpr_err(m->msg_fp, "copy failed: chunk %s 0x%010lx-0x%010lx\n",
c->p->path, c->off, c->off + c->len);
if (t->ret < 0) {
pr_err("thread[%d]: copy failed: %s -> %s, 0x%010lx-0x%010lx, %s", t->id,
c->p->path, c->p->dst_path, c->off, c->off + c->len,
priv_get_err());
}
return NULL;
return NULL;
err_out:
t->finished = true;
t->ret = -1;
return NULL;
out:
t->finished = true;
t->ret = 0;
return NULL;
}
/* cleanup-related functions */
/* cleanup related functions */
static void free_src(struct list_head *list)
static void list_free_src(struct list_head *list)
{
struct src *s;
s = list_entry(list, typeof(*s), list);
@@ -681,21 +713,14 @@ static void free_src(struct list_head *list)
free(s);
}
static void free_path(struct list_head *list)
static void list_free_path(struct list_head *list)
{
struct path *p;
p = list_entry(list, typeof(*p), list);
free(p);
free_path(p);
}
static void free_chunk(struct list_head *list)
{
struct chunk *c;
c = list_entry(list, typeof(*c), list);
free(c);
}
static void free_thread(struct list_head *list)
static void list_free_thread(struct list_head *list)
{
struct mscp_thread *t;
t = list_entry(list, typeof(*t), list);
@@ -704,22 +729,22 @@ static void free_thread(struct list_head *list)
void mscp_cleanup(struct mscp *m)
{
if (m->first) {
ssh_sftp_close(m->first);
if (m->first) {
ssh_sftp_close(m->first);
m->first = NULL;
}
list_free_f(&m->src_list, free_src);
list_free_f(&m->src_list, list_free_src);
INIT_LIST_HEAD(&m->src_list);
list_free_f(&m->path_list, free_path);
list_free_f(&m->path_list, list_free_path);
INIT_LIST_HEAD(&m->path_list);
chunk_pool_release(&m->cp);
chunk_pool_init(&m->cp);
RWLOCK_WRITE_ACQUIRE(&m->thread_rwlock);
list_free_f(&m->thread_list, free_thread);
list_free_f(&m->thread_list, list_free_thread);
RWLOCK_RELEASE();
}
@@ -737,19 +762,20 @@ void mscp_free(struct mscp *m)
void mscp_get_stats(struct mscp *m, struct mscp_stats *s)
{
int nr_finished = 0, nr_threads = 0;
struct mscp_thread *t;
bool finished = true;
s->total = m->total_bytes;
s->done = 0;
RWLOCK_READ_ACQUIRE(&m->thread_rwlock);
list_for_each_entry(t, &m->thread_list, list) {
nr_threads++;
s->done += t->done;
if (!t->finished)
finished = false;
if (t->finished)
nr_finished++;
}
RWLOCK_RELEASE();
s->finished = finished;
s->finished = nr_threads > 0 ? (nr_finished == nr_threads) : false;
}

View File

@@ -0,0 +1,10 @@
#ifndef _OPENBSD_COMPAT_H
#define _OPENBSD_COMPAT_H
#include "config.h"
#ifndef HAVE_STRLCAT
size_t strlcat(char *dst, const char *src, size_t siz);
#endif
#endif /* _OPENBSD_COMPAT_H_ */

View File

@@ -0,0 +1,62 @@
/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */
/*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* OPENBSD ORIGINAL: lib/libc/string/strlcat.c */
#include "config.h"
#ifndef HAVE_STRLCAT
#include <sys/types.h>
#include <string.h>
/*
* Appends src to string dst of size siz (unlike strncat, siz is the
* full size of dst, not space left). At most siz-1 characters
* will be copied. Always NUL terminates (unless siz <= strlen(dst)).
* Returns strlen(src) + MIN(siz, strlen(initial dst)).
* If retval >= siz, truncation occurred.
*/
size_t
strlcat(char *dst, const char *src, size_t siz)
{
char *d = dst;
const char *s = src;
size_t n = siz;
size_t dlen;
/* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0')
d++;
dlen = d - dst;
n = siz - dlen;
if (n == 0)
return(dlen + strlen(s));
while (*s != '\0') {
if (n != 1) {
*d++ = *s;
n--;
}
s++;
}
*d = '\0';
return(dlen + (s - src)); /* count does not include NUL */
}
#endif /* !HAVE_STRLCAT */

View File

@@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#include <string.h>
#include <unistd.h>
#include <dirent.h>
@@ -6,17 +7,17 @@
#include <assert.h>
#include <ssh.h>
#include <util.h>
#include <minmax.h>
#include <fileops.h>
#include <list.h>
#include <atomic.h>
#include <path.h>
#include <message.h>
#include <strerrno.h>
#include <print.h>
/* chunk pool operations */
#define CHUNK_POOL_STATE_FILLING 0
#define CHUNK_POOL_STATE_FILLED 1
#define CHUNK_POOL_STATE_FILLING 0
#define CHUNK_POOL_STATE_FILLED 1
void chunk_pool_init(struct chunk_pool *cp)
{
@@ -49,6 +50,10 @@ size_t chunk_pool_size(struct chunk_pool *cp)
return cp->count;
}
bool chunk_pool_is_empty(struct chunk_pool *cp)
{
return list_empty(&cp->list);
}
struct chunk *chunk_pool_pop(struct chunk_pool *cp)
{
@@ -68,7 +73,7 @@ struct chunk *chunk_pool_pop(struct chunk_pool *cp)
}
LOCK_RELEASE();
/* return CHUNK_POP_WAIT would be very rare case, because it
/* return CHUNK_POP_WAIT would be a rare case, because it
* means copying over SSH is faster than traversing
* local/remote file paths.
*/
@@ -78,9 +83,9 @@ struct chunk *chunk_pool_pop(struct chunk_pool *cp)
static void chunk_free(struct list_head *list)
{
struct chunk *c;
c = list_entry(list, typeof(*c), list);
free(c);
struct chunk *c;
c = list_entry(list, typeof(*c), list);
free(c);
}
void chunk_pool_release(struct chunk_pool *cp)
@@ -89,106 +94,128 @@ void chunk_pool_release(struct chunk_pool *cp)
}
/* paths of copy source resoltion */
static int resolve_dst_path(const char *src_file_path, char *dst_file_path,
struct path_resolve_args *a)
static char *resolve_dst_path(const char *src_file_path, struct path_resolve_args *a)
{
char copy[PATH_MAX];
char *prefix;
int offset;
char copy[PATH_MAX + 1], dst_file_path[PATH_MAX + 1];
char *prefix;
int offset;
int ret;
strncpy(copy, a->src_path, PATH_MAX - 1);
prefix = dirname(copy);
if (!prefix) {
mscp_set_error("dirname: %s", strerrno());
return -1;
}
if (strlen(prefix) == 1 && prefix[0] == '.')
offset = 0;
else
offset = strlen(prefix) + 1;
strncpy(copy, a->src_path, PATH_MAX);
prefix = dirname(copy);
if (!prefix) {
pr_err("dirname: %s", strerrno());
return NULL;
}
if (!a->src_path_is_dir && !a->dst_path_is_dir) {
/* src path is file. dst path is (1) file, or (2) does not exist.
offset = strlen(prefix) + 1;
if (strlen(prefix) == 1) { /* corner cases */
switch (prefix[0]) {
case '.':
offset = 0;
break;
case '/':
offset = 1;
break;
}
}
if (!a->src_path_is_dir && !a->dst_path_is_dir) {
/* src path is file. dst path is (1) file, or (2) does not exist.
* In the second case, we need to put src under the dst.
*/
if (a->dst_path_should_dir)
snprintf(dst_file_path, PATH_MAX - 1, "%s/%s",
a->dst_path, a->src_path + offset);
else
strncpy(dst_file_path, a->dst_path, PATH_MAX - 1);
}
if (a->dst_path_should_dir)
ret = snprintf(dst_file_path, PATH_MAX, "%s/%s", a->dst_path,
a->src_path + offset);
else
ret = snprintf(dst_file_path, PATH_MAX, "%s", a->dst_path);
}
/* src is file, and dst is dir */
if (!a->src_path_is_dir && a->dst_path_is_dir)
snprintf(dst_file_path, PATH_MAX - 1, "%s/%s",
a->dst_path, a->src_path + offset);
/* src is file, and dst is dir */
if (!a->src_path_is_dir && a->dst_path_is_dir)
ret = snprintf(dst_file_path, PATH_MAX, "%s/%s", a->dst_path,
a->src_path + offset);
/* both are directory */
if (a->src_path_is_dir && a->dst_path_is_dir)
snprintf(dst_file_path, PATH_MAX - 1, "%s/%s",
a->dst_path, src_file_path + offset);
/* both are directory */
if (a->src_path_is_dir && a->dst_path_is_dir)
ret = snprintf(dst_file_path, PATH_MAX, "%s/%s", a->dst_path,
src_file_path + offset);
/* dst path does not exist. change dir name to dst_path */
if (a->src_path_is_dir && !a->dst_path_is_dir)
snprintf(dst_file_path, PATH_MAX - 1, "%s/%s",
a->dst_path, src_file_path + strlen(a->src_path) + 1);
/* dst path does not exist. change dir name to dst_path */
if (a->src_path_is_dir && !a->dst_path_is_dir)
ret = snprintf(dst_file_path, PATH_MAX, "%s/%s", a->dst_path,
src_file_path + strlen(a->src_path) + 1);
mpr_debug(a->msg_fp, "file: %s -> %s\n", src_file_path, dst_file_path);
if (ret >= PATH_MAX) {
pr_warn("Too long path: %s", dst_file_path);
return NULL;
}
return 0;
pr_debug("file: %s -> %s", src_file_path, dst_file_path);
return strndup(dst_file_path, PATH_MAX);
}
/* chunk preparation */
static struct chunk *alloc_chunk(struct path *p)
{
struct chunk *c;
struct chunk *c;
if (!(c = malloc(sizeof(*c)))) {
mscp_set_error("malloc %s", strerrno());
return NULL;
}
memset(c, 0, sizeof(*c));
if (!(c = malloc(sizeof(*c)))) {
pr_err("malloc %s", strerrno());
return NULL;
}
memset(c, 0, sizeof(*c));
c->p = p;
c->off = 0;
c->len = 0;
refcnt_inc(&p->refcnt);
return c;
c->p = p;
c->off = 0;
c->len = 0;
refcnt_inc(&p->refcnt);
return c;
}
static int resolve_chunk(struct path *p, struct path_resolve_args *a)
{
struct chunk *c;
size_t chunk_sz;
size_t size;
struct chunk *c;
size_t chunk_sz;
size_t size;
if (p->size <= a->min_chunk_sz)
chunk_sz = p->size;
else if (a->max_chunk_sz)
chunk_sz = a->max_chunk_sz;
else {
chunk_sz = (p->size - (p->size % a->nr_conn)) / a->nr_conn;
chunk_sz &= ~a->chunk_align; /* align with page_sz */
if (chunk_sz <= a->min_chunk_sz)
chunk_sz = a->min_chunk_sz;
}
if (p->size <= a->min_chunk_sz)
chunk_sz = p->size;
else if (a->max_chunk_sz)
chunk_sz = a->max_chunk_sz;
else {
chunk_sz = (p->size - (p->size % a->nr_conn)) / a->nr_conn;
chunk_sz &= ~a->chunk_align; /* align with page_sz */
if (chunk_sz <= a->min_chunk_sz)
chunk_sz = a->min_chunk_sz;
}
/* for (size = f->size; size > 0;) does not create a file
/* for (size = f->size; size > 0;) does not create a file
* (chunk) when file size is 0. This do {} while (size > 0)
* creates just open/close a 0-byte file.
*/
size = p->size;
do {
c = alloc_chunk(p);
if (!c)
return -1;
c->off = p->size - size;
c->len = size < chunk_sz ? size : chunk_sz;
size -= c->len;
chunk_pool_add(a->cp, c);
} while (size > 0);
size = p->size;
do {
c = alloc_chunk(p);
if (!c)
return -1;
c->off = p->size - size;
c->len = size < chunk_sz ? size : chunk_sz;
size -= c->len;
chunk_pool_add(a->cp, c);
} while (size > 0);
return 0;
return 0;
}
void free_path(struct path *p)
{
if (p->path)
free(p->path);
if (p->dst_path)
free(p->dst_path);
free(p);
}
static int append_path(sftp_session sftp, const char *path, struct stat st,
@@ -197,19 +224,24 @@ static int append_path(sftp_session sftp, const char *path, struct stat st,
struct path *p;
if (!(p = malloc(sizeof(*p)))) {
mscp_set_error("failed to allocate memory: %s", strerrno());
priv_set_errv("failed to allocate memory: %s", strerrno());
return -1;
}
memset(p, 0, sizeof(*p));
INIT_LIST_HEAD(&p->list);
strncpy(p->path, path, PATH_MAX - 1);
p->path = strndup(path, PATH_MAX);
if (!p->path) {
pr_err("strndup: %s", strerrno());
goto free_out;
}
p->size = st.st_size;
p->mode = st.st_mode;
p->state = FILE_STATE_INIT;
lock_init(&p->lock);
if (resolve_dst_path(p->path, p->dst_path, a) < 0)
p->dst_path = resolve_dst_path(p->path, a);
if (!p->dst_path)
goto free_out;
if (resolve_chunk(p, a) < 0)
@@ -222,31 +254,33 @@ static int append_path(sftp_session sftp, const char *path, struct stat st,
return 0;
free_out:
free(p);
free_path(p);
return -1;
}
static bool check_path_should_skip(const char *path)
{
int len = strlen(path);
if ((len == 1 && strncmp(path, ".", 1) == 0) ||
(len == 2 && strncmp(path, "..", 2) == 0)) {
return true;
}
return false;
{
int len = strlen(path);
if ((len == 1 && strncmp(path, ".", 1) == 0) ||
(len == 2 && strncmp(path, "..", 2) == 0)) {
return true;
}
return false;
}
static int walk_path_recursive(sftp_session sftp, const char *path,
struct list_head *path_list, struct path_resolve_args *a)
{
char next_path[PATH_MAX];
char next_path[PATH_MAX + 1];
struct dirent *e;
struct stat st;
MDIR *d;
int ret;
if (mscp_stat(path, &st, sftp) < 0)
if (mscp_stat(path, &st, sftp) < 0) {
pr_err("stat: %s: %s", path, strerrno());
return -1;
}
if (S_ISREG(st.st_mode)) {
/* this path is regular file. it is to be copied */
@@ -256,22 +290,29 @@ static int walk_path_recursive(sftp_session sftp, const char *path,
if (!S_ISDIR(st.st_mode))
return 0; /* not a regular file and not a directory, skip it. */
/* ok, this path is directory. walk it. */
if (!(d = mscp_opendir(path, sftp)))
/* ok, this path is a directory. walk through it. */
if (!(d = mscp_opendir(path, sftp))) {
pr_err("opendir: %s: %s", path, strerrno());
return -1;
}
for (e = mscp_readdir(d); e; e = mscp_readdir(d)) {
if (check_path_should_skip(e->d_name))
continue;
if (strlen(path) + 1 + strlen(e->d_name) > PATH_MAX) {
mscp_set_error("too long path: %s/%s", path, e->d_name);
return -1;
ret = snprintf(next_path, PATH_MAX, "%s/%s", path, e->d_name);
if (ret >= PATH_MAX) {
pr_warn("Too long path: %s/%s", path, e->d_name);
continue;
}
snprintf(next_path, sizeof(next_path), "%s/%s", path, e->d_name);
ret = walk_path_recursive(sftp, next_path, path_list, a);
if (ret < 0)
return ret;
walk_path_recursive(sftp, next_path, path_list, a);
/* do not stop even when walk_path_recursive returns
* -1 due to an unreadable file. go to a next
* file. Thus, do not pass error messages via
* priv_set_err() under walk_path_recursive. Print
* the error with pr_err immediately.
*/
}
mscp_closedir(d);
@@ -285,69 +326,59 @@ int walk_src_path(sftp_session src_sftp, const char *src_path,
return walk_path_recursive(src_sftp, src_path, path_list, a);
}
void path_dump(struct list_head *path_list)
{
struct path *p;
list_for_each_entry(p, path_list, list) {
printf("src: %s %lu-byte\n", p->path, p->size);
printf("dst: %s\n", p->dst_path);
}
}
/* based on
* https://stackoverflow.com/questions/2336242/recursive-mkdir-system-call-on-unix */
static int touch_dst_path(struct path *p, sftp_session sftp)
{
/* XXX: should reflect the permission of the original directory? */
mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO;
/* XXX: should reflect the permission of the original directory? */
mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO;
struct stat st;
char path[PATH_MAX];
char *needle;
int ret;
char path[PATH_MAX];
char *needle;
int ret;
mf *f;
strncpy(path, p->dst_path, sizeof(path));
strncpy(path, p->dst_path, sizeof(path));
/* mkdir -p.
/* mkdir -p.
* XXX: this may be slow when dst is the remote side. need speed-up. */
for (needle = strchr(path + 1, '/'); needle; needle = strchr(needle + 1, '/')) {
*needle = '\0';
for (needle = strchr(path + 1, '/'); needle; needle = strchr(needle + 1, '/')) {
*needle = '\0';
if (mscp_stat(path, &st, sftp) == 0) {
if (S_ISDIR(st.st_mode))
goto next; /* directory exists. go deeper */
else
else {
priv_set_errv("mscp_stat %s: not a directory", path);
return -1; /* path exists, but not directory. */
}
}
if (errno == ENOENT) {
/* no file on the path. create directory. */
if (mscp_mkdir(path, mode, sftp) < 0) {
mscp_set_error("mscp_mkdir %s: %s", path, strerrno());
priv_set_errv("mscp_mkdir %s: %s", path, strerrno());
return -1;
}
}
next:
*needle = '/';
}
next:
*needle = '/';
}
/* Do not set O_TRUNC here. Instead, do mscp_setstat() at the
* end. see https://bugzilla.mindrot.org/show_bug.cgi?id=3431 */
f = mscp_open(p->dst_path, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR, sftp);
f = mscp_open(p->dst_path, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR, sftp);
if (!f) {
mscp_set_error("mscp_open %s: %s\n", p->dst_path, strerrno());
priv_set_errv("mscp_open %s: %s\n", p->dst_path, strerrno());
return -1;
}
mscp_close(f);
return 0;
return 0;
}
static int prepare_dst_path(FILE *msg_fp, struct path *p, sftp_session dst_sftp)
static int prepare_dst_path(struct path *p, sftp_session dst_sftp)
{
int ret = 0;
@@ -358,7 +389,7 @@ static int prepare_dst_path(FILE *msg_fp, struct path *p, sftp_session dst_sftp)
goto out;
}
p->state = FILE_STATE_OPENED;
mpr_info(msg_fp, "copy start: %s\n", p->path);
pr_info("copy start: %s", p->path);
}
out:
@@ -366,161 +397,157 @@ out:
return ret;
}
/* functions for copy */
static ssize_t read_to_buf(void *ptr, size_t len, void *userdata)
{
int fd = *((int *)userdata);
return read(fd, ptr, len);
int fd = *((int *)userdata);
return read(fd, ptr, len);
}
static int copy_chunk_l2r(struct chunk *c, int fd, sftp_file sf,
int nr_ahead, int buf_sz, size_t *counter)
static int copy_chunk_l2r(struct chunk *c, int fd, sftp_file sf, int nr_ahead, int buf_sz,
size_t *counter)
{
ssize_t read_bytes, remaind, thrown;
int idx, ret;
struct {
uint32_t id;
ssize_t len;
} reqs[nr_ahead];
ssize_t read_bytes, remaind, thrown;
int idx, ret;
struct {
uint32_t id;
ssize_t len;
} reqs[nr_ahead];
if (c->len == 0)
return 0;
if (c->len == 0)
return 0;
remaind = thrown = c->len;
for (idx = 0; idx < nr_ahead && thrown > 0; idx++) {
reqs[idx].len = min(thrown, buf_sz);
reqs[idx].len = sftp_async_write(sf, read_to_buf, reqs[idx].len, &fd,
&reqs[idx].id);
if (reqs[idx].len < 0) {
mscp_set_error("sftp_async_write: %s or %s",
sftp_get_ssh_error(sf->sftp), strerrno());
return -1;
}
thrown -= reqs[idx].len;
}
remaind = thrown = c->len;
for (idx = 0; idx < nr_ahead && thrown > 0; idx++) {
reqs[idx].len = min(thrown, buf_sz);
reqs[idx].len = sftp_async_write(sf, read_to_buf, reqs[idx].len, &fd,
&reqs[idx].id);
if (reqs[idx].len < 0) {
priv_set_errv("sftp_async_write: %s or %s",
sftp_get_ssh_error(sf->sftp), strerrno());
return -1;
}
thrown -= reqs[idx].len;
}
for (idx = 0; remaind > 0; idx = (idx + 1) % nr_ahead) {
ret = sftp_async_write_end(sf, reqs[idx].id, 1);
if (ret != SSH_OK) {
mscp_set_error("sftp_async_write_end: %s",
sftp_get_ssh_error(sf->sftp));
return -1;
}
for (idx = 0; remaind > 0; idx = (idx + 1) % nr_ahead) {
ret = sftp_async_write_end(sf, reqs[idx].id, 1);
if (ret != SSH_OK) {
priv_set_errv("sftp_async_write_end: %s",
sftp_get_ssh_error(sf->sftp));
return -1;
}
*counter += reqs[idx].len;
remaind -= reqs[idx].len;
*counter += reqs[idx].len;
remaind -= reqs[idx].len;
if (remaind <= 0)
break;
if (remaind <= 0)
break;
if (thrown <= 0)
continue;
if (thrown <= 0)
continue;
reqs[idx].len = min(thrown, buf_sz);
reqs[idx].len = sftp_async_write(sf, read_to_buf, reqs[idx].len, &fd,
&reqs[idx].id);
if (reqs[idx].len < 0) {
mscp_set_error("sftp_async_write: %s or %s",
sftp_get_ssh_error(sf->sftp), strerrno());
return -1;
}
thrown -= reqs[idx].len;
}
reqs[idx].len = min(thrown, buf_sz);
reqs[idx].len = sftp_async_write(sf, read_to_buf, reqs[idx].len, &fd,
&reqs[idx].id);
if (reqs[idx].len < 0) {
priv_set_errv("sftp_async_write: %s or %s",
sftp_get_ssh_error(sf->sftp), strerrno());
return -1;
}
thrown -= reqs[idx].len;
}
if (remaind < 0) {
mscp_set_error("invalid remaind bytes %ld. "
"last async_write_end bytes %lu.",
remaind, reqs[idx].len);
return -1;
}
return 0;
if (remaind < 0) {
priv_set_errv("invalid remaind bytes %ld. "
"last async_write_end bytes %lu.",
remaind, reqs[idx].len);
return -1;
}
return 0;
}
static int copy_chunk_r2l(struct chunk *c, sftp_file sf, int fd,
int nr_ahead, int buf_sz, size_t *counter)
static int copy_chunk_r2l(struct chunk *c, sftp_file sf, int fd, int nr_ahead, int buf_sz,
size_t *counter)
{
ssize_t read_bytes, write_bytes, remaind, thrown;
char buf[buf_sz];
int idx;
struct {
int id;
ssize_t len;
} reqs[nr_ahead];
ssize_t read_bytes, write_bytes, remaind, thrown;
char buf[buf_sz];
int idx;
struct {
int id;
ssize_t len;
} reqs[nr_ahead];
if (c->len == 0)
return 0;
if (c->len == 0)
return 0;
remaind = thrown = c->len;
remaind = 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) {
mscp_set_error("sftp_async_read_begin: %d",
sftp_get_error(sf->sftp));
return -1;
}
thrown -= reqs[idx].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",
sftp_get_error(sf->sftp));
return -1;
}
thrown -= reqs[idx].len;
}
for (idx = 0; remaind > 0; idx = (idx + 1) % nr_ahead) {
read_bytes = sftp_async_read(sf, buf, reqs[idx].len, reqs[idx].id);
if (read_bytes == SSH_ERROR) {
mscp_set_error("sftp_async_read: %d",
sftp_get_error(sf->sftp));
return -1;
}
for (idx = 0; remaind > 0; idx = (idx + 1) % nr_ahead) {
read_bytes = sftp_async_read(sf, buf, reqs[idx].len, reqs[idx].id);
if (read_bytes == SSH_ERROR) {
priv_set_errv("sftp_async_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;
}
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;
}
write_bytes = write(fd, buf, read_bytes);
if (write_bytes < 0) {
mscp_set_error("write: %s", strerrno());
return -1;
}
write_bytes = write(fd, buf, read_bytes);
if (write_bytes < 0) {
priv_set_errv("write: %s", strerrno());
return -1;
}
if (write_bytes < read_bytes) {
mscp_set_error("failed to write full bytes");
return -1;
}
if (write_bytes < read_bytes) {
priv_set_errv("failed to write full bytes");
return -1;
}
*counter += write_bytes;
remaind -= read_bytes;
}
*counter += write_bytes;
remaind -= read_bytes;
}
if (remaind < 0) {
mscp_set_error("invalid remaind bytes %ld. last async_read bytes %ld. "
"last write bytes %ld",
remaind, read_bytes, write_bytes);
return -1;
}
if (remaind < 0) {
priv_set_errv("invalid remaind bytes %ld. last async_read bytes %ld. "
"last write bytes %ld",
remaind, read_bytes, write_bytes);
return -1;
}
return 0;
return 0;
}
static int _copy_chunk(struct chunk *c, mf *s, mf *d,
int nr_ahead, int buf_sz, size_t *counter)
static int _copy_chunk(struct chunk *c, mf *s, mf *d, int nr_ahead, int buf_sz,
size_t *counter)
{
if (s->local && d->remote) /* local to remote copy */
return copy_chunk_l2r(c, s->local, d->remote, nr_ahead, buf_sz, counter);
else if (s->remote && d->local) /* remote to local copy */
else if (s->remote && d->local) /* remote to local copy */
return copy_chunk_r2l(c, s->remote, d->local, nr_ahead, buf_sz, counter);
assert(false);
return -1; /* not reached */
}
int copy_chunk(FILE *msg_fp, struct chunk *c,
sftp_session src_sftp, sftp_session dst_sftp,
int nr_ahead, int buf_sz, size_t *counter)
int copy_chunk(struct chunk *c, sftp_session src_sftp, sftp_session dst_sftp,
int nr_ahead, int buf_sz, bool preserve_ts, size_t *counter)
{
mode_t mode;
int flags;
@@ -529,44 +556,41 @@ int copy_chunk(FILE *msg_fp, struct chunk *c,
assert((src_sftp && !dst_sftp) || (!src_sftp && dst_sftp));
if (prepare_dst_path(msg_fp, c->p, dst_sftp) < 0)
if (prepare_dst_path(c->p, dst_sftp) < 0)
return -1;
/* open src */
flags = O_RDONLY;
mode = S_IRUSR;
flags = O_RDONLY;
mode = S_IRUSR;
s = mscp_open(c->p->path, flags, mode, src_sftp);
if (!s) {
mscp_set_error("mscp_open: %s: %s", c->p->path, strerrno());
priv_set_errv("mscp_open: %s: %s", c->p->path, strerrno());
return -1;
}
if (mscp_lseek(s, c->off) < 0) {
mscp_set_error("mscp_lseek: %s: %s", c->p->path, strerrno());
priv_set_errv("mscp_lseek: %s: %s", c->p->path, strerrno());
return -1;
}
/* open dst */
flags = O_WRONLY;
mode = S_IRUSR|S_IWUSR;
flags = O_WRONLY;
mode = S_IRUSR | S_IWUSR;
d = mscp_open(c->p->dst_path, flags, mode, dst_sftp);
if (!d) {
mscp_close(s);
mscp_set_error("mscp_open: %s: %s", c->p->dst_path, strerrno());
priv_set_errv("mscp_open: %s: %s", c->p->dst_path, strerrno());
return -1;
}
if (mscp_lseek(d, c->off) < 0) {
mscp_set_error("mscp_lseek: %s: %s", c->p->dst_path, strerrno());
priv_set_errv("mscp_lseek: %s: %s", c->p->dst_path, strerrno());
return -1;
}
mpr_debug(msg_fp, "copy chunk start: %s 0x%lx-0x%lx\n",
c->p->path, c->off, c->off + c->len);
pr_debug("copy chunk start: %s 0x%lx-0x%lx", c->p->path, c->off, c->off + c->len);
ret = _copy_chunk(c, s, d, nr_ahead, buf_sz, counter);
mpr_debug(msg_fp, "copy chunk done: %s 0x%lx-0x%lx\n",
c->p->path, c->off, c->off + c->len);
pr_debug("copy chunk done: %s 0x%lx-0x%lx", c->p->path, c->off, c->off + c->len);
mscp_close(d);
mscp_close(s);
@@ -574,11 +598,19 @@ int copy_chunk(FILE *msg_fp, struct chunk *c,
return ret;
if (refcnt_dec(&c->p->refcnt) == 0) {
struct stat st;
c->p->state = FILE_STATE_DONE;
if (mscp_setstat(c->p->dst_path, c->p->mode, c->p->size, dst_sftp) < 0)
mpr_err(msg_fp, "failed to chmod and truncate %s: %s\n",
c->p->path, strerrno());
mpr_info(msg_fp, "copy done: %s\n", c->p->path);
/* sync stat */
if (mscp_stat(c->p->path, &st, src_sftp) < 0) {
priv_set_errv("mscp_stat: %s: %s", c->p->path, strerrno());
return -1;
}
if (mscp_setstat(c->p->dst_path, &st, preserve_ts, dst_sftp) < 0) {
priv_set_errv("mscp_setstat: %s: %s", c->p->path, strerrno());
return -1;
}
pr_info("copy done: %s", c->p->path);
}
return ret;

View File

@@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#ifndef _PATH_H_
#define _PATH_H_
@@ -9,42 +10,40 @@
#include <list.h>
#include <atomic.h>
#include <ssh.h>
#include <message.h>
struct path {
struct list_head list; /* mscp->path_list */
struct list_head list; /* mscp->path_list */
char path[PATH_MAX]; /* file path */
size_t size; /* size of file on this path */
mode_t mode; /* permission */
char *path; /* file path */
size_t size; /* size of file on this path */
mode_t mode; /* permission */
char dst_path[PATH_MAX]; /* copy dst path */
char *dst_path; /* copy dst path */
int state;
lock lock;
refcnt refcnt;
int state;
lock lock;
refcnt refcnt;
};
#define FILE_STATE_INIT 0
#define FILE_STATE_OPENED 1
#define FILE_STATE_DONE 2
#define FILE_STATE_INIT 0
#define FILE_STATE_OPENED 1
#define FILE_STATE_DONE 2
struct chunk {
struct list_head list; /* chunk_pool->list */
struct list_head list; /* chunk_pool->list */
struct path *p;
size_t off; /* offset of this chunk on the file on path p */
size_t len; /* length of this chunk */
size_t done; /* copied bytes for this chunk by a thread */
size_t off; /* offset of this chunk on the file on path p */
size_t len; /* length of this chunk */
size_t done; /* copied bytes for this chunk by a thread */
};
struct chunk_pool {
struct list_head list; /* list of struct chunk */
size_t count;
lock lock;
int state;
struct list_head list; /* list of struct chunk */
size_t count;
lock lock;
int state;
};
/* initialize chunk pool */
void chunk_pool_init(struct chunk_pool *cp);
@@ -53,7 +52,7 @@ void chunk_pool_init(struct chunk_pool *cp);
* added, or pointer to chunk.
*/
struct chunk *chunk_pool_pop(struct chunk_pool *cp);
#define CHUNK_POP_WAIT ((void *) -1)
#define CHUNK_POP_WAIT ((void *)-1)
/* set and check fillingchunks to this pool has finished */
void chunk_pool_set_filled(struct chunk_pool *cp);
@@ -62,38 +61,40 @@ bool chunk_pool_is_filled(struct chunk_pool *cp);
/* return number of chunks in the pool */
size_t chunk_pool_size(struct chunk_pool *cp);
/* return true if chunk pool is empty (all chunks are already poped) */
bool chunk_pool_is_empty(struct chunk_pool *cp);
/* free chunks in the chunk_pool */
void chunk_pool_release(struct chunk_pool *cp);
struct path_resolve_args {
FILE *msg_fp;
size_t *total_bytes;
size_t *total_bytes;
/* args to resolve src path to dst path */
const char *src_path;
const char *dst_path;
bool src_path_is_dir;
bool dst_path_is_dir;
bool dst_path_should_dir;
/* args to resolve src path to dst path */
const char *src_path;
const char *dst_path;
bool src_path_is_dir;
bool dst_path_is_dir;
bool dst_path_should_dir;
/* args to resolve chunks for a path */
struct chunk_pool *cp;
int nr_conn;
size_t min_chunk_sz;
size_t max_chunk_sz;
size_t chunk_align;
/* args to resolve chunks for a path */
struct chunk_pool *cp;
int nr_conn;
size_t min_chunk_sz;
size_t max_chunk_sz;
size_t chunk_align;
};
/* recursivly walk through src_path and fill path_list for each file */
int walk_src_path(sftp_session src_sftp, const char *src_path,
struct list_head *path_list, struct path_resolve_args *a);
/* free struct path */
void free_path(struct path *p);
/* copy a chunk. either src_sftp or dst_sftp is not null, and another is null */
int copy_chunk(FILE *msg_fp, struct chunk *c,
sftp_session src_sftp, sftp_session dst_sftp,
int nr_ahead, int buf_sz, size_t *counter);
int copy_chunk(struct chunk *c, sftp_session src_sftp, sftp_session dst_sftp,
int nr_ahead, int buf_sz, bool preserve_ts, size_t *counter);
/* just print contents. just for debugging */
void path_dump(struct list_head *path_list);

View File

@@ -1,19 +1,30 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#ifdef __APPLE__
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/sysctl.h>
#elif linux
#define _GNU_SOURCE
#include <sched.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#elif __FreeBSD__
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread_np.h>
#else
#error unsupported platform
#endif
#include <util.h>
#include <platform.h>
#include <message.h>
#include <strerrno.h>
#include <print.h>
#ifdef __APPLE__
int nr_cpus()
@@ -22,7 +33,7 @@ int nr_cpus()
size_t size = sizeof(n);
if (sysctlbyname("machdep.cpu.core_count", &n, &size, NULL, 0) != 0) {
mscp_set_error("failed to get number of cpu cores: %s", strerrno());
priv_set_errv("failed to get number of cpu cores: %s", strerrno());
return -1;
}
@@ -31,21 +42,35 @@ int nr_cpus()
int set_thread_affinity(pthread_t tid, int core)
{
pr_warn("setting thread afinity is not implemented on apple\n");
pr_warn("setting thread afinity is not implemented on apple");
return 0;
}
int setutimes(const char *path, struct timespec atime, struct timespec mtime)
{
struct timeval tv[2] = {
{
.tv_sec = atime.tv_sec,
.tv_usec = atime.tv_nsec * 1000,
},
{
.tv_sec = mtime.tv_sec,
.tv_usec = mtime.tv_nsec * 1000,
},
};
return utimes(path, tv);
}
static void random_string(char *buf, size_t size)
{
char chars[] = "abcdefhijklmnopqrstuvwxyz1234567890";
int n, x;
char chars[] = "abcdefhijklmnopqrstuvwxyz1234567890";
int n, x;
for (n = 0; n < size - 1; n++) {
x = arc4random() % (sizeof(chars) - 1);
buf[n] = chars[x];
}
buf[size - 1] = '\0';
for (n = 0; n < size - 1; n++) {
x = arc4random() % (sizeof(chars) - 1);
buf[n] = chars[x];
}
buf[size - 1] = '\0';
}
sem_t *sem_create(int value)
@@ -54,10 +79,10 @@ sem_t *sem_create(int value)
sem_t *sem;
int n;
n = strlen(sem_name);
random_string(sem_name + n, sizeof(sem_name) - n - 1);
if ((sem = sem_open(sem_name, O_CREAT, 600, value)) == SEM_FAILED)
return NULL;
n = strlen(sem_name);
random_string(sem_name + n, sizeof(sem_name) - n - 1);
if ((sem = sem_open(sem_name, O_CREAT, 600, value)) == SEM_FAILED)
return NULL;
return sem;
}
@@ -77,6 +102,17 @@ int nr_cpus()
return CPU_COUNT(&cpu_set);
return -1;
}
#endif
#ifdef __FreeBSD__
int nr_cpus()
{
long nr_cpus = sysconf(_SC_NPROCESSORS_ONLN);
return nr_cpus;
}
#endif
#if defined(linux) || defined(__FreeBSD__)
int set_thread_affinity(pthread_t tid, int core)
{
@@ -87,8 +123,21 @@ int set_thread_affinity(pthread_t tid, int core)
CPU_SET(core, &target_cpu_set);
ret = pthread_setaffinity_np(tid, sizeof(target_cpu_set), &target_cpu_set);
if (ret < 0)
mscp_set_error("failed to set thread/cpu affinity for core %d: %s",
core, strerrno());
priv_set_errv("failed to set thread/cpu affinity for core %d: %s", core,
strerrno());
return ret;
}
int setutimes(const char *path, struct timespec atime, struct timespec mtime)
{
struct timespec ts[2] = { atime, mtime };
int fd = open(path, O_WRONLY);
int ret;
if (fd < 0)
return -1;
ret = futimens(fd, ts);
close(fd);
return ret;
}
@@ -114,4 +163,3 @@ int sem_release(sem_t *sem)
}
#endif

View File

@@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#ifndef _PLATFORM_H_
#define _PLATFORM_H_
@@ -6,6 +7,7 @@
int nr_cpus(void);
int set_thread_affinity(pthread_t tid, int core);
int setutimes(const char *path, struct timespec atime, struct timespec mtime);
/*
* macOS does not support sem_init(). macOS (seems to) releases the

18
src/print.c Normal file
View File

@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#include <print.h>
/* message print functions */
static int __print_severity = MSCP_SEVERITY_WARN;
void set_print_severity(int serverity)
{
if (serverity < 0)
__print_severity = -1; /* no print */
__print_severity = serverity;
}
int get_print_severity()
{
return __print_severity;
}

30
src/print.h Normal file
View File

@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#ifndef _PRINT_H_
#define _PRINT_H_
#include <libgen.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <mscp.h>
/* message print. printed messages are passed to application via msg_fd */
void set_print_severity(int severity);
int get_print_severity();
#define __print(fp, severity, fmt, ...) \
do { \
if (severity <= get_print_severity()) { \
fprintf(fp, "\r\033[K" fmt "\n", ##__VA_ARGS__); \
fflush(fp); \
} \
} while (0)
#define pr_err(fmt, ...) __print(stderr, MSCP_SEVERITY_ERR, fmt, ##__VA_ARGS__)
#define pr_warn(fmt, ...) __print(stderr, MSCP_SEVERITY_WARN, fmt, ##__VA_ARGS__)
#define pr_notice(fmt, ...) __print(stdout, MSCP_SEVERITY_NOTICE, fmt, ##__VA_ARGS__)
#define pr_info(fmt, ...) __print(stdout, MSCP_SEVERITY_INFO, fmt, ##__VA_ARGS__)
#define pr_debug(fmt, ...) __print(stdout, MSCP_SEVERITY_DEBUG, fmt, ##__VA_ARGS__)
#endif /* _PRINT_H_ */

View File

@@ -1,492 +0,0 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <mscp.h>
/*
* This is a wrapper for python binding of libmscp. setup.py builds
* pymscp.c after libmscp was built, and setup.py installs pymscp
* modlue and mscp python module (mscp/mscp.py), which is a warpper
* for pymscp.
*/
#define MAX_MSCP_INSTS 64
/* XXX: cut corners */
struct instance {
struct mscp_opts mo;
struct mscp_ssh_opts so;
struct mscp *m;
};
struct instance *insts[MAX_MSCP_INSTS];
static int add_instance(struct instance *i)
{
int n;
for (n = 0; n < MAX_MSCP_INSTS; n++) {
if (insts[n] == NULL) {
insts[n] = i;
return 0;
}
}
return -1; /* full of mscp instances */
}
static struct instance *get_instance(unsigned long long addr)
{
int n;
for (n = 0; n < MAX_MSCP_INSTS; n++) {
if (insts[n] == (void *)addr)
return insts[n];
}
return NULL;
}
static struct mscp *get_mscp(unsigned long long addr)
{
struct instance *i = get_instance(addr);
if (!i)
return NULL;
return i->m;
}
static int release_instance(struct instance *i)
{
int n;
for (n = 0; n < MAX_MSCP_INSTS; n++) {
if (insts[n] == i) {
insts[n] = NULL;
return 0;
}
}
free(i);
return -1;
}
/* wrapper functions */
static PyObject *wrap_mscp_init(PyObject *self, PyObject *args, PyObject *kw)
{
/*
* Initialize struct mscp with options. wrap_mscp_init
* receives all the arguments with keywords.
*/
char *remote;
char *keywords[] = {
"remote", /* const char * */
"direction", /* int, MSCP_DIRECTION_L2R or MSCP_DIRECTION_R2L */
/* mscp_opts */
"nr_threads", /* int */
"nr_ahead", /* int */
"min_chunk_sz", /* unsigned long */
"max_chunk_sz", /* unsigned long */
"buf_sz", /* unsigned long */
"coremask", /* const char * */
"max_startups", /* int */
"severity", /* int, MSCP_SERVERITY_* */
"msg_fd", /* int */
/* mscp_ssh_opts */
"login_name", /* const char * */
"port", /* const char * */
"config", /* const char * */
"identity", /* const char * */
"cipher", /* const char * */
"hmac", /* const char * */
"compress", /* const char * */
"password", /* const char * */
"passphrase", /* const char * */
"debug_level", /* int */
"no_hostkey_check", /* bool */
"enable_nagle", /* bool */
NULL,
};
const char *fmt = "si" "|" "ii" "kkk" "s" "iii" "ssss" "sssss" "ipp";
char *coremask = NULL;
char *login_name = NULL, *port = NULL, *config = NULL, *identity = NULL;
char *cipher = NULL, *hmac = NULL, *compress = NULL;
char *password = NULL, *passphrase = NULL;
struct instance *i;
int direction;
int ret;
i = malloc(sizeof(*i));
if (!i) {
PyErr_Format(PyExc_RuntimeError, strerror(errno));
return NULL;
}
memset(i, 0, sizeof(*i));
ret = PyArg_ParseTupleAndKeywords(args, kw, fmt, keywords,
&remote,
&direction,
&i->mo.nr_threads,
&i->mo.nr_ahead,
&i->mo.min_chunk_sz,
&i->mo.max_chunk_sz,
&i->mo.buf_sz,
&coremask,
&i->mo.max_startups,
&i->mo.severity,
&i->mo.msg_fd,
&login_name,
&port,
&config,
&identity,
&cipher,
&hmac,
&compress,
&password,
&passphrase,
&i->so.debug_level,
&i->so.no_hostkey_check,
&i->so.enable_nagle);
if (!ret)
return NULL;
if (coremask)
strncpy(i->mo.coremask, coremask, MSCP_MAX_COREMASK_STR - 1);
if (login_name)
strncpy(i->so.login_name, login_name, MSCP_SSH_MAX_LOGIN_NAME - 1);
if (port)
strncpy(i->so.port, port, MSCP_SSH_MAX_PORT_STR - 1);
if (config)
strncpy(i->so.config, config, PATH_MAX - 1);
if (identity)
strncpy(i->so.identity, identity, MSCP_SSH_MAX_IDENTITY_PATH - 1);
if (cipher)
strncpy(i->so.cipher, cipher, MSCP_SSH_MAX_CIPHER_STR - 1);
if (hmac)
strncpy(i->so.hmac, hmac, MSCP_SSH_MAX_HMAC_STR - 1);
if (compress)
strncpy(i->so.compress, compress, MSCP_SSH_MAX_COMP_STR - 1);
if (password)
strncpy(i->so.password, password, MSCP_SSH_MAX_PASSWORD - 1);
if (passphrase)
strncpy(i->so.passphrase, passphrase, MSCP_SSH_MAX_PASSPHRASE - 1);
i->m = mscp_init(remote, direction, &i->mo, &i->so);
if (!i->m) {
PyErr_Format(PyExc_RuntimeError, "%s", mscp_get_error());
free(i);
return NULL;
}
if (add_instance(i) < 0) {
PyErr_Format(PyExc_RuntimeError, "too many mscp isntances");
mscp_free(i->m);
free(i);
return NULL;
}
return Py_BuildValue("K", (unsigned long long)i);
}
static PyObject *wrap_mscp_connect(PyObject *self, PyObject *args, PyObject *kw)
{
char *keywords[] = { "m", NULL };
unsigned long long addr;
struct mscp *m;
if (!PyArg_ParseTupleAndKeywords(args, kw, "K", keywords, &addr))
return NULL;
m = get_mscp(addr);
if (!m) {
PyErr_Format(PyExc_RuntimeError, "invalid mscp instance address");
return NULL;
}
if (mscp_connect(m) < 0) {
PyErr_Format(PyExc_RuntimeError, mscp_get_error());
return NULL;
}
return Py_BuildValue("");
}
static PyObject *wrap_mscp_add_src_path(PyObject *self, PyObject *args, PyObject *kw)
{
char *keywords[] = { "m", "src_path", NULL };
unsigned long long addr;
char *src_path;
struct mscp *m;
if (!PyArg_ParseTupleAndKeywords(args, kw, "Ks", keywords, &addr, &src_path))
return NULL;
m = get_mscp(addr);
if (!m) {
PyErr_Format(PyExc_RuntimeError, "invalid mscp instance address");
return NULL;
}
if (mscp_add_src_path(m, src_path) < 0) {
PyErr_Format(PyExc_RuntimeError, mscp_get_error());
return NULL;
}
return Py_BuildValue("");
}
static PyObject *wrap_mscp_set_dst_path(PyObject *self, PyObject *args, PyObject *kw)
{
char *keywords[] = { "m", "dst_path", NULL };
unsigned long long addr;
char *dst_path;
struct mscp *m;
if (!PyArg_ParseTupleAndKeywords(args, kw, "Ks", keywords, &addr, &dst_path))
return NULL;
m = get_mscp(addr);
if (!m) {
PyErr_Format(PyExc_RuntimeError, "invalid mscp instance address");
return NULL;
}
if (mscp_set_dst_path(m, dst_path) < 0) {
PyErr_Format(PyExc_RuntimeError, mscp_get_error());
return NULL;
}
return Py_BuildValue("");
}
static PyObject *wrap_mscp_scan(PyObject *self, PyObject *args, PyObject *kw)
{
char *keywords[] = { "m", NULL };
unsigned long long addr;
struct mscp *m;
if (!PyArg_ParseTupleAndKeywords(args, kw, "K", keywords, &addr))
return NULL;
m = get_mscp(addr);
if (!m) {
PyErr_Format(PyExc_RuntimeError, "invalid mscp instance address");
return NULL;
}
if (mscp_scan(m) < 0) {
PyErr_Format(PyExc_RuntimeError, mscp_get_error());
return NULL;
}
return Py_BuildValue("");
}
static PyObject *wrap_mscp_start(PyObject *self, PyObject *args, PyObject *kw)
{
char *keywords[] = { "m", NULL };
unsigned long long addr;
struct mscp *m;
if (!PyArg_ParseTupleAndKeywords(args, kw, "K", keywords, &addr))
return NULL;
m = get_mscp(addr);
if (!m) {
PyErr_Format(PyExc_RuntimeError, "invalid mscp instance address");
return NULL;
}
if (mscp_start(m) < 0) {
PyErr_Format(PyExc_RuntimeError, mscp_get_error());
return NULL;
}
return Py_BuildValue("");
}
static PyObject *wrap_mscp_stop(PyObject *self, PyObject *args, PyObject *kw)
{
char *keywords[] = { "m", NULL };
unsigned long long addr;
struct mscp *m;
if (!PyArg_ParseTupleAndKeywords(args, kw, "K", keywords, &addr))
return NULL;
m = get_mscp(addr);
if (!m) {
PyErr_Format(PyExc_RuntimeError, "invalid mscp instance address");
return NULL;
}
mscp_stop(m);
return Py_BuildValue("");
}
static PyObject *wrap_mscp_join(PyObject *self, PyObject *args, PyObject *kw)
{
char *keywords[] = { "m", NULL };
unsigned long long addr;
struct mscp *m;
if (!PyArg_ParseTupleAndKeywords(args, kw, "K", keywords, &addr))
return NULL;
m = get_mscp(addr);
if (!m) {
PyErr_Format(PyExc_RuntimeError, "invalid mscp instance address");
return NULL;
}
if (mscp_join(m) < 0) {
PyErr_Format(PyExc_RuntimeError, mscp_get_error());
return NULL;
}
return Py_BuildValue("");
}
static PyObject *wrap_mscp_get_stats(PyObject *self, PyObject *args, PyObject *kw)
{
char *keywords[] = { "m", NULL };
unsigned long long addr;
struct mscp_stats s;
struct mscp *m;
if (!PyArg_ParseTupleAndKeywords(args, kw, "K", keywords, &addr))
return NULL;
m = get_mscp(addr);
if (!m) {
PyErr_Format(PyExc_RuntimeError, "invalid mscp instance address");
return NULL;
}
mscp_get_stats(m, &s);
return Py_BuildValue("KKO", s.total, s.done, PyBool_FromLong(s.finished));
}
static PyObject *wrap_mscp_cleanup(PyObject *self, PyObject *args, PyObject *kw)
{
char *keywords[] = { "m", NULL };
unsigned long long addr;
struct mscp *m;
if (!PyArg_ParseTupleAndKeywords(args, kw, "K", keywords, &addr))
return NULL;
m = get_mscp(addr);
if (!m) {
PyErr_Format(PyExc_RuntimeError, "invalid mscp instance address");
return NULL;
}
mscp_cleanup(m);
return Py_BuildValue("");
}
static PyObject *wrap_mscp_free(PyObject *self, PyObject *args, PyObject *kw)
{
char *keywords[] = { "m", NULL };
unsigned long long addr;
struct instance *i;
if (!PyArg_ParseTupleAndKeywords(args, kw, "K", keywords, &addr))
return NULL;
i = get_instance(addr);
if (!i) {
PyErr_Format(PyExc_RuntimeError, "invalid mscp instance address");
return NULL;
}
mscp_free(i->m);
release_instance(i);
return Py_BuildValue("");
}
static PyMethodDef pymscpMethods[] = {
{
"mscp_init", (PyCFunction)wrap_mscp_init,
METH_VARARGS | METH_KEYWORDS, NULL
},
{
"mscp_connect", (PyCFunction)wrap_mscp_connect,
METH_VARARGS | METH_KEYWORDS, NULL
},
{
"mscp_add_src_path", (PyCFunction)wrap_mscp_add_src_path,
METH_VARARGS | METH_KEYWORDS, NULL
},
{
"mscp_set_dst_path", (PyCFunction)wrap_mscp_set_dst_path,
METH_VARARGS | METH_KEYWORDS, NULL
},
{
"mscp_scan", (PyCFunction)wrap_mscp_scan,
METH_VARARGS | METH_KEYWORDS, NULL
},
{
"mscp_start", (PyCFunction)wrap_mscp_start,
METH_VARARGS | METH_KEYWORDS, NULL
},
{
"mscp_stop", (PyCFunction)wrap_mscp_stop,
METH_VARARGS | METH_KEYWORDS, NULL
},
{
"mscp_join", (PyCFunction)wrap_mscp_join,
METH_VARARGS | METH_KEYWORDS, NULL
},
{
"mscp_get_stats", (PyCFunction)wrap_mscp_get_stats,
METH_VARARGS | METH_KEYWORDS, NULL
},
{
"mscp_cleanup", (PyCFunction)wrap_mscp_cleanup,
METH_VARARGS | METH_KEYWORDS, NULL
},
{
"mscp_free", (PyCFunction)wrap_mscp_free,
METH_VARARGS | METH_KEYWORDS, NULL
},
{ NULL, NULL, 0, NULL },
};
static PyModuleDef pymscpModule = {
PyModuleDef_HEAD_INIT, "pymscp", NULL, -1, pymscpMethods,
};
PyMODINIT_FUNC PyInit_pymscp(void) {
PyObject *mod = PyModule_Create(&pymscpModule);
PyModule_AddIntConstant(mod, "LOCAL2REMOTE", MSCP_DIRECTION_L2R);
PyModule_AddIntConstant(mod, "REMOTE2LOCAL", MSCP_DIRECTION_R2L);
PyModule_AddIntConstant(mod, "SEVERITY_NONE", MSCP_SEVERITY_NONE);
PyModule_AddIntConstant(mod, "SEVERITY_ERR", MSCP_SEVERITY_ERR);
PyModule_AddIntConstant(mod, "SEVERITY_WARN", MSCP_SEVERITY_WARN);
PyModule_AddIntConstant(mod, "SEVERITY_NOTICE", MSCP_SEVERITY_NOTICE);
PyModule_AddIntConstant(mod, "SEVERITY_INFO", MSCP_SEVERITY_INFO);
PyModule_AddIntConstant(mod, "SEVERITY_DEBUG", MSCP_SEVERITY_DEBUG);
return mod;
}

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env python3
from os.path import dirname, basename, isfile, isdir, exists
from os import listdir
import sys
"""
This file simply implements the src_path to dst_path conversion logic
just for test. file_fill() and file_fill_recursive() in file.c
implements this logic.
"""
def recursive(src, rel_path, dst, dst_should_dir, replace_dir_name):
if isfile(src):
if dst_should_dir:
print("{} => {}/{}{}".format(src, dst, rel_path, basename(src)))
else:
print("{} => {}{}".format(src, rel_path, dst))
return
# src is directory
for f in listdir(src):
next_src = "{}/{}".format(src, f)
if replace_dir_name and dst_should_dir:
next_rel_path = ""
else:
next_rel_path = "{}{}/".format(rel_path, basename(src))
recursive(next_src, next_rel_path, dst, dst_should_dir, False)
def fill_dst(srclist, dst):
dst_must_dir = len(srclist) > 1
for src in srclist:
dst_should_dir = isdir(src) | isdir(dst)
replace_dir_name = not isdir(dst)
recursive(src, "", dst, dst_should_dir | dst_must_dir, replace_dir_name)
def main():
if (len(sys.argv) < 2):
print("usage: {} source ... target".format(sys.argv[0]))
fill_dst(sys.argv[1:len(sys.argv) - 1], sys.argv[len(sys.argv) - 1])
main()

116
src/ssh.c
View File

@@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
@@ -6,61 +7,68 @@
#include "libssh/callbacks.h"
#include <ssh.h>
#include <util.h>
#include <message.h>
#include <mscp.h>
#include <strerrno.h>
static int ssh_verify_known_hosts(ssh_session session);
#define is_specified(s) (strlen(s) > 0)
static int ssh_set_opts(ssh_session ssh, struct mscp_ssh_opts *opts)
{
ssh_set_log_level(opts->debug_level);
if (is_specified(opts->login_name) &&
if (opts->login_name &&
ssh_options_set(ssh, SSH_OPTIONS_USER, opts->login_name) < 0) {
mscp_set_error("failed to set login name");
priv_set_errv("failed to set login name");
return -1;
}
if (is_specified(opts->port) &&
ssh_options_set(ssh, SSH_OPTIONS_PORT_STR, opts->port) < 0) {
mscp_set_error("failed to set port number");
if (opts->port && ssh_options_set(ssh, SSH_OPTIONS_PORT_STR, opts->port) < 0) {
priv_set_errv("failed to set port number");
return -1;
}
if (is_specified(opts->identity) &&
if (opts->ai_family &&
ssh_options_set(ssh, SSH_OPTIONS_AI_FAMILY, &opts->ai_family) < 0) {
priv_set_errv("failed to set address family");
return -1;
}
if (opts->identity &&
ssh_options_set(ssh, SSH_OPTIONS_IDENTITY, opts->identity) < 0) {
mscp_set_error("failed to set identity");
priv_set_errv("failed to set identity");
return -1;
}
if (is_specified(opts->cipher)) {
if (opts->cipher) {
if (ssh_options_set(ssh, SSH_OPTIONS_CIPHERS_C_S, opts->cipher) < 0) {
mscp_set_error("failed to set cipher for client to server");
priv_set_errv("failed to set cipher for client to server");
return -1;
}
if (ssh_options_set(ssh, SSH_OPTIONS_CIPHERS_S_C, opts->cipher) < 0) {
mscp_set_error("failed to set cipher for server to client");
priv_set_errv("failed to set cipher for server to client");
return -1;
}
}
if (is_specified(opts->hmac)) {
if (opts->hmac) {
if (ssh_options_set(ssh, SSH_OPTIONS_HMAC_C_S, opts->hmac) < 0) {
mscp_set_error("failed to set hmac for client to server");
priv_set_errv("failed to set hmac for client to server");
return -1;
}
if (ssh_options_set(ssh, SSH_OPTIONS_HMAC_S_C, opts->hmac) < 0) {
mscp_set_error("failed to set hmac for server to client");
priv_set_errv("failed to set hmac for server to client");
return -1;
}
}
if (is_specified(opts->compress) &&
if (opts->compress &&
ssh_options_set(ssh, SSH_OPTIONS_COMPRESSION, opts->compress) < 0) {
mscp_set_error("failed to enable ssh compression");
priv_set_errv("failed to enable ssh compression");
return -1;
}
if (opts->ccalgo && ssh_options_set(ssh, SSH_OPTIONS_CCALGO, opts->ccalgo) < 0) {
priv_set_errv("failed to set cclago");
return -1;
}
@@ -68,14 +76,13 @@ static int ssh_set_opts(ssh_session ssh, struct mscp_ssh_opts *opts)
if (!opts->enable_nagle) {
int v = 1;
if (ssh_options_set(ssh, SSH_OPTIONS_NODELAY, &v) < 0) {
mscp_set_error("failed to set TCP_NODELAY");
priv_set_errv("failed to set TCP_NODELAY");
return -1;
}
}
if (is_specified(opts->config) &&
ssh_options_parse_config(ssh, opts->config) < 0) {
mscp_set_error("failed to parse ssh_config: %s", opts->config);
if (opts->config && ssh_options_parse_config(ssh, opts->config) < 0) {
priv_set_errv("failed to parse ssh_config: %s", opts->config);
return -1;
}
@@ -99,15 +106,20 @@ static int ssh_authenticate(ssh_session ssh, struct mscp_ssh_opts *opts)
return 0;
if (auth_bit_mask & SSH_AUTH_METHOD_PUBLICKEY) {
char *p = is_specified(opts->passphrase) ? opts->passphrase : NULL;
char *p = opts->passphrase ? opts->passphrase : NULL;
if (ssh_userauth_publickey_auto(ssh, NULL, p) == SSH_AUTH_SUCCESS)
return 0;
}
if (auth_bit_mask & SSH_AUTH_METHOD_PASSWORD) {
if (!is_specified(opts->password)) {
if (ssh_getpass("Password: ", opts->password,
MSCP_SSH_MAX_PASSWORD, 0, 0) < 0) {
if (!opts->password) {
char buf[128] = {};
if (ssh_getpass("Password: ", buf, sizeof(buf), 0, 0) < 0) {
priv_set_errv("ssh_getpass failed");
return -1;
}
if (!(opts->password = strndup(buf, sizeof(buf)))) {
priv_set_errv("strndup: %s", strerrno());
return -1;
}
}
@@ -129,15 +141,21 @@ static int ssh_cache_passphrase(const char *prompt, char *buf, size_t len, int e
* second time or after because cached passphrase is passed
* to ssh_userauth_publickey_auto(). */
/* ToDo: use
* ssh_userauth_publickey_auto_get_current_identity() to print
* id for which we ask passphrase */
if (ssh_getpass("Passphrase: ", buf, len, echo, verify) < 0)
return -1;
/* cache the passphrase */
if (strlen(buf) > MSCP_SSH_MAX_PASSPHRASE - 1) {
pr_warn("sorry, passphrase is too long to cache...\n");
return 0;
if (opts->passphrase)
free(opts->passphrase);
if (!(opts->passphrase = strndup(buf, len))) {
priv_set_errv("strndup: %s", strerrno());
return -1;
}
strncpy(opts->passphrase, buf, MSCP_SSH_MAX_PASSPHRASE);
return 0;
}
@@ -156,7 +174,7 @@ static ssh_session ssh_init_session(const char *sshdst, struct mscp_ssh_opts *op
ssh_set_callbacks(ssh, &cb);
if (ssh_options_set(ssh, SSH_OPTIONS_HOST, sshdst) != SSH_OK) {
mscp_set_error("failed to set destination host");
priv_set_errv("failed to set destination host");
goto free_out;
}
@@ -164,16 +182,17 @@ static ssh_session ssh_init_session(const char *sshdst, struct mscp_ssh_opts *op
goto free_out;
if (ssh_connect(ssh) != SSH_OK) {
mscp_set_error("failed to connect ssh server: %s", ssh_get_error(ssh));
priv_set_errv("failed to connect ssh server: %s", ssh_get_error(ssh));
goto free_out;
}
if (ssh_authenticate(ssh, opts) != 0) {
mscp_set_error("authentication failed: %s", ssh_get_error(ssh));
priv_set_errv("authentication failed: %s", ssh_get_error(ssh));
goto disconnect_out;
}
if (!opts->no_hostkey_check && ssh_verify_known_hosts(ssh) != 0) {
priv_set_errv("ssh_veriy_known_hosts failed");
goto disconnect_out;
}
@@ -191,20 +210,18 @@ sftp_session ssh_init_sftp_session(const char *sshdst, struct mscp_ssh_opts *opt
sftp_session sftp;
ssh_session ssh = ssh_init_session(sshdst, opts);
if (!ssh) {
if (!ssh)
return NULL;
}
sftp = sftp_new(ssh);
if (!sftp) {
mscp_set_error("failed to allocate sftp session: %s",
ssh_get_error(ssh));
priv_set_errv("failed to allocate sftp session: %s", ssh_get_error(ssh));
goto err_out;
}
if (sftp_init(sftp) != SSH_OK) {
mscp_set_error("failed to initialize sftp session: err code %d",
sftp_get_error(sftp));
priv_set_errv("failed to initialize sftp session: err code %d",
sftp_get_error(sftp));
goto err_out;
}
@@ -215,7 +232,6 @@ err_out:
return NULL;
}
/* copied from https://api.libssh.org/stable/libssh_tutor_guided_tour.html*/
static int ssh_verify_known_hosts(ssh_session session)
{
@@ -234,10 +250,7 @@ static int ssh_verify_known_hosts(ssh_session session)
return -1;
}
rc = ssh_get_publickey_hash(srv_pubkey,
SSH_PUBLICKEY_HASH_SHA1,
&hash,
&hlen);
rc = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash, &hlen);
ssh_key_free(srv_pubkey);
if (rc < 0) {
return -1;
@@ -258,8 +271,9 @@ static int ssh_verify_known_hosts(ssh_session session)
return -1;
case SSH_KNOWN_HOSTS_OTHER:
fprintf(stderr, "The host key for this server was not found but an other"
"type of key exists.\n");
fprintf(stderr, "An attacker might change the default server key to"
"type of key exists.\n");
fprintf(stderr,
"An attacker might change the default server key to"
"confuse your client into thinking the key does not exist\n");
ssh_clean_pubkey_hash(&hash);
@@ -267,7 +281,7 @@ static int ssh_verify_known_hosts(ssh_session session)
case SSH_KNOWN_HOSTS_NOT_FOUND:
fprintf(stderr, "Could not find known host file.\n");
fprintf(stderr, "If you accept the host key here, the file will be"
"automatically created.\n");
"automatically created.\n");
/* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */
@@ -290,13 +304,13 @@ static int ssh_verify_known_hosts(ssh_session session)
rc = ssh_session_update_known_hosts(session);
if (rc < 0) {
fprintf(stderr, "Error %s\n", strerror(errno));
priv_set_errv("%s", ssh_get_error(session));
return -1;
}
break;
case SSH_KNOWN_HOSTS_ERROR:
fprintf(stderr, "Error %s", ssh_get_error(session));
fprintf(stderr, "known hosts error: %s", ssh_get_error(session));
ssh_clean_pubkey_hash(&hash);
return -1;
}

View File

@@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#ifndef _SSH_H_
#define _SSH_H_

36
src/strerrno.c Normal file
View File

@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <strerrno.h>
#define STRERRNO_TLS_BUFSIZ 128
__thread char tls_strerrno_buf[STRERRNO_TLS_BUFSIZ];
const char *strerrno(void)
{
snprintf(tls_strerrno_buf, sizeof(tls_strerrno_buf), "%s", "strerror_r error");
strerror_r(errno, tls_strerrno_buf, sizeof(tls_strerrno_buf));
return tls_strerrno_buf;
}
#define PRIV_ERR_BUFSIZ (1 << 12)
__thread char priv_err_buf[PRIV_ERR_BUFSIZ], internal[PRIV_ERR_BUFSIZ];
void priv_set_err(const char *fmt, ...)
{
va_list va;
memset(internal, 0, sizeof(internal));
va_start(va, fmt);
vsnprintf(internal, sizeof(internal), fmt, va);
va_end(va);
snprintf(priv_err_buf, sizeof(priv_err_buf), "%s", internal);
}
const char *priv_get_err()
{
return priv_err_buf;
}

36
src/strerrno.h Normal file
View File

@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-3.0-only */
#ifndef _STRERRNO_
#define _STRERRNO_
#include <libgen.h> /* basename() */
/**
* strerrno() returns error message string corresponding to errno.
* strerrno() is thread safe.
*/
const char *strerrno(void);
/**
* priv_set_err() sets an error message into a thread-local private
* buffer. This error message can be accessed via priv_get_err().
*
* The top-level function in a thread should print errors using
* priv_get_err(), while lower-level functions should set error
* messages using priv_set_err().
*/
void priv_set_err(const char *fmt, ...);
/**
* priv_set_errv(), a wrapper for priv_set_err(), just adds filename,
* line, and function name to the error message.
*/
#define priv_set_errv(fmt, ...) \
priv_set_err("[%s:%d:%s] " fmt "\0", basename(__FILE__), __LINE__, __func__, \
##__VA_ARGS__)
/**
* priv_get_err() gets the error message sotred in the thread-local private buffer.
*/
const char *priv_get_err();
#endif /* _STRERRNO_ */

View File

@@ -1,38 +0,0 @@
#ifndef _UTIL_H_
#define _UTIL_H_
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <libgen.h>
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#define pr(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
#define pr_info(fmt, ...) fprintf(stderr, "INFO:%s(): " fmt, \
__func__, ##__VA_ARGS__)
#define pr_warn(fmt, ...) fprintf(stderr, "\x1b[1m\x1b[33m" \
"WARN:%s():\x1b[0m " fmt, \
__func__, ##__VA_ARGS__)
#define pr_err(fmt, ...) fprintf(stderr, "\x1b[1m\x1b[31m" \
"ERR:%s:%d:%s():\x1b[0m " fmt, \
basename(__FILE__), __LINE__, __func__, ##__VA_ARGS__)
#ifdef DEBUG
#define pr_debug(fmt, ...) fprintf(stderr, "\x1b[1m\x1b[33m" \
"DEBUG:%s():\x1b[0m " fmt, \
__func__, ##__VA_ARGS__);
#else
#define pr_debug(fmt, ...)
#endif
#define min(a, b) (((a) > (b)) ? (b) : (a))
#define max(a, b) (((a) > (b)) ? (a) : (b))
#endif /* _UTIL_H_ */

View File

@@ -3,23 +3,25 @@
test_e2e.py: End-to-End test for mscp executable.
"""
import platform
import pytest
import getpass
import os
from subprocess import check_call, CalledProcessError, PIPE
from util import File, check_same_md5sum
def run2ok(args):
def run2ok(args, env = None):
cmd = list(map(str, args))
print("cmd: {}".format(" ".join(cmd)))
check_call(cmd)
check_call(cmd, env = env)
def run2ng(args):
def run2ng(args, env = None):
cmd = list(map(str, args))
print("cmd: {}".format(" ".join(cmd)))
with pytest.raises(CalledProcessError) as e:
check_call(cmd)
check_call(cmd, env = env)
""" usage test """
@@ -89,6 +91,47 @@ def test_double_copy(mscp, src_prefix, dst_prefix, s1, s2, d1, d2):
d1.cleanup()
d2.cleanup()
remote_v6_prefix = "[::1]:{}/".format(os.getcwd())
param_remote_v6_prefix = [
("", remote_v6_prefix), (remote_v6_prefix, "")
]
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_v6_prefix)
@pytest.mark.parametrize("s1, s2, d1, d2", param_double_copy)
def test_double_copy_with_ipv6_notation(mscp, src_prefix, dst_prefix, s1, s2, d1, d2):
s1.make()
s2.make()
run2ok([mscp, "-H", "-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_user_v6_prefix = "{}@[::1]:{}/".format(getpass.getuser(), os.getcwd())
param_remote_user_v6_prefix = [
("", remote_user_v6_prefix), (remote_user_v6_prefix, "")
]
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_user_v6_prefix)
@pytest.mark.parametrize("s1, s2, d1, d2", param_double_copy)
def test_double_copy_with_user_and_ipv6_notation(mscp, src_prefix, dst_prefix,
s1, s2, d1, d2):
s1.make()
s2.make()
run2ok([mscp, "-H", "-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()
param_dir_copy = [
( "src_dir", "dst_dir",
[ File("src_dir/t1", size = 64),
@@ -143,7 +186,7 @@ param_dir_copy_single = [
def test_dir_copy_single(mscp, src_prefix, dst_prefix, src_dir, dst_dir, src, dst):
src.make()
os.mkdir(dst_dir)
run2ok(["mscp", "-H", "-vvv", src_prefix + src_dir, dst_prefix + dst_dir])
run2ok([mscp, "-H", "-vvv", src_prefix + src_dir, dst_prefix + dst_dir])
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
@@ -160,6 +203,22 @@ def test_override_single_file(mscp, src_prefix, dst_prefix):
src.cleanup()
dst.cleanup()
absolute_remote_prefix = "localhost:"
param_absolute_remote_prefix = [
("", absolute_remote_prefix), (absolute_remote_prefix, "")
]
@pytest.mark.parametrize("src_prefix, dst_prefix", param_absolute_remote_prefix)
def test_copy_file_under_root_to_dir(mscp, src_prefix, dst_prefix):
src = File("/mscp-test-src", size = 1024).make()
dst = File("/tmp/mscp-test-src")
run2ok([mscp, "-H", "-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_min_chunk(mscp, src_prefix, dst_prefix):
src = File("src", size = 16 * 1024).make()
@@ -257,6 +316,60 @@ def test_dont_truncate_dst(mscp, src_prefix, dst_prefix):
assert md5_before == md5_after
f.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
@pytest.mark.parametrize("src, dst", param_single_copy)
def test_set_port(mscp, src_prefix, dst_prefix, src, dst):
src.make()
run2ng([mscp, "-H", "-vvv", "-p", 21, src_prefix + src.path, dst_prefix + dst.path])
run2ng([mscp, "-H", "-vvv", "-P", 21, src_prefix + src.path, dst_prefix + dst.path])
src.cleanup()
def test_v4only(mscp):
src = File("src", size = 1024).make()
dst = File("dst")
dst_prefix = "localhost:{}/".format(os.getcwd())
run2ok([mscp, "-H", "-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()
dst = File("dst")
dst_prefix = "localhost:{}/".format(os.getcwd())
run2ok([mscp, "-H", "-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, "-H", "-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, "-H", "-vvv", "-6", src.path, dst_prefix + dst.path])
src.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_set_conn_interval(mscp, src_prefix, dst_prefix):
srcs = []
dsts = []
for x in range(500):
srcs.append(File("src/file{}".format(x), size = 128).make())
dsts.append(File("dst/file{}".format(x)))
run2ok([mscp, "-H", "-vvv", "-I", 1, src_prefix + "src", dst_prefix + "dst"])
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)
@pytest.mark.parametrize("compress", compressions)
@@ -268,6 +381,21 @@ def test_compression(mscp, src_prefix, dst_prefix, compress):
src.cleanup()
dst.cleanup()
@pytest.mark.parametrize("src_prefix, dst_prefix", param_remote_prefix)
def test_ccalgo(mscp, src_prefix, dst_prefix):
src = File("src", size = 1024 * 1024).make()
dst = File("dst").make()
if platform.system() == "Darwin":
# Darwin does not support TCP_CONGESTION
algo = "cubic"
run = run2ng
elif platform.system() == "Linux":
# Linux supports TCP_CONGESTION
with open("/proc/sys/net/ipv4/tcp_allowed_congestion_control", "r") as f:
algo = f.read().strip().split().pop()
run = run2ok
run([mscp, "-H", "-vvv", "-g", algo, src_prefix + src.path, dst_prefix + "dst"])
testhost = "mscptestlocalhost"
testhost_prefix = "{}:{}/".format(testhost, os.getcwd()) # use current dir
@@ -305,3 +433,45 @@ def test_config_ng(mscp, src_prefix, dst_prefix):
os.remove(config)
src.cleanup()
dst.cleanup()
# username test assumes that this test runs inside a container, see Dockerfiles
def test_specify_passphrase_via_env(mscp):
src = File(os.getcwd() + "/src", size = 1024).make()
dst = File("/home/test/dst")
env = os.environ
env["MSCP_SSH_AUTH_PASSPHRASE"] = "keypassphrase"
run2ok([mscp, "-H", "-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()
dst = File("/home/test/dst")
env = os.environ
env["MSCP_SSH_AUTH_PASSPHRASE"] = "invalid-keypassphrase"
run2ng([mscp, "-H", "-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()
dst = File("/home/test/dst")
env = os.environ
env["MSCP_SSH_AUTH_PASSWORD"] = "userpassword"
run2ok([mscp, "-H", "-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()
dst = File("/home/test/dst")
env = os.environ
env["MSCP_SSH_AUTH_PASSWORD"] = "invalid-userpassword"
run2ng([mscp, "-H", "-vvv", "-l", "test",
src.path, "localhost:" + dst.path], env = env)
src.cleanup()

View File

@@ -1,130 +0,0 @@
"""
test_python.py: Testing libmscp through the mscp python binding.
"""
import pytest
import mscp
import os
from util import File, check_same_md5sum
def test_create_and_release():
m = mscp.mscp("localhost", mscp.LOCAL2REMOTE)
m.cleanup()
""" copy test """
remote = "localhost"
remote_prefix = "{}/".format(os.getcwd()) # use current dir
param_remote_prefix_and_direction = [
("", remote_prefix, mscp.LOCAL2REMOTE), (remote_prefix, "", mscp.REMOTE2LOCAL)
]
param_single_copy = [
(File("src", size = 64), File("dst")),
(File("src", size = 4096 * 1), File("dst")),
(File("src", size = 128 * 1024 * 1024), File("dst")),
]
@pytest.mark.parametrize("src_prefix, dst_prefix, direction",
param_remote_prefix_and_direction)
@pytest.mark.parametrize("src, dst", param_single_copy)
def test_single_copy(src_prefix, dst_prefix, direction, src, dst):
src.make()
m = mscp.mscp(remote, direction)
m.copy(src_prefix + src.path, dst_prefix + dst.path)
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
param_double_copy = [
(File("src1", size = 1024 * 1024), File("src2", size = 1024 * 1024),
File("dst/src1"), File("dst/src2")
)
]
@pytest.mark.parametrize("src_prefix, dst_prefix, direction",
param_remote_prefix_and_direction)
@pytest.mark.parametrize("s1, s2, d1, d2", param_double_copy)
def test_double_copy(src_prefix, dst_prefix, direction, s1, s2, d1, d2):
s1.make()
s2.make()
mscp.mscp(remote, direction).copy([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_single_copy = [
(File("src", size = 1024 * 1024 * 4), File("dst")),
]
param_kwargs = [
{ "nr_threads": 6 },
{ "nr_ahead": 64 },
{ "min_chunk_sz": 1 * 1024 * 1024 },
{ "max_chunk_sz": 64 * 1024 * 1024 },
{ "coremask": "0x0f" },
{ "max_startups": 5 },
{ "severity": mscp.SEVERITY_NONE },
{ "cipher": "aes128-gcm@openssh.com" },
{ "compress": "yes" },
{ "no_hostkey_check": True },
{ "enable_nagle": True },
]
@pytest.mark.parametrize("src_prefix, dst_prefix, direction",
param_remote_prefix_and_direction)
@pytest.mark.parametrize("src, dst", param_single_copy)
@pytest.mark.parametrize("kw", param_kwargs)
def test_kwargs(src_prefix, dst_prefix, direction, src, dst, kw):
src.make()
m = mscp.mscp(remote, direction, **kw)
m.copy(src_prefix + src.path, dst_prefix + dst.path)
assert check_same_md5sum(src, dst)
src.cleanup()
dst.cleanup()
def test_login_failed():
m = mscp.mscp("asdfasdf@" + remote, mscp.LOCAL2REMOTE)
with pytest.raises(RuntimeError) as e:
m.connect()
m = mscp.mscp(remote, mscp.LOCAL2REMOTE, login_name = "asdfadsf")
with pytest.raises(RuntimeError) as e:
m.connect()
m = mscp.mscp(remote, mscp.LOCAL2REMOTE, port = "65534")
with pytest.raises(RuntimeError) as e:
m.connect()
def test_get_stat_before_copy_start():
m = mscp.mscp("localhost", mscp.LOCAL2REMOTE)
m.connect()
(total, done, finished) = m.stats()
assert total == 0 and done == 0
param_invalid_kwargs = [
{ "nr_threads": -1 },
{ "nr_ahead": -1 },
{ "min_chunk_sz": 1 },
{ "max_chunk_sz": 1 },
{ "coremask": "xxxxx" },
{ "max_startups": -1 },
{ "cipher": "invalid" },
{ "hmac": "invalid"},
{ "compress": "invalid"},
]
@pytest.mark.parametrize("kw", param_invalid_kwargs)
def test_invalid_options(kw):
with pytest.raises(RuntimeError) as e:
m = mscp.mscp("localhost", mscp.LOCAL2REMOTE, **kw)
m.connect()

View File

@@ -46,8 +46,10 @@ class File():
with open(self.path, "wb") as f:
f.write(os.urandom(self.size))
def cleanup(self):
def cleanup(self, preserve_dir = False):
os.remove(self.path)
if preserve_dir:
return
tmp = os.path.dirname(self.path)
while tmp and not tmp in [".", "/"]:
if len(os.listdir(tmp)) == 0: