How to handle a password request when using QProcess for an rsync call?
-
wrote on 16 Dec 2023, 17:12 last edited by BwvB
I want to use QProcess for an rsync call from my application to my NAS device.
In short: when no password is needed, the code works perfectly (minimal example below). But for some rsync modules I need to provide a password. I cannot find a way how to trap this situation, pop-up a password dialog etc.Below follows a minimal, but complete program showing what I am trying to do: After compilation/linkage, the program is to be run with one argument, the same as would be provided to an rsync command from the command line. For instance, when I want to list the modules on my NAS device, called "diskstation", I can do that with
rsync diskstation::
The same information should be returned by running the below program, say./a.out diskstation::
wherea.out
is the default executable name on a linux platform.- a convenience macro
TRC_MSGLN
is defined to produce line-numbered feedback - A
QProcess
instance is defined. All its signals are connected to a lambda-slot, printing info, only to see what is going on. - the qprocess is started with
rsync
. We wait at most 10s for the process to finish. - After the process has terminated, the result is printed to
std::cout
.
#include <QtCore/QByteArray> #include <QtCore/QProcess> #include <iostream> #include <string> #define TRC_MSGLN( s ) \ { \ std::cout << __LINE__ << ": " << s << std::endl; \ } /*############################################################################*/ /*############################################################################*/ int main( int argc, char* argv[] ) { QProcess qproc; QObject::connect( &qproc, &QProcess::readyReadStandardOutput, []() { TRC_MSGLN( "out: new data" ); } ); QObject::connect( &qproc, &QProcess::readyReadStandardError, []() { TRC_MSGLN( "err: new data" ); } ); QObject::connect( &qproc, &QProcess::started, []() { TRC_MSGLN( "started" ); } ); QObject::connect( &qproc, &QProcess::finished, []( int ec ) { TRC_MSGLN( "finished: ec=" << ec ); } ); QObject::connect( &qproc, &QProcess::stateChanged, []( auto s ) { TRC_MSGLN( "state changed:" << s ); } ); QObject::connect( &qproc, &QProcess::errorOccurred, []( auto e ) { TRC_MSGLN( "error occurred:" << e ); } ); qproc.start( "rsync", QStringList() << argv[1] ); if ( !qproc.waitForFinished( 10000 ) ) { TRC_MSGLN( "wait for finished failed ..." ); return -2; } auto result = qproc.readAllStandardOutput().split( '\n' ); // type: QList<QByteArray> for ( int count = 0; const auto& r: result ) { TRC_MSGLN( ++count << ":" << r.toStdString() ); } return 0; }
As said, this works perfectly when no password needs to be given. But when a password is needed, this is prompted for in the terminal.
None of the connected channels show any sign of data being available (that is, the password prompt goes undetected by QProcess). For instance (waiting for the time-out of 10s to occur) the terminal shows21: state changed:1 21: state changed:2 19: started Password: 28: wait for finished failed ... QProcess: Destroyed while process ("rsync") is still running. 21: state changed:0 22: error occurred:1 20: finished: ec=9
Apparently, I am missing something here. Can anybody be of help?
Finally, the program above (main.cpp) can be compiled/linked by (linux)
g++ main.cpp -std=c++20 -fPIC -I$QTDIR/include -L$QTDIR/lib -lQt6Core
producing the executablea.out
$QTDIR
refers to the qt installation directory.You help is appreciated!
Bertwim
- a convenience macro
-
I want to use QProcess for an rsync call from my application to my NAS device.
In short: when no password is needed, the code works perfectly (minimal example below). But for some rsync modules I need to provide a password. I cannot find a way how to trap this situation, pop-up a password dialog etc.Below follows a minimal, but complete program showing what I am trying to do: After compilation/linkage, the program is to be run with one argument, the same as would be provided to an rsync command from the command line. For instance, when I want to list the modules on my NAS device, called "diskstation", I can do that with
rsync diskstation::
The same information should be returned by running the below program, say./a.out diskstation::
wherea.out
is the default executable name on a linux platform.- a convenience macro
TRC_MSGLN
is defined to produce line-numbered feedback - A
QProcess
instance is defined. All its signals are connected to a lambda-slot, printing info, only to see what is going on. - the qprocess is started with
rsync
. We wait at most 10s for the process to finish. - After the process has terminated, the result is printed to
std::cout
.
#include <QtCore/QByteArray> #include <QtCore/QProcess> #include <iostream> #include <string> #define TRC_MSGLN( s ) \ { \ std::cout << __LINE__ << ": " << s << std::endl; \ } /*############################################################################*/ /*############################################################################*/ int main( int argc, char* argv[] ) { QProcess qproc; QObject::connect( &qproc, &QProcess::readyReadStandardOutput, []() { TRC_MSGLN( "out: new data" ); } ); QObject::connect( &qproc, &QProcess::readyReadStandardError, []() { TRC_MSGLN( "err: new data" ); } ); QObject::connect( &qproc, &QProcess::started, []() { TRC_MSGLN( "started" ); } ); QObject::connect( &qproc, &QProcess::finished, []( int ec ) { TRC_MSGLN( "finished: ec=" << ec ); } ); QObject::connect( &qproc, &QProcess::stateChanged, []( auto s ) { TRC_MSGLN( "state changed:" << s ); } ); QObject::connect( &qproc, &QProcess::errorOccurred, []( auto e ) { TRC_MSGLN( "error occurred:" << e ); } ); qproc.start( "rsync", QStringList() << argv[1] ); if ( !qproc.waitForFinished( 10000 ) ) { TRC_MSGLN( "wait for finished failed ..." ); return -2; } auto result = qproc.readAllStandardOutput().split( '\n' ); // type: QList<QByteArray> for ( int count = 0; const auto& r: result ) { TRC_MSGLN( ++count << ":" << r.toStdString() ); } return 0; }
As said, this works perfectly when no password needs to be given. But when a password is needed, this is prompted for in the terminal.
None of the connected channels show any sign of data being available (that is, the password prompt goes undetected by QProcess). For instance (waiting for the time-out of 10s to occur) the terminal shows21: state changed:1 21: state changed:2 19: started Password: 28: wait for finished failed ... QProcess: Destroyed while process ("rsync") is still running. 21: state changed:0 22: error occurred:1 20: finished: ec=9
Apparently, I am missing something here. Can anybody be of help?
Finally, the program above (main.cpp) can be compiled/linked by (linux)
g++ main.cpp -std=c++20 -fPIC -I$QTDIR/include -L$QTDIR/lib -lQt6Core
producing the executablea.out
$QTDIR
refers to the qt installation directory.You help is appreciated!
Bertwim
wrote on 16 Dec 2023, 18:48 last edited by Pl45m4@BwvB said in How to handle a password request when using QProcess for an rsync call?:
But when a password is needed, this is prompted for in the terminal.
None of the connected channels show any sign of data being available (that is, the password prompt goes undetected by QProcess).Check the
rsync
documentation.
Doesn't it provide cmd line options to set a password or a path to some auth token or pw file?!
You have to set that before together with the other args. - a convenience macro
-
@BwvB said in How to handle a password request when using QProcess for an rsync call?:
But when a password is needed, this is prompted for in the terminal.
None of the connected channels show any sign of data being available (that is, the password prompt goes undetected by QProcess).Check the
rsync
documentation.
Doesn't it provide cmd line options to set a password or a path to some auth token or pw file?!
You have to set that before together with the other args. -
@Pl45m4 That is not my question. I want to capture the response from the process and act on that. But I don't understand how to do this with QProcess.
@BwvB
The wayrsync
(and most of the cmd tools dealing with passwords, e.g.sudo
orpasswd
) are implemented, prevents intercepting the password input. If you flip it around and assume that it would actually possible to react inQProcess
, this would be the ideal way to find out someone's password.OTOH, if you
rsync
into a remote machine, you can pretty much rely on the fact that a password is needed.sshpass
is a handy, non interactive tool for such usecases.I often use:
/usr/bin/rsync -ratlz --rsh="/usr/bin/sshpass -p password ssh -o StrictHostKeyChecking=no -l username" localDirectory host:/remote/path
-
I want to use QProcess for an rsync call from my application to my NAS device.
In short: when no password is needed, the code works perfectly (minimal example below). But for some rsync modules I need to provide a password. I cannot find a way how to trap this situation, pop-up a password dialog etc.Below follows a minimal, but complete program showing what I am trying to do: After compilation/linkage, the program is to be run with one argument, the same as would be provided to an rsync command from the command line. For instance, when I want to list the modules on my NAS device, called "diskstation", I can do that with
rsync diskstation::
The same information should be returned by running the below program, say./a.out diskstation::
wherea.out
is the default executable name on a linux platform.- a convenience macro
TRC_MSGLN
is defined to produce line-numbered feedback - A
QProcess
instance is defined. All its signals are connected to a lambda-slot, printing info, only to see what is going on. - the qprocess is started with
rsync
. We wait at most 10s for the process to finish. - After the process has terminated, the result is printed to
std::cout
.
#include <QtCore/QByteArray> #include <QtCore/QProcess> #include <iostream> #include <string> #define TRC_MSGLN( s ) \ { \ std::cout << __LINE__ << ": " << s << std::endl; \ } /*############################################################################*/ /*############################################################################*/ int main( int argc, char* argv[] ) { QProcess qproc; QObject::connect( &qproc, &QProcess::readyReadStandardOutput, []() { TRC_MSGLN( "out: new data" ); } ); QObject::connect( &qproc, &QProcess::readyReadStandardError, []() { TRC_MSGLN( "err: new data" ); } ); QObject::connect( &qproc, &QProcess::started, []() { TRC_MSGLN( "started" ); } ); QObject::connect( &qproc, &QProcess::finished, []( int ec ) { TRC_MSGLN( "finished: ec=" << ec ); } ); QObject::connect( &qproc, &QProcess::stateChanged, []( auto s ) { TRC_MSGLN( "state changed:" << s ); } ); QObject::connect( &qproc, &QProcess::errorOccurred, []( auto e ) { TRC_MSGLN( "error occurred:" << e ); } ); qproc.start( "rsync", QStringList() << argv[1] ); if ( !qproc.waitForFinished( 10000 ) ) { TRC_MSGLN( "wait for finished failed ..." ); return -2; } auto result = qproc.readAllStandardOutput().split( '\n' ); // type: QList<QByteArray> for ( int count = 0; const auto& r: result ) { TRC_MSGLN( ++count << ":" << r.toStdString() ); } return 0; }
As said, this works perfectly when no password needs to be given. But when a password is needed, this is prompted for in the terminal.
None of the connected channels show any sign of data being available (that is, the password prompt goes undetected by QProcess). For instance (waiting for the time-out of 10s to occur) the terminal shows21: state changed:1 21: state changed:2 19: started Password: 28: wait for finished failed ... QProcess: Destroyed while process ("rsync") is still running. 21: state changed:0 22: error occurred:1 20: finished: ec=9
Apparently, I am missing something here. Can anybody be of help?
Finally, the program above (main.cpp) can be compiled/linked by (linux)
g++ main.cpp -std=c++20 -fPIC -I$QTDIR/include -L$QTDIR/lib -lQt6Core
producing the executablea.out
$QTDIR
refers to the qt installation directory.You help is appreciated!
Bertwim
wrote on 17 Dec 2023, 08:15 last edited by JonB@Pl45m4 That is not my question. I want to capture the response from the process and act on that. But I don't understand how to do this with QProcess.
I am always interested in anything
QProcess
, especially under Linux :)First a couple of preliminaries about your code. I am "surprised" it works given that you do not create any kind of
QCoreApplication
. It is also not a great idea (at least while you have a problem) usingQProcess
signal/slots at the same time as callingwaitForFinished()
in case the way that works interferes; you might try without that (you would need to runreturn app.exec()
). But neither of these may affect the behaviour.I believe/suspect the "hang" you see is from where the
rsync
's input is attached to. See enum QProcess::InputChannelMode. The default isQProcess::ManagedInputChannel
:QProcess manages the input of the running process. This is the default input channel mode of QProcess.
You should change via void QProcess::setInputChannelMode(QProcess::InputChannelMode mode) to
QProcess::ForwardedInputChannel
:QProcess forwards the input of the main process onto the running process. The child process reads its standard input from the same source as the main process. Note that the main process must not try to read its standard input while the child process is running.
This should mean that if your Qt calling program stdin is attached to a terminal then the
rsync
can read from that terminal too.You may even now see the "prompt" message arrive in your code via
readRead...()
.rsync
code may well have code like:if isatty(0) fprintf(stderr, "Enter password:"); read(0, password, sizeof(password));
so the message part would require stdin to be attached to a terminal, not a pipe/redirection, as it is with
ManagedInputChannel
.It may do this by calling getpass(3):
The getpass() function opens /dev/tty (the controlling terminal
of the process), outputs the string prompt, turns off echoing,
reads one line (the "password"), restores the terminal state and
closes /dev/tty again.At the moment it is expecting its input from the calling Qt app code. Without the
ForwardedInputChannel
you may find that you can goproc.write("Password\n")
, probably in response to thestarted
signal, to send the password programmatically from the calling app.Finally, to test this all outside of Qt/
QProcess
you can try:- Use
system("rsync ...")
instead ofQProcess
from your code and see how it behaves. - Outside of any code of your own in terminal, try something like
rsync ... < input_file > out_file 2> err_file
This should put you in something like the default
QProcess
redirection situation to play with/understand howrsync
behaves. - a convenience macro
-
@BwvB
The wayrsync
(and most of the cmd tools dealing with passwords, e.g.sudo
orpasswd
) are implemented, prevents intercepting the password input. If you flip it around and assume that it would actually possible to react inQProcess
, this would be the ideal way to find out someone's password.OTOH, if you
rsync
into a remote machine, you can pretty much rely on the fact that a password is needed.sshpass
is a handy, non interactive tool for such usecases.I often use:
/usr/bin/rsync -ratlz --rsh="/usr/bin/sshpass -p password ssh -o StrictHostKeyChecking=no -l username" localDirectory host:/remote/path
wrote on 17 Dec 2023, 09:49 last edited by@Axel-Spoerl Ah thanks. This answer helps me understanding the situation.
-
@Pl45m4 That is not my question. I want to capture the response from the process and act on that. But I don't understand how to do this with QProcess.
I am always interested in anything
QProcess
, especially under Linux :)First a couple of preliminaries about your code. I am "surprised" it works given that you do not create any kind of
QCoreApplication
. It is also not a great idea (at least while you have a problem) usingQProcess
signal/slots at the same time as callingwaitForFinished()
in case the way that works interferes; you might try without that (you would need to runreturn app.exec()
). But neither of these may affect the behaviour.I believe/suspect the "hang" you see is from where the
rsync
's input is attached to. See enum QProcess::InputChannelMode. The default isQProcess::ManagedInputChannel
:QProcess manages the input of the running process. This is the default input channel mode of QProcess.
You should change via void QProcess::setInputChannelMode(QProcess::InputChannelMode mode) to
QProcess::ForwardedInputChannel
:QProcess forwards the input of the main process onto the running process. The child process reads its standard input from the same source as the main process. Note that the main process must not try to read its standard input while the child process is running.
This should mean that if your Qt calling program stdin is attached to a terminal then the
rsync
can read from that terminal too.You may even now see the "prompt" message arrive in your code via
readRead...()
.rsync
code may well have code like:if isatty(0) fprintf(stderr, "Enter password:"); read(0, password, sizeof(password));
so the message part would require stdin to be attached to a terminal, not a pipe/redirection, as it is with
ManagedInputChannel
.It may do this by calling getpass(3):
The getpass() function opens /dev/tty (the controlling terminal
of the process), outputs the string prompt, turns off echoing,
reads one line (the "password"), restores the terminal state and
closes /dev/tty again.At the moment it is expecting its input from the calling Qt app code. Without the
ForwardedInputChannel
you may find that you can goproc.write("Password\n")
, probably in response to thestarted
signal, to send the password programmatically from the calling app.Finally, to test this all outside of Qt/
QProcess
you can try:- Use
system("rsync ...")
instead ofQProcess
from your code and see how it behaves. - Outside of any code of your own in terminal, try something like
rsync ... < input_file > out_file 2> err_file
This should put you in something like the default
QProcess
redirection situation to play with/understand howrsync
behaves. - Use
-
wrote on 18 Dec 2023, 09:02 last edited by
If you really want to figure out if rsync requires a password or not, you need to parse the standard output of rsync and search for the prompt that asks for a password and then react accordingly. Connecting to
readyReadStandardOutput
would be a step in the right direction. It also means you have to fully embrace asynchronous programming. Everything afterqproc.start(...)
has to go somewhere else. You cannot useqproc.waitForFinished()
as it blocks the event queue (and you will not receive thereadyReadStandardOutput
signal before everything else has finished. And you certainly need to run an event loop! -
If you really want to figure out if rsync requires a password or not, you need to parse the standard output of rsync and search for the prompt that asks for a password and then react accordingly. Connecting to
readyReadStandardOutput
would be a step in the right direction. It also means you have to fully embrace asynchronous programming. Everything afterqproc.start(...)
has to go somewhere else. You cannot useqproc.waitForFinished()
as it blocks the event queue (and you will not receive thereadyReadStandardOutput
signal before everything else has finished. And you certainly need to run an event loop!wrote on 18 Dec 2023, 09:32 last edited by JonB@SimonSchroeder
Although possible, I doubt thatrsync
sends the password prompt to standard output. More likely standard error or directly to controlling terminal. And if you read my post you will see that it is possible/likely that it only prompts if stdin is a terminal.I think you are incorrect about the event loop and the signals when
QProcess::waitForFinished()
is called. Although I said it might be best not to use this if attaching signals, if you test it I think you will nonetheless find that all the signals work. OP's output already shows all the other signals are being delivered, I think there is nothing to read fromrsync
.waitForFinished()
does not block the event queue, it runs its own loop.
1/9