support non-interactive command processing

- app is now configured in setup_app function
- added process_command() to api
- support piped input and -c argument
- fix memleaks
This commit is contained in:
Levi Neely 2025-03-09 19:16:33 +01:00
parent dbf3e11473
commit 7bc9078e00
6 changed files with 77 additions and 53 deletions

View File

@ -85,13 +85,12 @@ static char** command_completion(const char* text, int start, int end) {
return nullptr; return nullptr;
} }
void process_commands() { void setup_app(CLI::App *app) {
CLI::App app{"pcloudcc-lneely"}; app->fallthrough();
app.fallthrough(); app->footer("Type 'help' or '?' for a list of supported commands.");
app.footer("Type 'help' or '?' for a list of supported commands.");
// top-level commands // top-level commands
app.add_subcommand("help", "Show help")->alias("?")->callback([] { app->add_subcommand("help", "Show help")->alias("?")->callback([] {
std::cout << "Supported commands are:" << std::endl std::cout << "Supported commands are:" << std::endl
<< " help(?): Show this help message" << std::endl << " help(?): Show this help message" << std::endl
<< " crypto(c):" << std::endl << " crypto(c):" << std::endl
@ -104,13 +103,13 @@ void process_commands() {
<< " finalize(f): Kill daemon and quit" << std::endl << " finalize(f): Kill daemon and quit" << std::endl
<< " quit(q): Exit this program" << std::endl; << " quit(q): Exit this program" << std::endl;
}); });
auto crypto_cmd = app.add_subcommand("crypto", "Crypto-related commands")->alias("c"); auto crypto_cmd = app->add_subcommand("crypto", "Crypto-related commands")->alias("c");
crypto_cmd->require_subcommand(); crypto_cmd->require_subcommand();
auto sync_cmd = app.add_subcommand("sync", "Sync-related commands")->alias("s"); auto sync_cmd = app->add_subcommand("sync", "Sync-related commands")->alias("s");
sync_cmd->require_subcommand(); sync_cmd->require_subcommand();
// finalize command // finalize command
app.add_subcommand("finalize", "Finalize and exit")->alias("f")->callback([] { app->add_subcommand("finalize", "Finalize and exit")->alias("f")->callback([] {
char *errm = NULL; char *errm = NULL;
size_t errm_size = 0; size_t errm_size = 0;
rpc_call(FINALIZE, "", &errm, &errm_size); rpc_call(FINALIZE, "", &errm, &errm_size);
@ -119,15 +118,15 @@ void process_commands() {
exit(0); exit(0);
}); });
app.add_subcommand("quit", "Quit the program")->alias("q")->callback([] { app->add_subcommand("quit", "Quit the program")->alias("q")->callback([] {
exit(0); exit(0);
}); });
// crypto start // crypto start
auto start_crypto_cmd = crypto_cmd->add_subcommand("start", "Start crypto"); auto start_crypto_cmd = crypto_cmd->add_subcommand("start", "Start crypto");
std::string start_crypto_pwd; static std::string start_crypto_pwd;
start_crypto_cmd->add_option("password", start_crypto_pwd, "Crypto password")->required(); start_crypto_cmd->add_option("password", start_crypto_pwd, "Crypto password")->required();
start_crypto_cmd->callback([&] { start_crypto_cmd->callback([] {
char *errm = NULL; char *errm = NULL;
size_t errm_size = 0; size_t errm_size = 0;
if(int result = rpc_call(STARTCRYPTO, start_crypto_pwd.c_str(), &errm, &errm_size) != 0) { if(int result = rpc_call(STARTCRYPTO, start_crypto_pwd.c_str(), &errm, &errm_size) != 0) {
@ -144,7 +143,7 @@ void process_commands() {
crypto_cmd->add_subcommand("stop", "Stop crypto")->callback([] { crypto_cmd->add_subcommand("stop", "Stop crypto")->callback([] {
char *errm = NULL; char *errm = NULL;
size_t errm_size = 0; size_t errm_size = 0;
if(int result = rpc_call(STOPCRYPTO, "", &errm, &errm_size) == 0) { if(int result = rpc_call(STOPCRYPTO, "", &errm, &errm_size) != 0) {
std::cout << "Stop Crypto failed: "<< (errm ? errm : "no message") << std::endl; std::cout << "Stop Crypto failed: "<< (errm ? errm : "no message") << std::endl;
if (errm) { free(errm); } if (errm) { free(errm); }
return result; return result;
@ -187,9 +186,10 @@ void process_commands() {
} else { } else {
std::cout << "No synchronized folders found." << std::endl; std::cout << "No synchronized folders found." << std::endl;
} }
free(flist); if(flist) { free(flist); }
} else { } else {
std::cout << "failed to read folder list from shm" << std::endl; std::cout << "failed to read folder list from shm" << std::endl;
if(flist) { free(flist); }
return -1; return -1;
} }
if(errm) { free(errm); } if(errm) { free(errm); }
@ -198,10 +198,10 @@ void process_commands() {
// sync add // sync add
auto sync_add_cmd = sync_cmd->add_subcommand("add", "Add sync folder"); auto sync_add_cmd = sync_cmd->add_subcommand("add", "Add sync folder");
std::string localpath, remotepath; static std::string localpath, remotepath;
sync_add_cmd->add_option("localpath", localpath, "Local Path")->required(); sync_add_cmd->add_option("localpath", localpath, "Local Path")->required();
sync_add_cmd->add_option("remotepath", remotepath, "Remote Path")->required(); sync_add_cmd->add_option("remotepath", remotepath, "Remote Path")->required();
sync_add_cmd->callback([&] { sync_add_cmd->callback([] {
char *errm = NULL; char *errm = NULL;
size_t errmsz = 0; size_t errmsz = 0;
@ -222,9 +222,9 @@ void process_commands() {
// sync remove // sync remove
auto sync_remove_cmd = sync_cmd->add_subcommand("remove", "Remove sync folder (use ls to get folder ID)")->alias("rm"); auto sync_remove_cmd = sync_cmd->add_subcommand("remove", "Remove sync folder (use ls to get folder ID)")->alias("rm");
std::string syncrm_fid; static std::string syncrm_fid;
sync_remove_cmd->add_option("folderid", syncrm_fid, "Folder ID")->required(); sync_remove_cmd->add_option("folderid", syncrm_fid, "Folder ID")->required();
sync_remove_cmd->callback([&] { sync_remove_cmd->callback([] {
char *errm = NULL; char *errm = NULL;
size_t errmsz = 0; size_t errmsz = 0;
const char *folderid = syncrm_fid.c_str(); const char *folderid = syncrm_fid.c_str();
@ -239,6 +239,23 @@ void process_commands() {
if(errm) { free(errm); } if(errm) { free(errm); }
return 0; return 0;
}); });
}
int process_command(const std::string &command) {
CLI::App app = CLI::App{"pcloudcc-lneely"};
setup_app(&app);
try {
app.parse(command);
return 0;
} catch (const CLI::ParseError &e) {
std::cerr << "Invalid command: '" << command << "'" << std::endl;
return 1;
}
}
void process_commands() {
CLI::App app = CLI::App{"pcloudcc-lneely"};
setup_app(&app);
// enable command history and auto-completion // enable command history and auto-completion
using_history(); using_history();
@ -266,7 +283,7 @@ void process_commands() {
args.push_back(arg); args.push_back(arg);
} }
invs << "Invalid command: '" << "line" invs << "Invalid command: '" << line
<< "'. Type 'help' or '?' to get a list of valid commands."; << "'. Type 'help' or '?' to get a list of valid commands.";
if (!args.empty()) { if (!args.empty()) {
try { try {

View File

@ -29,10 +29,13 @@
#ifndef CONTROL_TOOLS_H #ifndef CONTROL_TOOLS_H
#define CONTROL_TOOLS_H #define CONTROL_TOOLS_H
#include <string>
namespace control_tools { namespace control_tools {
void process_commands(); void process_commands();
int daemonize(bool do_commands); int daemonize(bool do_commands);
int process_command(const std::string &command);
} // namespace control_tools } // namespace control_tools

View File

@ -58,31 +58,21 @@ int main(int argc, char **argv) {
try { try {
po::options_description desc("Allowed options"); po::options_description desc("Allowed options");
desc.add_options()("help,h", "Show this help message.")( desc.add_options()
"username,u", po::value<std::string>(&username), ("help,h", "Show this help message.")
"pCloud account name.")( ("username,u", po::value<std::string>(&username), "pCloud account name.")
"password,p", po::bool_switch(&passwordsw), ("password,p", po::bool_switch(&passwordsw), "Ask for pCloud account password.")
"Ask for pCloud account password.")( ("tfa_code,t", po::value<std::string>(&tfa_code), "pCloud tfa code")
"tfa_code,t", po::value<std::string>(&tfa_code), ("trusted_device,r", po::bool_switch(&trusted_device), "Trust this device.")
"pCloud tfa code")( ("crypto,c", po::bool_switch(&crypto), "Ask for crypto password.")
"trusted_device,r", po::bool_switch(&trusted_device), ("passascrypto,y", po::value<std::string>(), "User password is the same as crypto password.")
"Trust this device.")( ("daemonize,d", po::bool_switch(&daemon), "Run the process as a background daemon.")
"crypto,c", po::bool_switch(&crypto), ("commands ,o", po::bool_switch(&commands), "Keep parent process alive and process commands. ")
"Ask for crypto password.")( ("mountpoint,m", po::value<std::string>(), "Specify where pCloud filesystem is mounted.")
"passascrypto,y", po::value<std::string>(), ("commands_only,k", po::bool_switch(&commands_only), "Open command prompt to interact with running daemon.")
"User password is the same as crypto password.")( ("command,c", po::value<std::string>(), "Execute a single command and exit.")
"daemonize,d", po::bool_switch(&daemon), ("newuser,n", po::bool_switch(&newuser), "Register a new pCloud user account.")
"Run the process as a background daemon.")( ("savepassword,s", po::bool_switch(&save_pass), "Save user password in the database.");
"commands ,o", po::bool_switch(&commands),
"Keep parent process alive and process commands. ")(
"mountpoint,m", po::value<std::string>(),
"Specify where pCloud filesystem is mounted.")(
"commands_only,k", po::bool_switch(&commands_only),
"Open command prompt to interact with running daemon.")(
"newuser,n", po::bool_switch(&newuser),
"Register a new pCloud user account.")(
"savepassword,s", po::bool_switch(&save_pass),
"Save user password in the database.");
po::command_line_parser parser{argc, argv}; po::command_line_parser parser{argc, argv};
po::positional_options_description p; po::positional_options_description p;
@ -102,6 +92,20 @@ int main(int argc, char **argv) {
exit(0); exit(0);
} }
bool has_piped_input = !isatty(STDIN_FILENO);
if (has_piped_input && !vm.count("help")) {
std::string line;
if (std::getline(std::cin, line) && !line.empty()) {
return ct::process_command(line);
}
}
if (vm.count("command")) {
std::string command = vm["command"].as<std::string>();
return ct::process_command(command);
}
if ((!vm.count("username"))) { if ((!vm.count("username"))) {
std::cout << "Username option is required, specify with " std::cout << "Username option is required, specify with "
<< "-u or --username." << std::endl; << "-u or --username." << std::endl;

View File

@ -191,11 +191,12 @@ char *ppath_private(char *name) {
if (!path) if (!path)
return NULL; return NULL;
rpath = psync_strcat(path, "/", name, NULL); rpath = psync_strcat(path, "/", name, NULL);
free(path);
if (stat(rpath, &st) && mkdir(path, PSYNC_DEFAULT_POSIX_FOLDER_MODE)) { if (stat(rpath, &st) && mkdir(path, PSYNC_DEFAULT_POSIX_FOLDER_MODE)) {
psync_free(rpath); psync_free(rpath);
free(path);
return NULL; return NULL;
} }
free(path);
return rpath; return rpath;
} }

View File

@ -21,6 +21,7 @@ key_t pshm_get_key() {
return (key_t)-1; return (key_t)-1;
} }
snprintf(path, sizeof(path), "%s/.pcloud/data.db", home); snprintf(path, sizeof(path), "%s/.pcloud/data.db", home);
free(home);
return ftok(path, 'A'); return ftok(path, 'A');
} }

View File

@ -610,9 +610,8 @@ psync_rsa_publickey_t prsa_load_public(const unsigned char *keydata,
// function gets the actual key length from the ASN.1 header, trims any bytes // function gets the actual key length from the ASN.1 header, trims any bytes
// that exceed that length, and writes the trimmed key and length to keydata // that exceed that length, and writes the trimmed key and length to keydata
// and keylen. // and keylen.
static unsigned char* trim_der_key(const unsigned char *keydata, size_t *keylen) { static void trim_der_key(unsigned char *keydata, size_t *keylen) {
size_t len, header_size, correct_len; size_t len, header_size, correct_len;
unsigned char tag;
unsigned char *p = (unsigned char *)keydata; unsigned char *p = (unsigned char *)keydata;
const unsigned char *end; const unsigned char *end;
int ret; int ret;
@ -621,17 +620,16 @@ static unsigned char* trim_der_key(const unsigned char *keydata, size_t *keylen)
end = keydata + *keylen; end = keydata + *keylen;
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE);
if (ret != 0) { if (ret != 0) {
return NULL; return;
} }
header_size = p - keydata; header_size = p - keydata;
correct_len = header_size + len; correct_len = header_size + len;
trimmed = malloc(correct_len); trimmed = malloc(correct_len);
if (!trimmed) return NULL; if (!trimmed) return;
memcpy(trimmed, keydata, correct_len); memcpy(trimmed, keydata, correct_len);
memcpy(keydata, trimmed, correct_len);
free(trimmed);
*keylen = correct_len; *keylen = correct_len;
return trimmed;
} }
psync_rsa_privatekey_t prsa_load_private(const unsigned char *keydata, size_t keylen) { psync_rsa_privatekey_t prsa_load_private(const unsigned char *keydata, size_t keylen) {
@ -639,7 +637,7 @@ psync_rsa_privatekey_t prsa_load_private(const unsigned char *keydata, size_t ke
mbedtls_rsa_context *rsa; mbedtls_rsa_context *rsa;
int ret; int ret;
trim_der_key(keydata, &keylen); trim_der_key((unsigned char *)keydata, &keylen);
mbedtls_pk_init(&ctx); mbedtls_pk_init(&ctx);
ret = mbedtls_pk_parse_key(&ctx, keydata, keylen, NULL, 0, rng_get, &rng); ret = mbedtls_pk_parse_key(&ctx, keydata, keylen, NULL, 0, rng_get, &rng);