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;
}
void process_commands() {
CLI::App app{"pcloudcc-lneely"};
app.fallthrough();
app.footer("Type 'help' or '?' for a list of supported commands.");
void setup_app(CLI::App *app) {
app->fallthrough();
app->footer("Type 'help' or '?' for a list of supported 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
<< " help(?): Show this help message" << std::endl
<< " crypto(c):" << std::endl
@ -104,13 +103,13 @@ void process_commands() {
<< " finalize(f): Kill daemon and quit" << 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();
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();
// 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;
size_t errm_size = 0;
rpc_call(FINALIZE, "", &errm, &errm_size);
@ -119,15 +118,15 @@ void process_commands() {
exit(0);
});
app.add_subcommand("quit", "Quit the program")->alias("q")->callback([] {
app->add_subcommand("quit", "Quit the program")->alias("q")->callback([] {
exit(0);
});
// crypto start
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->callback([&] {
start_crypto_cmd->callback([] {
char *errm = NULL;
size_t 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([] {
char *errm = NULL;
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;
if (errm) { free(errm); }
return result;
@ -187,9 +186,10 @@ void process_commands() {
} else {
std::cout << "No synchronized folders found." << std::endl;
}
free(flist);
if(flist) { free(flist); }
} else {
std::cout << "failed to read folder list from shm" << std::endl;
if(flist) { free(flist); }
return -1;
}
if(errm) { free(errm); }
@ -198,10 +198,10 @@ void process_commands() {
// sync add
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("remotepath", remotepath, "Remote Path")->required();
sync_add_cmd->callback([&] {
sync_add_cmd->callback([] {
char *errm = NULL;
size_t errmsz = 0;
@ -222,9 +222,9 @@ void process_commands() {
// sync remove
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->callback([&] {
sync_remove_cmd->callback([] {
char *errm = NULL;
size_t errmsz = 0;
const char *folderid = syncrm_fid.c_str();
@ -239,6 +239,23 @@ void process_commands() {
if(errm) { free(errm); }
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
using_history();
@ -266,7 +283,7 @@ void process_commands() {
args.push_back(arg);
}
invs << "Invalid command: '" << "line"
invs << "Invalid command: '" << line
<< "'. Type 'help' or '?' to get a list of valid commands.";
if (!args.empty()) {
try {

View File

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

View File

@ -58,31 +58,21 @@ int main(int argc, char **argv) {
try {
po::options_description desc("Allowed options");
desc.add_options()("help,h", "Show this help message.")(
"username,u", po::value<std::string>(&username),
"pCloud account name.")(
"password,p", po::bool_switch(&passwordsw),
"Ask for pCloud account password.")(
"tfa_code,t", po::value<std::string>(&tfa_code),
"pCloud tfa code")(
"trusted_device,r", po::bool_switch(&trusted_device),
"Trust this device.")(
"crypto,c", po::bool_switch(&crypto),
"Ask for crypto password.")(
"passascrypto,y", po::value<std::string>(),
"User password is the same as crypto password.")(
"daemonize,d", po::bool_switch(&daemon),
"Run the process as a background daemon.")(
"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.");
desc.add_options()
("help,h", "Show this help message.")
("username,u", po::value<std::string>(&username), "pCloud account name.")
("password,p", po::bool_switch(&passwordsw), "Ask for pCloud account password.")
("tfa_code,t", po::value<std::string>(&tfa_code), "pCloud tfa code")
("trusted_device,r", po::bool_switch(&trusted_device), "Trust this device.")
("crypto,c", po::bool_switch(&crypto), "Ask for crypto password.")
("passascrypto,y", po::value<std::string>(), "User password is the same as crypto password.")
("daemonize,d", po::bool_switch(&daemon), "Run the process as a background daemon.")
("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.")
("command,c", po::value<std::string>(), "Execute a single command and exit.")
("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::positional_options_description p;
@ -102,6 +92,20 @@ int main(int argc, char **argv) {
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"))) {
std::cout << "Username option is required, specify with "
<< "-u or --username." << std::endl;

View File

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

View File

@ -21,6 +21,7 @@ key_t pshm_get_key() {
return (key_t)-1;
}
snprintf(path, sizeof(path), "%s/.pcloud/data.db", home);
free(home);
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
// that exceed that length, and writes the trimmed key and length to keydata
// 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;
unsigned char tag;
unsigned char *p = (unsigned char *)keydata;
const unsigned char *end;
int ret;
@ -621,17 +620,16 @@ static unsigned char* trim_der_key(const unsigned char *keydata, size_t *keylen)
end = keydata + *keylen;
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE);
if (ret != 0) {
return NULL;
return;
}
header_size = p - keydata;
correct_len = header_size + len;
trimmed = malloc(correct_len);
if (!trimmed) return NULL;
if (!trimmed) return;
memcpy(trimmed, keydata, correct_len);
memcpy(keydata, trimmed, correct_len);
free(trimmed);
*keylen = correct_len;
return trimmed;
}
psync_rsa_privatekey_t prsa_load_private(const unsigned char *keydata, size_t keylen) {
@ -639,8 +637,8 @@ psync_rsa_privatekey_t prsa_load_private(const unsigned char *keydata, size_t ke
mbedtls_rsa_context *rsa;
int ret;
trim_der_key(keydata, &keylen);
trim_der_key((unsigned char *)keydata, &keylen);
mbedtls_pk_init(&ctx);
ret = mbedtls_pk_parse_key(&ctx, keydata, keylen, NULL, 0, rng_get, &rng);
if(unlikely(ret)) {