How to handle a password request when using QProcess for an rsync call?
-
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.outis the default executable name on a linux platform.- a convenience macro
TRC_MSGLNis defined to produce line-numbered feedback - A
QProcessinstance 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=9Apparently, 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
$QTDIRrefers 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.outis the default executable name on a linux platform.- a convenience macro
TRC_MSGLNis defined to produce line-numbered feedback - A
QProcessinstance 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=9Apparently, 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
$QTDIRrefers to the qt installation directory.You help is appreciated!
Bertwim
@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
rsyncdocumentation.
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
rsyncdocumentation.
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.sudoorpasswd) 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
rsyncinto a remote machine, you can pretty much rely on the fact that a password is needed.sshpassis 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.outis the default executable name on a linux platform.- a convenience macro
TRC_MSGLNis defined to produce line-numbered feedback - A
QProcessinstance 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=9Apparently, 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
$QTDIRrefers to the qt installation directory.You help is appreciated!
Bertwim
@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) usingQProcesssignal/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
rsynccan read from that terminal too.You may even now see the "prompt" message arrive in your code via
readRead...().rsynccode 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
ForwardedInputChannelyou may find that you can goproc.write("Password\n"), probably in response to thestartedsignal, to send the password programmatically from the calling app.Finally, to test this all outside of Qt/
QProcessyou can try:- Use
system("rsync ...")instead ofQProcessfrom 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_fileThis should put you in something like the default
QProcessredirection situation to play with/understand howrsyncbehaves. - a convenience macro
-
@BwvB
The wayrsync(and most of the cmd tools dealing with passwords, e.g.sudoorpasswd) 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
rsyncinto a remote machine, you can pretty much rely on the fact that a password is needed.sshpassis 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@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) usingQProcesssignal/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
rsynccan read from that terminal too.You may even now see the "prompt" message arrive in your code via
readRead...().rsynccode 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
ForwardedInputChannelyou may find that you can goproc.write("Password\n"), probably in response to thestartedsignal, to send the password programmatically from the calling app.Finally, to test this all outside of Qt/
QProcessyou can try:- Use
system("rsync ...")instead ofQProcessfrom 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_fileThis should put you in something like the default
QProcessredirection situation to play with/understand howrsyncbehaves. - Use
-
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
readyReadStandardOutputwould 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 thereadyReadStandardOutputsignal 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
readyReadStandardOutputwould 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 thereadyReadStandardOutputsignal before everything else has finished. And you certainly need to run an event loop!@SimonSchroeder
Although possible, I doubt thatrsyncsends 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.