1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-03 00:26:59 +02:00

SFTP support for repository storage.

This commit is contained in:
Reid Thompson
2023-05-13 12:16:16 -04:00
committed by GitHub
parent 0c1f823e7a
commit 87087fac23
48 changed files with 8826 additions and 160 deletions

View File

@ -35,6 +35,18 @@
<p>Block incremental backup.</p> <p>Block incremental backup.</p>
</release-item> </release-item>
<release-item>
<github-pull-request id="1959"/>
<release-item-contributor-list>
<release-item-contributor id="reid.thompson"/>
<release-item-reviewer id="stephen.frost"/>
<release-item-reviewer id="david.steele"/>
</release-item-contributor-list>
<p><proper>SFTP</proper> support for repository storage.</p>
</release-item>
<release-item> <release-item>
<github-pull-request id="2051"/> <github-pull-request id="2051"/>

View File

@ -169,8 +169,15 @@
<variable key="s3-key">accessKey1</variable> <variable key="s3-key">accessKey1</variable>
<variable key="s3-key-secret">verySecretKey1</variable> <variable key="s3-key-secret">verySecretKey1</variable>
<!-- SFTP Settings -->
<variable key="sftp-all">n</variable> <!-- Build all the documentation with SFTP? -->
<variable key="sftp-repo">demo-repo</variable>
<variable key="sftp-host-key-hash-type">sha1</variable>
<variable key="sftp-private-key-file">{[pg-home-path]}/.ssh/id_rsa_sftp</variable>
<variable key="sftp-public-key-file">{[pg-home-path]}/.ssh/id_rsa_sftp.pub</variable>
<!-- Is any object store being used to build all the documentation? --> <!-- Is any object store being used to build all the documentation? -->
<variable key="object-any-all">('{[azure-all]}' eq 'y' || '{[gcs-all]}' eq 'y' || '{[s3-all]}' eq 'y')</variable> <variable key="object-any-all">('{[azure-all]}' eq 'y' || '{[gcs-all]}' eq 'y' || '{[s3-all]}' eq 'y' || '{[sftp-all]}' eq 'y')</variable>
<!-- Hosts --> <!-- Hosts -->
<variable key="host-image">pgbackrest/doc:{[os-type]}</variable> <variable key="host-image">pgbackrest/doc:{[os-type]}</variable>
@ -192,6 +199,12 @@
<variable key="host-s3-id">s3</variable> <variable key="host-s3-id">s3</variable>
<variable key="host-s3">s3-server</variable> <variable key="host-s3">s3-server</variable>
<variable key="host-sftp-id">sftp</variable>
<variable key="host-sftp">sftp-server</variable>
<variable key="host-sftp-user">{[host-user]}</variable>
<variable key="host-sftp-image">{[host-image]}</variable>
<variable key="host-sftp-mount">{[host-mount]}</variable>
<variable key="host-pg1-id">pg1</variable> <variable key="host-pg1-id">pg1</variable>
<variable key="host-pg1">pg-primary</variable> <variable key="host-pg1">pg-primary</variable>
<variable key="host-pg1-user">{[host-user]}</variable> <variable key="host-pg1-user">{[host-user]}</variable>
@ -300,7 +313,7 @@
rm /etc/apt/apt.conf.d/70debconf &amp;&amp; \ rm /etc/apt/apt.conf.d/70debconf &amp;&amp; \
apt-get update &amp;&amp; \ apt-get update &amp;&amp; \
apt-get install -y --no-install-recommends sudo ssh wget vim gnupg lsb-release iputils-ping ca-certificates \ apt-get install -y --no-install-recommends sudo ssh wget vim gnupg lsb-release iputils-ping ca-certificates \
tzdata locales 2>&amp;1 tzdata locales libssh2-1-dev 2>&amp;1
{[sudo-disable-core-dump]} {[sudo-disable-core-dump]}
@ -361,6 +374,10 @@
# Enable PowerTools repository (only available on RHEL8) # Enable PowerTools repository (only available on RHEL8)
RUN dnf config-manager --set-enabled powertools || true RUN dnf config-manager --set-enabled powertools || true
# Install and enable epel repository
RUN dnf -y install epel-release
RUN crb enable
# Install CA certificate # Install CA certificate
RUN update-ca-trust extract RUN update-ca-trust extract
@ -551,13 +568,13 @@
<execute if="{[os-type-is-debian]}" user="root" pre="y"> <execute if="{[os-type-is-debian]}" user="root" pre="y">
<exe-cmd> <exe-cmd>
apt-get install postgresql-client libxml2</exe-cmd> apt-get install postgresql-client libxml2 libssh2-1</exe-cmd>
<exe-cmd-extra>-y 2>&amp;1</exe-cmd-extra> <exe-cmd-extra>-y 2>&amp;1</exe-cmd-extra>
</execute> </execute>
<execute if="{[os-type-is-rhel]}" user="root" pre="y"> <execute if="{[os-type-is-rhel]}" user="root" pre="y">
<exe-cmd> <exe-cmd>
yum install postgresql-libs yum install postgresql-libs libssh2
</exe-cmd> </exe-cmd>
<exe-cmd-extra>-y 2>&amp;1</exe-cmd-extra> <exe-cmd-extra>-y 2>&amp;1</exe-cmd-extra>
</execute> </execute>
@ -773,6 +790,57 @@
<admonition type="note">The region and endpoint will need to be configured to where the bucket is located. The values given here are for the <id>{[s3-region]}</id> region.</admonition> <admonition type="note">The region and endpoint will need to be configured to where the bucket is located. The values given here are for the <id>{[s3-region]}</id> region.</admonition>
</block-define> </block-define>
<!-- ======================================================================================================================= -->
<block-define id="sftp-setup">
<p><backrest/> supports locating repositories on <proper>SFTP</proper> hosts. SFTP file transfer is relatively slow so commands benefit by increasing <br-option>process-max</br-option> to parallelize file transfer.</p>
<backrest-config host="{[sftp-setup-host]}" file="{[backrest-config-demo]}" owner="{[sftp-setup-config-owner]}">
<title>Configure <proper>SFTP</proper></title>
<backrest-config-option section="global" key="repo{[sftp-setup-repo-id]}-type">sftp</backrest-config-option>
<backrest-config-option section="global" key="repo{[sftp-setup-repo-id]}-path">/{[sftp-repo]}</backrest-config-option>
<backrest-config-option section="global" key="repo{[sftp-setup-repo-id]}-bundle">y</backrest-config-option>
<backrest-config-option section="global" key="repo{[sftp-setup-repo-id]}-sftp-host">{[host-sftp]}</backrest-config-option>
<backrest-config-option section="global" key="repo{[sftp-setup-repo-id]}-sftp-host-key-hash-type">{[sftp-host-key-hash-type]}</backrest-config-option>
<backrest-config-option section="global" key="repo{[sftp-setup-repo-id]}-sftp-host-user">{[sftp-setup-user]}</backrest-config-option>
<backrest-config-option section="global" key="repo{[sftp-setup-repo-id]}-sftp-private-key-file">{[sftp-private-key-file]}</backrest-config-option>
<backrest-config-option section="global" key="repo{[sftp-setup-repo-id]}-sftp-public-key-file">{[sftp-public-key-file]}</backrest-config-option>
<backrest-config-option section="global" key="process-max">4</backrest-config-option>
</backrest-config>
<p>When utilizing <proper>SFTP</proper>, if libssh2 is compiled against OpenSSH then <br-option>repo{[sftp-setup-repo-id]}-sftp-public-key-file</br-option> is optional.</p>
<execute-list host="{[host-pg1]}">
<title>Generate ssh keypair for sftp backup</title>
<execute user="{[sftp-setup-user]}">
<exe-cmd>ssh-keygen -f {[pg-home-path]}/.ssh/id_rsa_sftp
-t rsa -b 4096 -N "" -m PEM</exe-cmd>
</execute>
</execute-list>
<execute-list host="{[host-sftp]}">
<title>Copy <host>{[host-pg1]}</host> sftp backup public key to <host>{[host-sftp]}</host></title>
<execute user="{[sftp-setup-user]}">
<exe-cmd>mkdir -m 750 -p {[pg-home-path]}/.ssh</exe-cmd>
</execute>
<execute user="root" err-suppress="n" user-force="y">
<exe-cmd>
mkdir -m 750 -p /{[sftp-repo]} &amp;&amp; chown {[sftp-setup-user]}:{[pg-group]} /{[sftp-repo]}
</exe-cmd>
</execute>
<execute user="root" err-suppress="y" user-force="y">
<exe-cmd>
(sudo ssh root@{[host-pg1]} cat {[pg-home-path]}/.ssh/id_rsa_sftp.pub) |
sudo -u {[sftp-setup-user]} tee -a {[pg-home-path]}/.ssh/authorized_keys
</exe-cmd>
</execute>
</execute-list>
</block-define>
<!-- ======================================================================================================================= --> <!-- ======================================================================================================================= -->
<section id="introduction"> <section id="introduction">
<title>Introduction</title> <title>Introduction</title>
@ -783,6 +851,9 @@
<!-- Create S3 server first to allow it time to boot before being used --> <!-- Create S3 server first to allow it time to boot before being used -->
<host-add if="'{[s3-local]}' eq 'y'" id="{[host-s3-id]}" name="{[host-s3]}" user="root" image="{[s3-image]}" os="{[os-type]}" option="-v {[fake-cert-path]}/s3-server.crt:/root/.minio/certs/public.crt:ro -v {[fake-cert-path]}/s3-server.key:/root/.minio/certs/private.key:ro -e MINIO_REGION={[s3-region]} -e MINIO_DOMAIN={[s3-endpoint]} -e MINIO_BROWSER=off -e MINIO_ACCESS_KEY={[s3-key]} -e MINIO_SECRET_KEY={[s3-key-secret]}" param="server /data --address :443" update-hosts="n"/> <host-add if="'{[s3-local]}' eq 'y'" id="{[host-s3-id]}" name="{[host-s3]}" user="root" image="{[s3-image]}" os="{[os-type]}" option="-v {[fake-cert-path]}/s3-server.crt:/root/.minio/certs/public.crt:ro -v {[fake-cert-path]}/s3-server.key:/root/.minio/certs/private.key:ro -e MINIO_REGION={[s3-region]} -e MINIO_DOMAIN={[s3-endpoint]} -e MINIO_BROWSER=off -e MINIO_ACCESS_KEY={[s3-key]} -e MINIO_SECRET_KEY={[s3-key-secret]}" param="server /data --address :443" update-hosts="n"/>
<!-- Create SFTP server first to allow it time to boot before being used -->
<host-add id="{[host-sftp-id]}" name="{[host-sftp]}" user="{[host-sftp-user]}" image="{[host-sftp-image]}" os="{[os-type]}" mount="{[host-sftp-mount]}" option="{[host-mem]} {[host-option]}"/>
<p>This user guide is intended to be followed sequentially from beginning to end &amp;mdash; each section depends on the last. For example, the <link section="/restore">Restore</link> section relies on setup that is performed in the <link section="/quickstart">Quick Start</link> section. Once <backrest/> is up and running then skipping around is possible but following the user guide in order is recommended the first time through.</p> <p>This user guide is intended to be followed sequentially from beginning to end &amp;mdash; each section depends on the last. For example, the <link section="/restore">Restore</link> section relies on setup that is performed in the <link section="/quickstart">Quick Start</link> section. Once <backrest/> is up and running then skipping around is possible but following the user guide in order is recommended the first time through.</p>
<p>Although the examples in this guide are targeted at <proper>{[user-guide-os]}</proper> and <postgres/> {[pg-version]}, it should be fairly easy to apply the examples to any Unix distribution and <postgres/> version. The only OS-specific commands are those to create, start, stop, and drop <postgres/> clusters. The <backrest/> commands will be the same on any Unix system though the location of the executable may vary. While <backrest/> strives to operate consistently across versions of <postgres/>, there are subtle differences between versions of <postgres/> that may show up in this guide when illustrating certain examples, e.g. <postgres/> path/file names and settings.</p> <p>Although the examples in this guide are targeted at <proper>{[user-guide-os]}</proper> and <postgres/> {[pg-version]}, it should be fairly easy to apply the examples to any Unix distribution and <postgres/> version. The only OS-specific commands are those to create, start, stop, and drop <postgres/> clusters. The <backrest/> commands will be the same on any Unix system though the location of the executable may vary. While <backrest/> strives to operate consistently across versions of <postgres/>, there are subtle differences between versions of <postgres/> that may show up in this guide when illustrating certain examples, e.g. <postgres/> path/file names and settings.</p>
@ -925,15 +996,15 @@
<execute if="{[os-type-is-debian]}" user="root" pre="y"> <execute if="{[os-type-is-debian]}" user="root" pre="y">
<exe-cmd> <exe-cmd>
apt-get install make gcc libpq-dev libssl-dev libxml2-dev pkg-config apt-get install make gcc libpq-dev libssl-dev libxml2-dev pkg-config
liblz4-dev libzstd-dev libbz2-dev libz-dev libyaml-dev liblz4-dev libzstd-dev libbz2-dev libz-dev libyaml-dev libssh2-1-dev
</exe-cmd> </exe-cmd>
<exe-cmd-extra>-y 2>&amp;1</exe-cmd-extra> <exe-cmd-extra>-y 2>&amp;1</exe-cmd-extra>
</execute> </execute>
<execute if="{[os-type-is-rhel]}" user="root" pre="y"> <execute if="{[os-type-is-rhel]}" user="root" pre="y">
<exe-cmd> <exe-cmd>
yum install make gcc postgresql{[pg-version-nodot]}-devel yum install make gcc postgresql{[pg-version-nodot]}-devel openssl-devel
openssl-devel libxml2-devel lz4-devel libzstd-devel bzip2-devel libyaml-devel libxml2-devel lz4-devel libzstd-devel bzip2-devel libyaml-devel libssh2-devel
</exe-cmd> </exe-cmd>
<exe-cmd-extra>-y 2>&amp;1</exe-cmd-extra> <exe-cmd-extra>-y 2>&amp;1</exe-cmd-extra>
</execute> </execute>
@ -1147,6 +1218,19 @@
</block> </block>
</section> </section>
<!-- =================================================================================================================== -->
<section id="sftp-support" if="'{[sftp-all]}' eq 'y'">
<title>SFTP Storage Support</title>
<block id="sftp-setup">
<block-variable-replace key="sftp-setup-repo-id">1</block-variable-replace>
<block-variable-replace key="sftp-setup-host">{[host-pg1]}</block-variable-replace>
<block-variable-replace key="sftp-setup-user">postgres</block-variable-replace>
<block-variable-replace key="sftp-setup-config-owner">postgres:postgres</block-variable-replace>
<block-variable-replace key="sftp-setup-user-home-path">{[pg-home-path]}</block-variable-replace>
</block>
</section>
<!-- =================================================================================================================== --> <!-- =================================================================================================================== -->
<section id="configure-archiving"> <section id="configure-archiving">
<title>Configure Archiving</title> <title>Configure Archiving</title>
@ -2401,12 +2485,45 @@
</execute-list> </execute-list>
</section> </section>
<!-- ======================================================================================================================= -->
<section id="sftp-support" if="!{[object-any-all]}" depend="/quickstart/configure-archiving">
<title>SFTP Support</title>
<block id="sftp-setup">
<block-variable-replace key="sftp-setup-repo-id">4</block-variable-replace>
<block-variable-replace key="sftp-setup-host">{[host-pg1]}</block-variable-replace>
<block-variable-replace key="sftp-setup-user">postgres</block-variable-replace>
<block-variable-replace key="sftp-setup-config-owner">postgres:postgres</block-variable-replace>
</block>
<p>Commands are run exactly as if the repository were stored on a local disk.</p>
<execute-list host="{[host-pg1]}">
<title>Create the stanza</title>
<execute user="postgres" output="y">
<exe-cmd>{[project-exe]} {[dash]}-stanza={[postgres-cluster-demo]} {[dash]}-log-level-console=info stanza-create</exe-cmd>
<exe-highlight>completed successfully</exe-highlight>
</execute>
</execute-list>
<execute-list host="{[host-pg1]}">
<title>Backup the {[postgres-cluster-demo]} cluster</title>
<execute user="postgres" output="y">
<exe-cmd>{[project-exe]} {[dash]}-stanza={[postgres-cluster-demo]} --repo=4
--log-level-console=info backup</exe-cmd>
<exe-highlight>no prior backup exists|full backup size</exe-highlight>
</execute>
</execute-list>
</section>
<!-- ======================================================================================================================= --> <!-- ======================================================================================================================= -->
<section id="gcs-support" if="!{[object-any-all]}" depend="/quickstart/configure-archiving"> <section id="gcs-support" if="!{[object-any-all]}" depend="/quickstart/configure-archiving">
<title>GCS-Compatible Object Store Support</title> <title>GCS-Compatible Object Store Support</title>
<block id="gcs-setup"> <block id="gcs-setup">
<block-variable-replace key="gcs-setup-repo-id">4</block-variable-replace> <block-variable-replace key="gcs-setup-repo-id">5</block-variable-replace>
<block-variable-replace key="gcs-setup-host">{[host-pg1]}</block-variable-replace> <block-variable-replace key="gcs-setup-host">{[host-pg1]}</block-variable-replace>
<block-variable-replace key="gcs-setup-user">postgres</block-variable-replace> <block-variable-replace key="gcs-setup-user">postgres</block-variable-replace>
<block-variable-replace key="gcs-setup-config-owner">postgres:postgres</block-variable-replace> <block-variable-replace key="gcs-setup-config-owner">postgres:postgres</block-variable-replace>
@ -2655,6 +2772,15 @@
<block-variable-replace key="s3-setup-config-owner">{[br-user]}:{[br-group]}</block-variable-replace> <block-variable-replace key="s3-setup-config-owner">{[br-user]}:{[br-group]}</block-variable-replace>
<block-variable-replace key="s3-setup-create-bucket">n</block-variable-replace> <block-variable-replace key="s3-setup-create-bucket">n</block-variable-replace>
</block> </block>
<p if="'{[sftp-all]}' eq 'y'">Configure SFTP storage if required.</p>
<block id="sftp-setup" if="'{[sftp-all]}' eq 'y'">
<block-variable-replace key="sftp-setup-repo-id">1</block-variable-replace>
<block-variable-replace key="sftp-setup-host">{[host-repo1]}</block-variable-replace>
<block-variable-replace key="sftp-setup-user">{[br-user]}</block-variable-replace>
<block-variable-replace key="sftp-setup-config-owner">{[br-user]}:{[br-group]}</block-variable-replace>
</block>
</section> </section>
<!-- =================================================================================================================== --> <!-- =================================================================================================================== -->

View File

@ -165,6 +165,13 @@ lib_yaml = dependency('yaml-0.1')
# Find required gz library # Find required gz library
lib_z = dependency('zlib') lib_z = dependency('zlib')
# Find optional libssh2 library
lib_ssh2 = dependency('libssh2', required: false)
if lib_ssh2.found()
configuration.set('HAVE_LIBSSH2', true, description: 'Is libssh2 present?')
endif
# Find optional zstd library # Find optional zstd library
lib_zstd = dependency('libzstd', version: '>=1.0', required: false) lib_zstd = dependency('libzstd', version: '>=1.0', required: false)

View File

@ -187,7 +187,11 @@ SRCS = \
storage/s3/helper.c \ storage/s3/helper.c \
storage/s3/read.c \ storage/s3/read.c \
storage/s3/storage.c \ storage/s3/storage.c \
storage/s3/write.c storage/s3/write.c \
storage/sftp/helper.c \
storage/sftp/read.c \
storage/sftp/storage.c \
storage/sftp/write.c
#################################################################################################################################### ####################################################################################################################################
# Compiler options # Compiler options

View File

@ -23,6 +23,9 @@ Build Flags Generated by Configure
// Is libzstd present? // Is libzstd present?
#undef HAVE_LIBZST #undef HAVE_LIBZST
// Is libssh2 present?
#undef HAVE_LIBSSH2
// Configuration path // Configuration path
#undef CFGOPTDEF_CONFIG_PATH #undef CFGOPTDEF_CONFIG_PATH

View File

@ -1735,6 +1735,7 @@ option:
- gcs - gcs
- posix - posix
- s3 - s3
- sftp
command: command:
annotate: annotate:
command-role: command-role:
@ -2350,6 +2351,75 @@ option:
command: repo-type command: repo-type
depend: repo-s3-bucket depend: repo-s3-bucket
repo-sftp-host:
section: global
group: repo
type: string
command: repo-type
depend:
option: repo-type
list:
- sftp
repo-sftp-host-fingerprint:
section: global
group: repo
type: string
required: false
command: repo-type
depend: repo-sftp-host
repo-sftp-host-key-hash-type:
section: global
group: repo
type: string-id
allow-list:
- md5
- sha1
- sha256
command: repo-type
depend: repo-sftp-host
repo-sftp-host-port:
section: global
group: repo
type: integer
default: 22
allow-range: [1, 65535]
command: repo-type
depend: repo-sftp-host
repo-sftp-host-user:
section: global
group: repo
type: string
command: repo-type
depend: repo-sftp-host
repo-sftp-private-key-file:
section: global
group: repo
type: string
command: repo-type
depend: repo-sftp-host
repo-sftp-private-key-passphrase:
section: global
group: repo
type: string
required: false
secure: true
command: repo-type
depend: repo-sftp-host
repo-sftp-public-key-file:
section: global
group: repo
type: string
required: false
command: repo-type
depend: repo-sftp-host
repo-storage-verify-tls: repo-storage-verify-tls:
section: global section: global
group: repo group: repo

View File

@ -106,6 +106,17 @@ AC_CHECK_LIB(
[AC_CHECK_HEADER(lz4frame.h, [AC_DEFINE(HAVE_LIBLZ4) AC_SUBST(LIBS, "${LIBS} -llz4")], [AC_CHECK_HEADER(lz4frame.h, [AC_DEFINE(HAVE_LIBLZ4) AC_SUBST(LIBS, "${LIBS} -llz4")],
[AC_MSG_ERROR([header file <lz4frame.h> is required])])]) [AC_MSG_ERROR([header file <lz4frame.h> is required])])])
# Check optional libSSH2 library
# ----------------------------------------------------------------------------------------------------------------------------------
AC_CHECK_LIB(
[ssh2], [libssh2_init],
[AC_CHECK_HEADER(libssh2.h, [AC_DEFINE(HAVE_LIBSSH2) AC_SUBST(LIBS, "${LIBS} -lssh2")],
[AC_MSG_ERROR([header file <libssh2.h> is required])])])
AC_CHECK_LIB(
[ssh2], [libssh2_sftp_init],
[AC_CHECK_HEADER(libssh2_sftp.h, [],
[AC_MSG_ERROR([header file <libssh2_sftp.h> is required])])])
# Check optional zst library. Ignore any versions below 1.0. # Check optional zst library. Ignore any versions below 1.0.
# ---------------------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------------------
AC_CHECK_LIB( AC_CHECK_LIB(

View File

@ -1005,6 +1005,86 @@
<example>path</example> <example>path</example>
</config-key> </config-key>
<config-key id="repo-sftp-host" name="SFTP Repository Host">
<summary>SFTP repository host.</summary>
<text>
<p>The SFTP host containing the repository.</p>
</text>
<example>sftprepo.domain</example>
</config-key>
<config-key id="repo-sftp-host-fingerprint" name="SFTP Repository Host Fingerprint">
<summary>SFTP repository host fingerprint.</summary>
<text>
<p>SFTP repository host fingerprint generation should match the <setting>repo-sftp-host-key-hash-type</setting>. Generate the fingeprint via <code>awk '{print $2}' ssh_host_xxx_key.pub | base64 -d | (md5sum or sha1sum) -b</code>. The ssh host keys are normally found in the <path>/etc/ssh</path> directory.</p>
</text>
<example>f84e172dfead7aeeeae6c1fdfb5aa8cf</example>
</config-key>
<config-key id="repo-sftp-host-key-hash-type" name="SFTP Repository Host Key Hash Type">
<summary>SFTP repository host key hash type.</summary>
<text>
<p>SFTP repository host key hash type. Declares the hash type to be used to compute the digest of the remote system's host key on SSH startup. Newer versions of libssh2 support <id>sha256</id> in addition to md5 and sha1.</p>
</text>
<example>sha256</example>
</config-key>
<config-key id="repo-sftp-host-port" name="SFTP Repository Host Port">
<summary>SFTP repository host port.</summary>
<text>
<p>SFTP repository host port.</p>
</text>
<example>22</example>
</config-key>
<config-key id="repo-sftp-host-user" name="SFTP Repository Host User">
<summary>SFTP repository host user.</summary>
<text>
<p>User on the host used to store the repository.</p>
</text>
<example>pg-backup</example>
</config-key>
<config-key id="repo-sftp-private-key-file" name="SFTP Repository Private Key File">
<summary>SFTP private key file.</summary>
<text>
<p>SFTP private key file used for authentication.</p>
</text>
<example>~/.ssh/id_ed25519</example>
</config-key>
<config-key id="repo-sftp-private-key-passphrase" name="SFTP Repository Private Key Passphrase">
<summary>SFTP private key passphrase.</summary>
<text>
<p>Passphrase used to access the private key. This is an optional feature when creating an SSH public/private key pair.</p>
</text>
<example>BeSureToGenerateAndUseASecurePassphrase</example>
</config-key>
<config-key id="repo-sftp-public-key-file" name="SFTP Repository Public Key File">
<summary>SFTP public key file.</summary>
<text>
<p>SFTP public key file used for authentication. Optional if compiled against OpenSSL, required if compiled against a different library.</p>
</text>
<example>~/.ssh/id_ed25519.pub</example>
</config-key>
<config-key id="repo-storage-ca-file" name="Repository Storage CA File"> <config-key id="repo-storage-ca-file" name="Repository Storage CA File">
<summary>Repository storage CA file.</summary> <summary>Repository storage CA file.</summary>
@ -1089,6 +1169,7 @@
<list-item><id>gcs</id> - Google Cloud Storage</list-item> <list-item><id>gcs</id> - Google Cloud Storage</list-item>
<list-item><id>posix</id> - Posix-compliant file systems</list-item> <list-item><id>posix</id> - Posix-compliant file systems</list-item>
<list-item><id>s3</id> - AWS Simple Storage Service</list-item> <list-item><id>s3</id> - AWS Simple Storage Service</list-item>
<list-item><id>sftp</id> - Secure File Transfer Protocol</list-item>
</list> </list>
<p>When an <proper>NFS</proper> mount is used as a <id>posix</id> repository, the same rules apply to <backrest/> as described in the <postgres/> documentation: <link url="https://www.postgresql.org/docs/current/creating-cluster.html#CREATING-CLUSTER-FILESYSTEM">Creating a Database Cluster - File Systems</link>.</p> <p>When an <proper>NFS</proper> mount is used as a <id>posix</id> repository, the same rules apply to <backrest/> as described in the <postgres/> documentation: <link url="https://www.postgresql.org/docs/current/creating-cluster.html#CREATING-CLUSTER-FILESYSTEM">Creating a Database Cluster - File Systems</link>.</p>

View File

@ -135,7 +135,7 @@ Option constants
#define CFGOPT_TYPE "type" #define CFGOPT_TYPE "type"
#define CFGOPT_VERBOSE "verbose" #define CFGOPT_VERBOSE "verbose"
#define CFG_OPTION_TOTAL 167 #define CFG_OPTION_TOTAL 175
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Option value constants Option value constants
@ -269,6 +269,13 @@ Option value constants
#define CFGOPTVAL_REPO_S3_URI_STYLE_PATH STRID5("path", 0x450300) #define CFGOPTVAL_REPO_S3_URI_STYLE_PATH STRID5("path", 0x450300)
#define CFGOPTVAL_REPO_S3_URI_STYLE_PATH_Z "path" #define CFGOPTVAL_REPO_S3_URI_STYLE_PATH_Z "path"
#define CFGOPTVAL_REPO_SFTP_HOST_KEY_HASH_TYPE_MD5 STRID5("md5", 0x748d0)
#define CFGOPTVAL_REPO_SFTP_HOST_KEY_HASH_TYPE_MD5_Z "md5"
#define CFGOPTVAL_REPO_SFTP_HOST_KEY_HASH_TYPE_SHA1 STRID6("sha1", 0x7412131)
#define CFGOPTVAL_REPO_SFTP_HOST_KEY_HASH_TYPE_SHA1_Z "sha1"
#define CFGOPTVAL_REPO_SFTP_HOST_KEY_HASH_TYPE_SHA256 STRID5("sha256", 0x3dde05130)
#define CFGOPTVAL_REPO_SFTP_HOST_KEY_HASH_TYPE_SHA256_Z "sha256"
#define CFGOPTVAL_REPO_TYPE_AZURE STRID5("azure", 0x5957410) #define CFGOPTVAL_REPO_TYPE_AZURE STRID5("azure", 0x5957410)
#define CFGOPTVAL_REPO_TYPE_AZURE_Z "azure" #define CFGOPTVAL_REPO_TYPE_AZURE_Z "azure"
#define CFGOPTVAL_REPO_TYPE_CIFS STRID5("cifs", 0x999230) #define CFGOPTVAL_REPO_TYPE_CIFS STRID5("cifs", 0x999230)
@ -279,6 +286,8 @@ Option value constants
#define CFGOPTVAL_REPO_TYPE_POSIX_Z "posix" #define CFGOPTVAL_REPO_TYPE_POSIX_Z "posix"
#define CFGOPTVAL_REPO_TYPE_S3 STRID6("s3", 0x7d31) #define CFGOPTVAL_REPO_TYPE_S3 STRID6("s3", 0x7d31)
#define CFGOPTVAL_REPO_TYPE_S3_Z "s3" #define CFGOPTVAL_REPO_TYPE_S3_Z "s3"
#define CFGOPTVAL_REPO_TYPE_SFTP STRID5("sftp", 0x850d30)
#define CFGOPTVAL_REPO_TYPE_SFTP_Z "sftp"
#define CFGOPTVAL_SORT_ASC STRID5("asc", 0xe610) #define CFGOPTVAL_SORT_ASC STRID5("asc", 0xe610)
#define CFGOPTVAL_SORT_ASC_Z "asc" #define CFGOPTVAL_SORT_ASC_Z "asc"
@ -499,6 +508,14 @@ typedef enum
cfgOptRepoS3Role, cfgOptRepoS3Role,
cfgOptRepoS3Token, cfgOptRepoS3Token,
cfgOptRepoS3UriStyle, cfgOptRepoS3UriStyle,
cfgOptRepoSftpHost,
cfgOptRepoSftpHostFingerprint,
cfgOptRepoSftpHostKeyHashType,
cfgOptRepoSftpHostPort,
cfgOptRepoSftpHostUser,
cfgOptRepoSftpPrivateKeyFile,
cfgOptRepoSftpPrivateKeyPassphrase,
cfgOptRepoSftpPublicKeyFile,
cfgOptRepoStorageCaFile, cfgOptRepoStorageCaFile,
cfgOptRepoStorageCaPath, cfgOptRepoStorageCaPath,
cfgOptRepoStorageHost, cfgOptRepoStorageHost,

View File

@ -23,6 +23,7 @@ Configuration Load
#include "storage/cifs/storage.h" #include "storage/cifs/storage.h"
#include "storage/helper.h" #include "storage/helper.h"
#include "storage/posix/storage.h" #include "storage/posix/storage.h"
#include "storage/sftp/storage.h"
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Load log settings Load log settings
@ -86,9 +87,10 @@ cfgLoadUpdateOption(void)
{ {
for (unsigned int optionIdx = 0; optionIdx < cfgOptionGroupIdxTotal(cfgOptGrpRepo); optionIdx++) for (unsigned int optionIdx = 0; optionIdx < cfgOptionGroupIdxTotal(cfgOptGrpRepo); optionIdx++)
{ {
// If the repo is local and either posix or cifs // If the repo is local and either posix, cifs or sftp
if (!cfgOptionIdxTest(cfgOptRepoHost, optionIdx) && if (!cfgOptionIdxTest(cfgOptRepoHost, optionIdx) &&
(cfgOptionIdxStrId(cfgOptRepoType, optionIdx) == STORAGE_POSIX_TYPE || (cfgOptionIdxStrId(cfgOptRepoType, optionIdx) == STORAGE_POSIX_TYPE ||
cfgOptionIdxStrId(cfgOptRepoType, optionIdx) == STORAGE_SFTP_TYPE ||
cfgOptionIdxStrId(cfgOptRepoType, optionIdx) == STORAGE_CIFS_TYPE)) cfgOptionIdxStrId(cfgOptRepoType, optionIdx) == STORAGE_CIFS_TYPE))
{ {
// Ensure a local repo does not have the same path as another local repo of the same type // Ensure a local repo does not have the same path as another local repo of the same type

View File

@ -24,6 +24,7 @@ static const StringPub parseRuleValueStr[] =
PARSE_RULE_STRPUB("1MiB"), // val/str PARSE_RULE_STRPUB("1MiB"), // val/str
PARSE_RULE_STRPUB("2"), // val/str PARSE_RULE_STRPUB("2"), // val/str
PARSE_RULE_STRPUB("20MiB"), // val/str PARSE_RULE_STRPUB("20MiB"), // val/str
PARSE_RULE_STRPUB("22"), // val/str
PARSE_RULE_STRPUB("256KiB"), // val/str PARSE_RULE_STRPUB("256KiB"), // val/str
PARSE_RULE_STRPUB("2MiB"), // val/str PARSE_RULE_STRPUB("2MiB"), // val/str
PARSE_RULE_STRPUB("3"), // val/str PARSE_RULE_STRPUB("3"), // val/str
@ -74,6 +75,7 @@ typedef enum
parseRuleValStrQT_1MiB_QT, // val/str/enum parseRuleValStrQT_1MiB_QT, // val/str/enum
parseRuleValStrQT_2_QT, // val/str/enum parseRuleValStrQT_2_QT, // val/str/enum
parseRuleValStrQT_20MiB_QT, // val/str/enum parseRuleValStrQT_20MiB_QT, // val/str/enum
parseRuleValStrQT_22_QT, // val/str/enum
parseRuleValStrQT_256KiB_QT, // val/str/enum parseRuleValStrQT_256KiB_QT, // val/str/enum
parseRuleValStrQT_2MiB_QT, // val/str/enum parseRuleValStrQT_2MiB_QT, // val/str/enum
parseRuleValStrQT_3_QT, // val/str/enum parseRuleValStrQT_3_QT, // val/str/enum
@ -139,6 +141,7 @@ static const StringId parseRuleValueStrId[] =
STRID5("json", 0x73e6a0), // val/strid STRID5("json", 0x73e6a0), // val/strid
STRID5("lsn", 0x3a6c0), // val/strid STRID5("lsn", 0x3a6c0), // val/strid
STRID6("lz4", 0x2068c1), // val/strid STRID6("lz4", 0x2068c1), // val/strid
STRID5("md5", 0x748d0), // val/strid
STRID5("name", 0x2b42e0), // val/strid STRID5("name", 0x2b42e0), // val/strid
STRID5("none", 0x2b9ee0), // val/strid STRID5("none", 0x2b9ee0), // val/strid
STRID5("off", 0x18cf0), // val/strid STRID5("off", 0x18cf0), // val/strid
@ -152,6 +155,9 @@ static const StringId parseRuleValueStrId[] =
STRID6("s3", 0x7d31), // val/strid STRID6("s3", 0x7d31), // val/strid
STRID5("sas", 0x4c330), // val/strid STRID5("sas", 0x4c330), // val/strid
STRID5("service", 0x1469b48b30), // val/strid STRID5("service", 0x1469b48b30), // val/strid
STRID5("sftp", 0x850d30), // val/strid
STRID6("sha1", 0x7412131), // val/strid
STRID5("sha256", 0x3dde05130), // val/strid
STRID5("shared", 0x85905130), // val/strid STRID5("shared", 0x85905130), // val/strid
STRID5("shutdown", 0x75de4a55130), // val/strid STRID5("shutdown", 0x75de4a55130), // val/strid
STRID5("ssh", 0x22730), // val/strid STRID5("ssh", 0x22730), // val/strid
@ -192,6 +198,7 @@ typedef enum
parseRuleValStrIdJson, // val/strid/enum parseRuleValStrIdJson, // val/strid/enum
parseRuleValStrIdLsn, // val/strid/enum parseRuleValStrIdLsn, // val/strid/enum
parseRuleValStrIdLz4, // val/strid/enum parseRuleValStrIdLz4, // val/strid/enum
parseRuleValStrIdMd5, // val/strid/enum
parseRuleValStrIdName, // val/strid/enum parseRuleValStrIdName, // val/strid/enum
parseRuleValStrIdNone, // val/strid/enum parseRuleValStrIdNone, // val/strid/enum
parseRuleValStrIdOff, // val/strid/enum parseRuleValStrIdOff, // val/strid/enum
@ -205,6 +212,9 @@ typedef enum
parseRuleValStrIdS3, // val/strid/enum parseRuleValStrIdS3, // val/strid/enum
parseRuleValStrIdSas, // val/strid/enum parseRuleValStrIdSas, // val/strid/enum
parseRuleValStrIdService, // val/strid/enum parseRuleValStrIdService, // val/strid/enum
parseRuleValStrIdSftp, // val/strid/enum
parseRuleValStrIdSha1, // val/strid/enum
parseRuleValStrIdSha256, // val/strid/enum
parseRuleValStrIdShared, // val/strid/enum parseRuleValStrIdShared, // val/strid/enum
parseRuleValStrIdShutdown, // val/strid/enum parseRuleValStrIdShutdown, // val/strid/enum
parseRuleValStrIdSsh, // val/strid/enum parseRuleValStrIdSsh, // val/strid/enum
@ -232,6 +242,7 @@ static const int64_t parseRuleValueInt[] =
2, // val/int 2, // val/int
3, // val/int 3, // val/int
9, // val/int 9, // val/int
22, // val/int
32, // val/int 32, // val/int
100, // val/int 100, // val/int
256, // val/int 256, // val/int
@ -281,6 +292,7 @@ typedef enum
parseRuleValInt2, // val/int/enum parseRuleValInt2, // val/int/enum
parseRuleValInt3, // val/int/enum parseRuleValInt3, // val/int/enum
parseRuleValInt9, // val/int/enum parseRuleValInt9, // val/int/enum
parseRuleValInt22, // val/int/enum
parseRuleValInt32, // val/int/enum parseRuleValInt32, // val/int/enum
parseRuleValInt100, // val/int/enum parseRuleValInt100, // val/int/enum
parseRuleValInt256, // val/int/enum parseRuleValInt256, // val/int/enum
@ -7857,6 +7869,666 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
), // opt/repo-s3-uri-style ), // opt/repo-s3-uri-style
), // opt/repo-s3-uri-style ), // opt/repo-s3-uri-style
// ----------------------------------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-sftp-host
( // opt/repo-sftp-host
PARSE_RULE_OPTION_NAME("repo-sftp-host"), // opt/repo-sftp-host
PARSE_RULE_OPTION_TYPE(cfgOptTypeString), // opt/repo-sftp-host
PARSE_RULE_OPTION_RESET(true), // opt/repo-sftp-host
PARSE_RULE_OPTION_REQUIRED(true), // opt/repo-sftp-host
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal), // opt/repo-sftp-host
PARSE_RULE_OPTION_GROUP_MEMBER(true), // opt/repo-sftp-host
PARSE_RULE_OPTION_GROUP_ID(cfgOptGrpRepo), // opt/repo-sftp-host
// opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/repo-sftp-host
( // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdExpire) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host
), // opt/repo-sftp-host
// opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND_ROLE_ASYNC_VALID_LIST // opt/repo-sftp-host
( // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host
), // opt/repo-sftp-host
// opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND_ROLE_LOCAL_VALID_LIST // opt/repo-sftp-host
( // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host
), // opt/repo-sftp-host
// opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/repo-sftp-host
( // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-host
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host
), // opt/repo-sftp-host
// opt/repo-sftp-host
PARSE_RULE_OPTIONAL // opt/repo-sftp-host
( // opt/repo-sftp-host
PARSE_RULE_OPTIONAL_GROUP // opt/repo-sftp-host
( // opt/repo-sftp-host
PARSE_RULE_OPTIONAL_DEPEND // opt/repo-sftp-host
( // opt/repo-sftp-host
PARSE_RULE_VAL_OPT(cfgOptRepoType), // opt/repo-sftp-host
PARSE_RULE_VAL_STRID(parseRuleValStrIdSftp), // opt/repo-sftp-host
), // opt/repo-sftp-host
), // opt/repo-sftp-host
), // opt/repo-sftp-host
), // opt/repo-sftp-host
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-sftp-host-fingerprint
( // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_NAME("repo-sftp-host-fingerprint"), // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_TYPE(cfgOptTypeString), // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_RESET(true), // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_REQUIRED(false), // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal), // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_GROUP_MEMBER(true), // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_GROUP_ID(cfgOptGrpRepo), // opt/repo-sftp-host-fingerprint
// opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/repo-sftp-host-fingerprint
( // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdExpire) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-fingerprint
), // opt/repo-sftp-host-fingerprint
// opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND_ROLE_ASYNC_VALID_LIST // opt/repo-sftp-host-fingerprint
( // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-fingerprint
), // opt/repo-sftp-host-fingerprint
// opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND_ROLE_LOCAL_VALID_LIST // opt/repo-sftp-host-fingerprint
( // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-fingerprint
), // opt/repo-sftp-host-fingerprint
// opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/repo-sftp-host-fingerprint
( // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-fingerprint
), // opt/repo-sftp-host-fingerprint
// opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTIONAL // opt/repo-sftp-host-fingerprint
( // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTIONAL_GROUP // opt/repo-sftp-host-fingerprint
( // opt/repo-sftp-host-fingerprint
PARSE_RULE_OPTIONAL_DEPEND // opt/repo-sftp-host-fingerprint
( // opt/repo-sftp-host-fingerprint
PARSE_RULE_VAL_OPT(cfgOptRepoType), // opt/repo-sftp-host-fingerprint
PARSE_RULE_VAL_STRID(parseRuleValStrIdSftp), // opt/repo-sftp-host-fingerprint
), // opt/repo-sftp-host-fingerprint
), // opt/repo-sftp-host-fingerprint
), // opt/repo-sftp-host-fingerprint
), // opt/repo-sftp-host-fingerprint
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-sftp-host-key-hash-type
( // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_NAME("repo-sftp-host-key-hash-type"), // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_TYPE(cfgOptTypeStringId), // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_RESET(true), // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_REQUIRED(true), // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal), // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_GROUP_MEMBER(true), // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_GROUP_ID(cfgOptGrpRepo), // opt/repo-sftp-host-key-hash-type
// opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/repo-sftp-host-key-hash-type
( // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdExpire) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-key-hash-type
), // opt/repo-sftp-host-key-hash-type
// opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND_ROLE_ASYNC_VALID_LIST // opt/repo-sftp-host-key-hash-type
( // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-key-hash-type
), // opt/repo-sftp-host-key-hash-type
// opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND_ROLE_LOCAL_VALID_LIST // opt/repo-sftp-host-key-hash-type
( // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-key-hash-type
), // opt/repo-sftp-host-key-hash-type
// opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/repo-sftp-host-key-hash-type
( // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-key-hash-type
), // opt/repo-sftp-host-key-hash-type
// opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTIONAL // opt/repo-sftp-host-key-hash-type
( // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTIONAL_GROUP // opt/repo-sftp-host-key-hash-type
( // opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTIONAL_DEPEND // opt/repo-sftp-host-key-hash-type
( // opt/repo-sftp-host-key-hash-type
PARSE_RULE_VAL_OPT(cfgOptRepoType), // opt/repo-sftp-host-key-hash-type
PARSE_RULE_VAL_STRID(parseRuleValStrIdSftp), // opt/repo-sftp-host-key-hash-type
), // opt/repo-sftp-host-key-hash-type
// opt/repo-sftp-host-key-hash-type
PARSE_RULE_OPTIONAL_ALLOW_LIST // opt/repo-sftp-host-key-hash-type
( // opt/repo-sftp-host-key-hash-type
PARSE_RULE_VAL_STRID(parseRuleValStrIdMd5), // opt/repo-sftp-host-key-hash-type
PARSE_RULE_VAL_STRID(parseRuleValStrIdSha1), // opt/repo-sftp-host-key-hash-type
PARSE_RULE_VAL_STRID(parseRuleValStrIdSha256), // opt/repo-sftp-host-key-hash-type
), // opt/repo-sftp-host-key-hash-type
), // opt/repo-sftp-host-key-hash-type
), // opt/repo-sftp-host-key-hash-type
), // opt/repo-sftp-host-key-hash-type
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-sftp-host-port
( // opt/repo-sftp-host-port
PARSE_RULE_OPTION_NAME("repo-sftp-host-port"), // opt/repo-sftp-host-port
PARSE_RULE_OPTION_TYPE(cfgOptTypeInteger), // opt/repo-sftp-host-port
PARSE_RULE_OPTION_RESET(true), // opt/repo-sftp-host-port
PARSE_RULE_OPTION_REQUIRED(true), // opt/repo-sftp-host-port
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal), // opt/repo-sftp-host-port
PARSE_RULE_OPTION_GROUP_MEMBER(true), // opt/repo-sftp-host-port
PARSE_RULE_OPTION_GROUP_ID(cfgOptGrpRepo), // opt/repo-sftp-host-port
// opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/repo-sftp-host-port
( // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdExpire) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-port
), // opt/repo-sftp-host-port
// opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND_ROLE_ASYNC_VALID_LIST // opt/repo-sftp-host-port
( // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-port
), // opt/repo-sftp-host-port
// opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND_ROLE_LOCAL_VALID_LIST // opt/repo-sftp-host-port
( // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-port
), // opt/repo-sftp-host-port
// opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/repo-sftp-host-port
( // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-host-port
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-port
), // opt/repo-sftp-host-port
// opt/repo-sftp-host-port
PARSE_RULE_OPTIONAL // opt/repo-sftp-host-port
( // opt/repo-sftp-host-port
PARSE_RULE_OPTIONAL_GROUP // opt/repo-sftp-host-port
( // opt/repo-sftp-host-port
PARSE_RULE_OPTIONAL_DEPEND // opt/repo-sftp-host-port
( // opt/repo-sftp-host-port
PARSE_RULE_VAL_OPT(cfgOptRepoType), // opt/repo-sftp-host-port
PARSE_RULE_VAL_STRID(parseRuleValStrIdSftp), // opt/repo-sftp-host-port
), // opt/repo-sftp-host-port
// opt/repo-sftp-host-port
PARSE_RULE_OPTIONAL_ALLOW_RANGE // opt/repo-sftp-host-port
( // opt/repo-sftp-host-port
PARSE_RULE_VAL_INT(parseRuleValInt1), // opt/repo-sftp-host-port
PARSE_RULE_VAL_INT(parseRuleValInt65535), // opt/repo-sftp-host-port
), // opt/repo-sftp-host-port
// opt/repo-sftp-host-port
PARSE_RULE_OPTIONAL_DEFAULT // opt/repo-sftp-host-port
( // opt/repo-sftp-host-port
PARSE_RULE_VAL_INT(parseRuleValInt22), // opt/repo-sftp-host-port
PARSE_RULE_VAL_STR(parseRuleValStrQT_22_QT), // opt/repo-sftp-host-port
), // opt/repo-sftp-host-port
), // opt/repo-sftp-host-port
), // opt/repo-sftp-host-port
), // opt/repo-sftp-host-port
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-sftp-host-user
( // opt/repo-sftp-host-user
PARSE_RULE_OPTION_NAME("repo-sftp-host-user"), // opt/repo-sftp-host-user
PARSE_RULE_OPTION_TYPE(cfgOptTypeString), // opt/repo-sftp-host-user
PARSE_RULE_OPTION_RESET(true), // opt/repo-sftp-host-user
PARSE_RULE_OPTION_REQUIRED(true), // opt/repo-sftp-host-user
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal), // opt/repo-sftp-host-user
PARSE_RULE_OPTION_GROUP_MEMBER(true), // opt/repo-sftp-host-user
PARSE_RULE_OPTION_GROUP_ID(cfgOptGrpRepo), // opt/repo-sftp-host-user
// opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/repo-sftp-host-user
( // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdExpire) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-user
), // opt/repo-sftp-host-user
// opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND_ROLE_ASYNC_VALID_LIST // opt/repo-sftp-host-user
( // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-user
), // opt/repo-sftp-host-user
// opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND_ROLE_LOCAL_VALID_LIST // opt/repo-sftp-host-user
( // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-user
), // opt/repo-sftp-host-user
// opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/repo-sftp-host-user
( // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-host-user
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-host-user
), // opt/repo-sftp-host-user
// opt/repo-sftp-host-user
PARSE_RULE_OPTIONAL // opt/repo-sftp-host-user
( // opt/repo-sftp-host-user
PARSE_RULE_OPTIONAL_GROUP // opt/repo-sftp-host-user
( // opt/repo-sftp-host-user
PARSE_RULE_OPTIONAL_DEPEND // opt/repo-sftp-host-user
( // opt/repo-sftp-host-user
PARSE_RULE_VAL_OPT(cfgOptRepoType), // opt/repo-sftp-host-user
PARSE_RULE_VAL_STRID(parseRuleValStrIdSftp), // opt/repo-sftp-host-user
), // opt/repo-sftp-host-user
), // opt/repo-sftp-host-user
), // opt/repo-sftp-host-user
), // opt/repo-sftp-host-user
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-sftp-private-key-file
( // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_NAME("repo-sftp-private-key-file"), // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_TYPE(cfgOptTypeString), // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_RESET(true), // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_REQUIRED(true), // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal), // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_GROUP_MEMBER(true), // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_GROUP_ID(cfgOptGrpRepo), // opt/repo-sftp-private-key-file
// opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/repo-sftp-private-key-file
( // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdExpire) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-private-key-file
), // opt/repo-sftp-private-key-file
// opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND_ROLE_ASYNC_VALID_LIST // opt/repo-sftp-private-key-file
( // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-private-key-file
), // opt/repo-sftp-private-key-file
// opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND_ROLE_LOCAL_VALID_LIST // opt/repo-sftp-private-key-file
( // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-private-key-file
), // opt/repo-sftp-private-key-file
// opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/repo-sftp-private-key-file
( // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-private-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-private-key-file
), // opt/repo-sftp-private-key-file
// opt/repo-sftp-private-key-file
PARSE_RULE_OPTIONAL // opt/repo-sftp-private-key-file
( // opt/repo-sftp-private-key-file
PARSE_RULE_OPTIONAL_GROUP // opt/repo-sftp-private-key-file
( // opt/repo-sftp-private-key-file
PARSE_RULE_OPTIONAL_DEPEND // opt/repo-sftp-private-key-file
( // opt/repo-sftp-private-key-file
PARSE_RULE_VAL_OPT(cfgOptRepoType), // opt/repo-sftp-private-key-file
PARSE_RULE_VAL_STRID(parseRuleValStrIdSftp), // opt/repo-sftp-private-key-file
), // opt/repo-sftp-private-key-file
), // opt/repo-sftp-private-key-file
), // opt/repo-sftp-private-key-file
), // opt/repo-sftp-private-key-file
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-sftp-private-key-passphrase
( // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_NAME("repo-sftp-private-key-passphrase"), // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_TYPE(cfgOptTypeString), // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_RESET(true), // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_REQUIRED(false), // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal), // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_SECURE(true), // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_GROUP_MEMBER(true), // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_GROUP_ID(cfgOptGrpRepo), // opt/repo-sftp-private-key-passphrase
// opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/repo-sftp-private-key-passphrase
( // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdExpire) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-private-key-passphrase
), // opt/repo-sftp-private-key-passphrase
// opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND_ROLE_ASYNC_VALID_LIST // opt/repo-sftp-private-key-passphrase
( // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-private-key-passphrase
), // opt/repo-sftp-private-key-passphrase
// opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND_ROLE_LOCAL_VALID_LIST // opt/repo-sftp-private-key-passphrase
( // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-private-key-passphrase
), // opt/repo-sftp-private-key-passphrase
// opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/repo-sftp-private-key-passphrase
( // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-private-key-passphrase
), // opt/repo-sftp-private-key-passphrase
// opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTIONAL // opt/repo-sftp-private-key-passphrase
( // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTIONAL_GROUP // opt/repo-sftp-private-key-passphrase
( // opt/repo-sftp-private-key-passphrase
PARSE_RULE_OPTIONAL_DEPEND // opt/repo-sftp-private-key-passphrase
( // opt/repo-sftp-private-key-passphrase
PARSE_RULE_VAL_OPT(cfgOptRepoType), // opt/repo-sftp-private-key-passphrase
PARSE_RULE_VAL_STRID(parseRuleValStrIdSftp), // opt/repo-sftp-private-key-passphrase
), // opt/repo-sftp-private-key-passphrase
), // opt/repo-sftp-private-key-passphrase
), // opt/repo-sftp-private-key-passphrase
), // opt/repo-sftp-private-key-passphrase
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-sftp-public-key-file
( // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_NAME("repo-sftp-public-key-file"), // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_TYPE(cfgOptTypeString), // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_RESET(true), // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_REQUIRED(false), // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal), // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_GROUP_MEMBER(true), // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_GROUP_ID(cfgOptGrpRepo), // opt/repo-sftp-public-key-file
// opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/repo-sftp-public-key-file
( // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdExpire) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-public-key-file
), // opt/repo-sftp-public-key-file
// opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND_ROLE_ASYNC_VALID_LIST // opt/repo-sftp-public-key-file
( // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-public-key-file
), // opt/repo-sftp-public-key-file
// opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND_ROLE_LOCAL_VALID_LIST // opt/repo-sftp-public-key-file
( // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-public-key-file
), // opt/repo-sftp-public-key-file
// opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/repo-sftp-public-key-file
( // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdAnnotate) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchiveGet) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdArchivePush) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdCheck) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdManifest) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoCreate) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoGet) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoPut) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoRm) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdRestore) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaCreate) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaDelete) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdStanzaUpgrade) // opt/repo-sftp-public-key-file
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify) // opt/repo-sftp-public-key-file
), // opt/repo-sftp-public-key-file
// opt/repo-sftp-public-key-file
PARSE_RULE_OPTIONAL // opt/repo-sftp-public-key-file
( // opt/repo-sftp-public-key-file
PARSE_RULE_OPTIONAL_GROUP // opt/repo-sftp-public-key-file
( // opt/repo-sftp-public-key-file
PARSE_RULE_OPTIONAL_DEPEND // opt/repo-sftp-public-key-file
( // opt/repo-sftp-public-key-file
PARSE_RULE_VAL_OPT(cfgOptRepoType), // opt/repo-sftp-public-key-file
PARSE_RULE_VAL_STRID(parseRuleValStrIdSftp), // opt/repo-sftp-public-key-file
), // opt/repo-sftp-public-key-file
), // opt/repo-sftp-public-key-file
), // opt/repo-sftp-public-key-file
), // opt/repo-sftp-public-key-file
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-storage-ca-file PARSE_RULE_OPTION // opt/repo-storage-ca-file
( // opt/repo-storage-ca-file ( // opt/repo-storage-ca-file
PARSE_RULE_OPTION_NAME("repo-storage-ca-file"), // opt/repo-storage-ca-file PARSE_RULE_OPTION_NAME("repo-storage-ca-file"), // opt/repo-storage-ca-file
@ -8450,6 +9122,7 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
PARSE_RULE_VAL_STRID(parseRuleValStrIdGcs), // opt/repo-type PARSE_RULE_VAL_STRID(parseRuleValStrIdGcs), // opt/repo-type
PARSE_RULE_VAL_STRID(parseRuleValStrIdPosix), // opt/repo-type PARSE_RULE_VAL_STRID(parseRuleValStrIdPosix), // opt/repo-type
PARSE_RULE_VAL_STRID(parseRuleValStrIdS3), // opt/repo-type PARSE_RULE_VAL_STRID(parseRuleValStrIdS3), // opt/repo-type
PARSE_RULE_VAL_STRID(parseRuleValStrIdSftp), // opt/repo-type
), // opt/repo-type ), // opt/repo-type
// opt/repo-type // opt/repo-type
PARSE_RULE_OPTIONAL_DEFAULT // opt/repo-type PARSE_RULE_OPTIONAL_DEFAULT // opt/repo-type
@ -9990,6 +10663,14 @@ static const uint8_t optionResolveOrder[] =
cfgOptRepoS3Role, // opt-resolve-order cfgOptRepoS3Role, // opt-resolve-order
cfgOptRepoS3Token, // opt-resolve-order cfgOptRepoS3Token, // opt-resolve-order
cfgOptRepoS3UriStyle, // opt-resolve-order cfgOptRepoS3UriStyle, // opt-resolve-order
cfgOptRepoSftpHost, // opt-resolve-order
cfgOptRepoSftpHostFingerprint, // opt-resolve-order
cfgOptRepoSftpHostKeyHashType, // opt-resolve-order
cfgOptRepoSftpHostPort, // opt-resolve-order
cfgOptRepoSftpHostUser, // opt-resolve-order
cfgOptRepoSftpPrivateKeyFile, // opt-resolve-order
cfgOptRepoSftpPrivateKeyPassphrase, // opt-resolve-order
cfgOptRepoSftpPublicKeyFile, // opt-resolve-order
cfgOptRepoStorageCaFile, // opt-resolve-order cfgOptRepoStorageCaFile, // opt-resolve-order
cfgOptRepoStorageCaPath, // opt-resolve-order cfgOptRepoStorageCaPath, // opt-resolve-order
cfgOptRepoStorageHost, // opt-resolve-order cfgOptRepoStorageHost, // opt-resolve-order

101
src/configure vendored
View File

@ -4034,6 +4034,105 @@ fi
fi fi
# Check optional libSSH2 library
# ----------------------------------------------------------------------------------------------------------------------------------
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libssh2_init in -lssh2" >&5
printf %s "checking for libssh2_init in -lssh2... " >&6; }
if test ${ac_cv_lib_ssh2_libssh2_init+y}
then :
printf %s "(cached) " >&6
else $as_nop
ac_check_lib_save_LIBS=$LIBS
LIBS="-lssh2 $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
char libssh2_init ();
int
main (void)
{
return libssh2_init ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
ac_cv_lib_ssh2_libssh2_init=yes
else $as_nop
ac_cv_lib_ssh2_libssh2_init=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssh2_libssh2_init" >&5
printf "%s\n" "$ac_cv_lib_ssh2_libssh2_init" >&6; }
if test "x$ac_cv_lib_ssh2_libssh2_init" = xyes
then :
ac_fn_c_check_header_compile "$LINENO" "libssh2.h" "ac_cv_header_libssh2_h" "$ac_includes_default"
if test "x$ac_cv_header_libssh2_h" = xyes
then :
printf "%s\n" "#define HAVE_LIBSSH2 1" >>confdefs.h
LIBS="${LIBS} -lssh2"
else $as_nop
as_fn_error $? "header file <libssh2.h> is required" "$LINENO" 5
fi
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libssh2_sftp_init in -lssh2" >&5
printf %s "checking for libssh2_sftp_init in -lssh2... " >&6; }
if test ${ac_cv_lib_ssh2_libssh2_sftp_init+y}
then :
printf %s "(cached) " >&6
else $as_nop
ac_check_lib_save_LIBS=$LIBS
LIBS="-lssh2 $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
char libssh2_sftp_init ();
int
main (void)
{
return libssh2_sftp_init ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"
then :
ac_cv_lib_ssh2_libssh2_sftp_init=yes
else $as_nop
ac_cv_lib_ssh2_libssh2_sftp_init=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.beam \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ssh2_libssh2_sftp_init" >&5
printf "%s\n" "$ac_cv_lib_ssh2_libssh2_sftp_init" >&6; }
if test "x$ac_cv_lib_ssh2_libssh2_sftp_init" = xyes
then :
ac_fn_c_check_header_compile "$LINENO" "libssh2_sftp.h" "ac_cv_header_libssh2_sftp_h" "$ac_includes_default"
if test "x$ac_cv_header_libssh2_sftp_h" = xyes
then :
else $as_nop
as_fn_error $? "header file <libssh2_sftp.h> is required" "$LINENO" 5
fi
fi
# Check optional zst library. Ignore any versions below 1.0. # Check optional zst library. Ignore any versions below 1.0.
# ---------------------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------------------
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ZSTD_isError in -lzstd" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for ZSTD_isError in -lzstd" >&5
@ -5599,4 +5698,4 @@ if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
fi fi
# Generated from src/build/configure.ac sha1 5bd14429291b37c7b69a19b6086863c15530e138 # Generated from src/build/configure.ac sha1 60bcd5099f8e4bfc1e900636b46fc25312c9729f

View File

@ -47,6 +47,7 @@ Main
#include "storage/gcs/helper.h" #include "storage/gcs/helper.h"
#include "storage/helper.h" #include "storage/helper.h"
#include "storage/s3/helper.h" #include "storage/s3/helper.h"
#include "storage/sftp/helper.h"
#include "version.h" #include "version.h"
/*********************************************************************************************************************************** /***********************************************************************************************************************************
@ -68,6 +69,9 @@ main(int argListSize, const char *argList[])
STORAGE_CIFS_HELPER, STORAGE_CIFS_HELPER,
STORAGE_GCS_HELPER, STORAGE_GCS_HELPER,
STORAGE_S3_HELPER, STORAGE_S3_HELPER,
#ifdef HAVE_LIBSSH2
STORAGE_SFTP_HELPER,
#endif
STORAGE_END_HELPER STORAGE_END_HELPER
}; };

View File

@ -253,6 +253,10 @@ src_pgbackrest = [
'storage/s3/read.c', 'storage/s3/read.c',
'storage/s3/storage.c', 'storage/s3/storage.c',
'storage/s3/write.c', 'storage/s3/write.c',
'storage/sftp/helper.c',
'storage/sftp/read.c',
'storage/sftp/storage.c',
'storage/sftp/write.c',
'main.c', 'main.c',
] ]
@ -268,6 +272,7 @@ executable(
lib_openssl, lib_openssl,
lib_lz4, lib_lz4,
lib_pq, lib_pq,
lib_ssh2,
lib_xml, lib_xml,
lib_z, lib_z,
lib_zstd, lib_zstd,

38
src/storage/sftp/helper.c Normal file
View File

@ -0,0 +1,38 @@
/***********************************************************************************************************************************
SFTP Storage Helper
***********************************************************************************************************************************/
#include "build.auto.h"
#ifdef HAVE_LIBSSH2
#include "common/debug.h"
#include "common/log.h"
#include "config/config.h"
#include "storage/sftp/helper.h"
/**********************************************************************************************************************************/
FN_EXTERN Storage *
storageSftpHelper(const unsigned int repoIdx, const bool write, StoragePathExpressionCallback pathExpressionCallback)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(UINT, repoIdx);
FUNCTION_LOG_PARAM(BOOL, write);
FUNCTION_LOG_PARAM_P(VOID, pathExpressionCallback);
FUNCTION_LOG_END();
ASSERT(cfgOptionIdxStrId(cfgOptRepoType, repoIdx) == STORAGE_SFTP_TYPE);
FUNCTION_LOG_RETURN(
STORAGE,
storageSftpNewP(
cfgOptionIdxStr(cfgOptRepoPath, repoIdx), cfgOptionIdxStr(cfgOptRepoSftpHost, repoIdx),
cfgOptionIdxUInt(cfgOptRepoSftpHostPort, repoIdx), cfgOptionIdxStr(cfgOptRepoSftpHostUser, repoIdx),
cfgOptionUInt64(cfgOptIoTimeout), cfgOptionIdxStr(cfgOptRepoSftpPrivateKeyFile, repoIdx),
cfgOptionIdxStrId(cfgOptRepoSftpHostKeyHashType, repoIdx), .write = write,
.pathExpressionFunction = pathExpressionCallback, .modeFile = STORAGE_MODE_FILE_DEFAULT,
.modePath = STORAGE_MODE_PATH_DEFAULT, .keyPub = cfgOptionIdxStrNull(cfgOptRepoSftpPublicKeyFile, repoIdx),
.keyPassphrase = cfgOptionIdxStrNull(cfgOptRepoSftpPrivateKeyPassphrase, repoIdx),
.hostFingerprint = cfgOptionIdxStrNull(cfgOptRepoSftpHostFingerprint, repoIdx)));
}
#endif // HAVE_LIBSSH2

23
src/storage/sftp/helper.h Normal file
View File

@ -0,0 +1,23 @@
/***********************************************************************************************************************************
SFTP Storage Helper
***********************************************************************************************************************************/
#ifndef STORAGE_SFTP_STORAGE_HELPER_H
#define STORAGE_SFTP_STORAGE_HELPER_H
#ifdef HAVE_LIBSSH2
#include "storage/sftp/storage.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
FN_EXTERN Storage *storageSftpHelper(unsigned int repoIdx, bool write, StoragePathExpressionCallback pathExpressionCallback);
/***********************************************************************************************************************************
Storage helper for StorageHelper array passed to storageHelperInit()
***********************************************************************************************************************************/
#define STORAGE_SFTP_HELPER {.type = STORAGE_SFTP_TYPE, .helper = storageSftpHelper}
#endif // HAVE_LIBSSH2
#endif

300
src/storage/sftp/read.c Normal file
View File

@ -0,0 +1,300 @@
/***********************************************************************************************************************************
SFTP Storage Read
***********************************************************************************************************************************/
#include "build.auto.h"
#ifdef HAVE_LIBSSH2
#include "common/debug.h"
#include "common/io/session.h"
#include "common/log.h"
#include "common/wait.h"
#include "storage/read.intern.h"
#include "storage/sftp/read.h"
/***********************************************************************************************************************************
Object types
***********************************************************************************************************************************/
typedef struct StorageReadSftp
{
StorageReadInterface interface; // Interface
StorageSftp *storage; // Storage that created this object
IoSession *ioSession; // IoSession (socket) connection to SFTP server
LIBSSH2_SESSION *session; // LibSsh2 session
LIBSSH2_SFTP *sftpSession; // LibSsh2 session sftp session
LIBSSH2_SFTP_HANDLE *sftpHandle; // LibSsh2 session sftp handle
LIBSSH2_SFTP_ATTRIBUTES *attr; // LibSsh2 file attributes
uint64_t current; // Current bytes read from file
uint64_t limit; // Limit bytes to be read from file (UINT64_MAX for no limit)
bool eof; // Did we reach end of file
TimeMSec timeout; // Session timeout
} StorageReadSftp;
/***********************************************************************************************************************************
Macros for function logging
***********************************************************************************************************************************/
#define FUNCTION_LOG_STORAGE_READ_SFTP_TYPE \
StorageReadSftp *
#define FUNCTION_LOG_STORAGE_READ_SFTP_FORMAT(value, buffer, bufferSize) \
objNameToLog(value, "StorageReadSftp", buffer, bufferSize)
/***********************************************************************************************************************************
Open the file
***********************************************************************************************************************************/
static bool
storageReadSftpOpen(THIS_VOID)
{
THIS(StorageReadSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_READ_SFTP, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
// Open the file
Wait *const wait = waitNew(this->timeout);
do
{
this->sftpHandle = libssh2_sftp_open_ex(
this->sftpSession, strZ(this->interface.name), (unsigned int)strSize(this->interface.name), LIBSSH2_FXF_READ, 0,
LIBSSH2_SFTP_OPENFILE);
}
while (this->sftpHandle == NULL && waitMore(wait));
waitFree(wait);
if (this->sftpHandle == NULL)
{
int rc = libssh2_session_last_errno(this->session);
if (rc == LIBSSH2_ERROR_SFTP_PROTOCOL || rc == LIBSSH2_ERROR_EAGAIN)
{
if (libssh2_sftp_last_error(this->sftpSession) == LIBSSH2_FX_NO_SUCH_FILE)
{
if (!this->interface.ignoreMissing)
THROW_FMT(FileMissingError, STORAGE_ERROR_READ_MISSING, strZ(this->interface.name));
}
else
THROW_FMT(FileOpenError, STORAGE_ERROR_READ_OPEN, strZ(this->interface.name));
}
}
// Else success
else
{
// Seek to offset, libssh2_sftp_seek64 returns void
if (this->interface.offset != 0)
libssh2_sftp_seek64(this->sftpHandle, this->interface.offset);
}
FUNCTION_LOG_RETURN(BOOL, this->sftpHandle != NULL);
}
/***********************************************************************************************************************************
Read from a file
***********************************************************************************************************************************/
static size_t
storageReadSftp(THIS_VOID, Buffer *const buffer, const bool block)
{
THIS(StorageReadSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_READ_SFTP, this);
FUNCTION_LOG_PARAM(BUFFER, buffer);
FUNCTION_LOG_PARAM(BOOL, block);
FUNCTION_LOG_END();
ASSERT(this != NULL && this->sftpHandle != NULL);
ASSERT(buffer != NULL && !bufFull(buffer));
ssize_t actualBytes = 0;
// Read if EOF has not been reached
if (!this->eof)
{
// Determine expected bytes to read. If remaining size in the buffer would exceed the limit then reduce the expected read.
size_t expectedBytes = bufRemains(buffer);
if (this->current + expectedBytes > this->limit)
expectedBytes = (size_t)(this->limit - this->current);
bufLimitSet(buffer, expectedBytes);
ssize_t rc = 0;
// Read until EOF or buffer is full
do
{
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_read(this->sftpHandle, (char *)bufRemainsPtr(buffer), bufRemains(buffer));
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
// Break on EOF or error
if (rc <= 0)
break;
// Account/shift for bytes read
bufUsedInc(buffer, (size_t)rc);
}
while (!bufFull(buffer));
// Total bytes read into the buffer
actualBytes = (ssize_t)bufUsed(buffer);
// Error occurred during read
if (rc < 0)
{
if (rc == LIBSSH2_ERROR_SFTP_PROTOCOL)
{
uint64_t sftpErr = 0;
// libssh2 sftp lseek seems to return LIBSSH2_FX_BAD_MESSAGE on a seek too far
if ((sftpErr = libssh2_sftp_last_error(this->sftpSession)) == LIBSSH2_FX_BAD_MESSAGE && this->interface.offset > 0)
THROW_FMT(FileOpenError, STORAGE_ERROR_READ_SEEK, this->interface.offset, strZ(this->interface.name));
else
THROW_FMT(FileReadError, "unable to read '%s': sftp errno [%" PRIu64 "]", strZ(this->interface.name), sftpErr);
}
else
THROW_FMT(FileReadError, "unable to read '%s'", strZ(this->interface.name));
}
// Update amount of buffer used
this->current += (uint64_t)actualBytes;
// If less data than expected was read or the limit has been reached then EOF. The file may not actually be EOF but we are
// not concerned with files that are growing. Just read up to the point where the file is being extended.
if ((size_t)actualBytes != expectedBytes || this->current == this->limit)
this->eof = true;
}
FUNCTION_LOG_RETURN(SIZE, (size_t)actualBytes);
}
/***********************************************************************************************************************************
Close the file
***********************************************************************************************************************************/
static void
storageReadSftpClose(THIS_VOID)
{
THIS(StorageReadSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_READ_SFTP, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
if (this->sftpHandle != NULL)
{
int rc = 0;
Wait *const wait = waitNew(this->timeout);
// Close the file
do
{
rc = libssh2_sftp_close(this->sftpHandle);
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc)
{
THROW_FMT(
FileCloseError,
STORAGE_ERROR_READ_CLOSE ": libssh2 errno [%d]%s", strZ(this->interface.name), rc,
rc == LIBSSH2_ERROR_SFTP_PROTOCOL ?
strZ(strNewFmt(": sftp errno [%lu]", libssh2_sftp_last_error(this->sftpSession))) : "");
}
}
this->sftpHandle = NULL;
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Has file reached EOF?
***********************************************************************************************************************************/
static bool
storageReadSftpEof(THIS_VOID)
{
THIS(StorageReadSftp);
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STORAGE_READ_SFTP, this);
FUNCTION_TEST_END();
ASSERT(this != NULL);
FUNCTION_TEST_RETURN(BOOL, this->eof);
}
/**********************************************************************************************************************************/
FN_EXTERN StorageRead *
storageReadSftpNew(
StorageSftp *const storage, const String *const name, const bool ignoreMissing, IoSession *const ioSession,
LIBSSH2_SESSION *const session, LIBSSH2_SFTP *const sftpSession, LIBSSH2_SFTP_HANDLE *const sftpHandle,
const TimeMSec timeout, const uint64_t offset, const Variant *const limit)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STRING, name);
FUNCTION_LOG_PARAM(BOOL, ignoreMissing);
FUNCTION_LOG_PARAM(IO_SESSION, ioSession);
FUNCTION_LOG_PARAM_P(VOID, session);
FUNCTION_LOG_PARAM_P(VOID, sftpSession);
FUNCTION_LOG_PARAM_P(VOID, sftpHandle);
FUNCTION_LOG_PARAM(TIME_MSEC, timeout);
FUNCTION_LOG_PARAM(UINT64, offset);
FUNCTION_LOG_PARAM(VARIANT, limit);
FUNCTION_LOG_END();
ASSERT(name != NULL);
OBJ_NEW_BEGIN(StorageReadSftp, .childQty = MEM_CONTEXT_QTY_MAX)
{
*this = (StorageReadSftp)
{
.storage = storage,
.ioSession = ioSession,
.session = session,
.sftpSession = sftpSession,
.sftpHandle = sftpHandle,
.timeout = timeout,
// Rather than enable/disable limit checking just use a big number when there is no limit. We can feel pretty confident
// that no files will be > UINT64_MAX in size. This is a copy of the interface limit but it simplifies the code during
// read so it seems worthwhile.
.limit = limit == NULL ? UINT64_MAX : varUInt64(limit),
.interface = (StorageReadInterface)
{
.type = STORAGE_SFTP_TYPE,
.name = strDup(name),
.ignoreMissing = ignoreMissing,
.offset = offset,
.limit = varDup(limit),
.ioInterface = (IoReadInterface)
{
.close = storageReadSftpClose,
.eof = storageReadSftpEof,
.open = storageReadSftpOpen,
.read = storageReadSftp,
},
},
};
}
OBJ_NEW_END();
FUNCTION_LOG_RETURN(STORAGE_READ, storageReadNew(this, &this->interface));
}
#endif // HAVE_LIBSSH2

17
src/storage/sftp/read.h Normal file
View File

@ -0,0 +1,17 @@
/***********************************************************************************************************************************
SFTP Storage Read
***********************************************************************************************************************************/
#ifndef STORAGE_SFTP_READ_H
#define STORAGE_SFTP_READ_H
#include "storage/read.h"
#include "storage/sftp/storage.intern.h"
/***********************************************************************************************************************************
Constructors
***********************************************************************************************************************************/
FN_EXTERN StorageRead *storageReadSftpNew(
StorageSftp *storage, const String *name, bool ignoreMissing, IoSession *ioSession, LIBSSH2_SESSION *session,
LIBSSH2_SFTP *sftpSession, LIBSSH2_SFTP_HANDLE *sftpHandle, TimeMSec timeout, uint64_t offset, const Variant *limit);
#endif

891
src/storage/sftp/storage.c Normal file
View File

@ -0,0 +1,891 @@
/***********************************************************************************************************************************
SFTP Storage
***********************************************************************************************************************************/
#include "build.auto.h"
#ifdef HAVE_LIBSSH2
#include "common/crypto/hash.h"
#include "common/debug.h"
#include "common/io/socket/client.h"
#include "common/log.h"
#include "common/user.h"
#include "common/wait.h"
#include "storage/sftp/read.h"
#include "storage/sftp/storage.intern.h"
#include "storage/sftp/write.h"
/***********************************************************************************************************************************
Define PATH_MAX if it is not defined
***********************************************************************************************************************************/
#ifndef PATH_MAX
#define PATH_MAX (4 * 1024)
#endif
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
struct StorageSftp
{
STORAGE_COMMON_MEMBER;
IoSession *ioSession; // IoSession (socket) connection to SFTP server
LIBSSH2_SESSION *session; // LibSsh2 session
LIBSSH2_SFTP *sftpSession; // LibSsh2 session sftp session
LIBSSH2_SFTP_HANDLE *sftpHandle; // LibSsh2 session sftp handle
TimeMSec timeout; // Session timeout
};
/***********************************************************************************************************************************
Free libssh2 resources
***********************************************************************************************************************************/
static void
storageSftpLibSsh2SessionFreeResource(THIS_VOID)
{
THIS(StorageSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_SFTP, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
int rc;
if (this->sftpHandle != NULL)
{
do
{
rc = libssh2_sftp_close(this->sftpHandle);
}
while (rc == LIBSSH2_ERROR_EAGAIN);
if (rc != 0)
{
THROW_FMT(
ServiceError, "failed to free resource sftpHandle: libssh2 errno [%d]%s", rc,
rc == LIBSSH2_ERROR_SFTP_PROTOCOL ?
strZ(strNewFmt(": sftp errno [%lu]", libssh2_sftp_last_error(this->sftpSession))) : "");
}
}
if (this->sftpSession != NULL)
{
do
{
rc = libssh2_sftp_shutdown(this->sftpSession);
}
while (rc == LIBSSH2_ERROR_EAGAIN);
if (rc != 0)
{
THROW_FMT(
ServiceError, "failed to free resource sftpSession: libssh2 errno [%d]%s", rc,
rc == LIBSSH2_ERROR_SFTP_PROTOCOL ?
strZ(strNewFmt(": sftp errno [%lu]", libssh2_sftp_last_error(this->sftpSession))) : "");
}
}
if (this->session != NULL)
{
do
{
rc = libssh2_session_disconnect_ex(this->session, SSH_DISCONNECT_BY_APPLICATION, "pgbackrest instance shutdown", "");
}
while (rc == LIBSSH2_ERROR_EAGAIN);
if (rc != 0)
THROW_FMT(ServiceError, "failed to disconnect libssh2 session: libssh2 errno [%d]", rc);
do
{
rc = libssh2_session_free(this->session);
}
while (rc == LIBSSH2_ERROR_EAGAIN);
if (rc != 0)
THROW_FMT(ServiceError, "failed to free libssh2 session: libssh2 errno [%d]", rc);
}
libssh2_exit();
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
FN_EXTERN FN_NO_RETURN void
storageSftpEvalLibSsh2Error(
const int ssh2Errno, const uint64_t sftpErrno, const ErrorType *const errorType, const String *const message,
const String *const hint)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(INT, ssh2Errno);
FUNCTION_TEST_PARAM(UINT64, sftpErrno);
FUNCTION_TEST_PARAM(ERROR_TYPE, errorType);
FUNCTION_TEST_PARAM(STRING, message);
FUNCTION_TEST_PARAM(STRING, hint);
FUNCTION_TEST_END();
ASSERT(errorType != NULL);
THROWP_FMT(
errorType, "%slibssh2 error [%d]%s%s", message != NULL ? zNewFmt("%s: ", strZ(message)) : "", ssh2Errno,
ssh2Errno == LIBSSH2_ERROR_SFTP_PROTOCOL ? zNewFmt(": sftp error [%" PRIu64 "]", sftpErrno) : "",
hint != NULL ? zNewFmt("\n%s", strZ(hint)) : "");
FUNCTION_TEST_NO_RETURN();
}
/**********************************************************************************************************************************/
static bool
storageSftpLibSsh2FxNoSuchFile(THIS_VOID, const int rc)
{
THIS(StorageSftp);
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STORAGE_SFTP, this);
FUNCTION_TEST_PARAM(INT, rc);
FUNCTION_TEST_END();
ASSERT(this != NULL);
FUNCTION_TEST_RETURN(
BOOL, rc == LIBSSH2_ERROR_SFTP_PROTOCOL && libssh2_sftp_last_error(this->sftpSession) == LIBSSH2_FX_NO_SUCH_FILE);
}
/**********************************************************************************************************************************/
static StorageInfo
storageSftpInfo(THIS_VOID, const String *const file, const StorageInfoLevel level, const StorageInterfaceInfoParam param)
{
THIS(StorageSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_SFTP, this);
FUNCTION_LOG_PARAM(STRING, file);
FUNCTION_LOG_PARAM(ENUM, level);
FUNCTION_LOG_PARAM(BOOL, param.followLink);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(this != NULL);
ASSERT(file != NULL);
StorageInfo result = {.level = level};
// Stat the file to check if it exists
LIBSSH2_SFTP_ATTRIBUTES attr;
int rc;
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_stat_ex(
this->sftpSession, strZ(file), (unsigned int)strSize(file), param.followLink ? LIBSSH2_SFTP_STAT : LIBSSH2_SFTP_LSTAT,
&attr);
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
// Throw libssh2 errors other than no such file
if (rc != 0)
{
if (!storageSftpLibSsh2FxNoSuchFile(this, rc))
THROW_FMT(FileOpenError, STORAGE_ERROR_INFO, strZ(file));
}
// Else the file exists
else
{
result.exists = true;
// Add type info (no need set file type since it is the default)
if (result.level >= storageInfoLevelType && !LIBSSH2_SFTP_S_ISREG(attr.permissions))
{
if (LIBSSH2_SFTP_S_ISDIR(attr.permissions))
result.type = storageTypePath;
else if (LIBSSH2_SFTP_S_ISLNK(attr.permissions))
result.type = storageTypeLink;
else
result.type = storageTypeSpecial;
}
// Add basic level info
if (result.level >= storageInfoLevelBasic)
{
if ((attr.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) != 0)
result.timeModified = (time_t)attr.mtime;
if (result.type == storageTypeFile)
if ((attr.flags & LIBSSH2_SFTP_ATTR_SIZE) != 0)
result.size = (uint64_t)attr.filesize;
}
// Add detail level info
if (result.level >= storageInfoLevelDetail)
{
if ((attr.flags & LIBSSH2_SFTP_ATTR_UIDGID) != 0)
{
result.groupId = (unsigned int)attr.gid;
result.userId = (unsigned int)attr.uid;
}
if ((attr.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) != 0)
result.mode = attr.permissions & (LIBSSH2_SFTP_S_IRWXU | LIBSSH2_SFTP_S_IRWXG | LIBSSH2_SFTP_S_IRWXO);
if (result.type == storageTypeLink)
{
char linkDestination[PATH_MAX] = {0};
ssize_t linkDestinationSize = 0;
Wait *const wait = waitNew(this->timeout);
do
{
linkDestinationSize = libssh2_sftp_symlink_ex(
this->sftpSession, strZ(file), (unsigned int)strSize(file), linkDestination, PATH_MAX - 1,
LIBSSH2_SFTP_READLINK);
}
while (linkDestinationSize == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (linkDestinationSize < 0)
THROW_FMT(FileReadError, "unable to get destination for link '%s'", strZ(file));
result.linkDestination = strNewZN(linkDestination, (size_t)linkDestinationSize);
}
}
}
FUNCTION_LOG_RETURN(STORAGE_INFO, result);
}
/**********************************************************************************************************************************/
// Helper function to get info for a file if it exists. This logic can't live directly in storageSftpList() because there is a race
// condition where a file might exist while listing the directory but it is gone before stat() can be called. In order to get
// complete test coverage this function must be split out.
static void
storageSftpListEntry(
StorageSftp *const this, StorageList *const list, const String *const path, const char *const name,
const StorageInfoLevel level)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STORAGE_SFTP, this);
FUNCTION_TEST_PARAM(STORAGE_LIST, list);
FUNCTION_TEST_PARAM(STRING, path);
FUNCTION_TEST_PARAM(STRINGZ, name);
FUNCTION_TEST_PARAM(ENUM, level);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(this != NULL);
ASSERT(list != NULL);
ASSERT(path != NULL);
ASSERT(name != NULL);
StorageInfo info = storageInterfaceInfoP(this, strNewFmt("%s/%s", strZ(path), name), level);
if (info.exists)
{
info.name = STR(name);
storageLstAdd(list, &info);
}
FUNCTION_TEST_RETURN_VOID();
}
static StorageList *
storageSftpList(THIS_VOID, const String *const path, const StorageInfoLevel level, const StorageInterfaceListParam param)
{
THIS(StorageSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_SFTP, this);
FUNCTION_LOG_PARAM(STRING, path);
FUNCTION_LOG_PARAM(ENUM, level);
(void)param; // No parameters are used
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(path != NULL);
StorageList *result = NULL;
// Open the directory for read
LIBSSH2_SFTP_HANDLE *sftpHandle;
Wait *const wait = waitNew(this->timeout);
do
{
sftpHandle = libssh2_sftp_open_ex(this->sftpSession, strZ(path), (unsigned int)strSize(path), 0, 0, LIBSSH2_SFTP_OPENDIR);
}
while (sftpHandle == NULL && libssh2_session_last_errno(this->session) == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
// If the directory could not be opened process errors and report missing directories
if (sftpHandle == NULL)
{
const int rc = libssh2_session_last_errno(this->session);
// If sftpHandle == NULL is due to LIBSSH2_FX_NO_SUCH_FILE, do not throw error here, return NULL result
if (!storageSftpLibSsh2FxNoSuchFile(this, rc))
{
storageSftpEvalLibSsh2Error(
rc, libssh2_sftp_last_error(this->sftpSession), &PathOpenError, strNewFmt(STORAGE_ERROR_LIST_INFO, strZ(path)),
NULL);
}
}
else
{
// Directory was found
result = storageLstNew(level);
TRY_BEGIN()
{
MEM_CONTEXT_TEMP_RESET_BEGIN()
{
LIBSSH2_SFTP_ATTRIBUTES attr;
char filename[PATH_MAX] = {0};
int len;
Wait *wait = waitNew(this->timeout);
// Read the directory entries
do
{
len = libssh2_sftp_readdir_ex(sftpHandle, filename, PATH_MAX - 1, NULL, 0, &attr);
if (len > 0)
{
filename[len] = '\0';
// Always skip . and ..
if (!strEqZ(DOT_STR, filename) && !strEqZ(DOTDOT_STR, filename))
{
if (level == storageInfoLevelExists)
{
const StorageInfo storageInfo =
{
.name = STR(filename),
.level = storageInfoLevelExists,
.exists = true,
};
storageLstAdd(result, &storageInfo);
}
else
storageSftpListEntry(this, result, path, filename, level);
}
// Reset the memory context occasionally so we don't use too much memory or slow down processing
MEM_CONTEXT_TEMP_RESET(1000);
// Reset the timeout so we don't timeout before reading all entries
waitFree(wait);
wait = waitNew(this->timeout);
}
}
while (len > 0 || (len == LIBSSH2_ERROR_EAGAIN && waitMore(wait)));
waitFree(wait);
}
MEM_CONTEXT_TEMP_END();
}
FINALLY()
{
int rc;
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_closedir(sftpHandle);
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc != 0)
THROW_FMT(PathCloseError, "unable to close path '%s' after listing", strZ(path));
sftpHandle = NULL;
}
TRY_END();
}
FUNCTION_LOG_RETURN(STORAGE_LIST, result);
}
/**********************************************************************************************************************************/
static void
storageSftpRemove(THIS_VOID, const String *const file, const StorageInterfaceRemoveParam param)
{
THIS(StorageSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_SFTP, this);
FUNCTION_LOG_PARAM(STRING, file);
FUNCTION_LOG_PARAM(BOOL, param.errorOnMissing);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(file != NULL);
// Attempt to unlink the file
int rc;
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_unlink_ex(this->sftpSession, strZ(file), (unsigned int)strSize(file));
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
if (rc != 0)
{
if (rc == LIBSSH2_ERROR_SFTP_PROTOCOL)
{
if (param.errorOnMissing || !storageSftpLibSsh2FxNoSuchFile(this, rc))
{
storageSftpEvalLibSsh2Error(
rc, libssh2_sftp_last_error(this->sftpSession), &FileRemoveError,
strNewFmt("unable to remove '%s'", strZ(file)), NULL);
}
}
else
{
if (param.errorOnMissing)
THROW_FMT(FileRemoveError, "unable to remove '%s'", strZ(file));
}
}
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
static StorageRead *
storageSftpNewRead(THIS_VOID, const String *const file, const bool ignoreMissing, const StorageInterfaceNewReadParam param)
{
THIS(StorageSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_SFTP, this);
FUNCTION_LOG_PARAM(STRING, file);
FUNCTION_LOG_PARAM(BOOL, ignoreMissing);
FUNCTION_LOG_PARAM(UINT64, param.offset);
FUNCTION_LOG_PARAM(VARIANT, param.limit);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(file != NULL);
FUNCTION_LOG_RETURN(
STORAGE_READ,
storageReadSftpNew(
this, file, ignoreMissing, this->ioSession, this->session, this->sftpSession, this->sftpHandle, this->timeout,
param.offset, param.limit));
}
/**********************************************************************************************************************************/
static StorageWrite *
storageSftpNewWrite(THIS_VOID, const String *const file, const StorageInterfaceNewWriteParam param)
{
THIS(StorageSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_SFTP, this);
FUNCTION_LOG_PARAM(STRING, file);
FUNCTION_LOG_PARAM(MODE, param.modeFile);
FUNCTION_LOG_PARAM(MODE, param.modePath);
FUNCTION_LOG_PARAM(STRING, param.user);
FUNCTION_LOG_PARAM(STRING, param.group);
FUNCTION_LOG_PARAM(TIME, param.timeModified);
FUNCTION_LOG_PARAM(BOOL, param.createPath);
FUNCTION_LOG_PARAM(BOOL, param.syncFile);
FUNCTION_LOG_PARAM(BOOL, param.syncPath);
FUNCTION_LOG_PARAM(BOOL, param.atomic);
FUNCTION_LOG_PARAM(BOOL, param.truncate);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(file != NULL);
ASSERT(param.createPath);
ASSERT(param.truncate);
ASSERT(param.user == NULL);
ASSERT(param.group == NULL);
ASSERT(param.timeModified == 0);
FUNCTION_LOG_RETURN(
STORAGE_WRITE,
storageWriteSftpNew(
this, file, this->ioSession, this->session, this->sftpSession, this->sftpHandle, this->timeout, param.modeFile,
param.modePath, param.user, param.group, param.timeModified, param.createPath, param.syncFile,
this->interface.pathSync != NULL ? param.syncPath : false, param.atomic, param.truncate));
}
/**********************************************************************************************************************************/
static void
storageSftpPathCreate(
THIS_VOID, const String *const path, const bool errorOnExists, const bool noParentCreate, const mode_t mode,
const StorageInterfacePathCreateParam param)
{
THIS(StorageSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_SFTP, this);
FUNCTION_LOG_PARAM(STRING, path);
FUNCTION_LOG_PARAM(BOOL, errorOnExists);
FUNCTION_LOG_PARAM(BOOL, noParentCreate);
FUNCTION_LOG_PARAM(MODE, mode);
(void)param; // No parameters are used
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(path != NULL);
int rc;
Wait *const wait = waitNew(this->timeout);
// Attempt to create the directory
do
{
rc = libssh2_sftp_mkdir_ex(this->sftpSession, strZ(path), (unsigned int)strSize(path), (int)mode);
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc != 0)
{
if (rc == LIBSSH2_ERROR_SFTP_PROTOCOL)
{
uint64_t sftpErrno = libssh2_sftp_last_error(this->sftpSession);
// libssh2 may return LIBSSH2_FX_FAILURE if the directory already exists
if (sftpErrno == LIBSSH2_FX_FAILURE)
{
// Check if the directory already exists
LIBSSH2_SFTP_ATTRIBUTES attr;
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_stat_ex(
this->sftpSession, strZ(path), (unsigned int)strSize(path), LIBSSH2_SFTP_STAT, &attr);
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
// If rc = 0 then already exists
if (rc == 0 && errorOnExists)
THROW_FMT(PathCreateError, "unable to create path '%s': path already exists", strZ(path));
}
// If the parent path does not exist then create it if allowed
else if (sftpErrno == LIBSSH2_FX_NO_SUCH_FILE && !noParentCreate)
{
String *const pathParent = strPath(path);
storageInterfacePathCreateP(this, pathParent, errorOnExists, noParentCreate, mode);
storageInterfacePathCreateP(this, path, errorOnExists, noParentCreate, mode);
strFree(pathParent);
}
else if (sftpErrno != LIBSSH2_FX_FILE_ALREADY_EXISTS || errorOnExists)
THROW_FMT(PathCreateError, "sftp error unable to create path '%s'", strZ(path));
}
else
THROW_FMT(PathCreateError, "ssh2 error [%d] unable to create path '%s'", rc, strZ(path));
}
FUNCTION_LOG_RETURN_VOID();
}
static bool
storageSftpPathRemove(THIS_VOID, const String *const path, const bool recurse, const StorageInterfacePathRemoveParam param)
{
THIS(StorageSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_SFTP, this);
FUNCTION_LOG_PARAM(STRING, path);
FUNCTION_LOG_PARAM(BOOL, recurse);
(void)param; // No parameters are used
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(path != NULL);
bool result = true;
MEM_CONTEXT_TEMP_BEGIN()
{
// Recurse if requested
if (recurse)
{
StorageList *const list = storageInterfaceListP(this, path, storageInfoLevelExists);
if (list != NULL)
{
MEM_CONTEXT_TEMP_RESET_BEGIN()
{
for (unsigned int listIdx = 0; listIdx < storageLstSize(list); listIdx++)
{
const String *const file = strNewFmt("%s/%s", strZ(path), strZ(storageLstGet(list, listIdx).name));
// Rather than stat the file to discover what type it is, just try to unlink it and see what happens
int rc;
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_unlink_ex(this->sftpSession, strZ(file), (unsigned int)strSize(file));
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc != 0)
{
// Attempting to unlink a directory appears to return LIBSSH2_FX_FAILURE
if (rc == LIBSSH2_ERROR_SFTP_PROTOCOL &&
libssh2_sftp_last_error(this->sftpSession) == LIBSSH2_FX_FAILURE)
{
storageInterfacePathRemoveP(this, file, true);
}
else
THROW_FMT(PathRemoveError, STORAGE_ERROR_PATH_REMOVE_FILE, strZ(file));
}
// Reset the memory context occasionally so we don't use too much memory or slow down processing
MEM_CONTEXT_TEMP_RESET(1000);
}
}
MEM_CONTEXT_TEMP_END();
}
}
// Delete the path
int rc;
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_rmdir_ex(this->sftpSession, strZ(path), (unsigned int)strSize(path));
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc != 0)
{
if (rc == LIBSSH2_ERROR_SFTP_PROTOCOL)
{
if (libssh2_sftp_last_error(this->sftpSession) != LIBSSH2_FX_NO_SUCH_FILE)
THROW_FMT(PathRemoveError, STORAGE_ERROR_PATH_REMOVE, strZ(path));
// Path does not exist
result = false;
}
else
{
// Path does not exist
result = false;
THROW_FMT(PathRemoveError, STORAGE_ERROR_PATH_REMOVE, strZ(path));
}
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(BOOL, result);
}
/**********************************************************************************************************************************/
static const StorageInterface storageInterfaceSftp =
{
.feature = 1 << storageFeaturePath | 1 << storageFeatureInfoDetail,
.info = storageSftpInfo,
.list = storageSftpList,
.newRead = storageSftpNewRead,
.newWrite = storageSftpNewWrite,
.pathCreate = storageSftpPathCreate,
.pathRemove = storageSftpPathRemove,
.remove = storageSftpRemove,
};
FN_EXTERN Storage *
storageSftpNew(
const String *const path, const String *const host, const unsigned int port, const String *const user,
const TimeMSec timeout, const String *const keyPriv, const StringId hostKeyHashType, const StorageSftpNewParam param)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, path);
FUNCTION_LOG_PARAM(STRING, host);
FUNCTION_LOG_PARAM(UINT, port);
FUNCTION_LOG_PARAM(STRING, user);
FUNCTION_LOG_PARAM(TIME_MSEC, timeout);
FUNCTION_LOG_PARAM(STRING, keyPriv);
FUNCTION_LOG_PARAM(STRING_ID, hostKeyHashType);
FUNCTION_LOG_PARAM(STRING, param.keyPub);
FUNCTION_TEST_PARAM(STRING, param.keyPassphrase);
FUNCTION_LOG_PARAM(STRING, param.hostFingerprint);
FUNCTION_LOG_PARAM(MODE, param.modeFile);
FUNCTION_LOG_PARAM(MODE, param.modePath);
FUNCTION_LOG_PARAM(BOOL, param.write);
FUNCTION_LOG_PARAM(FUNCTIONP, param.pathExpressionFunction);
FUNCTION_LOG_END();
ASSERT(path != NULL);
ASSERT(host != NULL);
ASSERT(port != 0);
ASSERT(user != NULL);
ASSERT(keyPriv != NULL);
ASSERT(hostKeyHashType != 0);
// Initialize user module
userInit();
// Create the object
OBJ_NEW_BEGIN(StorageSftp, .childQty = MEM_CONTEXT_QTY_MAX, .callbackQty = 1)
{
*this = (StorageSftp)
{
.interface = storageInterfaceSftp,
.timeout = timeout,
};
if (libssh2_init(0) != 0)
THROW_FMT(ServiceError, "unable to init libssh2");
this->ioSession = ioClientOpen(sckClientNew(host, port, timeout, timeout));
this->session = libssh2_session_init();
if (this->session == NULL)
THROW_FMT(ServiceError, "unable to init libssh2 session");
// Returns void
libssh2_session_set_blocking(this->session, 0);
int handshakeStatus = 0;
Wait *wait = waitNew(timeout);
do
{
handshakeStatus = libssh2_session_handshake(this->session, ioSessionFd(this->ioSession));
}
while (handshakeStatus == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (handshakeStatus != 0)
THROW_FMT(ServiceError, "libssh2 handshake failed [%d]", handshakeStatus);
int hashType = LIBSSH2_HOSTKEY_HASH_SHA1;
size_t hashSize = 0;
// Verify that the fingerprint[N] buffer declared below is large enough when adding a new hashType
switch (hostKeyHashType)
{
case hashTypeMd5:
hashType = LIBSSH2_HOSTKEY_HASH_MD5;
hashSize = HASH_TYPE_M5_SIZE;
break;
case hashTypeSha1:
hashType = LIBSSH2_HOSTKEY_HASH_SHA1;
hashSize = HASH_TYPE_SHA1_SIZE;
break;
#ifdef LIBSSH2_HOSTKEY_HASH_SHA256
case hashTypeSha256:
hashType = LIBSSH2_HOSTKEY_HASH_SHA256;
hashSize = HASH_TYPE_SHA256_SIZE;
break;
#endif // LIBSSH2_HOSTKEY_HASH_SHA256
default:
THROW_FMT(
ServiceError, "requested ssh2 hostkey hash type (%s) not available", strZ(strIdToStr(hostKeyHashType)));
break;
}
const char *binaryFingerprint = libssh2_hostkey_hash(this->session, hashType);
if (binaryFingerprint == NULL)
THROW_FMT(ServiceError, "libssh2 hostkey hash failed: libssh2 errno [%d]", libssh2_session_last_errno(this->session));
// Compare fingerprint if provided
if (param.hostFingerprint != NULL)
{
// 256 bytes is large enough to hold the hex representation of currently supported hash types. The hex encoded version
// requires twice as much space (hashSize * 2) as the raw version.
char fingerprint[256];
encodeToStr(encodingHex, (unsigned char *)binaryFingerprint, hashSize, fingerprint);
if (strcmp(fingerprint, strZ(param.hostFingerprint)) != 0)
{
THROW_FMT(
ServiceError, "host [%s] and configured fingerprint (repo-sftp-host-fingerprint) [%s] do not match",
fingerprint, strZ(param.hostFingerprint));
}
}
LOG_DEBUG_FMT("attempting public key authentication");
int rc;
wait = waitNew(timeout);
do
{
rc = libssh2_userauth_publickey_fromfile(
this->session, strZ(user), strZNull(param.keyPub), strZ(keyPriv), strZNull(param.keyPassphrase));
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc != 0)
{
storageSftpEvalLibSsh2Error(
rc, libssh2_sftp_last_error(this->sftpSession), &ServiceError,
STRDEF("public key authentication failed"),
STRDEF(
"HINT: libssh2 compiled against non-openssl libraries requires --repo-sftp-private-key-file and"
" --repo-sftp-public-key-file to be provided\n"
"HINT: libssh2 versions before 1.9.0 expect a PEM format keypair, try ssh-keygen -m PEM -t rsa -P \"\" to"
" generate the keypair"));
}
wait = waitNew(timeout);
do
{
this->sftpSession = libssh2_sftp_init(this->session);
}
while (this->sftpSession == NULL && waitMore(wait));
waitFree(wait);
if (this->sftpSession == NULL)
THROW_FMT(ServiceError, "unable to init libssh2_sftp session");
// Ensure libssh2/libssh2_sftp resources freed
memContextCallbackSet(objMemContext(this), storageSftpLibSsh2SessionFreeResource, this);
}
OBJ_NEW_END();
FUNCTION_LOG_RETURN(
STORAGE,
storageNew(
STORAGE_SFTP_TYPE, path, param.modeFile == 0 ? STORAGE_MODE_FILE_DEFAULT : param.modeFile,
param.modePath == 0 ? STORAGE_MODE_PATH_DEFAULT : param.modePath, param.write, param.pathExpressionFunction,
this, this->interface));
}
#endif // HAVE_LIBSSH2

View File

@ -0,0 +1,40 @@
/***********************************************************************************************************************************
SFTP Storage
***********************************************************************************************************************************/
#ifndef STORAGE_SFTP_STORAGE_H
#define STORAGE_SFTP_STORAGE_H
#include "storage/storage.h"
/***********************************************************************************************************************************
Storage type
***********************************************************************************************************************************/
#define STORAGE_SFTP_TYPE STRID5("sftp", 0x850d30)
#ifdef HAVE_LIBSSH2
/***********************************************************************************************************************************
Constructors
***********************************************************************************************************************************/
typedef struct StorageSftpNewParam
{
VAR_PARAM_HEADER;
bool write;
mode_t modeFile;
mode_t modePath;
StoragePathExpressionCallback *pathExpressionFunction;
const String *keyPub;
const String *keyPassphrase;
const String *hostFingerprint;
} StorageSftpNewParam;
#define storageSftpNewP(path, host, port, user, timeout, keyPriv, hostKeyHashType, ...) \
storageSftpNew(path, host, port, user, timeout, keyPriv, hostKeyHashType, (StorageSftpNewParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN Storage *storageSftpNew(
const String *path, const String *host, unsigned int port, const String *user, TimeMSec timeout, const String *keyPriv,
StringId hostKeyHashType, const StorageSftpNewParam param);
#endif // HAVE_LIBSSH2
#endif

View File

@ -0,0 +1,31 @@
/***********************************************************************************************************************************
SFTP Storage Internal
***********************************************************************************************************************************/
#ifndef STORAGE_SFTP_STORAGE_INTERN_H
#define STORAGE_SFTP_STORAGE_INTERN_H
#include <libssh2.h>
#include <libssh2_sftp.h>
#include "storage/sftp/storage.h"
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
typedef struct StorageSftp StorageSftp;
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
FN_EXTERN void storageSftpEvalLibSsh2Error(
int ssh2Errno, uint64_t sftpErrno, const ErrorType *errorType, const String *msg, const String *hint);
/***********************************************************************************************************************************
Macros for function logging
***********************************************************************************************************************************/
#define FUNCTION_LOG_STORAGE_SFTP_TYPE \
StorageSftp *
#define FUNCTION_LOG_STORAGE_SFTP_FORMAT(value, buffer, bufferSize) \
objNameToLog(value, "StorageSftp *", buffer, bufferSize)
#endif

414
src/storage/sftp/write.c Normal file
View File

@ -0,0 +1,414 @@
/***********************************************************************************************************************************
SFTP Storage File Write
***********************************************************************************************************************************/
#include "build.auto.h"
#ifdef HAVE_LIBSSH2
#include "common/debug.h"
#include "common/log.h"
#include "common/user.h"
#include "common/wait.h"
#include "storage/sftp/write.h"
#include "storage/write.intern.h"
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
typedef struct StorageWriteSftp
{
StorageWriteInterface interface; // Interface
StorageSftp *storage; // Storage that created this object
const String *nameTmp; // Temporary filename utilized for atomic ops
const String *path; // Utilized for path operations
IoSession *ioSession; // IoSession (socket) connection to SFTP server
LIBSSH2_SESSION *session; // LibSsh2 session
LIBSSH2_SFTP *sftpSession; // LibSsh2 session sftp session
LIBSSH2_SFTP_HANDLE *sftpHandle; // LibSsh2 session sftp handle
TimeMSec timeout; // Session timeout
} StorageWriteSftp;
/***********************************************************************************************************************************
Macros for function logging
***********************************************************************************************************************************/
#define FUNCTION_LOG_STORAGE_WRITE_SFTP_TYPE \
StorageWriteSftp *
#define FUNCTION_LOG_STORAGE_WRITE_SFTP_FORMAT(value, buffer, bufferSize) \
objNameToLog(value, "StorageWriteSftp", buffer, bufferSize)
/***********************************************************************************************************************************
Open the file
***********************************************************************************************************************************/
static void
storageWriteSftpOpen(THIS_VOID)
{
THIS(StorageWriteSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_WRITE_SFTP, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(this->sftpSession != NULL);
const unsigned long int flags = LIBSSH2_FXF_CREAT | LIBSSH2_FXF_WRITE | LIBSSH2_FXF_TRUNC;
// Open the file
Wait *const wait = waitNew(this->timeout);
do
{
this->sftpHandle = libssh2_sftp_open_ex(
this->sftpSession, strZ(this->nameTmp), (unsigned int)strSize(this->nameTmp), flags, (int)this->interface.modeFile,
LIBSSH2_SFTP_OPENFILE);
}
while (this->sftpHandle == NULL && libssh2_session_last_errno(this->session) == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
// Attempt to create the path if it is missing
if (this->sftpHandle == NULL && libssh2_session_last_errno(this->session) == LIBSSH2_ERROR_SFTP_PROTOCOL &&
libssh2_sftp_last_error(this->sftpSession) == LIBSSH2_FX_NO_SUCH_FILE)
{
// Create the path
storageInterfacePathCreateP(this->storage, this->path, false, false, this->interface.modePath);
// Open file again
Wait *const wait = waitNew(this->timeout);
do
{
this->sftpHandle = libssh2_sftp_open_ex(
this->sftpSession, strZ(this->nameTmp), (unsigned int)strSize(this->nameTmp), flags, (int)this->interface.modeFile,
LIBSSH2_SFTP_OPENFILE);
}
while (this->sftpHandle == NULL && (libssh2_session_last_errno(this->session) == LIBSSH2_ERROR_EAGAIN && waitMore(wait)));
waitFree(wait);
}
// Handle error
if (this->sftpHandle == NULL)
{
const int rc = libssh2_session_last_errno(this->session);
if (rc == LIBSSH2_ERROR_SFTP_PROTOCOL)
{
uint64_t sftpErr = libssh2_sftp_last_error(this->sftpSession);
if (sftpErr == LIBSSH2_FX_NO_SUCH_FILE)
THROW_FMT(FileMissingError, STORAGE_ERROR_WRITE_MISSING, strZ(this->interface.name));
else
{
storageSftpEvalLibSsh2Error(
rc, sftpErr, &FileOpenError, strNewFmt(STORAGE_ERROR_WRITE_OPEN, strZ(this->interface.name)), NULL);
}
}
else
THROW_FMT(FileOpenError, STORAGE_ERROR_WRITE_OPEN, strZ(this->interface.name));
}
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Write to the file
***********************************************************************************************************************************/
static void
storageWriteSftp(THIS_VOID, const Buffer *const buffer)
{
THIS(StorageWriteSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_WRITE_SFTP, this);
FUNCTION_LOG_PARAM(BUFFER, buffer);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(buffer != NULL);
ASSERT(this->sftpHandle != NULL);
ssize_t rc;
size_t remains = bufUsed(buffer); // Amount left to write
size_t offset = 0; // Offset into the buffer
// Loop until all the data is written
do
{
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_write(this->sftpHandle, (const char *)bufPtrConst(buffer) + offset, remains);
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
// Break on error. Error will be thrown below the loop.
if (rc < 0)
break;
// Offset for next write start point
offset += (size_t)rc;
// Update amount left to write
remains -= (size_t)rc;
}
while (remains);
if (rc < 0)
THROW_FMT(FileWriteError, "unable to write '%s'", strZ(this->nameTmp));
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Unlink already existing file
***********************************************************************************************************************************/
static void
storageWriteSftpUnlinkExisting(THIS_VOID)
{
THIS(StorageWriteSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_WRITE_SFTP, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
int rc;
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_unlink_ex(this->sftpSession, strZ(this->interface.name), (unsigned int)strSize(this->interface.name));
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc)
{
storageSftpEvalLibSsh2Error(
rc, libssh2_sftp_last_error(this->sftpSession), &FileRemoveError,
strNewFmt("unable to remove existing '%s'", strZ(this->interface.name)), NULL);
}
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Rename a file
***********************************************************************************************************************************/
static void
storageWriteSftpRename(THIS_VOID)
{
THIS(StorageWriteSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_WRITE_SFTP, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
int rc;
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_rename_ex(
this->sftpSession, strZ(this->nameTmp), (unsigned int)strSize(this->nameTmp), strZ(this->interface.name),
(unsigned int)strSize(this->interface.name),
LIBSSH2_SFTP_RENAME_OVERWRITE | LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE);
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc)
{
storageSftpEvalLibSsh2Error(
rc, libssh2_sftp_last_error(this->sftpSession), &FileRemoveError,
strNewFmt("unable to move '%s' to '%s'", strZ(this->nameTmp), strZ(this->interface.name)), NULL);
}
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Close the file
***********************************************************************************************************************************/
static void
storageWriteSftpClose(THIS_VOID)
{
THIS(StorageWriteSftp);
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_WRITE_SFTP, this);
FUNCTION_LOG_END();
ASSERT(this != NULL);
// Close if the file has not already been closed
if (this->sftpHandle != NULL)
{
int rc;
char *libSsh2ErrMsg;
int errMsgLen;
int libSsh2ErrNo;
if (this->interface.syncFile)
{
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_fsync(this->sftpHandle);
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc)
THROW_FMT(FileSyncError, STORAGE_ERROR_WRITE_SYNC, strZ(this->nameTmp));
}
// Close the file
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_close(this->sftpHandle);
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc != 0)
{
libSsh2ErrNo = libssh2_session_last_error(this->session, &libSsh2ErrMsg, &errMsgLen, 0);
THROW_FMT(
FileCloseError,
STORAGE_ERROR_WRITE_CLOSE ": libssh2 error [%d] %s", strZ(this->nameTmp), libSsh2ErrNo, libSsh2ErrMsg);
}
this->sftpHandle = NULL;
// Rename from temp file
if (this->interface.atomic)
{
Wait *const wait = waitNew(this->timeout);
do
{
rc = libssh2_sftp_rename_ex(
this->sftpSession, strZ(this->nameTmp), (unsigned int)strSize(this->nameTmp), strZ(this->interface.name),
(unsigned int)strSize(this->interface.name),
LIBSSH2_SFTP_RENAME_OVERWRITE | LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE);
}
while (rc == LIBSSH2_ERROR_EAGAIN && waitMore(wait));
waitFree(wait);
if (rc)
{
// Some/most sftp servers will not rename over an existing file, in testing this returned LIBSSH2_FX_FAILURE
if (rc == LIBSSH2_ERROR_SFTP_PROTOCOL && libssh2_sftp_last_error(this->sftpSession) == LIBSSH2_FX_FAILURE)
{
// Remove the existing file and retry the rename
storageWriteSftpUnlinkExisting(this);
storageWriteSftpRename(this);
}
else
{
storageSftpEvalLibSsh2Error(
rc, libssh2_sftp_last_error(this->sftpSession), &FileCloseError,
strNewFmt("unable to move '%s' to '%s'", strZ(this->nameTmp), strZ(this->interface.name)), NULL);
}
}
}
}
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
FN_EXTERN StorageWrite *
storageWriteSftpNew(
StorageSftp *const storage, const String *const name, IoSession *const ioSession, LIBSSH2_SESSION *const session,
LIBSSH2_SFTP *const sftpSession, LIBSSH2_SFTP_HANDLE *const sftpHandle, const TimeMSec timeout, const mode_t modeFile,
const mode_t modePath, const String *const user, const String *const group, const time_t timeModified, const bool createPath,
const bool syncFile, const bool syncPath, const bool atomic, const bool truncate)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STORAGE_SFTP, storage);
FUNCTION_LOG_PARAM(STRING, name);
FUNCTION_LOG_PARAM_P(VOID, session);
FUNCTION_LOG_PARAM_P(VOID, sftpSession);
FUNCTION_LOG_PARAM_P(VOID, sftpHandle);
FUNCTION_LOG_PARAM(TIME_MSEC, timeout);
FUNCTION_LOG_PARAM(MODE, modeFile);
FUNCTION_LOG_PARAM(MODE, modePath);
FUNCTION_LOG_PARAM(STRING, user);
FUNCTION_LOG_PARAM(STRING, group);
FUNCTION_LOG_PARAM(TIME, timeModified);
FUNCTION_LOG_PARAM(BOOL, createPath);
FUNCTION_LOG_PARAM(BOOL, syncFile);
FUNCTION_LOG_PARAM(BOOL, syncPath);
FUNCTION_LOG_PARAM(BOOL, atomic);
FUNCTION_LOG_PARAM(BOOL, truncate);
FUNCTION_LOG_END();
ASSERT(storage != NULL);
ASSERT(name != NULL);
ASSERT(modeFile != 0);
ASSERT(modePath != 0);
OBJ_NEW_BEGIN(StorageWriteSftp, .childQty = MEM_CONTEXT_QTY_MAX)
{
*this = (StorageWriteSftp)
{
.storage = storage,
.path = strPath(name),
.ioSession = ioSession,
.session = session,
.sftpSession = sftpSession,
.sftpHandle = sftpHandle,
.timeout = timeout,
.interface = (StorageWriteInterface)
{
.type = STORAGE_SFTP_TYPE,
.name = strDup(name),
.atomic = atomic,
.createPath = createPath,
.group = strDup(group),
.modeFile = modeFile,
.modePath = modePath,
.syncFile = syncFile,
.syncPath = syncPath,
.truncate = truncate,
.user = strDup(user),
.timeModified = timeModified,
.ioInterface = (IoWriteInterface)
{
.close = storageWriteSftpClose,
.open = storageWriteSftpOpen,
.write = storageWriteSftp,
},
},
};
// Create temp file name
this->nameTmp = atomic ? strNewFmt("%s." STORAGE_FILE_TEMP_EXT, strZ(name)) : this->interface.name;
}
OBJ_NEW_END();
FUNCTION_LOG_RETURN(STORAGE_WRITE, storageWriteNew(this, &this->interface));
}
#endif // HAVE_LIBSSH2

19
src/storage/sftp/write.h Normal file
View File

@ -0,0 +1,19 @@
/***********************************************************************************************************************************
SFTP Storage File Write
***********************************************************************************************************************************/
#ifndef STORAGE_SFTP_WRITE_H
#define STORAGE_SFTP_WRITE_H
#include "common/io/session.h"
#include "storage/sftp/storage.h"
#include "storage/sftp/storage.intern.h"
/***********************************************************************************************************************************
Constructors
***********************************************************************************************************************************/
FN_EXTERN StorageWrite *storageWriteSftpNew(
StorageSftp *storage, const String *name, IoSession *ioSession, LIBSSH2_SESSION *session, LIBSSH2_SFTP *sftpSession,
LIBSSH2_SFTP_HANDLE *sftpHandle, TimeMSec timeout, mode_t modeFile, mode_t modePath, const String *user, const String *group,
time_t timeModified, bool createPath, bool syncFile, bool syncPath, bool atomic, bool truncate);
#endif

View File

@ -13,7 +13,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
libdbd-pg-perl libxml-checker-perl libyaml-perl \ libdbd-pg-perl libxml-checker-perl libyaml-perl \
devscripts build-essential lintian git cloc txt2man debhelper libssl-dev zlib1g-dev libperl-dev libxml2-dev liblz4-dev \ devscripts build-essential lintian git cloc txt2man debhelper libssl-dev zlib1g-dev libperl-dev libxml2-dev liblz4-dev \
liblz4-tool libpq-dev lcov autoconf-archive zstd libzstd-dev bzip2 libbz2-dev pkg-config libyaml-dev libc6-dbg wget meson \ liblz4-tool libpq-dev lcov autoconf-archive zstd libzstd-dev bzip2 libbz2-dev pkg-config libyaml-dev libc6-dbg wget meson \
ccache valgrind tzdata uncrustify ccache valgrind tzdata uncrustify libssh2-1-dev
# Install Docker # Install Docker
RUN groupadd -g5000 docker RUN groupadd -g5000 docker

2
test/Vagrantfile vendored
View File

@ -76,7 +76,7 @@ Vagrant.configure(2) do |config|
echo 'Install Build Tools' && date echo 'Install Build Tools' && date
apt-get install -y devscripts build-essential lintian git cloc txt2man debhelper libssl-dev zlib1g-dev libperl-dev \ apt-get install -y devscripts build-essential lintian git cloc txt2man debhelper libssl-dev zlib1g-dev libperl-dev \
libxml2-dev liblz4-dev liblz4-tool libpq-dev lcov autoconf-archive zstd libzstd-dev bzip2 libbz2-dev pkg-config \ libxml2-dev liblz4-dev liblz4-tool libpq-dev lcov autoconf-archive zstd libzstd-dev bzip2 libbz2-dev pkg-config \
libyaml-dev libc6-dbg valgrind meson ccache uncrustify libyaml-dev libc6-dbg valgrind meson ccache uncrustify libssh2-1-dev
#----------------------------------------------------------------------------------------------------------------------- #-----------------------------------------------------------------------------------------------------------------------
echo 'Install Docker' && date echo 'Install Docker' && date

View File

@ -0,0 +1,16 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQDR0yJsZW5d5LcqteiOtv8d+FFeFFHDPI0VTcTOdMn1iDiIP1ou
X3Q2OyNjsBaDbsRJd+sp9IRq1LKX3zsBcgGZANwm0zduuNEPEU94ajS/uRoejIqY
/XkKOpnEF6ZbQ2S7TaE4sWeGLvba7kUFs0QTOO+N+nV2dMbdqZf6C8lazwIDAQAB
AoGBAJXa6xzrnFVmwgK5BKzYuX/YF5TPgk2j80ch0ct50buQXH/Cb0/rUH5i4jWS
T6Hy/DFUehnuzpvV6O9auTOhDs3BhEKFRuRLn1nBwTtZny5Hh+cw7azUCEHFCJlz
makCrVbgawtno6oU/pFgQm1FcxD0f+Me5ruNcLHqUZsPQwkRAkEA+8pG+ckOlz6R
AJLIHedmfcrEY9T7sfdo83bzMOz8H5soUUP4aOTLJYCla1LO7JdDnXMGo0KxaHBP
l8j5zDmVewJBANVVPDJr1w37m0FBi37QgUOAijVfLXgyPMxYp2uc9ddjncif0063
0Wc0FQefoPszf3CDrHv/RHvhHq97jXDwTb0CQQDgH83NygoS1r57pCw9chzpG/R0
aMEiSPhCvz757fj+qT3aGIal2AJ7/2c/gRZvwrWNETZ3XIZOUKqIkXzJLPjBAkEA
wnP799W2Y8d4/+VX2pMBkF7lG7sSviHEq1sP2BZtPBRQKSQNvw3scM7XcGh/mxmY
yx0qpqfKa8SKbNgI1+4iXQJBAOlg8MJLwkUtrG+p8wf69oCuZsnyv0K6UMDxm6/8
cbvfmvODulYFaIahaqHWEZoRo5CLYZ7gN43WHPOrKxdDL78=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDR0yJsZW5d5LcqteiOtv8d+FFeFFHDPI0VTcTOdMn1iDiIP1ouX3Q2OyNjsBaDbsRJd+sp9IRq1LKX3zsBcgGZANwm0zduuNEPEU94ajS/uRoejIqY/XkKOpnEF6ZbQ2S7TaE4sWeGLvba7kUFs0QTOO+N+nV2dMbdqZf6C8lazw==

View File

@ -189,7 +189,7 @@ eval
# Build list of packages that need to be installed # Build list of packages that need to be installed
my $strPackage = my $strPackage =
"make gcc ccache meson python3-pip git rsync zlib1g-dev libssl-dev libxml2-dev libpq-dev libyaml-dev pkg-config" . "make gcc ccache meson python3-pip git rsync zlib1g-dev libssl-dev libxml2-dev libpq-dev libyaml-dev pkg-config" .
" uncrustify"; " uncrustify libssh2-1-dev";
# Add lcov when testing coverage # Add lcov when testing coverage
if (vmCoverageC($strVm)) if (vmCoverageC($strVm))

View File

@ -1859,6 +1859,42 @@ src/storage/s3/write.h:
class: core class: core
type: c/h type: c/h
src/storage/sftp/helper.c:
class: core
type: c
src/storage/sftp/helper.h:
class: core
type: c/h
src/storage/sftp/read.c:
class: core
type: c
src/storage/sftp/read.h:
class: core
type: c/h
src/storage/sftp/storage.c:
class: core
type: c
src/storage/sftp/storage.h:
class: core
type: c/h
src/storage/sftp/storage.intern.h:
class: core
type: c/h
src/storage/sftp/write.c:
class: core
type: c
src/storage/sftp/write.h:
class: core
type: c/h
src/storage/storage.c: src/storage/storage.c:
class: core class: core
type: c type: c
@ -2163,6 +2199,22 @@ test/src/common/harnessInfo.h:
class: test/harness class: test/harness
type: c/h type: c/h
test/src/common/harnessIo.c:
class: test/harness
type: c
test/src/common/harnessIo.h:
class: test/harness
type: c/h
test/src/common/harnessLibssh2.c:
class: test/harness
type: c
test/src/common/harnessLibssh2.h:
class: test/harness
type: c/h
test/src/common/harnessLog.c: test/src/common/harnessLog.c:
class: test/harness class: test/harness
type: c type: c
@ -2639,6 +2691,10 @@ test/src/module/storage/s3Test.c:
class: test/module class: test/module
type: c type: c
test/src/module/storage/sftpTest.c:
class: test/module
type: c
test/src/module/test/testTest.c: test/src/module/test/testTest.c:
class: test/module class: test/module
type: c type: c

View File

@ -12,19 +12,9 @@
# - docker login -u pgbackrest # - docker login -u pgbackrest
# - VM=XXX;DATE=YYYYMMDDX;BASE=pgbackrest/test:${VM?}-base;docker tag ${BASE?} ${BASE?}-${DATE?} && docker push ${BASE?}-${DATE?} # - VM=XXX;DATE=YYYYMMDDX;BASE=pgbackrest/test:${VM?}-base;docker tag ${BASE?} ${BASE?}-${DATE?} && docker push ${BASE?}-${DATE?}
# ********************************************************************************************************************************** # **********************************************************************************************************************************
20230427A: 20230513A:
x86_64: x86_64:
u22: 6a07d63ae869c3d84d1bf6622826df9fe768691d d10: 633def5323eeed51d7aa187ad1d61e854c47d6fa
f36: 640d3ed0d9786ef61263e27a77686a331f72c58e
20230405A: rh7: 18dd0cbe19aa66dd9d72d312da5cb8c4bddea8b9
x86_64: u22: eba71d24c8636c7dfea5942f78a82a4566bef784
d10: 01a943f8d68ee5bbb2a5ee10cc474925c6cc0cde
20221220A:
x86_64:
u20: 2db467d873c0aff06335592c8a22b8441b5c6440
20220726A:
x86_64:
f36: 099b329ca7988b05f2cb8ef759e146ea9faab108
rh7: 6072f05804b369681efad5cebe01704cb9d2a81a

View File

@ -365,6 +365,12 @@ unit:
total: 5 total: 5
feature: SOCKET feature: SOCKET
harness: server harness: server
harness:
name: socket
shim:
common/io/socket/client:
function:
- sckClientOpen
coverage: coverage:
- common/io/client - common/io/client
@ -598,6 +604,23 @@ unit:
- storage/storage - storage/storage
- storage/write - storage/write
# ----------------------------------------------------------------------------------------------------------------------------
- name: sftp
total: 20
harness: libSsh2
coverage:
- storage/sftp/helper
- storage/sftp/read
- storage/sftp/storage
- storage/sftp/write
include:
- storage/helper
- storage/read
- storage/storage
- storage/write
# ******************************************************************************************************************************** # ********************************************************************************************************************************
- name: postgres - name: postgres

View File

@ -215,6 +215,7 @@ sub sshSetup
} }
$strScript .= $strScript .=
" cp ${strUserPath}/.ssh/authorized_keys ${strUserPath}/.ssh/id_rsa.pub && \\\n" .
" chown -R ${strUser}:${strGroup} ${strUserPath}/.ssh && \\\n" . " chown -R ${strUser}:${strGroup} ${strUserPath}/.ssh && \\\n" .
" chmod 700 ${strUserPath}/.ssh && \\\n" . " chmod 700 ${strUserPath}/.ssh && \\\n" .
" chmod 600 ${strUserPath}/.ssh/*"; " chmod 600 ${strUserPath}/.ssh/*";
@ -385,7 +386,8 @@ sub containerBuild
" yum -y install openssh-server openssh-clients wget sudo valgrind git \\\n" . " yum -y install openssh-server openssh-clients wget sudo valgrind git \\\n" .
" perl perl-Digest-SHA perl-DBD-Pg perl-YAML-LibYAML openssl \\\n" . " perl perl-Digest-SHA perl-DBD-Pg perl-YAML-LibYAML openssl \\\n" .
" gcc make perl-ExtUtils-MakeMaker perl-Test-Simple openssl-devel perl-ExtUtils-Embed rpm-build \\\n" . " gcc make perl-ExtUtils-MakeMaker perl-Test-Simple openssl-devel perl-ExtUtils-Embed rpm-build \\\n" .
" libyaml-devel zlib-devel libxml2-devel lz4-devel lz4 bzip2-devel bzip2 perl-JSON-PP ccache meson"; " libyaml-devel zlib-devel libxml2-devel lz4-devel lz4 bzip2-devel bzip2 perl-JSON-PP ccache meson \\\n" .
" libssh2-devel";
} }
else else
{ {
@ -396,7 +398,8 @@ sub containerBuild
" libdbd-pg-perl libhtml-parser-perl libssl-dev libperl-dev \\\n" . " libdbd-pg-perl libhtml-parser-perl libssl-dev libperl-dev \\\n" .
" libyaml-libyaml-perl tzdata devscripts lintian libxml-checker-perl txt2man debhelper \\\n" . " libyaml-libyaml-perl tzdata devscripts lintian libxml-checker-perl txt2man debhelper \\\n" .
" libppi-html-perl libtemplate-perl libtest-differences-perl zlib1g-dev libxml2-dev pkg-config \\\n" . " libppi-html-perl libtemplate-perl libtest-differences-perl zlib1g-dev libxml2-dev pkg-config \\\n" .
" libbz2-dev bzip2 libyaml-dev libjson-pp-perl liblz4-dev liblz4-tool gnupg lsb-release ccache meson"; " libbz2-dev bzip2 libyaml-dev libjson-pp-perl liblz4-dev liblz4-tool gnupg lsb-release ccache meson \\\n" .
" libssh2-1-dev";
# This package is required to build valgrind on 32-bit # This package is required to build valgrind on 32-bit
if ($oVm->{$strOS}{&VM_ARCH} eq VM_ARCH_I386) if ($oVm->{$strOS}{&VM_ARCH} eq VM_ARCH_I386)
@ -612,6 +615,14 @@ sub containerBuild
" echo '***********************************************' >> /etc/issue.net && \\\n" . " echo '***********************************************' >> /etc/issue.net && \\\n" .
" echo 'Banner /etc/issue.net' >> /etc/ssh/sshd_config"; " echo 'Banner /etc/issue.net' >> /etc/ssh/sshd_config";
if ($strOS eq VM_U22)
{
$strScript .= sectionHeader() .
" echo '# Add PubkeyAcceptedAlgorithms (required for SFTP)' >> /etc/ssh/sshd_config && \\\n" .
" echo 'HostKeyAlgorithms=+ssh-rsa,ssh-rsa-cert-v01\@openssh.com' >> /etc/ssh/sshd_config && \\\n" .
" echo 'PubkeyAcceptedAlgorithms=+ssh-rsa,ssh-rsa-cert-v01\@openssh.com' >> /etc/ssh/sshd_config";
}
$strScript .= sectionHeader() . $strScript .= sectionHeader() .
"# Create test user\n" . "# Create test user\n" .
' ' . groupCreate($strOS, TEST_GROUP, TEST_GROUP_ID) . " && \\\n" . ' ' . groupCreate($strOS, TEST_GROUP, TEST_GROUP_ID) . " && \\\n" .

View File

@ -162,16 +162,15 @@ my $oyVm =
&VM_DB => &VM_DB =>
[ [
PG_VERSION_10,
PG_VERSION_11, PG_VERSION_11,
PG_VERSION_12, PG_VERSION_12,
PG_VERSION_13, PG_VERSION_13,
PG_VERSION_14, PG_VERSION_14,
PG_VERSION_15,
], ],
&VM_DB_TEST => &VM_DB_TEST =>
[ [
PG_VERSION_10,
PG_VERSION_11, PG_VERSION_11,
PG_VERSION_12, PG_VERSION_12,
PG_VERSION_13, PG_VERSION_13,
@ -193,7 +192,6 @@ my $oyVm =
&VM_DB => &VM_DB =>
[ [
PG_VERSION_10,
PG_VERSION_11, PG_VERSION_11,
PG_VERSION_12, PG_VERSION_12,
PG_VERSION_13, PG_VERSION_13,
@ -269,6 +267,7 @@ my $oyVm =
[ [
PG_VERSION_95, PG_VERSION_95,
PG_VERSION_96, PG_VERSION_96,
PG_VERSION_10,
PG_VERSION_15, PG_VERSION_15,
PG_VERSION_16, PG_VERSION_16,
], ],

View File

@ -33,6 +33,7 @@ use pgBackRestTest::Env::Host::HostAzureTest;
use pgBackRestTest::Env::Host::HostGcsTest; use pgBackRestTest::Env::Host::HostGcsTest;
use pgBackRestTest::Env::Host::HostBaseTest; use pgBackRestTest::Env::Host::HostBaseTest;
use pgBackRestTest::Env::Host::HostS3Test; use pgBackRestTest::Env::Host::HostS3Test;
use pgBackRestTest::Env::Host::HostSftpTest;
use pgBackRestTest::Env::Manifest; use pgBackRestTest::Env::Manifest;
use pgBackRestTest::Common::ContainerTest; use pgBackRestTest::Common::ContainerTest;
use pgBackRestTest::Common::ExecuteTest; use pgBackRestTest::Common::ExecuteTest;
@ -92,6 +93,8 @@ use constant POSIX => STORAGE_P
push @EXPORT, qw(POSIX); push @EXPORT, qw(POSIX);
use constant S3 => 's3'; use constant S3 => 's3';
push @EXPORT, qw(S3); push @EXPORT, qw(S3);
use constant SFTP => 'sftp';
push @EXPORT, qw(SFTP);
use constant CFGOPTVAL_RESTORE_TYPE_DEFAULT => 'default'; use constant CFGOPTVAL_RESTORE_TYPE_DEFAULT => 'default';
push @EXPORT, qw(CFGOPTVAL_RESTORE_TYPE_DEFAULT); push @EXPORT, qw(CFGOPTVAL_RESTORE_TYPE_DEFAULT);
@ -161,7 +164,7 @@ sub new
bless $self, $class; bless $self, $class;
# If repo is on local filesystem then set the repo-path locally # If repo is on local filesystem then set the repo-path locally
if ($oParam->{bRepoLocal}) if ($oParam->{bRepoLocal} || $oParam->{strBackupDestination} eq HOST_SFTP)
{ {
$self->{strRepoPath} = $self->testRunGet()->testPath() . "/$$oParam{strBackupDestination}/" . HOST_PATH_REPO; $self->{strRepoPath} = $self->testRunGet()->testPath() . "/$$oParam{strBackupDestination}/" . HOST_PATH_REPO;
} }
@ -1265,6 +1268,35 @@ sub configCreate
} }
} }
} }
elsif ($oParam->{strStorage} eq SFTP)
{
my $oHostDb1 = $oHostDbPrimary;
my $oHostDb2 = $oHostDbStandby;
if ($self->nameTest(HOST_DB_STANDBY))
{
$oHostDb1 = $oHostDbStandby;
$oHostDb2 = $oHostDbPrimary;
}
# Set a flag so we know there's a bogus host
$self->{bBogusHost} = true;
# Set a valid replica to a higher index to ensure skipping indexes does not make a difference
$oParamHash{$strStanza}{"pg256-host"} = $oHostDb2->nameGet();
$oParamHash{$strStanza}{"pg256-host-user"} = $oHostDb2->userGet();
$oParamHash{$strStanza}{"pg256-host-cmd"} = $oHostDb2->backrestExe();
$oParamHash{$strStanza}{"pg256-host-config"} = $oHostDb2->backrestConfig();
$oParamHash{$strStanza}{"pg256-path"} = $oHostDb2->dbBasePath();
# !!! is this needed???
# Only test explicit ports on the backup server. This is so locally configured ports are also tested.
if (!$self->synthetic())
{
$oParamHash{$strStanza}{"pg256-port"} = $oHostDb2->pgPort();
}
}
# If this is a database host # If this is a database host
if ($self->isHostDb()) if ($self->isHostDb())
@ -1287,10 +1319,29 @@ sub configCreate
# If the backup host is remote # If the backup host is remote
if (!$self->isHostBackup()) if (!$self->isHostBackup())
{ {
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-host'} = $oHostBackup->nameGet();
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-host-user'} = $oHostBackup->userGet(); $oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-host-user'} = $oHostBackup->userGet();
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-host-cmd'} = $oHostBackup->backrestExe();
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-host-config'} = $oHostBackup->backrestConfig(); if ($oHostBackup->nameGet() eq HOST_SFTP)
{
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-type'} = "sftp";
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-sftp-host'} = HOST_SFTP;
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-sftp-host-key-hash-type'} = "sha1";
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-sftp-host-user'} = TEST_USER;
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-sftp-private-key-file'} = testRunGet()->basePath() . SSH_PRIVATE_KEY;
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-sftp-public-key-file'} = testRunGet()->basePath() . SSH_PUBLIC_KEY;
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-path'} = $self->repoPath();
# At what count do we hit diminishing returns
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'process-max'} = 8;
$oParamHash{&CFGDEF_SECTION_GLOBAL . ':backup'}{'start-fast'} = 'y';
}
else
{
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-host'} = $oHostBackup->nameGet();
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-host-cmd'} = $oHostBackup->backrestExe();
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'repo1-host-config'} = $oHostBackup->backrestConfig();
}
if ($oParam->{bTls}) if ($oParam->{bTls})
{ {

View File

@ -43,6 +43,8 @@ use constant HOST_AZURE => 'azure';
push @EXPORT, qw(HOST_AZURE); push @EXPORT, qw(HOST_AZURE);
use constant HOST_S3 => 's3-server'; use constant HOST_S3 => 's3-server';
push @EXPORT, qw(HOST_S3); push @EXPORT, qw(HOST_S3);
use constant HOST_SFTP => 'sftp-srvr';
push @EXPORT, qw(HOST_SFTP);
#################################################################################################################################### ####################################################################################################################################
# CA/cert/key constants # CA/cert/key constants
@ -59,6 +61,16 @@ use constant HOST_SERVER_CA => HOST_CERT
use constant HOST_SERVER_CERT => HOST_CERT_PATH . 'pgbackrest-test-server.crt'; use constant HOST_SERVER_CERT => HOST_CERT_PATH . 'pgbackrest-test-server.crt';
use constant HOST_SERVER_KEY => HOST_CERT_PATH . 'pgbackrest-test-server.key'; use constant HOST_SERVER_KEY => HOST_CERT_PATH . 'pgbackrest-test-server.key';
####################################################################################################################################
# SFTP key constants
####################################################################################################################################
use constant SSH_KEY_PATH => '/test/certificate/ssh/';
use constant SSH_PRIVATE_KEY => SSH_KEY_PATH . 'id_rsa';
push @EXPORT, qw(SSH_PRIVATE_KEY);
use constant SSH_PUBLIC_KEY => SSH_KEY_PATH . 'id_rsa.pub';
push @EXPORT, qw(SSH_PUBLIC_KEY);
#################################################################################################################################### ####################################################################################################################################
# new # new
#################################################################################################################################### ####################################################################################################################################

View File

@ -0,0 +1,72 @@
####################################################################################################################################
# SFTP Test Host
####################################################################################################################################
package pgBackRestTest::Env::Host::HostSftpTest;
use parent 'pgBackRestTest::Common::HostTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Cwd qw(abs_path);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use Storable qw(dclone);
use pgBackRestDoc::Common::Exception;
use pgBackRestDoc::Common::Ini;
use pgBackRestDoc::Common::Log;
use pgBackRestDoc::ProjectInfo;
use pgBackRestTest::Common::ContainerTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::HostGroupTest;
use pgBackRestTest::Common::RunTest;
use pgBackRestTest::Common::StorageRepo;
use pgBackRestTest::Common::Wait;
use pgBackRestTest::Env::Host::HostBaseTest;
use pgBackRestTest::Env::Manifest;
####################################################################################################################################
# SFTP defaults
####################################################################################################################################
use constant HOST_SFTP_ACCOUNT => TEST_USER;
push @EXPORT, qw(HOST_SFTP_ACCOUNT);
use constant HOST_SFTP_HOSTKEY_HASH_TYPE => 'sha1';
push @EXPORT, qw(HOST_SFTP_HOSTKEY_HASH_TYPE);
####################################################################################################################################
# new
####################################################################################################################################
sub new
{
my $class = shift; # Class name
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
);
# Create the host
my $self = $class->SUPER::new(
HOST_SFTP, 'test-' . testRunGet()->vmId() . '-' . HOST_SFTP, containerRepo() . ':' . testRunGet()->vm() . '-test', 'root');
bless $self, $class;
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self, trace => true}
);
}
1;

View File

@ -70,6 +70,10 @@ sub setup
{ {
$oHostObject = new pgBackRestTest::Env::Host::HostGcsTest(); $oHostObject = new pgBackRestTest::Env::Host::HostGcsTest();
} }
elsif ($oConfigParam->{strStorage} eq SFTP)
{
$oHostObject = new pgBackRestTest::Env::Host::HostSftpTest();
}
# Get host group # Get host group
my $oHostGroup = hostGroupGet(); my $oHostGroup = hostGroupGet();
@ -132,7 +136,7 @@ sub setup
{ {
$oHostGroup->hostAdd($oHostObject, {rstryHostName => ['pgbackrest-dev.s3.amazonaws.com', 's3.amazonaws.com']}); $oHostGroup->hostAdd($oHostObject, {rstryHostName => ['pgbackrest-dev.s3.amazonaws.com', 's3.amazonaws.com']});
} }
elsif ($oConfigParam->{strStorage} eq AZURE || $oConfigParam->{strStorage} eq GCS) elsif ($oConfigParam->{strStorage} eq AZURE || $oConfigParam->{strStorage} eq GCS || $oConfigParam->{strStorage} eq SFTP)
{ {
$oHostGroup->hostAdd($oHostObject); $oHostGroup->hostAdd($oHostObject);
} }
@ -164,7 +168,8 @@ sub setup
# If backup host is not defined set it to db-primary # If backup host is not defined set it to db-primary
else else
{ {
$oHostBackup = $strBackupDestination eq HOST_DB_PRIMARY ? $oHostDbPrimary : $oHostDbStandby; $oHostBackup = $strBackupDestination eq HOST_DB_PRIMARY || $strBackupDestination eq HOST_SFTP ? $oHostDbPrimary :
$oHostDbStandby;
} }
storageRepoCommandSet( storageRepoCommandSet(
@ -172,7 +177,8 @@ sub setup
' --config=' . $oHostBackup->backrestConfig() . ' --stanza=' . $self->stanza() . ' --log-level-console=off' . ' --config=' . $oHostBackup->backrestConfig() . ' --stanza=' . $self->stanza() . ' --log-level-console=off' .
' --log-level-stderr=error' . ' --log-level-stderr=error' .
($oConfigParam->{strStorage} ne POSIX ? ($oConfigParam->{strStorage} ne POSIX ?
" --no-repo1-storage-verify-tls --repo1-$oConfigParam->{strStorage}-" . ($oConfigParam->{strStorage} ne SFTP ? " --no-repo1-storage-verify-tls" : '') .
" --repo1-$oConfigParam->{strStorage}-" .
($oConfigParam->{strStorage} eq GCS ? 'endpoint' : 'host') . "=" . $oHostObject->ipGet() : '') . ($oConfigParam->{strStorage} eq GCS ? 'endpoint' : 'host') . "=" . $oHostObject->ipGet() : '') .
($oConfigParam->{strStorage} eq GCS ? ':' . HOST_GCS_PORT : ''), ($oConfigParam->{strStorage} eq GCS ? ':' . HOST_GCS_PORT : ''),
$oConfigParam->{strStorage} eq POSIX ? STORAGE_POSIX : STORAGE_OBJECT); $oConfigParam->{strStorage} eq POSIX ? STORAGE_POSIX : STORAGE_OBJECT);

View File

@ -54,13 +54,13 @@ sub run
( (
{pg => '9.3', dst => 'backup', tls => 0, stg => AZURE, enc => 0, cmp => NONE, rt => 2, bnd => 0, bi => 0}, {pg => '9.3', dst => 'backup', tls => 0, stg => AZURE, enc => 0, cmp => NONE, rt => 2, bnd => 0, bi => 0},
{pg => '9.4', dst => 'db-standby', tls => 0, stg => POSIX, enc => 1, cmp => LZ4, rt => 1, bnd => 1, bi => 0}, {pg => '9.4', dst => 'db-standby', tls => 0, stg => POSIX, enc => 1, cmp => LZ4, rt => 1, bnd => 1, bi => 0},
{pg => '9.5', dst => 'backup', tls => 1, stg => S3, enc => 0, cmp => BZ2, rt => 1, bnd => 0, bi => 1}, {pg => '9.5', dst => 'backup', tls => 1, stg => GCS, enc => 0, cmp => BZ2, rt => 1, bnd => 0, bi => 1},
{pg => '9.6', dst => 'backup', tls => 0, stg => POSIX, enc => 0, cmp => NONE, rt => 2, bnd => 1, bi => 1}, {pg => '9.6', dst => 'backup', tls => 0, stg => POSIX, enc => 0, cmp => NONE, rt => 2, bnd => 1, bi => 1},
{pg => '10', dst => 'db-standby', tls => 1, stg => GCS, enc => 1, cmp => GZ, rt => 2, bnd => 0, bi => 0}, {pg => '10', dst => 'sftp-srvr', tls => 0, stg => SFTP, enc => 1, cmp => GZ, rt => 1, bnd => 1, bi => 0},
{pg => '11', dst => 'backup', tls => 1, stg => AZURE, enc => 0, cmp => ZST, rt => 2, bnd => 1, bi => 0}, {pg => '11', dst => 'backup', tls => 1, stg => AZURE, enc => 0, cmp => ZST, rt => 2, bnd => 0, bi => 0},
{pg => '12', dst => 'backup', tls => 0, stg => S3, enc => 1, cmp => LZ4, rt => 1, bnd => 0, bi => 1}, {pg => '12', dst => 'backup', tls => 0, stg => S3, enc => 1, cmp => LZ4, rt => 1, bnd => 0, bi => 1},
{pg => '13', dst => 'db-standby', tls => 1, stg => GCS, enc => 0, cmp => ZST, rt => 1, bnd => 1, bi => 1}, {pg => '13', dst => 'db-standby', tls => 1, stg => GCS, enc => 0, cmp => ZST, rt => 1, bnd => 1, bi => 1},
{pg => '14', dst => 'backup', tls => 0, stg => POSIX, enc => 1, cmp => LZ4, rt => 2, bnd => 0, bi => 0}, {pg => '14', dst => 'sftp-srvr', tls => 0, stg => SFTP, enc => 0, cmp => LZ4, rt => 1, bnd => 1, bi => 0},
{pg => '15', dst => 'db-standby', tls => 0, stg => AZURE, enc => 0, cmp => NONE, rt => 2, bnd => 1, bi => 1}, {pg => '15', dst => 'db-standby', tls => 0, stg => AZURE, enc => 0, cmp => NONE, rt => 2, bnd => 1, bi => 1},
{pg => '16', dst => 'db-standby', tls => 0, stg => S3, enc => 1, cmp => NONE, rt => 1, bnd => 0, bi => 0}, {pg => '16', dst => 'db-standby', tls => 0, stg => S3, enc => 1, cmp => NONE, rt => 1, bnd => 0, bi => 0},
) )

View File

@ -552,6 +552,7 @@ testBldUnit(TestBuild *const this)
" lib_openssl,\n" " lib_openssl,\n"
" lib_lz4,\n" " lib_lz4,\n"
" lib_pq,\n" " lib_pq,\n"
" lib_ssh2,\n"
" lib_xml,\n" " lib_xml,\n"
" lib_yaml,\n" " lib_yaml,\n"
" lib_z,\n" " lib_z,\n"
@ -661,6 +662,7 @@ testBldUnit(TestBuild *const this)
strReplace(testC, STRDEF("{[C_TEST_GROUP]}"), groupName()); strReplace(testC, STRDEF("{[C_TEST_GROUP]}"), groupName());
strReplace(testC, STRDEF("{[C_TEST_GROUP_ID]}"), strNewFmt("%u", groupId())); strReplace(testC, STRDEF("{[C_TEST_GROUP_ID]}"), strNewFmt("%u", groupId()));
strReplace(testC, STRDEF("{[C_TEST_USER]}"), userName()); strReplace(testC, STRDEF("{[C_TEST_USER]}"), userName());
strReplace(testC, STRDEF("{[C_TEST_USER_LEN]}"), strNewFmt("%zu", strSize(userName())));
strReplace(testC, STRDEF("{[C_TEST_USER_ID]}"), strNewFmt("%u", userId())); strReplace(testC, STRDEF("{[C_TEST_USER_ID]}"), strNewFmt("%u", userId()));
// Test id // Test id

View File

@ -0,0 +1,719 @@
/***********************************************************************************************************************************
libssh2 Test Harness
***********************************************************************************************************************************/
#include "build.auto.h"
#ifdef HAVE_LIBSSH2
#include <stdio.h>
#include <string.h>
#include "common/type/json.h"
#include "common/type/string.h"
#include "common/type/variantList.h"
#include "common/harnessLibSsh2.h"
#include "common/harnessTest.h"
/***********************************************************************************************************************************
libssh2 shim error prefix
***********************************************************************************************************************************/
#define LIBSSH2_ERROR_PREFIX "LIBSSH2 SHIM ERROR"
/***********************************************************************************************************************************
Script that defines how shim functions operate
***********************************************************************************************************************************/
HrnLibSsh2 hrnLibSsh2Script[1024];
bool hrnLibSsh2ScriptDone = true;
unsigned int hrnLibSsh2ScriptIdx;
// If there is a script failure change the behavior of cleanup functions to return immediately so the real error will be reported
// rather than a bogus scripting error during cleanup
bool hrnLibSsh2ScriptFail;
char hrnLibSsh2ScriptError[4096];
/***********************************************************************************************************************************
Set libssh2 script
***********************************************************************************************************************************/
void
hrnLibSsh2ScriptSet(HrnLibSsh2 *hrnLibSsh2ScriptParam)
{
if (!hrnLibSsh2ScriptDone)
THROW(AssertError, "previous libssh2 script has not yet completed");
if (hrnLibSsh2ScriptParam[0].function == NULL)
THROW(AssertError, "libssh2 script must have entries");
// Copy records into local storage
unsigned int copyIdx = 0;
while (hrnLibSsh2ScriptParam[copyIdx].function != NULL)
{
hrnLibSsh2Script[copyIdx] = hrnLibSsh2ScriptParam[copyIdx];
copyIdx++;
}
hrnLibSsh2Script[copyIdx].function = NULL;
hrnLibSsh2ScriptDone = false;
hrnLibSsh2ScriptIdx = 0;
}
/***********************************************************************************************************************************
Run libssh2 script
***********************************************************************************************************************************/
static HrnLibSsh2 *
hrnLibSsh2ScriptRun(const char *const function, const VariantList *const param, const HrnLibSsh2 *const parent)
{
// If an error has already been thrown then throw the same error again
if (hrnLibSsh2ScriptFail)
THROW(AssertError, hrnLibSsh2ScriptError);
// Convert params to json for comparison and reporting
String *paramStr = NULL;
if (param)
{
Variant *const varList = varNewVarLst(param);
paramStr = jsonFromVar(varList);
varFree(varList);
}
else
paramStr = strNew();
// Ensure script has not ended
if (hrnLibSsh2ScriptDone)
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError), "libssh2 script ended before %s (%s)", function,
strZ(paramStr));
TEST_LOG_FMT(LIBSSH2_ERROR_PREFIX ": %s", hrnLibSsh2ScriptError);
hrnLibSsh2ScriptFail = true;
THROW(AssertError, hrnLibSsh2ScriptError);
}
// Get current script item
HrnLibSsh2 *result = &hrnLibSsh2Script[hrnLibSsh2ScriptIdx];
// Check that expected function was called
if (strcmp(result->function, function) != 0)
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script [%u] expected function %s (%s) but got %s (%s)", hrnLibSsh2ScriptIdx, result->function,
result->param == NULL ? "" : result->param, function, strZ(paramStr));
TEST_LOG_FMT(LIBSSH2_ERROR_PREFIX ": %s", hrnLibSsh2ScriptError);
hrnLibSsh2ScriptFail = true;
THROW(AssertError, hrnLibSsh2ScriptError);
}
// Check that parameters match
if ((param != NULL && result->param == NULL) || (param == NULL && result->param != NULL) ||
(param != NULL && result->param != NULL && !strEqZ(paramStr, result->param)))
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script [%u] function '%s', expects param '%s' but got '%s'",
hrnLibSsh2ScriptIdx, result->function, result->param ? result->param : "NULL", param ? strZ(paramStr) : "NULL");
TEST_LOG_FMT(LIBSSH2_ERROR_PREFIX ": %s", hrnLibSsh2ScriptError);
hrnLibSsh2ScriptFail = true;
THROW(AssertError, hrnLibSsh2ScriptError);
}
// Make sure the session matches with the parent as a sanity check
if (parent != NULL && result->session != parent->session)
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script [%u] function '%s', expects session '%u' but got '%u'",
hrnLibSsh2ScriptIdx, result->function, result->session, parent->session);
TEST_LOG_FMT(LIBSSH2_ERROR_PREFIX ": %s", hrnLibSsh2ScriptError);
hrnLibSsh2ScriptFail = true;
THROW(AssertError, hrnLibSsh2ScriptError);
}
// Sleep if requested
if (result->sleep > 0)
sleepMSec(result->sleep);
hrnLibSsh2ScriptIdx++;
if (hrnLibSsh2Script[hrnLibSsh2ScriptIdx].function == NULL)
hrnLibSsh2ScriptDone = true;
strFree(paramStr);
return result;
}
/***********************************************************************************************************************************
Shim for libssh2_init
***********************************************************************************************************************************/
int
libssh2_init(int flags)
{
HrnLibSsh2 *hrnLibSsh2 = hrnLibSsh2ScriptRun(HRNLIBSSH2_INIT, varLstAdd(varLstNew(), varNewInt(flags)), NULL);
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_session_init
***********************************************************************************************************************************/
LIBSSH2_SESSION *
libssh2_session_init_ex(
LIBSSH2_ALLOC_FUNC((*myalloc)), LIBSSH2_FREE_FUNC((*myfree)), LIBSSH2_REALLOC_FUNC((*myrealloc)), void *abstract)
{
// All of these should always be the default NULL
if (myalloc != NULL && myfree != NULL && myrealloc != NULL && abstract != NULL)
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script function 'libssh2_session_init_ex', expects all params to be NULL");
THROW(AssertError, hrnLibSsh2ScriptError);
}
HrnLibSsh2 *hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SESSION_INIT_EX,
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewStr(NULL)),
varNewStr(NULL)),
varNewStr(NULL)),
varNewStr(NULL)),
NULL);
return hrnLibSsh2->resultNull ? NULL : (LIBSSH2_SESSION *)hrnLibSsh2;
}
/***********************************************************************************************************************************
Shim for libssh2_session_set_blocking
***********************************************************************************************************************************/
void
libssh2_session_set_blocking(LIBSSH2_SESSION *session, int blocking)
{
if (session == NULL)
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script function 'libssh2_session_set_blocking', expects session to be not NULL");
THROW(AssertError, hrnLibSsh2ScriptError);
}
if (!(INT_MIN <= blocking && blocking <= INT_MAX))
{
snprintf(
hrnLibSsh2ScriptError, sizeof(hrnLibSsh2ScriptError),
"libssh2 script function 'libssh2_session_set_blocking', expects blocking to be an integer value");
THROW(AssertError, hrnLibSsh2ScriptError);
}
return;
}
/***********************************************************************************************************************************
Shim for libssh2_session_handshake
***********************************************************************************************************************************/
int
libssh2_session_handshake(LIBSSH2_SESSION *session, libssh2_socket_t sock)
{
return hrnLibSsh2ScriptRun(
HRNLIBSSH2_SESSION_HANDSHAKE, varLstAdd(varLstNew(), varNewInt(sock)), (HrnLibSsh2 *)session)->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_hostkey_hash
***********************************************************************************************************************************/
const char *
libssh2_hostkey_hash(LIBSSH2_SESSION *session, int hash_type)
{
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_HOSTKEY_HASH, varLstAdd(varLstNew(), varNewInt(hash_type)), (HrnLibSsh2 *)session);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultNull ? NULL : (const char *)hrnLibSsh2->resultZ;
}
/***********************************************************************************************************************************
Shim for libssh2_userauth_publickey_fromfile_ex
***********************************************************************************************************************************/
int
libssh2_userauth_publickey_fromfile_ex(
LIBSSH2_SESSION *session, const char *username, unsigned int ousername_len, const char *publickey, const char *privatekey,
const char *passphrase)
{
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX,
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(username)),
varNewUInt(ousername_len)),
varNewStrZ(publickey)),
varNewStrZ(privatekey)),
varNewStrZ(passphrase)),
(HrnLibSsh2 *)session);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_init
***********************************************************************************************************************************/
LIBSSH2_SFTP *
libssh2_sftp_init(LIBSSH2_SESSION *session)
{
HrnLibSsh2 *hrnLibSsh2 = hrnLibSsh2ScriptRun(HRNLIBSSH2_SFTP_INIT, NULL, (HrnLibSsh2 *)session);
return hrnLibSsh2->resultNull ? NULL : (LIBSSH2_SFTP *)hrnLibSsh2;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_close_handle
***********************************************************************************************************************************/
int
libssh2_sftp_close_handle(LIBSSH2_SFTP_HANDLE *handle)
{
return hrnLibSsh2ScriptRun(HRNLIBSSH2_SFTP_CLOSE_HANDLE, NULL, (HrnLibSsh2 *)handle)->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_shutdown
***********************************************************************************************************************************/
int
libssh2_sftp_shutdown(LIBSSH2_SFTP *sftp)
{
return hrnLibSsh2ScriptRun(HRNLIBSSH2_SFTP_SHUTDOWN, NULL, (HrnLibSsh2 *)sftp)->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_session_disconnect_ex
***********************************************************************************************************************************/
int
libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason, const char *description, const char *lang)
{
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SESSION_DISCONNECT_EX,
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewInt(reason)),
varNewStrZ(description)),
varNewStrZ(lang)),
(HrnLibSsh2 *)session);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for int libssh2_session_free
***********************************************************************************************************************************/
int
libssh2_session_free(LIBSSH2_SESSION *session)
{
return hrnLibSsh2ScriptRun(HRNLIBSSH2_SESSION_FREE, NULL, (HrnLibSsh2 *)session)->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_stat_ex
***********************************************************************************************************************************/
int
libssh2_sftp_stat_ex(
LIBSSH2_SFTP *sftp, const char *path, unsigned int path_len, int stat_type, LIBSSH2_SFTP_ATTRIBUTES *attrs)
{
// Avoid compiler complaining of unused param. Not passing to hrnLibSsh2ScriptRun, as parameter will vary depending on where
// tests are being run.
//
// Could we utilize test.c/build.c to calculate/define this and other length params?
(void)path_len;
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_STAT_EX,
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(path)),
varNewInt(stat_type)),
(HrnLibSsh2 *)sftp);
}
MEM_CONTEXT_TEMP_END();
if (attrs == NULL)
THROW(AssertError, "attrs is NULL");
attrs->flags = 0;
attrs->flags |= (unsigned long)hrnLibSsh2->flags;
attrs->permissions = 0;
attrs->permissions |= (unsigned long)hrnLibSsh2->attrPerms;
attrs->mtime = (unsigned long)hrnLibSsh2->mtime;
attrs->uid = (unsigned long)hrnLibSsh2->uid;
attrs->gid = (unsigned long)hrnLibSsh2->gid;
attrs->filesize = hrnLibSsh2->filesize;
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_last_error
***********************************************************************************************************************************/
unsigned long
libssh2_sftp_last_error(LIBSSH2_SFTP *sftp)
{
return (unsigned long)hrnLibSsh2ScriptRun(HRNLIBSSH2_SFTP_LAST_ERROR, NULL, (HrnLibSsh2 *)sftp)->resultUInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_symlink_ex
***********************************************************************************************************************************/
int
libssh2_sftp_symlink_ex(
LIBSSH2_SFTP *sftp, const char *path, unsigned int path_len, char *target, unsigned int target_len, int link_type)
{
// Avoid compiler complaining of unused param. Not passing to hrnLibSsh2ScriptRun, as parameter will vary depending on where
// tests are being run.
(void)path_len;
(void)target_len;
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_SYMLINK_EX,
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(path)),
varNewStrZ(target)),
varNewInt(link_type)),
(HrnLibSsh2 *)sftp);
}
MEM_CONTEXT_TEMP_END();
int rc = 0;
switch (link_type)
{
case LIBSSH2_SFTP_READLINK:
case LIBSSH2_SFTP_REALPATH:
if (hrnLibSsh2->symlinkExTarget != NULL)
{
if (strSize(hrnLibSsh2->symlinkExTarget) < PATH_MAX)
strncpy(target, strZ(hrnLibSsh2->symlinkExTarget), strSize(hrnLibSsh2->symlinkExTarget));
else
THROW_FMT(AssertError, "symlinkExTarget too large for target buffer");
}
rc = hrnLibSsh2->resultInt != 0 ? hrnLibSsh2->resultInt : (int)strSize(hrnLibSsh2->symlinkExTarget);
break;
default:
THROW_FMT(AssertError, "UNKNOWN link_type");
break;
}
return rc;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_open_ex
***********************************************************************************************************************************/
LIBSSH2_SFTP_HANDLE *
libssh2_sftp_open_ex(
LIBSSH2_SFTP *sftp, const char *filename, unsigned int filename_len, unsigned long flags, long mode, int open_type)
{
// To avoid compiler complaining of unused param. Not passing to hrnLibSsh2ScriptRun, as parameter will vary depending on where
// tests are being run.
(void)filename_len;
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_OPEN_EX,
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(filename)),
varNewUInt64(flags)),
varNewInt64(mode)),
varNewInt(open_type)),
(HrnLibSsh2 *)sftp);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultNull ? NULL : (LIBSSH2_SFTP_HANDLE *)hrnLibSsh2;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_readdir_ex
***********************************************************************************************************************************/
int
libssh2_sftp_readdir_ex(
LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen, char *longentry, size_t longentry_maxlen,
LIBSSH2_SFTP_ATTRIBUTES *attrs)
{
if (attrs == NULL)
THROW_FMT(AssertError, "attrs is NULL");
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_READDIR_EX,
varLstAdd(
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(buffer)),
varNewUInt64(buffer_maxlen)),
varNewStrZ(longentry)),
varNewUInt64(longentry_maxlen)),
(HrnLibSsh2 *)handle);
}
MEM_CONTEXT_TEMP_END();
if (hrnLibSsh2->fileName != NULL)
strncpy(buffer, strZ(hrnLibSsh2->fileName), buffer_maxlen);
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_session_last_errno
***********************************************************************************************************************************/
int
libssh2_session_last_errno(LIBSSH2_SESSION *session)
{
return hrnLibSsh2ScriptRun(HRNLIBSSH2_SESSION_LAST_ERRNO, NULL, (HrnLibSsh2 *)session)->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_fsync
***********************************************************************************************************************************/
int
libssh2_sftp_fsync(LIBSSH2_SFTP_HANDLE *handle)
{
return hrnLibSsh2ScriptRun(HRNLIBSSH2_SFTP_FSYNC, NULL, (HrnLibSsh2 *)handle)->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_mkdir_ex
***********************************************************************************************************************************/
int
libssh2_sftp_mkdir_ex(LIBSSH2_SFTP *sftp, const char *path, unsigned int path_len, long mode)
{
// To avoid compiler complaining of unused param. Not passing to hrnLibSsh2ScriptRun, as parameter will vary depending on where
// tests are being run.
(void)path_len;
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_MKDIR_EX,
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(path)),
varNewInt64(mode)),
(HrnLibSsh2 *)sftp);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_read
***********************************************************************************************************************************/
ssize_t
libssh2_sftp_read(LIBSSH2_SFTP_HANDLE *handle, char *buffer, size_t buffer_maxlen)
{
// We don't pass buffer to hrnLibSsh2ScriptRun. The first call for each invocation passes buffer with random data, which is
// an issue for sftpTest.c.
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_READ,
varLstAdd(
varLstNew(), varNewUInt64(buffer_maxlen)),
(HrnLibSsh2 *)handle);
}
MEM_CONTEXT_TEMP_END();
// copy read into buffer
if (hrnLibSsh2->readBuffer != NULL)
strncpy(buffer, strZ(hrnLibSsh2->readBuffer), strSize(hrnLibSsh2->readBuffer));
// number of bytes populated
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_rename_ex
***********************************************************************************************************************************/
int
libssh2_sftp_rename_ex(
LIBSSH2_SFTP *sftp, const char *source_filename, unsigned int source_filename_len, const char *dest_filename,
unsigned int dest_filename_len, long flags)
{
// To avoid compiler complaining of unused param. Not passing to hrnLibSsh2ScriptRun, as parameter will vary depending on where
// tests are being run.
(void)source_filename_len;
(void)dest_filename_len;
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_RENAME_EX,
varLstAdd(
varLstAdd(
varLstAdd(
varLstNew(), varNewStrZ(source_filename)),
varNewStrZ(dest_filename)),
varNewInt64(flags)),
(HrnLibSsh2 *)sftp);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_rmdir_ex
***********************************************************************************************************************************/
int
libssh2_sftp_rmdir_ex(LIBSSH2_SFTP *sftp, const char *path, unsigned int path_len)
{
// Avoid compiler complaining of unused param. Not passing to hrnLibSsh2ScriptRun, as parameter will vary depending on where
// tests are being run.
(void)path_len;
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_RMDIR_EX,
varLstAdd(
varLstNew(), varNewStrZ(path)),
(HrnLibSsh2 *)sftp);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_seek64
***********************************************************************************************************************************/
void
libssh2_sftp_seek64(LIBSSH2_SFTP_HANDLE *handle, libssh2_uint64_t offset)
{
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_SEEK64,
varLstAdd(
varLstNew(), varNewUInt64(offset)),
(HrnLibSsh2 *)handle);
}
MEM_CONTEXT_TEMP_END();
return;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_unlink_ex
***********************************************************************************************************************************/
int
libssh2_sftp_unlink_ex(LIBSSH2_SFTP *sftp, const char *filename, unsigned int filename_len)
{
// Avoid compiler complaining of unused param. Not passing to hrnLibSsh2ScriptRun, as parameter will vary depending on where
// tests are being run.
(void)filename_len;
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_UNLINK_EX,
varLstAdd(
varLstNew(), varNewStrZ(filename)),
(HrnLibSsh2 *)sftp);
}
MEM_CONTEXT_TEMP_END();
return hrnLibSsh2->resultInt;
}
/***********************************************************************************************************************************
Shim for libssh2_sftp_write
***********************************************************************************************************************************/
ssize_t
libssh2_sftp_write(LIBSSH2_SFTP_HANDLE *handle, const char *buffer, size_t count)
{
// We don't pass buffer to hrnLibSsh2ScriptRun. The first call for each invocation passes buffer with random data, which is
// an issue for sftpTest.c.
(void)buffer;
HrnLibSsh2 *hrnLibSsh2 = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
hrnLibSsh2 = hrnLibSsh2ScriptRun(
HRNLIBSSH2_SFTP_WRITE,
varLstAdd(
varLstNew(), varNewUInt64(count)),
(HrnLibSsh2 *)handle);
}
MEM_CONTEXT_TEMP_END();
// Return number of bytes written
return hrnLibSsh2->resultInt;
}
#endif // HAVE_LIBSSH2

View File

@ -0,0 +1,122 @@
/***********************************************************************************************************************************
libssh2 Test Harness
Scripted testing for libssh2 so exact results can be returned for unit testing. See sftp unit tests for usage examples.
***********************************************************************************************************************************/
#ifndef TEST_COMMON_HARNESS_LIBSSH2_H
#define TEST_COMMON_HARNESS_LIBSSH2_H
#ifdef HAVE_LIBSSH2
#ifndef HARNESS_LIBSSH2_REAL
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <stdbool.h>
#include "common/macro.h"
#include "common/time.h"
#include "version.h"
/***********************************************************************************************************************************
libssh2 authorization constants
***********************************************************************************************************************************/
#define KEYPRIV STRDEF("/home/" TEST_USER "/.ssh/id_rsa")
#define KEYPUB STRDEF("/home/" TEST_USER "/.ssh/id_rsa.pub")
#define KEYPRIV_CSTR "/home/" TEST_USER "/.ssh/id_rsa"
#define KEYPUB_CSTR "/home/" TEST_USER "/.ssh/id_rsa.pub"
/***********************************************************************************************************************************
Function constants
***********************************************************************************************************************************/
#define HRNLIBSSH2_HOSTKEY_HASH "libssh2_hostkey_hash"
#define HRNLIBSSH2_INIT "libssh2_init"
#define HRNLIBSSH2_SESSION_DISCONNECT_EX "libssh2_session_disconnect_ex"
#define HRNLIBSSH2_SESSION_FREE "libssh2_session_free"
#define HRNLIBSSH2_SESSION_HANDSHAKE "libssh2_session_handshake"
#define HRNLIBSSH2_SESSION_INIT_EX "libssh2_session_init_ex"
#define HRNLIBSSH2_SESSION_LAST_ERRNO "libssh2_session_last_errno"
#define HRNLIBSSH2_SESSION_LAST_ERROR "libssh2_session_last_error"
#define HRNLIBSSH2_SESSION_SET_BLOCKING "libssh2_session_set_blocking"
#define HRNLIBSSH2_SFTP_CLOSE_HANDLE "libssh2_sftp_close_handle"
#define HRNLIBSSH2_SFTP_FSYNC "libssh2_sftp_fsync"
#define HRNLIBSSH2_SFTP_INIT "libssh2_sftp_init"
#define HRNLIBSSH2_SFTP_LAST_ERROR "libssh2_sftp_last_error"
#define HRNLIBSSH2_SFTP_MKDIR_EX "libssh2_sftp_mkdir_ex"
#define HRNLIBSSH2_SFTP_OPEN_EX "libssh2_sftp_open_ex"
#define HRNLIBSSH2_SFTP_READ "libssh2_sftp_read"
#define HRNLIBSSH2_SFTP_READDIR_EX "libssh2_sftp_readdir_ex"
#define HRNLIBSSH2_SFTP_RENAME_EX "libssh2_sftp_rename_ex"
#define HRNLIBSSH2_SFTP_RMDIR_EX "libssh2_sftp_rmdir_ex"
#define HRNLIBSSH2_SFTP_SEEK64 "libssh2_sftp_seek64"
#define HRNLIBSSH2_SFTP_SHUTDOWN "libssh2_sftp_shutdown"
#define HRNLIBSSH2_SFTP_STAT_EX "libssh2_sftp_stat_ex"
#define HRNLIBSSH2_SFTP_SYMLINK_EX "libssh2_sftp_symlink_ex"
#define HRNLIBSSH2_SFTP_UNLINK_EX "libssh2_sftp_unlink_ex"
#define HRNLIBSSH2_SFTP_WRITE "libssh2_sftp_write"
#define HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX "libssh2_userauth_publickey_fromfile_ex"
/***********************************************************************************************************************************
Macros for defining groups of functions that implement commands
***********************************************************************************************************************************/
// Set of functions mimicking libssh2 inititialization and authorization
#define HRNLIBSSH2_MACRO_STARTUP() \
{.function = HRNLIBSSH2_INIT, .param = "[0]", .resultInt = 0}, \
{.function = HRNLIBSSH2_SESSION_INIT_EX, .param = "[null,null,null,null]"}, \
{.function = HRNLIBSSH2_SESSION_HANDSHAKE, .param = HANDSHAKE_PARAM, .resultInt = 0}, \
{.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[2]", .resultZ = "12345678910123456789"}, \
{.function = HRNLIBSSH2_USERAUTH_PUBLICKEY_FROMFILE_EX, \
.param = "[\"" TEST_USER "\"," TEST_USER_LEN ",\"" KEYPUB_CSTR "\",\"" KEYPRIV_CSTR "\",null]", \
.resultInt = 0}, \
{.function = HRNLIBSSH2_SFTP_INIT}
// Set of functions mimicking libssh2 shutdown and disconnect
#define HRNLIBSSH2_MACRO_SHUTDOWN() \
{.function = HRNLIBSSH2_SFTP_SHUTDOWN, .resultInt = 0}, \
{.function = HRNLIBSSH2_SESSION_DISCONNECT_EX, .param ="[11,\"pgbackrest instance shutdown\",\"\"]", .resultInt = 0}, \
{.function = HRNLIBSSH2_SESSION_FREE, .resultInt = 0}, \
{.function = NULL} \
// older systems do not support LIBSSH2_HOSTKEY_HASH_SHA256
#ifdef LIBSSH2_HOSTKEY_HASH_SHA256
#define HOSTKEY_HASH_ENTRY() \
{.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[3]", .resultZ = "12345678910123456789"}
#else
#define HOSTKEY_HASH_ENTRY() \
{.function = HRNLIBSSH2_HOSTKEY_HASH, .param = "[2]", .resultZ = "12345678910123456789"}
#endif
/***********************************************************************************************************************************
Structure for scripting libssh2 responses
***********************************************************************************************************************************/
typedef struct HrnLibSsh2
{
unsigned int session; // Session number when multiple sessions are run concurrently
const char *function; // Function call expected
const char *param; // Params expected by the function for verification
int resultInt; // Int result value
uint64_t resultUInt; // UInt result value
const char *resultZ; // Zero-terminated result value
bool resultNull; // Return null from function that normally returns a struct ptr
uint64_t flags; // libssh2 flags
uint64_t attrPerms; // libssh2 attr perms
uint64_t atime, mtime; // libssh2 timestamps
uint64_t uid, gid; // libssh2 uid/gid
uint64_t filesize; // libssh2 filesize
uint64_t offset; // libssh2 seek offset
const String *symlinkExTarget; // libssh2_sftp_symlink_ex target
const String *fileName; // libssh2_readdir* libssh2_stat* filename
const String *readBuffer; // what to copy into read buffer
TimeMSec sleep; // Sleep specified milliseconds before returning from function
} HrnLibSsh2;
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
void hrnLibSsh2ScriptSet(HrnLibSsh2 *hrnLibSsh2ScriptParam);
#endif // HARNESS_LIBSSH2_REAL
#endif // HAVE_LIBSSH2
#endif // TEST_COMMON_HARNESS_LIBSSH2_H

View File

@ -0,0 +1,75 @@
/***********************************************************************************************************************************
Harness for Io Testing
***********************************************************************************************************************************/
#include "build.auto.h"
#include "common/harnessConfig.h"
#include "common/harnessDebug.h"
#include "common/harnessSocket.h"
/***********************************************************************************************************************************
Include shimmed C modules
***********************************************************************************************************************************/
{[SHIM_MODULE]}
/***********************************************************************************************************************************
Shim install state
***********************************************************************************************************************************/
static struct
{
// Local process shims
bool localShimSckClientOpen;
} hrnIoStatic;
/***********************************************************************************************************************************
Shim sckClientOpen()
***********************************************************************************************************************************/
IoSession *
sckClientOpen(THIS_VOID)
{
THIS(SocketClient);
FUNCTION_HARNESS_BEGIN();
FUNCTION_HARNESS_PARAM(SOCKET_CLIENT, this);
FUNCTION_HARNESS_END();
ASSERT(this != NULL);
IoSession *result = NULL;
// When shim is installed create IoSession with a known fd
if (hrnIoStatic.localShimSckClientOpen)
{
result = sckSessionNew(ioSessionRoleClient, HRN_SCK_FILE_DESCRIPTOR, this->host, this->port, this->timeoutSession);
// Remove the callback so we will not try to close the fake descriptor
memContextCallbackClear(objMemContext(((IoSessionPub *)result)->driver));
}
// Else call normal function
else
result = sckClientOpen_SHIMMED(this);
FUNCTION_HARNESS_RETURN(IO_SESSION, result);
}
/**********************************************************************************************************************************/
void
hrnSckClientOpenShimInstall(void)
{
FUNCTION_HARNESS_VOID();
hrnIoStatic.localShimSckClientOpen = true;
FUNCTION_HARNESS_RETURN_VOID();
}
/**********************************************************************************************************************************/
void
hrnSckClientOpenShimUninstall(void)
{
FUNCTION_HARNESS_VOID();
hrnIoStatic.localShimSckClientOpen = false;
FUNCTION_HARNESS_RETURN_VOID();
}

View File

@ -0,0 +1,16 @@
/***********************************************************************************************************************************
Harness for Io Testing
***********************************************************************************************************************************/
/***********************************************************************************************************************************
Constants
***********************************************************************************************************************************/
// Arbitrary value for the shimmed socket file descriptor
#define HRN_SCK_FILE_DESCRIPTOR 1163581
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Install/uninstall the shims for testing
void hrnSckClientOpenShimInstall(void);
void hrnSckClientOpenShimUninstall(void);

View File

@ -202,132 +202,142 @@ testRun(void)
"\n" "\n"
"Command Options:\n" "Command Options:\n"
"\n" "\n"
" --archive-mode preserve or disable archiving on restored\n" " --archive-mode preserve or disable archiving on restored\n"
" cluster [default=preserve]\n" " cluster [default=preserve]\n"
" --db-exclude restore excluding the specified databases\n" " --db-exclude restore excluding the specified databases\n"
" --db-include restore only specified databases\n" " --db-include restore only specified databases\n"
" [current=db1, db2]\n" " [current=db1, db2]\n"
" --force force a restore [default=n]\n" " --force force a restore [default=n]\n"
" --link-all restore all symlinks [default=n]\n" " --link-all restore all symlinks [default=n]\n"
" --link-map modify the destination of a symlink\n" " --link-map modify the destination of a symlink\n"
" [current=/link1=/dest1, /link2=/dest2]\n" " [current=/link1=/dest1, /link2=/dest2]\n"
" --recovery-option set an option in recovery.conf\n" " --recovery-option set an option in recovery.conf\n"
" --set backup set to restore [default=latest]\n" " --set backup set to restore [default=latest]\n"
" --tablespace-map restore a tablespace into the specified\n" " --tablespace-map restore a tablespace into the specified\n"
" directory\n" " directory\n"
" --tablespace-map-all restore all tablespaces into the specified\n" " --tablespace-map-all restore all tablespaces into the\n"
" directory\n" " specified directory\n"
" --target recovery target\n" " --target recovery target\n"
" --target-action action to take when recovery target is\n" " --target-action action to take when recovery target is\n"
" reached [default=pause]\n" " reached [default=pause]\n"
" --target-exclusive stop just before the recovery target is\n" " --target-exclusive stop just before the recovery target is\n"
" reached [default=n]\n" " reached [default=n]\n"
" --target-timeline recover along a timeline\n" " --target-timeline recover along a timeline\n"
" --type recovery type [default=default]\n" " --type recovery type [default=default]\n"
"\n" "\n"
"General Options:\n" "General Options:\n"
"\n" "\n"
" --buffer-size buffer size for I/O operations\n" " --buffer-size buffer size for I/O operations\n"
" [current=32768, default=1MiB]\n" " [current=32768, default=1MiB]\n"
" --cmd pgBackRest command\n" " --cmd pgBackRest command\n"
" [default=/path/to/pgbackrest]\n" " [default=/path/to/pgbackrest]\n"
" --cmd-ssh SSH client command [default=ssh]\n" " --cmd-ssh SSH client command [default=ssh]\n"
" --compress-level-network network compression level [default=3]\n" " --compress-level-network network compression level [default=3]\n"
" --config pgBackRest configuration file\n" " --config pgBackRest configuration file\n"
" [default=/etc/pgbackrest/pgbackrest.conf]\n" " [default=/etc/pgbackrest/pgbackrest.conf]\n"
" --config-include-path path to additional pgBackRest configuration\n" " --config-include-path path to additional pgBackRest\n"
" files [default=/etc/pgbackrest/conf.d]\n" " configuration files\n"
" --config-path base path of pgBackRest configuration files\n" " [default=/etc/pgbackrest/conf.d]\n"
" [default=/etc/pgbackrest]\n" " --config-path base path of pgBackRest configuration\n"
" --delta restore or backup using checksums\n" " files [default=/etc/pgbackrest]\n"
" [default=n]\n" " --delta restore or backup using checksums\n"
" --io-timeout I/O timeout [default=60]\n" " [default=n]\n"
" --lock-path path where lock files are stored\n" " --io-timeout I/O timeout [default=60]\n"
" [default=/tmp/pgbackrest]\n" " --lock-path path where lock files are stored\n"
" --neutral-umask use a neutral umask [default=y]\n" " [default=/tmp/pgbackrest]\n"
" --process-max max processes to use for compress/transfer\n" " --neutral-umask use a neutral umask [default=y]\n"
" [default=1]\n" " --process-max max processes to use for\n"
" --protocol-timeout protocol timeout [default=1830]\n" " compress/transfer [default=1]\n"
" --sck-keep-alive keep-alive enable [default=y]\n" " --protocol-timeout protocol timeout [default=1830]\n"
" --stanza defines the stanza\n" " --sck-keep-alive keep-alive enable [default=y]\n"
" --tcp-keep-alive-count keep-alive count\n" " --stanza defines the stanza\n"
" --tcp-keep-alive-idle keep-alive idle time\n" " --tcp-keep-alive-count keep-alive count\n"
" --tcp-keep-alive-interval keep-alive interval time\n" " --tcp-keep-alive-idle keep-alive idle time\n"
" --tcp-keep-alive-interval keep-alive interval time\n"
"\n" "\n"
"Log Options:\n" "Log Options:\n"
"\n" "\n"
" --log-level-console level for console logging [default=warn]\n" " --log-level-console level for console logging [default=warn]\n"
" --log-level-file level for file logging [default=info]\n" " --log-level-file level for file logging [default=info]\n"
" --log-level-stderr level for stderr logging [default=warn]\n" " --log-level-stderr level for stderr logging [default=warn]\n"
" --log-path path where log files are stored\n" " --log-path path where log files are stored\n"
" [default=/var/log/pgbackrest]\n" " [default=/var/log/pgbackrest]\n"
" --log-subprocess enable logging in subprocesses [default=n]\n" " --log-subprocess enable logging in subprocesses [default=n]\n"
" --log-timestamp enable timestamp in logging [default=y]\n" " --log-timestamp enable timestamp in logging [default=y]\n"
"\n", "\n",
"Repository Options:\n" "Repository Options:\n"
"\n" "\n"
" --repo set repository\n" " --repo set repository\n"
" --repo-azure-account azure repository account\n" " --repo-azure-account azure repository account\n"
" --repo-azure-container azure repository container\n" " --repo-azure-container azure repository container\n"
" --repo-azure-endpoint azure repository endpoint\n" " --repo-azure-endpoint azure repository endpoint\n"
" [default=blob.core.windows.net]\n" " [default=blob.core.windows.net]\n"
" --repo-azure-key azure repository key\n" " --repo-azure-key azure repository key\n"
" --repo-azure-key-type azure repository key type [default=shared]\n" " --repo-azure-key-type azure repository key type [default=shared]\n"
" --repo-azure-uri-style azure URI Style [default=host]\n" " --repo-azure-uri-style azure URI Style [default=host]\n"
" --repo-cipher-pass repository cipher passphrase\n" " --repo-cipher-pass repository cipher passphrase\n"
" [current=<redacted>]\n" " [current=<redacted>]\n"
" --repo-cipher-type cipher used to encrypt the repository\n" " --repo-cipher-type cipher used to encrypt the repository\n"
" [current=aes-256-cbc, default=none]\n" " [current=aes-256-cbc, default=none]\n"
" --repo-gcs-bucket GCS repository bucket\n" " --repo-gcs-bucket GCS repository bucket\n"
" --repo-gcs-endpoint GCS repository endpoint\n" " --repo-gcs-endpoint GCS repository endpoint\n"
" [default=storage.googleapis.com]\n" " [default=storage.googleapis.com]\n"
" --repo-gcs-key GCS repository key\n" " --repo-gcs-key GCS repository key\n"
" --repo-gcs-key-type GCS repository key type [default=service]\n" " --repo-gcs-key-type GCS repository key type [default=service]\n"
" --repo-host repository host when operating remotely\n" " --repo-host repository host when operating remotely\n"
" [current=backup.example.net]\n" " [current=backup.example.net]\n"
" --repo-host-ca-file repository host certificate authority file\n" " --repo-host-ca-file repository host certificate authority file\n"
" --repo-host-ca-path repository host certificate authority path\n" " --repo-host-ca-path repository host certificate authority path\n"
" --repo-host-cert-file repository host certificate file\n" " --repo-host-cert-file repository host certificate file\n"
" --repo-host-cmd repository host pgBackRest command\n" " --repo-host-cmd repository host pgBackRest command\n"
" [default=/path/to/pgbackrest]\n" " [default=/path/to/pgbackrest]\n"
" --repo-host-config pgBackRest repository host configuration\n" " --repo-host-config pgBackRest repository host configuration\n"
" file\n" " file\n"
" [default=/etc/pgbackrest/pgbackrest.conf]\n" " [default=/etc/pgbackrest/pgbackrest.conf]\n"
" --repo-host-config-include-path pgBackRest repository host configuration\n" " --repo-host-config-include-path pgBackRest repository host configuration\n"
" include path\n" " include path\n"
" [default=/etc/pgbackrest/conf.d]\n" " [default=/etc/pgbackrest/conf.d]\n"
" --repo-host-config-path pgBackRest repository host configuration\n" " --repo-host-config-path pgBackRest repository host configuration\n"
" path [default=/etc/pgbackrest]\n" " path [default=/etc/pgbackrest]\n"
" --repo-host-key-file repository host key file\n" " --repo-host-key-file repository host key file\n"
" --repo-host-port repository host port when repo-host is set\n" " --repo-host-port repository host port when repo-host is set\n"
" --repo-host-type repository host protocol type [default=ssh]\n" " --repo-host-type repository host protocol type\n"
" --repo-host-user repository host user when repo-host is set\n" " [default=ssh]\n"
" [default=pgbackrest]\n" " --repo-host-user repository host user when repo-host is\n"
" --repo-path path where backups and archive are stored\n" " set [default=pgbackrest]\n"
" [default=/var/lib/pgbackrest]\n" " --repo-path path where backups and archive are stored\n"
" --repo-s3-bucket S3 repository bucket\n" " [default=/var/lib/pgbackrest]\n"
" --repo-s3-endpoint S3 repository endpoint\n" " --repo-s3-bucket S3 repository bucket\n"
" --repo-s3-key S3 repository access key\n" " --repo-s3-endpoint S3 repository endpoint\n"
" --repo-s3-key-secret S3 repository secret access key\n" " --repo-s3-key S3 repository access key\n"
" --repo-s3-key-type S3 repository key type [default=shared]\n" " --repo-s3-key-secret S3 repository secret access key\n"
" --repo-s3-kms-key-id S3 repository KMS key\n" " --repo-s3-key-type S3 repository key type [default=shared]\n"
" --repo-s3-region S3 repository region\n" " --repo-s3-kms-key-id S3 repository KMS key\n"
" --repo-s3-role S3 repository role\n" " --repo-s3-region S3 repository region\n"
" --repo-s3-token S3 repository security token\n" " --repo-s3-role S3 repository role\n"
" --repo-s3-uri-style S3 URI Style [default=host]\n" " --repo-s3-token S3 repository security token\n"
" --repo-storage-ca-file repository storage CA file\n" " --repo-s3-uri-style S3 URI Style [default=host]\n"
" --repo-storage-ca-path repository storage CA path\n" " --repo-sftp-host SFTP repository host\n"
" --repo-storage-host repository storage host\n" " --repo-sftp-host-fingerprint SFTP repository host fingerprint\n"
" --repo-storage-port repository storage port [default=443]\n" " --repo-sftp-host-key-hash-type SFTP repository host key hash type\n"
" --repo-storage-upload-chunk-size repository storage upload chunk size\n" " --repo-sftp-host-port SFTP repository host port [default=22]\n"
" --repo-storage-verify-tls repository storage certificate verify\n" " --repo-sftp-host-user SFTP repository host user\n"
" [default=y]\n" " --repo-sftp-private-key-file SFTP private key file\n"
" --repo-type type of storage used for the repository\n" " --repo-sftp-private-key-passphrase SFTP private key passphrase\n"
" [default=posix]\n" " --repo-sftp-public-key-file SFTP public key file\n"
" --repo-storage-ca-file repository storage CA file\n"
" --repo-storage-ca-path repository storage CA path\n"
" --repo-storage-host repository storage host\n"
" --repo-storage-port repository storage port [default=443]\n"
" --repo-storage-upload-chunk-size repository storage upload chunk size\n"
" --repo-storage-verify-tls repository storage certificate verify\n"
" [default=y]\n"
" --repo-type type of storage used for the repository\n"
" [default=posix]\n"
"\n" "\n"
"Stanza Options:\n" "Stanza Options:\n"
"\n" "\n"
" --pg-path postgreSQL data directory\n" " --pg-path postgreSQL data directory\n"
"\n" "\n"
"Use 'pgbackrest help restore [option]' for more information.\n"); "Use 'pgbackrest help restore [option]' for more information.\n");

View File

@ -117,6 +117,25 @@ testRun(void)
hrnCfgLoadP(cfgCmdInfo, argList), OptionInvalidValueError, hrnCfgLoadP(cfgCmdInfo, argList), OptionInvalidValueError,
"local repo1 and repo2 paths are both '/var/lib/pgbackrest' but must be different"); "local repo1 and repo2 paths are both '/var/lib/pgbackrest' but must be different");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("local default repo paths for sftp repo type must be different");
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptRepo, "2");
hrnCfgArgKeyRawStrId(argList, cfgOptRepoType, 1, STORAGE_SFTP_TYPE);
hrnCfgArgKeyRawStrId(argList, cfgOptRepoType, 2, STORAGE_SFTP_TYPE);
hrnCfgArgKeyRawZ(argList, cfgOptRepoSftpHostUser, 1, "user1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoSftpHostUser, 2, "user2");
hrnCfgArgKeyRawZ(argList, cfgOptRepoSftpHost, 1, "host1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoSftpHost, 2, "host2");
hrnCfgArgKeyRawZ(argList, cfgOptRepoSftpHostKeyHashType, 1, "sha1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoSftpHostKeyHashType, 2, "md5");
hrnCfgArgKeyRawZ(argList, cfgOptRepoSftpPrivateKeyFile, 1, "/keyfile1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoSftpPrivateKeyFile, 2, "/keyfile2");
TEST_ERROR(
hrnCfgLoadP(cfgCmdInfo, argList), OptionInvalidValueError,
"local repo1 and repo2 paths are both '/var/lib/pgbackrest' but must be different");
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("local repo paths same but types different"); TEST_TITLE("local repo paths same but types different");

File diff suppressed because it is too large Load Diff

View File

@ -142,6 +142,7 @@ testRun(void)
strReplace(testC, STRDEF("{[C_TEST_USER]}"), STRDEF(TEST_USER)); strReplace(testC, STRDEF("{[C_TEST_USER]}"), STRDEF(TEST_USER));
strReplace(testC, STRDEF("{[C_TEST_USER_ID]}"), STRDEF(TEST_USER_ID_Z)); strReplace(testC, STRDEF("{[C_TEST_USER_ID]}"), STRDEF(TEST_USER_ID_Z));
strReplace(testC, STRDEF("{[C_TEST_USER_ID_Z]}"), STRDEF("\"" TEST_USER_ID_Z "\"")); strReplace(testC, STRDEF("{[C_TEST_USER_ID_Z]}"), STRDEF("\"" TEST_USER_ID_Z "\""));
strReplace(testC, STRDEF("{[C_TEST_USER_LEN]}"), strNewFmt("%zu", sizeof(TEST_USER) - 1));
// Test definition // Test definition
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
@ -314,6 +315,7 @@ testRun(void)
" lib_openssl,\n" " lib_openssl,\n"
" lib_lz4,\n" " lib_lz4,\n"
" lib_pq,\n" " lib_pq,\n"
" lib_ssh2,\n"
" lib_xml,\n" " lib_xml,\n"
" lib_yaml,\n" " lib_yaml,\n"
" lib_z,\n" " lib_z,\n"
@ -429,6 +431,7 @@ testRun(void)
" lib_openssl,\n" " lib_openssl,\n"
" lib_lz4,\n" " lib_lz4,\n"
" lib_pq,\n" " lib_pq,\n"
" lib_ssh2,\n"
" lib_xml,\n" " lib_xml,\n"
" lib_yaml,\n" " lib_yaml,\n"
" lib_z,\n" " lib_z,\n"
@ -639,6 +642,7 @@ testRun(void)
" lib_openssl,\n" " lib_openssl,\n"
" lib_lz4,\n" " lib_lz4,\n"
" lib_pq,\n" " lib_pq,\n"
" lib_ssh2,\n"
" lib_xml,\n" " lib_xml,\n"
" lib_yaml,\n" " lib_yaml,\n"
" lib_z,\n" " lib_z,\n"
@ -809,6 +813,7 @@ testRun(void)
" lib_openssl,\n" " lib_openssl,\n"
" lib_lz4,\n" " lib_lz4,\n"
" lib_pq,\n" " lib_pq,\n"
" lib_ssh2,\n"
" lib_xml,\n" " lib_xml,\n"
" lib_yaml,\n" " lib_yaml,\n"
" lib_z,\n" " lib_z,\n"

View File

@ -77,6 +77,7 @@ STRING_EXTERN(HRN_PATH_STR, HRN_PATH);
#define TEST_USER "{[C_TEST_USER]}" #define TEST_USER "{[C_TEST_USER]}"
#define TEST_USER_ID {[C_TEST_USER_ID]} #define TEST_USER_ID {[C_TEST_USER_ID]}
#define TEST_USER_ID_Z "{[C_TEST_USER_ID]}" #define TEST_USER_ID_Z "{[C_TEST_USER_ID]}"
#define TEST_USER_LEN "{[C_TEST_USER_LEN]}"
#ifdef HRN_FEATURE_STRING #ifdef HRN_FEATURE_STRING
STRING_EXTERN(TEST_USER_STR, TEST_USER); STRING_EXTERN(TEST_USER_STR, TEST_USER);