urbackup_backend/clientctl/main.cpp
2021-06-20 20:58:50 +02:00

1633 lines
36 KiB
C++

/*************************************************************************
* UrBackup - Client/Server backup system
* Copyright (C) 2011-2017 Martin Raiber
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include "Connector.h"
#include "../stringtools.h"
#include "../tclap/CmdLine.h"
#include "json/json.h"
#include "../urbackupcommon/os_functions.h"
#ifndef _WIN32
#include <sys/ioctl.h>
#include <unistd.h>
#include "../config.h"
#include <time.h>
#include <sys/time.h>
#define PWFILE VARDIR "/urbackup/pw.txt"
#define PWFILE_CHANGE VARDIR "/urbackup/pw_change.txt"
#else
#include <Windows.h>
#define PACKAGE_VERSION "$version_full_numeric$"
#define VARDIR ""
#define PWFILE "pw.txt"
#define PWFILE_CHANGE "pw_change.txt"
#endif
#ifdef __MACH__
#include <mach/clock.h>
#include <mach/mach.h>
#endif
void wait(unsigned int ms)
{
#ifdef _WIN32
Sleep(ms);
#else
usleep(ms * 1000);
#endif
}
int64 getTimeMS()
{
#ifdef _WIN32
return GetTickCount64();
#else
//return (unsigned int)(((double)clock()/(double)CLOCKS_PER_SEC)*1000.0);
/*
boost::xtime xt;
boost::xtime_get(&xt, boost::TIME_UTC);
static boost::int_fast64_t start_t=xt.sec;
xt.sec-=start_t;
unsigned int t=xt.sec*1000+(unsigned int)((double)xt.nsec/1000000.0);
return t;*/
/*timeval tp;
gettimeofday(&tp, NULL);
static long start_t=tp.tv_sec;
tp.tv_sec-=start_t;
return tp.tv_sec*1000+tp.tv_usec/1000;
*/
#ifdef __APPLE__
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
return static_cast<int64>(mts.tv_sec) * 1000 + mts.tv_nsec / 1000000;
#else
timespec tp;
if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0)
{
timeval tv;
gettimeofday(&tv, nullptr);
static long start_t = tv.tv_sec;
tv.tv_sec -= start_t;
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
return static_cast<int64>(tp.tv_sec) * 1000 + tp.tv_nsec / 1000000;
#endif //__APPLE__
#endif
}
const std::string cmdline_version = PACKAGE_VERSION;
void show_version()
{
std::cout << "UrBackup Client Controller v" << cmdline_version << std::endl;
std::cout << "Copyright (C) 2011-2019 Martin Raiber" << std::endl;
std::cout << "This is free software; see the source for copying conditions. There is NO"<< std::endl;
std::cout << "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."<< std::endl;
}
void action_help(std::string cmd)
{
std::cout << std::endl;
std::cout << "USAGE:" << std::endl;
std::cout << std::endl;
std::cout << "\t" << cmd << " [--help] [--version] <command> [<args>]" << std::endl;
std::cout << std::endl;
std::cout << "Get specific command help with " << cmd << " <command> --help" << std::endl;
std::cout << std::endl;
std::cout << "\t" << cmd << " start" << std::endl;
std::cout << "\t\t" "Start an incremental/full image/file backup" << std::endl;
std::cout << std::endl;
std::cout << "\t" << cmd << " status" << std::endl;
std::cout << "\t\t" "Get current backup status" << std::endl;
std::cout << std::endl;
std::cout << "\t" << cmd << " browse" << std::endl;
std::cout << "\t\t" "Browse backups and files/folders in backups" << std::endl;
std::cout << std::endl;
std::cout << "\t" << cmd << " restore-start" << std::endl;
std::cout << "\t\t" "Restore files/folders from backup" << std::endl;
std::cout << std::endl;
std::cout << "\t" << cmd << " set-settings" << std::endl;
std::cout << "\t\t" "Set backup settings" << std::endl;
std::cout << std::endl;
std::cout << "\t" << cmd << " reset-keep" << std::endl;
std::cout << "\t\t" "Reset keeping files during incremental backups" << std::endl;
std::cout << std::endl;
std::cout << "\t" << cmd << " add-backupdir" << std::endl;
std::cout << "\t\t" "Add new directory to backup set" << std::endl;
std::cout << std::endl;
std::cout << "\t" << cmd << " list-backupdirs" << std::endl;
std::cout << "\t\t" "List directories that are being backed up" << std::endl;
std::cout << std::endl;
std::cout << "\t" << cmd << " remove-backupdir" << std::endl;
std::cout << "\t\t" "Remove directory from backup set" << std::endl;
std::cout << std::endl;
}
const size_t c_speed_size = 15;
const size_t c_max_l_length = 80;
size_t get_terminal_width()
{
#ifndef _WIN32
struct winsize w;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != 0)
{
return c_max_l_length;
}
else
{
return w.ws_col;
}
#else
return c_max_l_length;
#endif
}
void draw_progress(int pc_done, double speed_bpms, int64 done_bytes, int64 total_bytes, std::string details, int detail_pc)
{
static size_t max_line_length = 0;
size_t term_width = get_terminal_width();
size_t draw_segments = term_width / 3;
size_t segments = (size_t)(pc_done / 100.*draw_segments);
std::string toc = "\r[";
for (size_t i = 0; i<draw_segments; ++i)
{
if (i<segments)
{
toc += "=";
}
else if (i == segments)
{
toc += ">";
}
else
{
toc += " ";
}
}
std::string speed_str = PrettyPrintSpeed(static_cast<size_t>(speed_bpms*1000.0));
while (speed_str.size()<c_speed_size)
speed_str += " ";
std::string pcdone = convert(pc_done);
if (pcdone.size() == 1)
pcdone = " " + pcdone;
if (!details.empty() && detail_pc >= 0)
{
std::string detailpc_s = convert(detail_pc);
if (detailpc_s.size() == 1)
detailpc_s = " " + detailpc_s;
details += " " + detailpc_s + "%";
}
toc += "] " + pcdone + "% ";
if (total_bytes >= 0)
{
toc += PrettyPrintBytes(done_bytes) + "/" + PrettyPrintBytes(total_bytes) + " ";
}
if (speed_bpms > 0)
{
toc += "at " + speed_str + " ";
}
if (!details.empty())
{
toc += details;
}
if (toc.size() >= term_width)
toc = toc.substr(0, term_width);
if (toc.size()>max_line_length)
max_line_length = toc.size();
max_line_length = (std::min)(max_line_length, term_width);
while (toc.size()<max_line_length)
toc += " ";
std::cout << toc;
std::cout.flush();
}
typedef int(*action_fun)(std::vector<std::string> args);
class PwClientCmd
{
public:
PwClientCmd(TCLAP::CmdLine& cmd, bool change)
: cmd(cmd),
pw_file_arg("p", "pw-file",
"Use password in file",
false, change ? PWFILE_CHANGE : PWFILE, "path", cmd),
client_arg("c", "client",
"Start backup on this client",
false, "127.0.0.1", "hostname/IP", cmd),
change(change)
{
}
void wait(int64 maxtimems)
{
int64 starttime = getTimeMS();
do
{
if (FileExists(pw_file_arg.getValue()))
{
return;
}
::wait(100);
} while (getTimeMS() - starttime < maxtimems);
}
bool set()
{
if (change)
{
Connector::setPWFileChange(pw_file_arg.getValue());
}
else
{
Connector::setPWFile(pw_file_arg.getValue());
}
Connector::setClient(client_arg.getValue());
if (trim(getFile(pw_file_arg.getValue())).empty())
{
if (errno != 0)
{
perror("urbackupclientctl");
}
std::cerr << "Cannot read backend password from " << pw_file_arg.getValue() << std::endl;
return false;
}
else
{
return true;
}
}
private:
TCLAP::CmdLine& cmd;
bool change;
TCLAP::ValueArg<std::string> pw_file_arg;
TCLAP::ValueArg<std::string> client_arg;
};
std::vector<int64> get_current_processes()
{
SStatusDetails sd = Connector::getStatusDetails();
std::vector<int64> ret;
for (size_t i = 0; i < sd.running_processes.size(); ++i)
{
ret.push_back(sd.running_processes[i].process_id);
}
return ret;
}
const std::string spinner = "|/-\\";
int64 wait_for_new_process(std::string type, const std::vector<int64>& current_processes)
{
int tries = 60;
std::string message = "Waiting for server to start backup... ";
for (int i = 0; i < tries; ++i)
{
int tries = 20;
SStatusDetails sd = Connector::getStatusDetails();
while (!sd.ok && tries>0)
{
--tries;
wait(100);
sd = Connector::getStatusDetails();
}
if (!sd.ok)
{
return 0;
}
for (size_t j = 0; j < sd.running_processes.size(); ++j)
{
if (sd.running_processes[j].action == type)
{
if (std::find(current_processes.begin(), current_processes.end(), sd.running_processes[j].process_id) == current_processes.end())
{
if (i > 0)
{
std::cout << "\r" << message << "done" << std::endl;
}
return sd.running_processes[j].process_id;
}
}
}
std::cout << "\r" << message << spinner[i%spinner.size()];
std::cout.flush();
wait(1000);
}
std::cout << "\r" << message << "done" << std::endl;
return 0;
}
int follow_status(bool restore, int64 process_id)
{
bool found_once = false;
size_t preparing_idx = 0;
size_t waiting_for_id_idx = 0;
std::string waiting_msg;
std::string preparing_msg;
while (true)
{
int tries = 20;
SStatusDetails status = Connector::getStatusDetails();
while (!status.ok && tries>0)
{
--tries;
wait(100);
status = Connector::getStatusDetails();
}
if (!status.ok)
{
std::cerr << "Could not get status from backend" << std::endl;
return 3;
}
bool found = false;
for (size_t i = 0; i < status.running_processes.size(); ++i)
{
SRunningProcess& proc = status.running_processes[i];
if (status.running_processes[i].process_id == process_id)
{
if (!found_once && waiting_for_id_idx>0)
{
std::cout << "\r" << waiting_msg << " done" << std::endl;
}
found_once = true;
if (proc.percent_done < 0)
{
if (restore)
{
preparing_msg = "Preparing restore... ";
std::cout << "\r" << preparing_msg << spinner[preparing_idx%spinner.size()];
}
else
{
preparing_msg = "Preparing... ";
std::cout << "\r" << preparing_msg << spinner[preparing_idx%spinner.size()];
}
std::cout.flush();
++preparing_idx;
}
else
{
if (preparing_idx > 0)
{
std::cout << "\r" << preparing_msg << "done" << std::endl;
preparing_idx = 0;
}
draw_progress(proc.percent_done, proc.speed_bpms, proc.done_bytes, proc.total_bytes, proc.details, proc.detail_pc);
}
found = true;
break;
}
}
if (!found)
{
for (size_t i = 0; i < status.finished_processes.size(); ++i)
{
if (status.finished_processes[i].id == process_id)
{
if (status.finished_processes[i].success)
{
std::cout << std::endl;
if (restore)
{
std::cout << "Restore completed successfully." << std::endl;
}
else
{
std::cout << "Completed successfully." << std::endl;
}
return 0;
}
else
{
std::cout << std::endl;
if (restore)
{
std::cerr << "Restore failed." << std::endl;
}
else
{
std::cerr << "Failed." << std::endl;
}
return 4;
}
}
}
if (!found_once)
{
if (restore)
{
waiting_msg = "Starting restore. Waiting for backup server... ";
std::cout << "\r" << waiting_msg << spinner[waiting_for_id_idx%spinner.size()];;
}
else
{
waiting_msg = "Waiting for process to become available... ";
std::cout << "\r" << waiting_msg << spinner[waiting_for_id_idx%spinner.size()];;
}
std::cout.flush();
++waiting_for_id_idx;
}
}
wait(1000);
}
}
int action_start(std::vector<std::string> args)
{
TCLAP::CmdLine cmd("Start an incremental/full image/file backup", ' ', cmdline_version);
TCLAP::SwitchArg incr_backup("i", "incremental", "Start incremental backup");
TCLAP::SwitchArg full_backup("f", "full", "Start full backup");
cmd.xorAdd(incr_backup, full_backup);
#ifdef _WIN32
TCLAP::SwitchArg file_backup("l", "file", "Start file backup");
TCLAP::SwitchArg image_backup("m", "image", "Start image backup");
cmd.xorAdd(file_backup, image_backup);
#endif
TCLAP::SwitchArg non_blocking_arg("b", "non-blocking",
"Do not show backup progress and block till the backup is finished but return immediately after starting it", cmd);
TCLAP::ValueArg<std::string> virtual_client_arg("v", "virtual-client",
"Virtual client name",
false, "", "client name", cmd);
PwClientCmd pw_client_cmd(cmd, false);
cmd.parse(args);
if (!pw_client_cmd.set())
{
return 3;
}
std::vector<int64> current_processes = get_current_processes();
std::string type;
int rc;
#ifdef _WIN32
if(file_backup.getValue())
{
#endif
type = full_backup.getValue() ? "FULL" : "INCR";
rc = Connector::startBackup(virtual_client_arg.getValue(), full_backup.getValue());
#ifdef _WIN32
}
else
{
type = full_backup.getValue() ? "FULLI" : "INCRI";
rc = Connector::startImage(virtual_client_arg.getValue(), full_backup.getValue());
}
#endif
if(rc==2)
{
std::cerr << "Backup is already running" << std::endl;
return 2;
}
else if(rc==1)
{
if (non_blocking_arg.getValue())
{
std::cout << "Backup started" << std::endl;
return 0;
}
else
{
int64 new_process = wait_for_new_process(type, current_processes);
if (new_process == 0)
{
std::cerr << "Timeout while waiting for server to start backup" << std::endl;
return 4;
}
return follow_status(false, new_process);
}
}
else if(rc==3)
{
std::cerr << "Error starting backup. No backup server found." << std::endl;
return 3;
}
else
{
std::cerr << "Error starting backup." << std::endl;
return 1;
}
}
int action_status(std::vector<std::string> args)
{
TCLAP::CmdLine cmd("Get current backup status", ' ', cmdline_version);
PwClientCmd pw_client_cmd(cmd, false);
TCLAP::ValueArg<int> follow_arg("f", "follow",
"Follow proccess status",
false, 0, "process id", cmd);
cmd.parse(args);
if (!pw_client_cmd.set())
{
return 3;
}
if (follow_arg.getValue() == 0)
{
std::string status = Connector::getStatusDetailsRaw();
if (!status.empty())
{
std::cout << status << std::endl;
return 0;
}
else
{
std::cerr << "Error getting status" << std::endl;
return 1;
}
}
else
{
return follow_status(false, follow_arg.getValue());
}
}
int action_browse(std::vector<std::string> args)
{
TCLAP::CmdLine cmd("Browse backups and files/folders in backups", ' ', cmdline_version);
PwClientCmd pw_client_cmd(cmd, false);
TCLAP::ValueArg<std::string> backupid_arg("b", "backupid",
"Backupid of backup in which to browse files/folders or \"last\" for last complete backup",
false, "", "id", cmd);
TCLAP::ValueArg<std::string> path_arg("d", "path",
"Path of folder/file to which to browse",
false, "", "path", cmd);
TCLAP::ValueArg<std::string> virtual_client_arg("v", "virtual-client",
"Virtual client name",
false, "", "client name", cmd);
cmd.parse(args);
if (!pw_client_cmd.set())
{
return 3;
}
if(path_arg.getValue().empty() && !backupid_arg.isSet())
{
Connector::EAccessError access_error;
std::string filebackups = Connector::getFileBackupsList(virtual_client_arg.getValue(), access_error);
if(!filebackups.empty())
{
std::cout << filebackups << std::endl;
return 0;
}
else
{
if(access_error==Connector::EAccessError_NoServer)
{
std::cerr << "Error getting file backups. No backup server found." << std::endl;
return 2;
}
else if (access_error == Connector::EAccessError_NoTokens)
{
std::cerr << "No file backup access tokens found. Did you run a file backup yet?" << std::endl;
return 3;
}
else
{
std::cerr << "Error getting file backups" << std::endl;
return 1;
}
}
}
else
{
int* pbackupid = nullptr;
int backupid = 0;
if(backupid_arg.isSet())
{
if (backupid_arg.getValue() != "last"
&& convert(atoi(backupid_arg.getValue().c_str())) != backupid_arg.getValue())
{
std::cerr << "Not a valid backupid: \"" << backupid_arg.getValue() << "\"" << std::endl;
return 3;
}
if (backupid_arg.getValue() != "last")
{
backupid = atoi(backupid_arg.getValue().c_str());
}
pbackupid = &backupid;
}
Connector::EAccessError access_error;
std::string filelist = Connector::getFileList(path_arg.getValue(), pbackupid, virtual_client_arg.getValue(), access_error);
if(!filelist.empty())
{
std::cout << filelist << std::endl;
return 0;
}
else
{
if (access_error == Connector::EAccessError_NoServer)
{
std::cerr << "Error getting file list. No backup server found." << std::endl;
return 2;
}
else if (access_error == Connector::EAccessError_NoTokens)
{
std::cerr << "No file backup access tokens found. Did you run a file backup yet?" << std::endl;
return 3;
}
else
{
std::cerr << "Error getting file list" << std::endl;
return 1;
}
}
}
}
int wait_for_restore(std::string restore_info)
{
Json::Value root;
Json::Reader reader;
if (!reader.parse(restore_info, root, false))
{
return 1;
}
if (root.get("ok", false) == false)
{
std::cerr << "Error starting restore. Errorcode: " << root.get("err", -1).asInt() << std::endl;
return 2;
}
int64 process_id = root["process_id"].asInt64();
return follow_status(true, process_id);
}
std::string remove_ending_slash(const std::string& path)
{
if (path.size() > 1
&& path[path.size() - 1] == os_file_sep()[0])
{
return path.substr(0, path.size() - 1);
}
return path;
}
int action_start_restore(std::vector<std::string> args)
{
TCLAP::CmdLine cmd("Restore files/folders from backup", ' ', cmdline_version);
PwClientCmd pw_client_cmd(cmd, false);
TCLAP::ValueArg<std::string> backupid_arg("b", "backupid",
"Backupid of backup from which to restore files/folders or \"last\" for last complete backup",
true, "", "id", cmd);
TCLAP::ValueArg<std::string> path_arg("d", "path",
"Path of folder/file to restore",
false, "", "path", cmd);
TCLAP::MultiArg<std::string> map_from_arg("m", "map-from",
"Map from local output path of folders/files to a different local path",
false, "path", cmd);
TCLAP::MultiArg<std::string> map_to_arg("t", "map-to",
"Map to local output path of folders/files to a different local path",
false, "path", cmd);
TCLAP::SwitchArg no_remove_arg("n", "no-remove",
"Do not remove files/directories not in backup", cmd);
TCLAP::SwitchArg consider_other_fs_arg("o", "consider-other-fs",
"Consider other file systems when removing files/directories not in backup", cmd);
TCLAP::SwitchArg non_blocking_arg("l", "non-blocking",
"Do not show restore progress and block till the restore is finished but return immediately after starting it", cmd);
TCLAP::SwitchArg no_follow_symlinks("s", "no-follow-symlinks",
"Do not follow symlinks outside of restored path during restore", cmd);
TCLAP::ValueArg<std::string> virtual_client_arg("v", "virtual-client",
"Virtual client name",
false, "", "client name", cmd);
cmd.parse(args);
if (map_from_arg.getValue().size() != map_to_arg.getValue().size())
{
std::cerr << "There need to be an equal amount of -m/--map-from and -t/--map-to arguments" << std::endl;
return 2;
}
if (!pw_client_cmd.set())
{
return 3;
}
if (backupid_arg.getValue() != "last"
&& convert(atoi(backupid_arg.getValue().c_str())) != backupid_arg.getValue())
{
std::cerr << "Not a valid backupid: \"" << backupid_arg.getValue() << "\"" << std::endl;
return 2;
}
std::vector<SPathMap> path_map;
for (size_t i = 0; i < map_from_arg.getValue().size(); ++i)
{
SPathMap new_pm;
new_pm.source = remove_ending_slash(map_from_arg.getValue()[i]);
new_pm.target = remove_ending_slash(map_to_arg.getValue()[i]);
if (new_pm.source == os_file_sep()
&& new_pm.target != os_file_sep())
{
new_pm.target += os_file_sep();
}
if (new_pm.target == os_file_sep()
&& new_pm.source != os_file_sep())
{
new_pm.target = std::string();
}
path_map.push_back(new_pm);
}
int backupid = 0;
if (backupid_arg.getValue() != "last")
{
backupid = atoi(backupid_arg.getValue().c_str());
}
Connector::EAccessError access_error;
std::string restore_info = Connector::startRestore(path_arg.getValue(), backupid, virtual_client_arg.getValue(),
path_map, access_error, !no_remove_arg.getValue(), !consider_other_fs_arg.getValue(),
!no_follow_symlinks.getValue());
if(!restore_info.empty())
{
if (non_blocking_arg.getValue())
{
std::cout << restore_info << std::endl;
return 0;
}
else
{
return wait_for_restore(restore_info);
}
}
else
{
if(access_error == Connector::EAccessError_NoServer)
{
std::cerr << "Error starting restore. No backup server found." << std::endl;
return 2;
}
else if (access_error == Connector::EAccessError_NoTokens)
{
std::cerr << "Error starting restore. No file backup access tokens found. Did you run a file backup yet?" << std::endl;
return 3;
}
else
{
std::cerr << "Error starting restore" << std::endl;
return 1;
}
}
}
int action_set_settings(std::vector<std::string> args)
{
TCLAP::CmdLine cmd("Set backup settings", ' ', cmdline_version);
PwClientCmd pw_client_cmd(cmd, true);
TCLAP::MultiArg<std::string> key_arg("k", "key",
"Key of the setting to set",
false, "setting key", cmd);
TCLAP::MultiArg<std::string> value_arg("v", "value",
"New value to set the setting to",
false, "setting value", cmd);
cmd.parse(args);
if (key_arg.getValue().size() != value_arg.getValue().size())
{
std::cerr << "There need to be an equal amount of -k/--key and -v/--value arguments" << std::endl;
return 2;
}
if (!pw_client_cmd.set())
{
return 3;
}
std::string s_settings;
for (size_t i = 0; i < key_arg.getValue().size(); ++i)
{
s_settings += key_arg.getValue()[i] + "=" + value_arg.getValue()[i] + "\n";
}
s_settings += "keep_old_settings=true\n";
bool no_perm;
bool b = Connector::updateSettings(s_settings, no_perm);
if (!b)
{
if (no_perm)
{
std::cerr << "Error setting settings. Client is not allowed to change settings." << std::endl;
}
else
{
std::cerr << "Error setting settings." << std::endl;
}
return 1;
}
else
{
return 0;
}
}
int action_reset_keep(std::vector<std::string> args)
{
TCLAP::CmdLine cmd("Reset keeping files during incremental backups", ' ', cmdline_version);
PwClientCmd pw_client_cmd(cmd, true);
TCLAP::ValueArg<std::string> virtual_client_arg("v", "virtual-client",
"Virtual client name",
false, "", "client name", cmd);
TCLAP::ValueArg<std::string> backup_folder_arg("b", "backup-folder",
"Backup folder name",
false, "", "folder name", cmd);
TCLAP::ValueArg<int> group_arg("g", "backup-group",
"Backup group index",
false, 0, "group index", cmd);
cmd.parse(args);
if (!pw_client_cmd.set())
{
return 3;
}
std::string ret = Connector::resetKeep(virtual_client_arg.getValue(), backup_folder_arg.getValue(), group_arg.getValue());
if (ret == "OK")
{
return 0;
}
else if (ret == "err_virtual_client_not_found")
{
std::cerr << "Error: Virtual client not found" << std::endl;
return 4;
}
else if (ret == "err_backup_folder_not_found")
{
std::cerr << "Error: Backup folder not found" << std::endl;
return 5;
}
else
{
std::cerr << "Error: " << ret << std::endl;
return 6;
}
}
std::string removeChars(std::string in)
{
char illegalchars[] = { '*', ':', '/' , '\\' };
std::string ret;
for (size_t i = 0; i<in.size(); ++i)
{
bool found = false;
for (size_t j = 0; j<sizeof(illegalchars) / sizeof(illegalchars[0]); ++j)
{
if (illegalchars[j] == in[i])
{
found = true;
break;
}
}
if (!found)
{
ret += in[i];
}
}
return ret;
}
bool findPathName(const std::vector<SBackupDir>& dirs, const std::string &pn)
{
for (size_t i = 0; i<dirs.size(); ++i)
{
if (dirs[i].name == pn)
{
return true;
}
}
return false;
}
std::string getDefaultDirname(const std::vector<SBackupDir>& dirs, const std::string &path)
{
std::string dirname = removeChars(ExtractFileName(path));
if (dirname.empty())
dirname = "rootfs";
if (findPathName(dirs, dirname))
{
for (int k = 0; k<100; ++k)
{
if (!findPathName(dirs, dirname + "_" + convert(k)))
{
dirname = dirname + "_" + convert(k);
break;
}
}
}
return dirname;
}
int action_add_backupdir(std::vector<std::string> args)
{
TCLAP::CmdLine cmd("Add new directory to backup set", ' ', cmdline_version);
PwClientCmd pw_client_cmd(cmd, true);
TCLAP::ValueArg<std::string> virtual_client_arg("v", "virtual-client",
"Virtual client name",
false, "", "client name", cmd);
TCLAP::ValueArg<std::string> name_arg("n", "name",
"Backup directory name",
false, "", "name", cmd);
TCLAP::ValueArg<std::string> path_arg("d", "path",
"Backup path",
true, "", "path", cmd);
TCLAP::ValueArg<int> group_arg("g", "backup-group",
"Backup group index",
false, 0, "group index", cmd);
TCLAP::SwitchArg optional_arg("o", "optional",
"Do not fail backup if path does not exist",
cmd);
TCLAP::SwitchArg no_follow_symlinks_arg("f", "no-follow-symlinks",
"Do not follow symbolic links outside of backup path",
cmd);
TCLAP::SwitchArg symlinks_required_arg("r", "require-symlinks",
"Fail backup if symbolic link targets do not exist",
cmd);
TCLAP::SwitchArg one_filesystem_arg("x", "one-filesystem",
"Do not cross filesystem boundary during backup",
cmd);
TCLAP::SwitchArg require_snapshot_arg("s", "require-snapshot",
"Fail backup if snapshot of backup path cannot be created",
cmd);
TCLAP::SwitchArg separate_hashes_arg("a", "separate-hashes",
"Do not share local hashes with other virtual clients",
cmd);
TCLAP::SwitchArg keep_arg("k", "keep",
"Keep deleted files and directories during incremental backups. DO NOT USE",
cmd);
cmd.parse(args);
if (!pw_client_cmd.set())
{
return 3;
}
std::string flags;
if (optional_arg.getValue())
{
if (!flags.empty()) flags += ",";
flags += "optional";
}
if (!no_follow_symlinks_arg.getValue())
{
if (!flags.empty()) flags += ",";
flags += "follow_symlinks";
}
if (!symlinks_required_arg.getValue())
{
if (!flags.empty()) flags += ",";
flags += "symlinks_optional";
}
if (one_filesystem_arg.getValue())
{
if (!flags.empty()) flags += ",";
flags += "one_filesystem";
}
if (require_snapshot_arg.getValue())
{
if (!flags.empty()) flags += ",";
flags += "require_snapshot";
}
if (!separate_hashes_arg.getValue())
{
if (!flags.empty()) flags += ",";
flags += "share_hashes";
}
if (keep_arg.getValue())
{
if (!flags.empty()) flags += ",";
flags += "keep";
}
std::vector<SBackupDir> backup_dirs = Connector::getSharedPaths(true);
if (Connector::hasError())
{
std::cerr << "Error retrieving current backup directories from backend" << std::endl;
return 1;
}
SBackupDir new_dir;
new_dir.path = path_arg.getValue();
if (name_arg.getValue().empty())
{
new_dir.name = getDefaultDirname(backup_dirs, new_dir.path);
}
else
{
new_dir.name = name_arg.getValue();
}
new_dir.group = group_arg.getValue();
new_dir.flags = flags;
new_dir.virtual_client = virtual_client_arg.getValue();
backup_dirs.push_back(new_dir);
if (!Connector::saveSharedPaths(backup_dirs))
{
std::cerr << "Error adding new backup path via backend" << std::endl;
return 2;
}
else
{
return 0;
}
}
void ouput_val(std::string val, size_t max_size)
{
if (val.size() > max_size)
{
val = val.substr(0, max_size);
}
std::cout << val;
for (size_t i = val.size(); i < max_size; ++i)
{
std::cout << ' ';
}
}
void display_table(const std::vector<std::vector<std::string> >& rows)
{
if (rows.empty())
{
return;
}
const size_t val_gap = 1;
std::vector<size_t> max_size;
max_size.resize(rows[0].size());
for (size_t i = 0; i < rows[0].size(); ++i)
{
for (size_t j = 0; j < rows.size(); ++j)
{
max_size[i] = (std::max)(rows[j][i].size(), max_size[i]);
}
}
for (size_t i = 0; i < rows[0].size(); ++i)
{
ouput_val(rows[0][i], max_size[i]+ val_gap);
}
std::cout << std::endl;
for (size_t i = 0; i < rows[0].size(); ++i)
{
std::cout << std::string(max_size[i], '-');
std::cout << std::string(val_gap, ' ');
}
std::cout << std::endl;
for (size_t i = 1; i < rows.size(); ++i)
{
for (size_t j = 0; j < rows[i].size(); ++j)
{
ouput_val(rows[i][j], max_size[j] + val_gap);
}
std::cout << std::endl;
}
}
int action_list_backupdirs(std::vector<std::string> args)
{
TCLAP::CmdLine cmd("List directories that are being backed up", ' ', cmdline_version);
PwClientCmd pw_client_cmd(cmd, false);
TCLAP::ValueArg<std::string> virtual_client_arg("v", "virtual-client",
"Display only for virtual with name",
false, "", "client name", cmd);
TCLAP::SwitchArg raw_arg("r", "raw",
"Return raw JSON output", cmd);
cmd.parse(args);
if (!pw_client_cmd.set())
{
return 3;
}
if (raw_arg.getValue())
{
std::string ret = Connector::getSharedPathsRaw();
if (ret.empty())
{
std::cerr << "Error retrieving current backup directories from backend" << std::endl;
return 1;
}
std::cout << ret;
std::cout.flush();
return 0;
}
std::vector<SBackupDir> backup_dirs = Connector::getSharedPaths(false);
if (Connector::hasError())
{
std::cerr << "Error retrieving current backup directories from backend" << std::endl;
return 1;
}
if (backup_dirs.empty())
{
std::cout << "No directories are being backed up" << std::endl;
return 0;
}
bool has_virtual_client = false;
bool has_group = false;
bool has_server_default = false;
for (size_t i = 0; i < backup_dirs.size(); ++i)
{
if (!backup_dirs[i].virtual_client.empty())
{
has_virtual_client = true;
}
if (backup_dirs[i].group != 0)
{
has_group = true;
}
if (backup_dirs[i].server_default)
has_server_default = true;
}
std::vector<std::vector<std::string> > tab;
std::vector<std::string> tab_header;
tab_header.push_back("PATH");
tab_header.push_back("NAME");
if (has_group)
{
tab_header.push_back("GROUP");
}
if (has_virtual_client)
{
tab_header.push_back("VIRTUAL CLIENT");
}
tab_header.push_back("FLAGS");
if (has_server_default)
{
tab_header.push_back("CONFIGURED ON SERVER");
}
tab.push_back(tab_header);
for (size_t i = 0; i < backup_dirs.size(); ++i)
{
std::vector<std::string> row;
row.push_back(backup_dirs[i].path);
if (backup_dirs[i].name.empty())
{
backup_dirs[i].name = getDefaultDirname(backup_dirs, backup_dirs[i].path);
}
row.push_back(backup_dirs[i].name);
if (has_group)
{
row.push_back(convert(backup_dirs[i].group));
}
if (has_virtual_client)
{
if (backup_dirs[i].virtual_client.empty())
{
row.push_back("-");
}
else
{
row.push_back(backup_dirs[i].virtual_client);
}
}
row.push_back(backup_dirs[i].flags);
if (has_server_default)
{
if (backup_dirs[i].server_default)
{
row.push_back("Yes");
}
else
{
row.push_back("No");
}
}
tab.push_back(row);
}
display_table(tab);
return 0;
}
int action_remove_backupdir(std::vector<std::string> args)
{
TCLAP::CmdLine cmd("Remove directory from backup set", ' ', cmdline_version);
PwClientCmd pw_client_cmd(cmd, true);
TCLAP::ValueArg<std::string> name_arg("n", "name",
"Backup directory name",
false, "", "name");
TCLAP::ValueArg<std::string> path_arg("d", "path",
"Backup path",
true, "", "path");
cmd.xorAdd(name_arg, path_arg);
cmd.parse(args);
if (!pw_client_cmd.set())
{
return 3;
}
std::vector<SBackupDir> backup_dirs = Connector::getSharedPaths(true);
if (Connector::hasError())
{
std::cerr << "Error retrieving current backup directories from backend" << std::endl;
return 1;
}
bool del_ok = false;
bool del_server_default = true;
for (size_t i = 0; i < backup_dirs.size();)
{
if (!name_arg.getValue().empty()
&& backup_dirs[i].name == name_arg.getValue())
{
if (backup_dirs[i].server_default)
{
del_server_default = true;
}
else
{
backup_dirs.erase(backup_dirs.begin() + i);
del_ok = true;
}
}
else if (!path_arg.getValue().empty()
&& backup_dirs[i].path == path_arg.getValue())
{
if (backup_dirs[i].server_default)
{
del_server_default = true;
}
else
{
backup_dirs.erase(backup_dirs.begin() + i);
del_ok = true;
}
}
else
{
++i;
}
}
if (!del_ok)
{
if (del_server_default)
{
std::cerr << "Backup directory was configured on the server. Please remove it there" << std::endl;
}
else
{
std::cerr << "Backup directory to remove not found" << std::endl;
}
return 1;
}
if (!Connector::saveSharedPaths(backup_dirs))
{
std::cerr << "Error removing backup directory via backend" << std::endl;
return 2;
}
else
{
return 0;
}
}
int action_wait_for_backend(std::vector<std::string> args)
{
TCLAP::CmdLine cmd("Wait for backend to become available", ' ', cmdline_version);
PwClientCmd pw_client_cmd(cmd, false);
TCLAP::ValueArg<int> time_arg("t", "time",
"Max time in seconds to wait",
false, 60, "seconds", cmd);
cmd.parse(args);
pw_client_cmd.wait(time_arg.getValue() * 1000);
if (!pw_client_cmd.set())
{
return 3;
}
int64 starttime = getTimeMS();
do
{
int64 thistime = getTimeMS();
std::string d = Connector::getStatusRawNoWait();
if (!Connector::hasError()
&& !d.empty())
{
return 0;
}
if (getTimeMS() - thistime < 30)
{
wait(100);
}
} while (getTimeMS() - starttime < time_arg.getValue() * 1000);
std::cerr << "Could not connect to backend in specified time" << std::endl;
return 1;
}
int main(int argc, char *argv[])
{
if(argc==0)
{
std::cerr << "Not enough arguments (zero arguments) -- no program name" << std::endl;
return 1;
}
#ifdef _WIN32
HMODULE hModule = GetModuleHandleW(NULL);
if (hModule != INVALID_HANDLE_VALUE)
{
WCHAR path[MAX_PATH];
if (GetModuleFileNameW(hModule, path, MAX_PATH) != 0)
{
SetCurrentDirectoryW(path);
}
}
#endif
std::vector<std::string> actions;
std::vector<action_fun> action_funs;
actions.push_back("start");
action_funs.push_back(action_start);
actions.push_back("status");
action_funs.push_back(action_status);
actions.push_back("browse");
action_funs.push_back(action_browse);
actions.push_back("restore-start");
action_funs.push_back(action_start_restore);
actions.push_back("set-settings");
action_funs.push_back(action_set_settings);
actions.push_back("reset-keep");
action_funs.push_back(action_reset_keep);
actions.push_back("add-backupdir");
action_funs.push_back(action_add_backupdir);
actions.push_back("list-backupdirs");
action_funs.push_back(action_list_backupdirs);
actions.push_back("remove-backupdir");
action_funs.push_back(action_remove_backupdir);
actions.push_back("wait-for-backend");
action_funs.push_back(action_wait_for_backend);
bool has_help=false;
bool has_version=false;
size_t action_idx=std::string::npos;
std::vector<std::string> args;
args.push_back(argv[0]);
for(int i=1;i<argc;++i)
{
std::string arg = argv[i];
if(arg=="--help" || arg=="-h")
{
has_help=true;
}
if(arg=="--version")
{
has_version=true;
}
if(!arg.empty() && arg[0]=='-')
{
args.push_back(arg);
continue;
}
bool found_action=false;
for(size_t j=0;j<actions.size();++j)
{
if(next(actions[j], 0, arg))
{
if(action_idx!=std::string::npos)
{
action_help(argv[0]);
exit(1);
}
action_idx=j;
found_action=true;
}
}
if(!found_action)
{
args.push_back(arg);
}
}
if(action_idx==std::string::npos)
{
if(has_help)
{
action_help(argv[0]);
exit(1);
}
if(has_version)
{
show_version();
exit(1);
}
action_help(argv[0]);
exit(1);
}
try
{
args[0]+=" "+actions[action_idx];
int rc = action_funs[action_idx](args);
return rc;
}
catch (TCLAP::ArgException &e)
{
std::cerr << "error: " << e.error() << " for arg " << e.argId() << std::endl;
return 1;
}
}