Qtの基礎 - SCP
概要
Qtにおいて、簡潔にSSH接続を確立する手順を記載する。
- libSSHライブラリ (LGPLライセンス) を使用する。
- libSSH2ライブラリ (3条項BSDライセンス) を使用する。
QProcess
クラスを使用して、外部のSSHプロセスをフォークして接続を確立する。- wolfSSH
- wolfSSHライブラリは、wolfSSLと連携して使用する。
- 無償版は、GPL v2ライセンスである。
- 有償版は、独自ライセンスである。
- https://wolfssl.jp/products/wolfssh/
- Github : https://github.com/wolfSSL/wolfssh
- wolfSSLのGithub : https://github.com/wolfSSL/wolfssl
- また、wolfSSLは、IoTデバイス、組み込み向け小型デバイスにも対応した軽量のSSL/TLSライブラリである。
- QSshライブラリを使用する。
- QSshは、QtアプリケーションにSSHとSFTPのサポートを提供する。
- QSshプロジェクトは、過去のQt CreatorのSSHプラグインに基づいており、全てのクレジットはQt Creatorチームに帰属する。
- ただし、現在では、Qt CreatorはOpenSSHを使用している。
- https://github.com/sandsmark/QSsh
libSSHとlibSSH2の比較
libSSHとlibSSH2は、どちらもSSHプロトコルを実装したライブラリである。
libSSHはC言語、libSSH2はC++言語で記述されている。
また、libSSHは単独のライブラリとして提供されているのに対して、libSSH2はlibSSL等の他のライブラリと組み合わせて使用することが多い。
プロジェクトの起源と開発者
- libSSH
- libSSHは、フランスのBenjamin GilbertとArel Corderoによって開発された。
- libSSHは、SSHプロトコルの実装を提供しており、クライアントおよびサーバの機能をサポートしている。
- libSSH2
- libSSH2は、curlライブラリの主要な開発者でもあるスウェーデンのDaniel Stenbergによって開発された。
- libSSH2は、SSHのクライアントおよびサーバ機能を提供することを目的としている。
- ただし、libSSH2はサーバサイドのサポートは行っていない。
使用されるコードベース
- libSSH
- libSSHは、元々PuTTYの一部として開発され、その後独立したプロジェクトになった。
- PuTTYは、Windows環境でのSSHクライアントとして広く知られている。
- libSSH2
- libSSH2は、curlライブラリとは独立したプロジェクトとして開発されている。
項目 | libSSH2 | libSSH |
---|---|---|
ライブラリ名 | libssh2.so | libssh.so |
ライセンス | 3条項BSD | LGPL 2.1 |
サーバサイドのサポート | No | Yes |
GSSAPI認証 | No | Yes |
楕円曲線鍵の交換 | No | Yes |
楕円曲線鍵のホスト鍵 | No | Yes |
ナイトリーテストによるテストケースの自動化 | No | Yes |
Stable API | Yes | ほとんどの部分 |
C言語との互換 | C89 | C99 |
厳格な名前空間 | Yes | Yes |
全ての関数のマニュアル | Yes | No |
全ての関数のDoxygenドキュメント | No | Yes |
Tutorial | Yes | Yes |
SSH v1のサポート | No | Yes |
libSSHライブラリのインストール
libSSHとは
libSSHの多くは、LGPL 2.1ライセンスである。
一部の機能には、2条項BSDライセンスのものがある。
パッケージ管理システムからインストール
sudo zypper install libssh-devel
ソースコードからインストール
libSSHのビルドに必要なライブラリをインストールする。
sudo zypper install zlib-devel readline-devel libpcap-devel libopenssl-devel libopenssl-1_1-devel libgcrypt-devel p11-kit-devel libsodium-devel libcmocka-devel doxygen \ # 以下に示すライブラリは不要の可能性あり openpgm-devel ldns-devel zeromq-devel unbound-devel libunwind-devel \ libheimdal-devel libgssglue-devel gssntlmssp-devel
libSSHの公式WebサイトまたはlibSSH向けのGitにアクセスして、libSSHのソースコードをダウンロードする。
ダウンロードしたファイルを解凍する。
tar xf libssh-<バージョン>.tar.xz cd libssh-<バージョン>
libSSHをビルドおよびインストールする。
mkdir build && cd build cmake .. \ -DCMAKE_INSTALL_PREFIX=/<libSSHのインストールディレクトリ> -DCMAKE_BUILD_TYPE=Release \ -DWITH_GSSAPI=ON -DWITH_DSA=ON -DWITH_GCRYPT=ON \ # オプション -DWITH_PKCS11_URI=ON -DWITH_BLOWFISH_CIPHER=ON \ # オプション -DCMAKE_C_COMPILER=/<GCCのインストールディレクトリ>/bin/gcc \ # オプション -DCMAKE_CXX_COMPILER=/<GCCのインストールディレクトリ>/bin/g++ # オプション make -j $(nproc) make install
libSSHの使用例は、以下に示すURLを参考にすること。
https://api.libssh.org/stable/libssh_tutorial.html
libSSH2ライブラリのインストール
libSSH2とは
libSSH2は、3条項BSDライセンスである。(4条項BSDライセンスから、3番目にあった「宣伝条項」を削除したもの)
これにより、GPLと互換性が生まれたため、BSDライセンスのソフトウェアをGPLで配布することができる。(その逆は不可)
パッケージ管理システムからインストール
sudo zypper install libssh2-devel
ソースコードからインストール
libSSH2のビルドに必要なライブラリをインストールする。
sudo zypper install zlib-devel libopenssl-devel libopenssl-1_1-devel
libSSH2の公式WebサイトまたはGithubにアクセスして、libSSH2のソースコードをダウンロードする。
ダウンロードしたソースコードを解凍する。
tar xf libssh2.tar.xz cd libssh2
libSSH2をビルドおよびインストールする。
mkdir build && cd build # configureスクリプトを使用する場合 ../cofigur --prefix=<libSSH2のインストールディレクトリ> \ --enable-examples-build --disable-debug --with-libz make -j $(nproc) make install # CMakeを使用する場合 cmake .. \ -DCMAKE_INSTALL_PREFIX=/<libssh2のインストールディレクトリ> \ -DLINT=ON -DBUILD_SHARED_LIBS=ON -DCLEAR_MEMORY=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_DEBUG_LOGGING=OFF \ -DCMAKE_C_COMPILER=/<GCCのインストールディレクトリ>/bin/gcc \ # オプション -DCMAKE_CXX_COMPILER=/<GCCのインストールディレクトリ>/bin/g++ # オプション make -j $(nproc) make install
libSSH2の使用例は、以下に示すURLを参考にすること。
http://www.chaosstuff.com/2013/09/gnome-mplayer-remote-with-qt-and-libssh2.html
https://bitbucket.org/nchokoev/qtsshremote
Windowsの場合、CMakeおよびVisual Studioを使用してlibSSH2をビルドおよびインストールすることができる。
- CMakeを使用する場合
- 32ビット Windows
cmake -DBUILD_SHARED_LIBS=ON -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -A Win32 .. -B "x86"
cmake --build x86 --config Release
- 64ビット Windows
cmake -DBUILD_SHARED_LIBS=ON -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF -A x64 .. -B "x64"
cmake --build x64 --config Release
- コンパイルされたDLLファイルとlibファイルは、x86とx64のディレクトリのsrc/Releaseディレクトリに配置される。
- 32ビット Windows
- Visual Studioを使用する場合
- libSSH2ディレクトリと同階層にプロジェクトを作成する。
- プロジェクトのプロパティを選択して、プロパティ画面左ペインにある[General] - プロパティ画面右ペインにある[ターゲット名]を"libSSH2_x64"等と入力する。
- プロパティ画面左ペインにある[リンカ] - [Advanced] - プロパティ画面右ペインにある[インポートライブラリ]プルダウンから[<Inherit from parent or project defaults>]を選択する。
また、ビルドされたDLLファイルは、以下に示すWebサイトからダウンロードできる。
https://download.csdn.net/download/sdhongjun/15682389
libSSHライブラリを使用したSCPコマンドの使用例
以下の例では、公開鍵認証を使用してリモートPCへSSH接続している。
# .pro プロジェクトファイル
# pkg-configを使用する場合
CONFIG += link_pkgconfig
LIBS += \
-L/<libSSHのインストールディレクトリ>/lib64 -lssh
# pkg-configを使用しない場合
LIBS += \
-L/<libSSHのインストールディレクトリ>/lib64 -lssh
INCLUDEPATH += \
/<libSSHのインストールディレクトリ>/include
#include <QCoreApplication>
#include <QMessageBox>
#include <libssh/libssh.h>
int VerifyKnownsHost(ssh_session my_ssh_session, QString &strErrMsg);
int SCP(ssh_session my_ssh_session);
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// SSHセッションの作成
ssh_session my_ssh_session = ssh_new();
if (my_ssh_session == NULL) {
// SSHセッションの作成に失敗した場合
return -1;
}
// SSHセッションの設定
QString host = "<リモートPCのIPアドレス または ホスト名>";
QString user = "<リモートPCのユーザ名>";
QString port = "<SSHポート番号 例: 22>";
ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, host.toUtf8().data());
ssh_options_set(my_ssh_session, SSH_OPTIONS_USER, user.toUtf8().data());
ssh_options_set(my_ssh_session, SSH_OPTIONS_PORT_STR, port.toUtf8().data());
// SSH接続
int rc = ssh_connect(my_ssh_session);
if (rc != SSH_OK) {
// 接続に失敗した場合
fprintf(stderr, "Error connecting to host: %s\n", ssh_get_error(my_ssh_session));
ssh_free(my_ssh_session);
return -1;
}
// ~/.sshディレクトリ等にあるファイルに記述されているサーバのIDを検証
QString strErrMsg = "";
if (VerifyKnownsHost(my_ssh_session, strErrMsg) < 0) {
fprintf(stderr, "%s\n", strErrMsg.toUtf8().constData();
if (my_ssh_session != nullptr) {
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
}
return -1;
}
// 公開鍵認証
// 秘密鍵の設定
const char *private_key_path = "<秘密鍵のパス 例: /home/user/sshkey/id_rsa";
// 秘密鍵のパスフレーズを設定していない場合
rc = ssh_userauth_privatekey_file(my_ssh_session, nullptr, private_key_path, nullptr);
// 秘密鍵のパスフレーズを設定している場合
rc = ssh_userauth_privatekey_file(my_ssh_session, nullptr, private_key_path, "<秘密鍵のパスフレーズ>");
if (rc != SSH_AUTH_SUCCESS) {
// 認証に失敗した場合
fprintf(stderr, "Error authenticating with private key: %s\n", ssh_get_error(my_ssh_session));
ssh_disconnect(my_ssh_session);
ssh_free(my_ssh_session);
return -1;
}
// SSHセッションを使用して、SCPコマンドを実行
if (SCP(my_ssh_session)) {
// SSH接続の切断
ssh_disconnect(my_ssh_session);
// SSHセッションの解放
ssh_free(my_ssh_session);
return -1;
}
// SSH接続の切断
ssh_disconnect(my_ssh_session);
// SSHセッションの解放
ssh_free(my_ssh_session);
return 0;
}
int VerifyKnownsHost(ssh_session my_ssh_session, QString &strErrMsg)
{
// Authenticating the server.
ssh_key srv_pubkey = {};
if (ssh_get_server_publickey(my_ssh_session, &srv_pubkey) < 0) {
strErrMsg = tr("Failed to get public key.");
return -1;
}
unsigned char *hash = nullptr;
size_t hlen = 0L;
auto iRet = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen);
ssh_key_free(srv_pubkey);
if (iRet < 0) {
strErrMsg = tr("Failed to get public key hash.");
return -1;
}
auto state = ssh_session_is_known_server(my_ssh_session);
if (state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_OK) {
// Authentication Successful
}
else if (state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_CHANGED) {
QString strHexa = ssh_get_hexa(hash, hlen);
// print string in reverse order
strErrMsg = tr("Host key for server changed:") + "<br>" +
tr("For security reasons, connection will be stopped.") + "<br><br>" +
tr("Public key hash:") + "<br>" + strHexa + "<br>" + hlen;
ssh_clean_pubkey_hash(&hash);
return -1;
}
else if (state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_OTHER) {
strErrMsg = tr("The host key for this server was not found but an other type of key exists.") + "<br>" +
tr("An attacker might change the default server key to confuse your client into") + "<br>" +
tr("thinking the key does not exist");
ssh_clean_pubkey_hash(&hash);
return -1;
}
else if (state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_NOT_FOUND) {
/* FALL THROUGH to SSH_KNOWN_HOSTS_UNKNOWN behavior */
QString strHexa = ssh_get_hexa(hash, hlen);
QString strAddHostMessage = tr("Could not find known host file.") + "\n" +
tr("If you accept the host key here, the file will be automatically created.") + "\n\n" +
tr("The server is unknown. Do you trust the host key?") + "\n" +
tr("Public key hash: ") + "\n" + strHexa;
auto ret = QMessageBox(QMessageBox::Warning, QMessageBox::tr("Add Host"), strAddHostMessage,
QMessageBox::Yes | QMessageBox::No, nullptr).exec();
ssh_clean_pubkey_hash(&hash);
if(ret == QMessageBox::No) {
strErrMsg = tr("To connect, please add host key.");
return -1;
}
else {
iRet = ssh_session_update_known_hosts(my_ssh_session);
if (iRet < 0) {
strErrMsg = tr("Failed to update host key.");
return -1;
}
}
}
else if (state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_UNKNOWN) {
QString strHexa = ssh_get_hexa(hash, hlen);
QString strAddHostMessage = tr("The server is unknown. Do you trust the host key?") + "\n" +
tr("Public key hash: ") + "\n" + strHexa;
auto msgRet = QMessageBox(QMessageBox::Warning, QMessageBox::tr("Add Host"), strAddHostMessage,
QMessageBox::Yes | QMessageBox::No, nullptr).exec();
ssh_clean_pubkey_hash(&hash);
if (msgRet == QMessageBox::No) {
strErrMsg = tr("To connect, please add host key.");
return -1;
}
else {
iRet = ssh_session_update_known_hosts(my_ssh_session);
if (iRet < 0) {
strErrMsg = tr("Failed to update host key.");
return -1;
}
}
}
else if (state == ssh_known_hosts_e::SSH_KNOWN_HOSTS_ERROR) {
strErrMsg = tr("There was an error in checking the host.");
ssh_clean_pubkey_hash(&hash);
return -1;
}
ssh_clean_pubkey_hash(&hash);
return 0;
}
int SCP(ssh_session my_ssh_session)
{
// SCPセッションの作成
ssh_scp scp = ssh_scp_new(my_ssh_session, SSH_SCP_WRITE, "<送信先のリモートPCのパス>");
if (scp == NULL) {
fprintf(stderr, "SCPセッションの作成に失敗: %s\n", ssh_get_error(my_ssh_session));
return -1;
}
// SCPセッションの初期化
rc = ssh_scp_init(scp);
if (rc != SSH_OK) {
fprintf(stderr, "SCPセッションの作成の初期化に失敗: %s\n", ssh_get_error(my_ssh_session));
ssh_scp_free(scp);
return -1;
}
// ローカルPC上のファイルをオープン
QFile file("<送信元のファイルパス>");
if (!file.open(QIODevice::ReadOnly)) {
fprintf(stderr, "ファイルのオープンに失敗\n");
ssh_scp_free(scp);
return -1;
}
// ファイル内容を読み込む
QByteArray fileContent = file.readAll();
// ファイルの転送
rc = ssh_scp_push_file(scp, filename, fileContent.size(), S_IRUSR | S_IWUSR);
if (rc != SSH_OK) {
fprintf(stderr, "送信先のファイルのオープンに失敗: %s\n", ssh_get_error(my_ssh_session));
ssh_scp_free(scp);
return -1;
}
rc = ssh_scp_write(scp, fileContent.constData(), fileContent.size());
if (rc != SSH_OK) {
fprintf(stderr, "送信先のファイルの書き込みに失敗: %s\n", ssh_get_error(my_ssh_session));
ssh_scp_free(scp);
return -1;
}
// SCPセッションの終了
ssh_scp_close(scp);
ssh_scp_free(scp);
return 0;
}
libSSH2ライブラリを使用したSCPコマンドの使用例
ブロッキングモード
以下の例では、リモート側のPCにSSH接続して、SCPでファイルを送信している。
以下の例は、ブロッキングモードでSCPを実行している。
ノンブロッキングモードを有効にする場合、libSSH2ライブラリの関数呼び出しは即座に返されて、処理がバックグラウンドで非同期に進行する。
これにより、プログラムは他の処理を続行でき、必要に応じてリモートホストとの通信が完了するのを待つことができる。
ノンブロッキングモードを使用する場合、特に入出力操作が発生する待ち時間を最小限に抑え、プログラムがより効率的に動作することが期待される。
しかし、ノンブロッキングモードを扱う際には、非同期処理やイベント駆動型のプログラミングに慣れる必要がある。
以下の例では、GNU LIBCを使用している場合およびQTcpSocketクラスを使用している場合の2つを、#define NOQTCPSOCKET 1
プリプロセッサを使用して処理を別けている。
有償版Qtライセンスを購入している場合、GNU LIBCライブラリを使用せずにQTcpSocketクラスを使用するならば、ライセンスにおいてリバースエンジニアリングを禁止することができる。
GNU LIBCを使用している場合、(#define NOQTCPSOCKET 1
プリプロセッサの使用する場合)
インクルードしているLinux向けライブラリ群はglibc-devel (libc-dev) パッケージに含まれるため、LGPLライセンスであることに注意する。
- sys/socket.h
- ソケットプログラミングに必要なヘッダファイルである。
- arpa/inet.h
- IPv4やIPv6アドレスの変換やネットワークバイトオーダーとホストバイトオーダーの変換等、ネットワークプログラミングで使用される。
- netinet/in.h
- ネットワークプログラミングやソケットプログラミングに必要なヘッダファイルである。
- unistd.h
- UNIXシステムのシステムコールやその他の標準的なシステム関数を宣言するためのヘッダファイルである。
- ファイル操作、プロセス管理、システム情報の取得、メモリ管理、ファイルシステムの操作等の関数やマクロが含まれている。
- fcntl.h
- ファイル制御やファイルディスクリプタ関連の操作に使用される。
# .pro プロジェクトファイル
# QTcpSocketを使用する場合
QT += network
# pkg-configを使用する場合
CONFIG += link_pkgconfig
LIBS += \
-L/<libSSH2のインストールディレクトリ>/lib64 -lssh2
# pkg-configを使用しない場合
LIBS += \
-L/<libSSH2のインストールディレクトリ>/lib64 -lssh2
INCLUDEPATH += \
/<libSSH2のインストールディレクトリ>/include
// main.cpp
#include <QCoreApplication>
#include <QFileInfo>
#include <QTimer>
#include <QElapsedTimer>
#include <QDebug>
#include <libssh2.h>
#include <libssh2_sftp.h>
#include "DivideByZeroException.h"
#define NOQTCPSOCKET 1
#if NOQTCPSOCKET // QTcpSocketクラスを使用しない場合
#ifdef Q_OS_LINUX
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#endif
#else // QTcpSocketクラスを使用する場合
#include <QTcpSocket>
#endif
#if NOQTCPSOCKET // QTcpSocketクラスを使用しない場合
void DisConnect(int &sock, LIBSSH2_SESSION *session);
int Send(int &sock, LIBSSH2_SESSION *session);
int waitsocket(libssh2_socket_t socket_fd, LIBSSH2_SESSION *session);
#else // QTcpSocketクラスを使用する場合
void DisConnect(QTcpSocket &sock, LIBSSH2_SESSION *session);
int Send(QTcpSocket &sock, LIBSSH2_SESSION *session);
#endif
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int rc = 0;
#if NOQTCPSOCKET
int sock = 0;
#if defined(Q_OS_WIN32) || defined(Q_OS_WIN64)
WSADATA wsadata;
rc = WSAStartup(MAKEWORD(2, 0), &wsadata);
if(rc) {
std::cerr << "WSAStartup failed with error: " << rc << std::endl;
return -1;
}
#endif
#else
QTcpSocket sock;
#endif
LIBSSH2_SESSION *session = nullptr;
// 初期化
rc = libssh2_init(0);
if (rc != 0) {
qDebug() << QString("libssh2 initialization failed %1").arg(rc);
DisConnect(sock, session);
return -1;
}
#if NOQTCPSOCKET // QTcpSocketクラスを使用しない場合
// ソケットの作成
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
qDebug() << QString("failed to create socket.");
DisConnect(sock, session);
return -1;
}
struct sockaddr_in sin = {};
sin.sin_family = AF_INET;
sin.sin_port = htons(<SSHのポート番号>);
sin.sin_addr.s_addr = inet_addr("<リモート側PCのIPアドレス または ホスト名>");
// SSH接続
if (connect(sock, (struct sockaddr*)(&sin), sizeof(struct sockaddr_in)) == -1) {
qDebug() << QString("failed to connect.");
DisConnect(sock, session);
return -1;
}
#else // QTcpSocketクラスを使用する場合
QTcpSocket sock;
sock.connectToHost(host, port);
if (!sock.waitForConnected()) {
qDebug() << "Failed to connect";
DisConnect(sock, session);
return -1;
}
#endif
// libSSH2のセッションを初期化
session = libssh2_session_init();
if (!session) {
qDebug() << "Failed to initialize SSH session";
DisConnect(sock, session);
return -1;
}
#if NOQTCPSOCKET // QTcpSocketクラスを使用しない場合
// リモート側のPCとのハンドシェイク
if (libssh2_session_handshake(session, sock)) {
qDebug() << "SSH handshake failed";
DisConnect(sock, session);
return -1;
}
#else // QTcpSocketクラスを使用する場合
if (libssh2_session_handshake(session, sock.socketDescriptor())) {
qDebug() << "SSH handshake failed";
DisConnect(sock, session);
return -1;
}
#endif
// パスワード認証
if (libssh2_userauth_password(session, "<リモート側のユーザ名>", "<ユーザ名のパスワード>") != 0) {
qDebug() << QString("Authentication failed.");
DisConnect(sock, session);
return -1;
}
qDebug() << QString("Authentication succeeded.");
// SSH接続後の処理
// SCPでファイルを送信
rc = Send(sock, session);
if (rc != 0) {
DisConnect(sock, session);
return -1;
}
// SSH接続の終了
DisConnect(sock, session);
return 0;
}
#if NOQTCPSOCKET // QTcpSocketクラスを使用しない場合
void DisConnect(int &sock, LIBSSH2_SESSION *session)
{
// セッションの終了
if (session) {
libssh2_session_disconnect(session, "Normal Shutdown");
libssh2_session_free(session);
session = nullptr;
}
// ソケットを閉じる
if (sock != LIBSSH2_INVALID_SOCKET) {
shutdown(sock, 2);
#if defined(Q_OS_WIN32) || defined(Q_OS_WIN64)
closesocket(sock);
#else
close(sock);
#endif
}
// libSSH2の終了
libssh2_exit();
return;
}
#else // QTcpSocketクラスを使用する場合
void DisConnect(QTcpSocket &sock, LIBSSH2_SESSION *session)
{
// セッションの終了
if (session) {
libssh2_session_disconnect(session, "Normal Shutdown");
libssh2_session_free(session);
session = nullptr;
}
// ソケットを閉じる
if(sock.isOpen()) {
sock.close();
}
// libSSH2の終了
libssh2_exit();
return;
}
#endif
#if NOQTCPSOCKET // QTcpSocketクラスを使用しない場合
int Send(int &sock, LIBSSH2_SESSION *session)
#else
int Send(QTcpSocket &sock, LIBSSH2_SESSION *session)
#endif
{
// Send a file via scp. The mode parameter must only have permissions.
LIBSSH2_CHANNEL *channel = nullptr;
QString localFilePath = "<ローカルPCのファイルパス 例: /tmp/hoge.png>";
QFile File(localFilePath);
if(!File.open(QIODevice::ReadOnly)) {
qDebug() << QString("Cannot open local File(%1) Open Error: %2").arg(QFileInfo(File).fileName(), File.errorString());
return -1;
}
// QFileInfoクラスではパーミッションは16進数のため、libssh2_scp_send64関数で使用するため8進数に変換する
// これは、見た目の数値を同じにする必要がある 例: 0x644(16進数) --> 0644(8進数)
QFileInfo FileInfo(localFilePath);
//// ステッキービットは不要なため削除
auto hexPermisshionString = QString::number(FileInfo.permissions(), 16).mid(1);
bool ok;
//// 8進数に変換
unsigned int octPermisshionValue = hexPermisshionString.toInt(&ok, 8);
if (!ok) {
qDebug() << QString("Failed to convert permission value.");
return -1;
}
QString remoteFilePath = "<リモート側PCのファイルパス 例: /home/remote-user/hoge.png>";
do {
channel = libssh2_scp_send64(session, remoteFilePath.toUtf8().constData(), octPermisshionValue & 0777, FileInfo.size(), 0, 0);
if (!channel && libssh2_session_last_errno(session) != LIBSSH2_ERROR_EAGAIN) {
File.close();
// チャンネルの終了
if (channel) {
libssh2_channel_free(channel);
channel = nullptr;
}
char *err_msg;
libssh2_session_last_error(session, &err_msg, NULL, 0);
qDebug() << err_msg;
return -1;
}
} while (!channel);
qDebug() << QString("SCP session waiting to send file.");
// タイマの開始
QElapsedTimer elapsedTimer;
elapsedTimer.start();
int nread = 0,
total = 0;
char mem[1024 * 100] = {};
do {
nread = File.read(mem, sizeof(mem));
if(nread <= 0) {
// EOF
break;
}
auto ptr = mem;
total += nread;
auto prev = 0;
do {
ssize_t nwritten;
#if NOQTCPSOCKET // QTcpSocketクラスを使用しない場合
while ((nwritten = libssh2_channel_write(channel, ptr, nread)) == LIBSSH2_ERROR_EAGAIN) {
waitsocket(sock, session);
prev = 0;
}
#else // QTcpSocketクラスを使用する場合
while ((nwritten = libssh2_channel_write(channel, ptr, nread)) == LIBSSH2_ERROR_EAGAIN) {
prev = 0;
}
// 書き込み完了を待機
sock.waitForBytesWritten();
#endif
if (nwritten < 0) {
qDebug() << QString("ERROR %1 total %2 / %3 prev %4").arg((int)nwritten)
.arg((long)total)
.arg((int)nread)
.arg((int)prev);
break;
}
else {
prev = nread;
// nwritten indicates how many bytes were written this time.
nread -= nwritten;
ptr += nwritten;
}
} while (nread);
} while (!nread); // only continue if nread was drained.
File.close();
// 経過時間をミリ秒単位で取得
try {
qint64 duration = elapsedTimer.elapsed() == 0 ? throw DivideByZeroException() : elapsedTimer.elapsed();
qDebug() << QString("%1 bytes in %2 milli-seconds makes %3 bytes/sec").arg(static_cast<long>(total))
.arg(duration)
.arg(QString::number((double)((total * 1000) / duration), 'f', 1));
}
catch (const DivideByZeroException &ex) {
qDebug() << ex.what();
qDebug() << QString("Transfer rate could not be calculated.");
}
qDebug() << QString("Sending EOF");
while(libssh2_channel_send_eof(channel) == LIBSSH2_ERROR_EAGAIN);
qDebug() << QString("Waiting for EOF");
while(libssh2_channel_wait_eof(channel) == LIBSSH2_ERROR_EAGAIN);
qDebug() << QString("Waiting for channel to close");
while(libssh2_channel_wait_closed(channel) == LIBSSH2_ERROR_EAGAIN);
return 0;
}
#if NOQTCPSOCKET // QTcpSocketクラスを使用しない場合
int waitsocket(libssh2_socket_t socket_fd, LIBSSH2_SESSION *session)
{
struct timeval timeout = {};
timeout.tv_sec = 10;
timeout.tv_usec = 0;
fd_set fd;
FD_ZERO(&fd);
FD_SET(socket_fd, &fd);
// now make sure we wait in the correct direction.
auto dir = libssh2_session_block_directions(session);
fd_set *writefd = nullptr;
fd_set *readfd = nullptr;
if(dir & LIBSSH2_SESSION_BLOCK_INBOUND) readfd = &fd;
if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) writefd = &fd;
auto rc = select((int)(socket_fd + 1), readfd, writefd, nullptr, &timeout);
return rc;
}
#endif
// DivideByZeroException.h
#ifndef DIVIDEBYZEROEXCEPTION_H
#define DIVIDEBYZEROEXCEPTION_H
#include <QException>
class DivideByZeroException : public QException
{
public:
DivideByZeroException() {};
virtual ~DivideByZeroException() {};
const char* what() const noexcept override {
return "Divide by zero exception";
}
};
#endif // DIVIDEBYZEROEXCEPTION_H