mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
1393 lines
30 KiB
C++
1393 lines
30 KiB
C++
#include "client_restore_http.h"
|
|
#include "client_restore.h"
|
|
#include <mutex>
|
|
#include <thread>
|
|
#include <set>
|
|
#include "../urbackupcommon/fileclient/tcpstack.h"
|
|
#include "../urbackupcommon/mbrdata.h"
|
|
#include "../fsimageplugin/IFSImageFactory.h"
|
|
#include "../urbackupcommon/os_functions.h"
|
|
#include "../Interface/Thread.h"
|
|
|
|
extern IFSImageFactory* image_fak;
|
|
|
|
namespace
|
|
{
|
|
std::mutex g_restore_data_mutex;
|
|
restore::LoginData g_login_data;
|
|
int64 res_id = 0;
|
|
|
|
std::atomic<bool> timezone_download_started(false);
|
|
|
|
struct SRestoreRes
|
|
{
|
|
restore::DownloadStatus dl_status;
|
|
restore::EDownloadResult ec;
|
|
bool finished;
|
|
std::string err;
|
|
std::atomic<int> pc_complete;
|
|
};
|
|
|
|
std::map<int64, SRestoreRes> restore_results;
|
|
|
|
void setGLoginData(restore::LoginData login_data)
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_restore_data_mutex);
|
|
g_login_data = login_data;
|
|
}
|
|
|
|
const std::string timezone_data_file = "/tmp/timezone_data.json";
|
|
|
|
class DownloadTimezoneData : public IThread
|
|
{
|
|
public:
|
|
void operator()()
|
|
{
|
|
int rc = system(("wget -q \"https://app.urbackup.com/api/online\" -O "+timezone_data_file).c_str());
|
|
|
|
if (rc != 0)
|
|
{
|
|
Server->deleteFile(timezone_data_file);
|
|
}
|
|
|
|
delete this;
|
|
}
|
|
};
|
|
}
|
|
|
|
ACTION_IMPL(status)
|
|
{
|
|
std::unique_ptr<IPipe> c(restore::connectToService());
|
|
|
|
JSON::Object ret;
|
|
|
|
ret.set("ok", true);
|
|
|
|
if (!c)
|
|
{
|
|
ret.set("err", "Error connecting to restore service");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
std::string pw = restore::restorePw();
|
|
CTCPStack tcpstack;
|
|
tcpstack.Send(c.get(), "STATUS DETAIL#pw=" + pw);
|
|
std::string r = restore::getResponse(c);
|
|
if (r.empty())
|
|
{
|
|
ret.set("err", "No response from restore service");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
Server->setContentType(tid, "application/json");
|
|
Server->Write(tid, r);
|
|
}
|
|
|
|
ACTION_IMPL(login)
|
|
{
|
|
restore::LoginData login_data;
|
|
login_data.has_login_data = POST["has_login_data"]=="1";
|
|
login_data.username = POST["username"];
|
|
login_data.password = POST["password"];
|
|
|
|
JSON::Object ret;
|
|
|
|
ret.set("ok", true);
|
|
|
|
if (restore::do_login(login_data, false))
|
|
{
|
|
ret.set("success", true);
|
|
setGLoginData(login_data);
|
|
}
|
|
else
|
|
{
|
|
ret.set("success", false);
|
|
}
|
|
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(get_clientnames)
|
|
{
|
|
int rc = 0;
|
|
std::vector<std::string> clients = restore::getBackupclients(rc);
|
|
|
|
JSON::Object ret;
|
|
|
|
ret.set("ok", true);
|
|
ret.set("rc", rc);
|
|
if (rc != 0)
|
|
ret.set("err", "Error getting clients on server. Rc=" + std::to_string(rc));
|
|
|
|
JSON::Array j_clients;
|
|
for (auto clientname : clients)
|
|
{
|
|
j_clients.add(clientname);
|
|
}
|
|
|
|
ret.set("clients", j_clients);
|
|
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(get_backupimages)
|
|
{
|
|
std::unique_ptr<IPipe> c(restore::connectToService());
|
|
|
|
JSON::Object ret;
|
|
|
|
ret.set("ok", true);
|
|
|
|
if (!c)
|
|
{
|
|
ret.set("err", "Error connecting to restore service");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
std::string pw = restore::restorePw();
|
|
CTCPStack tcpstack;
|
|
tcpstack.Send(c.get(), "GET BACKUPIMAGES " + POST["restore_name"]+ "#pw=" + pw);
|
|
std::string r = restore::getResponse(c);
|
|
if (r.empty())
|
|
{
|
|
ret.set("err", "No response from restore service");
|
|
}
|
|
else
|
|
{
|
|
if (r[0] == '0')
|
|
{
|
|
ret.set("err", "No backup server found");
|
|
}
|
|
else
|
|
{
|
|
ret.set("images", restore::backup_images_output_to_json(r.substr(1)));
|
|
}
|
|
}
|
|
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(start_download)
|
|
{
|
|
int img_id = watoi(POST["img_id"]);
|
|
std::string img_time = POST["img_time"];
|
|
std::string out = POST["out"];
|
|
bool mbr = POST["mbr"] == "1";
|
|
|
|
SRestoreRes* restore_res;
|
|
int64 curr_res_id;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_restore_data_mutex);
|
|
curr_res_id = res_id++;
|
|
restore_res = &restore_results[curr_res_id];
|
|
}
|
|
|
|
if(mbr && !FileExists(out))
|
|
{
|
|
writestring("", out);
|
|
}
|
|
|
|
Server->Log("Start_download: id = \""+std::to_string(img_id)+"\" time = \""+img_time+ "\"", LL_INFO);
|
|
|
|
std::thread dl_thread([img_id, img_time, mbr, out, restore_res]() {
|
|
restore_res->ec = restore::downloadImage(img_id, img_time, out, mbr, g_login_data, restore_res->dl_status);
|
|
std::lock_guard<std::mutex> lock(g_restore_data_mutex);
|
|
restore_res->finished = true;
|
|
});
|
|
|
|
dl_thread.detach();
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("res_id", curr_res_id);
|
|
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(download_progress)
|
|
{
|
|
int64 res_id = watoi64(POST["res_id"]);
|
|
|
|
SRestoreRes* restore_res = nullptr;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_restore_data_mutex);
|
|
auto it = restore_results.find(res_id);
|
|
if (it != restore_results.end())
|
|
restore_res = &it->second;
|
|
}
|
|
|
|
JSON::Object ret;
|
|
|
|
ret.set("ok", true);
|
|
|
|
if (restore_res == nullptr)
|
|
{
|
|
ret.set("err", "Restore not found");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
if (restore_res->finished)
|
|
{
|
|
ret.set("finished", true);
|
|
ret.set("ec", restore_res->ec);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
std::unique_ptr<IPipe> c(restore::connectToService());
|
|
|
|
if (!c)
|
|
{
|
|
ret.set("err", "Error connecting to restore service");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
CTCPStack tcpstack;
|
|
std::string pw = restore::restorePw();
|
|
tcpstack.Send(c.get(), "STATUS DETAIL#pw=" + pw);
|
|
std::string r = restore::getResponse(c);
|
|
if (r.empty())
|
|
{
|
|
ret.set("err", "No response from restore service");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
Server->setContentType(tid, "application/json");
|
|
Server->Write(tid, r);
|
|
|
|
/*std::unique_ptr<IPipe> c(restore::connectToService());
|
|
|
|
if (!c)
|
|
{
|
|
ret.set("err", "Error connecting to restore service");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
CTCPStack tcpstack;
|
|
std::string pw = restore::restorePw();
|
|
tcpstack.Send(c.get(), "GET DOWNLOADPROGRESS#pw=" + pw);
|
|
int lpc = 0;
|
|
bool got_pc = false;
|
|
|
|
std::string curr;
|
|
size_t r = c->Read(&curr, 10000);
|
|
for (int i = 0; i < linecount(curr); ++i)
|
|
{
|
|
std::string l = getline(i, curr);
|
|
if (!restore::trim2(l).empty())
|
|
{
|
|
int npc = atoi(restore::trim2(l).c_str());
|
|
got_pc = true;
|
|
if (npc != lpc)
|
|
{
|
|
lpc = npc;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (got_pc)
|
|
{
|
|
ret.set("pc", lpc);
|
|
}
|
|
|
|
restore::writeJsonResponse(tid, ret); */
|
|
}
|
|
|
|
ACTION_IMPL(has_network_device)
|
|
{
|
|
JSON::Object ret;
|
|
|
|
ret.set("ok", true);
|
|
bool has_device = restore::has_network_device();
|
|
ret.set("ret", has_device);
|
|
|
|
bool did_not_start = false;
|
|
if (has_device && timezone_download_started.compare_exchange_strong(did_not_start, true))
|
|
{
|
|
Server->createThread(new DownloadTimezoneData(), "dl tz data");
|
|
}
|
|
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(ping_server)
|
|
{
|
|
std::string servername = POST["servername"];
|
|
std::thread t([servername]() {
|
|
system(("./urbackuprestoreclient --ping-server \"" + servername + "\"").c_str());
|
|
});
|
|
|
|
t.detach();
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(has_internet_connection)
|
|
{
|
|
JSON::Object ret;
|
|
|
|
ret.set("ok", true);
|
|
int rc;
|
|
std::string errstatus;
|
|
ret.set("ret", restore::has_internet_connection(rc, errstatus));
|
|
ret.set("rc", rc);
|
|
ret.set("errstatus", errstatus);
|
|
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(configure_server)
|
|
{
|
|
bool active_client = POST["active"] == "1";
|
|
|
|
if (active_client)
|
|
{
|
|
restore::configure_internet_server(POST["url"], POST["authkey"], POST["proxy"], false);
|
|
}
|
|
else
|
|
{
|
|
restore::configure_local_server();
|
|
}
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(get_disks)
|
|
{
|
|
std::vector<restore::SLsblk> drives = restore::lsblk("");
|
|
|
|
bool get_parts = POST["partitions"] == "1";
|
|
|
|
JSON::Array j_disks;
|
|
|
|
std::string last_model;
|
|
|
|
for (restore::SLsblk& blk : drives)
|
|
{
|
|
if(blk.path.find("/dev/sr")==0)
|
|
continue;
|
|
if(blk.path.find("/dev/cdrom")==0)
|
|
continue;
|
|
if(blk.path.find("/dev/loop")==0)
|
|
continue;
|
|
if(blk.path.find("/dev/mapper")==0)
|
|
continue;
|
|
|
|
if(blk.type=="disk")
|
|
last_model = blk.model;
|
|
|
|
if (!get_parts && blk.type == "disk" ||
|
|
get_parts && blk.type=="part")
|
|
{
|
|
JSON::Object disk;
|
|
disk.set("model", blk.model);
|
|
if(blk.type=="part" && blk.model.empty() &&
|
|
!last_model.empty())
|
|
disk.set("model", last_model);
|
|
|
|
disk.set("maj_min", blk.maj_min);
|
|
disk.set("path", blk.path);
|
|
disk.set("size", blk.size);
|
|
disk.set("type", blk.type);
|
|
j_disks.add(disk);
|
|
}
|
|
}
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("disks", j_disks);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(get_is_disk_mbr)
|
|
{
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("res", is_disk_mbr(POST["mbrfn"]));
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(write_mbr)
|
|
{
|
|
std::string errmsg;
|
|
bool b = restore::do_restore_write_mbr(POST["mbrfn"],
|
|
POST["out_device"], true, errmsg);
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("success", b);
|
|
ret.set("errmsg", errmsg);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(get_partition)
|
|
{
|
|
std::unique_ptr<IFsFile> f(Server->openFile(POST["mbrfn"], MODE_READ));
|
|
if(f==nullptr)
|
|
{
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("success", false);
|
|
ret.set("errmsg", "Cannot open mbr file. "+os_last_error_str());
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
size_t fsize=(size_t)f->Size();
|
|
std::vector<char> buf(fsize);
|
|
f->Read(buf.data(), static_cast<_u32>(fsize));
|
|
|
|
CRData mbr(buf.data(), fsize);
|
|
SMBRData mbrdata(mbr);
|
|
if(mbrdata.hasError())
|
|
{
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("success", false);
|
|
ret.set("errmsg", "Error while reading MBR file");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
bool t_gpt_style;
|
|
std::vector<IFSImageFactory::SPartition> partitions
|
|
= image_fak->readPartitions(mbrdata.mbr_data, mbrdata.gpt_header, mbrdata.gpt_table, t_gpt_style);
|
|
|
|
std::string seldrive = POST["out_device"];
|
|
|
|
Server->Log("Selected device: "+seldrive+" Partition: "+convert(mbrdata.partition_number));
|
|
std::string partpath = restore::getPartitionPath(seldrive, mbrdata.partition_number);
|
|
Server->Log("Partition path: "+partpath);
|
|
std::unique_ptr<IFsFile> dev;
|
|
if(!partpath.empty())
|
|
{
|
|
dev.reset(Server->openFile(partpath, MODE_RW));
|
|
}
|
|
int try_c=0;
|
|
int delete_parts = 0;
|
|
bool fix_gpt=true;
|
|
|
|
while(!dev && try_c<10)
|
|
{
|
|
system(("partprobe "+seldrive+" > /dev/null 2>&1").c_str());
|
|
Server->wait(10000);
|
|
partpath = restore::getPartitionPath(seldrive, mbrdata.partition_number);
|
|
if(!partpath.empty())
|
|
{
|
|
dev.reset(Server->openFile(partpath, MODE_RW));
|
|
}
|
|
|
|
if(!dev)
|
|
{
|
|
if (!mbrdata.gpt_style)
|
|
{
|
|
Server->Log("Trying to fix LBA partitioning scheme via fdisk...");
|
|
//Fix LBA partition signature
|
|
system(("echo w | fdisk " + seldrive + " > /dev/null 2>&1").c_str());
|
|
system(("echo -e \"w\\nY\" | fdisk " + seldrive + " > /dev/null 2>&1").c_str());
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Trying to fix GPT partitioning scheme via gdisk...");
|
|
system(("echo -e \"w\\nY\" | gdisk " + seldrive).c_str());
|
|
}
|
|
|
|
if (fix_gpt && try_c>5
|
|
&& !partitions.empty())
|
|
{
|
|
//TODO: the last partition might not be the one with the highest offset
|
|
Server->Log("Deleting last GPT partition...");
|
|
++delete_parts;
|
|
std::string cmd;
|
|
for (int i = 0; i < delete_parts; ++i)
|
|
{
|
|
if (partitions.size() - i == mbrdata.partition_number)
|
|
{
|
|
Server->Log("Cannot delete last GPT partition. This is the partition to be restored. DISK IS PROBABLY TOO SMALL TO RESTORE TO.", LL_ERROR);
|
|
try_c = 100;
|
|
}
|
|
|
|
cmd += "d\\n" + convert(partitions.size() - i);
|
|
}
|
|
cmd += "\\nw\\nY";
|
|
|
|
if(fix_gpt)
|
|
system(("echo -e \""+cmd+"\" | gdisk " + seldrive).c_str());
|
|
}
|
|
}
|
|
|
|
++try_c;
|
|
}
|
|
if(dev==nullptr)
|
|
{
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("success", false);
|
|
ret.set("errmsg", "Could not open restore partition");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("success", true);
|
|
ret.set("partpath", partpath);
|
|
ret.set("partnum", mbrdata.partition_number);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
ACTION_IMPL(restart)
|
|
{
|
|
system("init 6");
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(get_keyboard_layouts)
|
|
{
|
|
std::string llist;
|
|
JSON::Array j_layouts;
|
|
if(os_popen("localectl list-x11-keymap-layouts", llist)==0)
|
|
{
|
|
std::vector<std::string> toks;
|
|
Tokenize(llist, toks, "\n");
|
|
|
|
for(auto tok: toks)
|
|
{
|
|
std::string ctok = trim(tok);
|
|
if(!ctok.empty())
|
|
j_layouts.add(ctok);
|
|
}
|
|
}
|
|
|
|
JSON::Object ret;
|
|
ret.set("layouts", j_layouts);
|
|
ret.set("ok", true);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(set_keyboard_layout)
|
|
{
|
|
writestring(POST["layout"]+"\n", "/home/urbackup/setxkbmap");
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(get_connection_settings)
|
|
{
|
|
std::string settingsfn = "/run/live/medium/connection_settings.json";
|
|
if(!FileExists(settingsfn))
|
|
{
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("no_config", true);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
Server->setContentType(tid, "application/json");
|
|
Server->Write(tid, getFile(settingsfn));
|
|
}
|
|
|
|
bool test_spill_space(const std::string& path)
|
|
{
|
|
IFile* f = Server->openFile(path+"/32d59234-64cc-42dd-bc56-48e59dd4db6a.test", MODE_WRITE);
|
|
ScopedDeleteFile del_f(f);
|
|
|
|
if(f==nullptr)
|
|
return false;
|
|
|
|
return f->Write("test")==4 && f->Sync();
|
|
}
|
|
|
|
std::string get_fs(const std::string& dev)
|
|
{
|
|
std::string out;
|
|
int rc = os_popen("file -s -L -b \""+dev+"\"", out);
|
|
|
|
if(rc!=0)
|
|
return std::string();
|
|
|
|
if(out.find("NTFS, ")!=std::string::npos)
|
|
return "ntfs";
|
|
|
|
if(out.find("FAT ")!=std::string::npos)
|
|
return "fat";
|
|
|
|
if(out.find("ext2 ")!=std::string::npos ||
|
|
out.find("ext3 ")!=std::string::npos ||
|
|
out.find("ext4 ")!=std::string::npos)
|
|
return "ext";
|
|
|
|
if(out.find("XFS ")!=std::string::npos)
|
|
return "xfs";
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
ACTION_IMPL(get_spill_disks)
|
|
{
|
|
std::vector<restore::SLsblk> drives = restore::lsblk("-b");
|
|
|
|
std::string exclude = POST["exclude"];
|
|
|
|
JSON::Object ret;
|
|
|
|
if(test_spill_space("/run/live/medium"))
|
|
{
|
|
ret.set("live_medium", true);
|
|
ret.set("live_medium_space", os_free_space("/run/live/medium"));
|
|
}
|
|
|
|
ret.set("ok", true);
|
|
|
|
std::string last_model;
|
|
std::string disk_path;
|
|
JSON::Array j_disks;
|
|
for (restore::SLsblk& blk : drives)
|
|
{
|
|
if(blk.path.find("/dev/sr")==0)
|
|
continue;
|
|
if(blk.path.find("/dev/cdrom")==0)
|
|
continue;
|
|
if(blk.path.find("/dev/loop")==0)
|
|
continue;
|
|
if(blk.path.find("/dev/mapper")==0)
|
|
continue;
|
|
|
|
if(blk.type=="disk")
|
|
{
|
|
last_model = blk.model;
|
|
disk_path = blk.path;
|
|
}
|
|
|
|
if(disk_path==exclude ||
|
|
blk.path==exclude)
|
|
continue;
|
|
|
|
JSON::Object disk;
|
|
disk.set("model", blk.model);
|
|
if(blk.type=="part" && blk.model.empty() &&
|
|
!last_model.empty())
|
|
disk.set("model", last_model);
|
|
|
|
disk.set("maj_min", blk.maj_min);
|
|
disk.set("path", blk.path);
|
|
disk.set("devpath", disk_path);
|
|
disk.set("size", watoi64(blk.size));
|
|
disk.set("type", blk.type);
|
|
disk.set("fstype", get_fs(blk.path));
|
|
j_disks.add(disk);
|
|
}
|
|
|
|
ret.set("disks", j_disks);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
struct SSpillFile
|
|
{
|
|
std::string fn;
|
|
int64 size;
|
|
std::string lodev;
|
|
int64 sizesz;
|
|
};
|
|
|
|
void cleanup_spill(const std::vector<SSpillFile>& spill_files)
|
|
{
|
|
for(auto sf: spill_files)
|
|
{
|
|
if(!sf.lodev.empty())
|
|
{
|
|
system(("losetup -d \""+sf.lodev+"\"").c_str());
|
|
}
|
|
}
|
|
|
|
std::string out;
|
|
int rc = os_popen("losetup -l -n", out);
|
|
if(rc==0)
|
|
{
|
|
std::vector<std::string> toks;
|
|
Tokenize(out, toks, "\n");
|
|
for(auto& tok: toks)
|
|
{
|
|
if(tok.find("32d59234-64cc-42dd-bc56-48e59dd4db6a")!=std::string::npos)
|
|
{
|
|
system(("losetup -d \""+getuntil(" ", tok)+"\"").c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<SFile> mnts = getFiles("/mnt");
|
|
|
|
for(const SFile& m: mnts)
|
|
{
|
|
if(m.isdir && m.name.find("spilldisk")==0)
|
|
{
|
|
std::vector<SFile> spillfiles = getFiles("/mnt/"+m.name);
|
|
|
|
for(const SFile& f: spillfiles)
|
|
{
|
|
if(f.name.find("32d59234-64cc-42dd-bc56-48e59dd4db6a")!=std::string::npos)
|
|
{
|
|
Server->deleteFile("/mnt/"+m.name+"/"+f.name);
|
|
}
|
|
}
|
|
|
|
system(("umount \"/mnt/"+m.name+"\"").c_str());
|
|
os_remove_dir("/mnt/"+m.name);
|
|
}
|
|
}
|
|
|
|
system("dmsetup remove spill_disk");
|
|
}
|
|
}
|
|
|
|
ACTION_IMPL(test_spill_disks)
|
|
{
|
|
system("dmsetup remove spill_disk");
|
|
|
|
std::vector<SSpillFile> spill_files;
|
|
cleanup_spill(spill_files);
|
|
|
|
std::vector<std::string> disks;
|
|
for(size_t idx=0;POST.find("disk"+std::to_string(idx))!=POST.end();++idx)
|
|
{
|
|
disks.push_back(POST["disk"+std::to_string(idx)]);
|
|
}
|
|
|
|
os_create_dir_recursive("/mnt/spilltest");
|
|
system("umount /mnt/spilltest");
|
|
|
|
JSON::Array j_disks;
|
|
|
|
for(std::string& disk: disks) {
|
|
|
|
std::string out;
|
|
int rc = os_popen("mount \""+disk+"\" /mnt/spilltest 2>&1", out);
|
|
|
|
if(rc==0) {
|
|
JSON::Object obj;
|
|
obj.set("path", disk);
|
|
obj.set("space", os_free_space("/mnt/spilltest"));
|
|
j_disks.add(obj);
|
|
} else {
|
|
Server->Log("Mounting "+disk+" failed: "+out);
|
|
}
|
|
|
|
system("umount /mnt/spilltest");
|
|
}
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("disks", j_disks);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(setup_spill_disks)
|
|
{
|
|
system("dmsetup remove spill_disk");
|
|
|
|
std::vector<SSpillFile> spill_files;
|
|
cleanup_spill(spill_files);
|
|
|
|
struct SSpillDisk
|
|
{
|
|
std::string path;
|
|
int destructive;
|
|
size_t idx;
|
|
std::string mountpath;
|
|
};
|
|
std::vector<SSpillDisk> disks;
|
|
for(size_t idx=0;POST.find("disk"+std::to_string(idx))!=POST.end();++idx)
|
|
{
|
|
SSpillDisk sd;
|
|
sd.path = POST["disk"+std::to_string(idx)];
|
|
sd.destructive = watoi(POST["destructive"+std::to_string(idx)]);
|
|
sd.idx = idx;
|
|
disks.push_back(sd);
|
|
}
|
|
|
|
std::string orig_dev = POST["orig_dev"];
|
|
|
|
int64 orig_dev_sz;
|
|
{
|
|
std::string out;
|
|
int rc = os_popen("blockdev --getsz \""+orig_dev+"\" 2>&1", out);
|
|
|
|
if(rc!=0)
|
|
{
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("err", "Error getting orig dev size "+orig_dev+": "+out);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
orig_dev_sz = watoi(trim(out));
|
|
}
|
|
|
|
std::string dm_table = "0 "+std::to_string(orig_dev_sz)+" linear "+orig_dev+" 0";
|
|
|
|
int64 dm_offs = orig_dev_sz;
|
|
|
|
for(auto& sd: disks)
|
|
{
|
|
if(sd.path=="live_medium")
|
|
{
|
|
sd.mountpath="/run/live/medium";
|
|
}
|
|
else
|
|
{
|
|
sd.mountpath = "/mnt/spilldisk"+std::to_string(sd.idx);
|
|
os_create_dir_recursive(sd.mountpath);
|
|
|
|
system(("umount "+sd.mountpath).c_str());
|
|
|
|
if(sd.destructive!=0)
|
|
{
|
|
std::string out;
|
|
int rc = os_popen("mkfs.ext4 -F \""+sd.path+"\" 2>&1", out);
|
|
|
|
if(rc!=0)
|
|
{
|
|
cleanup_spill(spill_files);
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("err", "Error creating filesystem at "+sd.path+": "+out);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
system(("tune2fs -m 0 \""+sd.path+"\"").c_str());
|
|
}
|
|
|
|
std::string out;
|
|
int rc = os_popen("mount \""+sd.path+"\" "+sd.mountpath+" 2>&1", out);
|
|
|
|
if(rc!=0)
|
|
{
|
|
cleanup_spill(spill_files);
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("err", "Error mounting "+sd.path+" at "+sd.mountpath+": "+out);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const int64 freespace_leave = 250LL*1024*1024;
|
|
|
|
int64 freespace = os_free_space(sd.mountpath);
|
|
|
|
if(freespace<freespace_leave)
|
|
freespace = 0;
|
|
else
|
|
freespace-=freespace_leave;
|
|
|
|
size_t idx=0;
|
|
|
|
const int64 spill_file_size_max = 1LL*1024*1024*1024*1024;
|
|
const int64 spill_file_size_min = 1LL*1024*1024*1024;
|
|
|
|
while(freespace>freespace_leave)
|
|
{
|
|
SSpillFile sf;
|
|
|
|
sf.size = std::min(spill_file_size_max, freespace);
|
|
sf.fn = sd.mountpath+"/spill-32d59234-64cc-42dd-bc56-48e59dd4db6a-"+std::to_string(idx)+".img";
|
|
|
|
++idx;
|
|
|
|
std::unique_ptr<IFsFile> f(Server->openFile(sf.fn , MODE_WRITE));
|
|
|
|
bool resize_ok=false;
|
|
if(f)
|
|
{
|
|
if(!f->Resize(sf.size, true))
|
|
{
|
|
if(sf.size>spill_file_size_min)
|
|
{
|
|
sf.size = spill_file_size_min;
|
|
resize_ok = f->Resize(sf.size, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
resize_ok=true;
|
|
}
|
|
}
|
|
|
|
if(resize_ok)
|
|
{
|
|
freespace -= sf.size;
|
|
std::string out;
|
|
int rc = os_popen("losetup -f --show \""+sf.fn+"\" 2>&1", out);
|
|
|
|
if(rc!=0)
|
|
{
|
|
cleanup_spill(spill_files);
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("err", "Error setting up loop device for "+sf.fn+ ": "+out);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
sf.lodev = trim(out);
|
|
|
|
out.clear();
|
|
rc = os_popen("blockdev --getsz \""+sf.lodev+"\" 2>&1", out);
|
|
|
|
if(rc!=0)
|
|
{
|
|
cleanup_spill(spill_files);
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("err", "Error loop device size of "+sf.lodev+ ": "+out);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
sf.sizesz = watoi64(trim(out));
|
|
|
|
dm_table+="\n"+std::to_string(dm_offs)+" "+std::to_string(sf.sizesz)+" linear "+sf.lodev+" 0";
|
|
dm_offs+=sf.sizesz;
|
|
|
|
spill_files.push_back(sf);
|
|
}
|
|
else
|
|
{
|
|
f.reset();
|
|
Server->deleteFile(sf.fn);
|
|
|
|
cleanup_spill(spill_files);
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("err", "Error creating spill file at \""+sf.fn+"\"");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
writestring(dm_table, "spill_disk_dm_table");
|
|
|
|
std::string out;
|
|
int rc = os_popen("cat spill_disk_dm_table | dmsetup create spill_disk 2>&1", out);
|
|
|
|
if(rc!=0)
|
|
{
|
|
cleanup_spill(spill_files);
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("err", "Error setting up spill disk: "+trim(out));
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
for(auto sf: spill_files)
|
|
{
|
|
if(!sf.lodev.empty())
|
|
{
|
|
system(("losetup -d \""+sf.lodev+"\"").c_str());
|
|
}
|
|
}
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("path", "/dev/mapper/spill_disk");
|
|
ret.set("orig_size", std::to_string(orig_dev_sz));
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(cleanup_spill_disks)
|
|
{
|
|
std::vector<SSpillFile> spill_files;
|
|
cleanup_spill(spill_files);
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(resize_disk)
|
|
{
|
|
std::string disk_fn = POST["disk_fn"];
|
|
|
|
int64 new_size = watoi64(POST["new_size"]);
|
|
|
|
SRestoreRes* restore_res;
|
|
int64 curr_res_id;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_restore_data_mutex);
|
|
curr_res_id = res_id++;
|
|
restore_res = &restore_results[curr_res_id];
|
|
}
|
|
|
|
std::thread t([disk_fn, new_size, restore_res]()
|
|
{
|
|
if(get_fs(disk_fn)!="ntfs")
|
|
{
|
|
restore_res->err = "Only resizing NTFS is currently supported";
|
|
restore_res->ec=restore::EDownloadResult_Ok;
|
|
restore_res->finished=true;
|
|
return;
|
|
}
|
|
|
|
std::string err = restore::resize_ntfs(new_size*512, disk_fn, restore_res->pc_complete);
|
|
|
|
restore_res->err = err;
|
|
restore_res->ec=restore::EDownloadResult_Ok;
|
|
restore_res->finished=true;
|
|
});
|
|
|
|
t.detach();
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
ret.set("res_id", curr_res_id);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(restore_finished)
|
|
{
|
|
int64 res_id = watoi64(POST["res_id"]);
|
|
|
|
SRestoreRes* restore_res = nullptr;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_restore_data_mutex);
|
|
auto it = restore_results.find(res_id);
|
|
if (it != restore_results.end())
|
|
restore_res = &it->second;
|
|
}
|
|
|
|
JSON::Object ret;
|
|
|
|
ret.set("ok", true);
|
|
|
|
if (restore_res == nullptr)
|
|
{
|
|
ret.set("err", "Restore not found");
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
if (restore_res->finished)
|
|
{
|
|
ret.set("finished", true);
|
|
ret.set("ec", restore_res->ec);
|
|
if(!restore_res->err.empty())
|
|
{
|
|
ret.set("err", restore_res->err);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret.set("pcdone", restore_res->pc_complete.load());
|
|
}
|
|
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(resize_part)
|
|
{
|
|
std::string disk_fn = POST["disk_fn"];
|
|
int partnum = watoi(POST["partnum"]);
|
|
|
|
int64 new_size = watoi64(POST["new_size"]);
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
|
|
std::string out;
|
|
int rc = os_popen("parted \""+disk_fn+"\" -sm unit B print", out);
|
|
if(rc!=0)
|
|
{
|
|
ret.set("err", out);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
int64 start = -1;
|
|
|
|
std::vector<std::string> lines;
|
|
Tokenize(out, lines, "\n");
|
|
for(auto line: lines)
|
|
{
|
|
std::vector<std::string> toks;
|
|
Tokenize(line, toks, ":");
|
|
|
|
if(toks.size()>2)
|
|
{
|
|
int cpartnum = watoi(toks[0]);
|
|
|
|
if(cpartnum==partnum)
|
|
{
|
|
start = watoi64(toks[1]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(start==-1)
|
|
{
|
|
ret.set("err", "Error finding partition start. Out: "+out);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
out.clear();
|
|
rc = os_popen("parted \""+disk_fn+"\" -sm resizepart "+std::to_string(partnum)+" "+std::to_string(start + new_size*512)+"B 2>&1", out);
|
|
|
|
if(rc!=0)
|
|
{
|
|
rc = os_popen("echo -e \"resizepart "+std::to_string(partnum)+" "+std::to_string(start + new_size*512)+"B\\nYes\\n\" | parted \""+disk_fn+"\" ---pretend-input-tty", out);
|
|
if(rc!=0)
|
|
{
|
|
ret.set("err", out);
|
|
}
|
|
}
|
|
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
namespace restore
|
|
{
|
|
std::string resize_ntfs(int64 new_size, const std::string& disk_fn, std::atomic<int>& pc_complete)
|
|
{
|
|
#ifndef _WIN32
|
|
FILE* in = NULL;
|
|
in = popen(("stdbuf -o0 ntfsresize -f -f -s "+std::to_string(new_size)+" \""+disk_fn+"\" 2>&1").c_str(), "re");
|
|
if(in==NULL)
|
|
{
|
|
return "Error opening command: "+ os_last_error_str();
|
|
}
|
|
|
|
char buf[4096];
|
|
std::string out;
|
|
std::string line;
|
|
while(true)
|
|
{
|
|
int ich = fgetc(in);
|
|
|
|
if(ich==EOF)
|
|
break;
|
|
|
|
char ch = static_cast<char>(ich);
|
|
|
|
if(ch!='\n' && ch!='\r')
|
|
{
|
|
line += ch;
|
|
}
|
|
else
|
|
{
|
|
if(line.find("percent completed")!=std::string::npos)
|
|
{
|
|
std::string str_pc = trim(getuntil("percent", line));
|
|
if(!str_pc.empty())
|
|
pc_complete = watoi(str_pc);
|
|
}
|
|
else
|
|
{
|
|
Server->Log("ntrfsresize: "+line, LL_INFO);
|
|
|
|
out += line + "\n";
|
|
}
|
|
|
|
line.clear();
|
|
}
|
|
}
|
|
|
|
int rc = pclose(in);
|
|
|
|
if(rc!=0)
|
|
{
|
|
return "Error runnning resize command. Rc: "+std::to_string(rc)+". Output: "+out;
|
|
}
|
|
|
|
#endif
|
|
|
|
return std::string();
|
|
}
|
|
}
|
|
|
|
ACTION_IMPL(capabilities)
|
|
{
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
|
|
#ifndef _WIN32
|
|
ret.set("keyboard_config", true);
|
|
ret.set("restore_spill", true);
|
|
#else
|
|
ret.set("keyboard_config", false);
|
|
ret.set("restore_spill", false);
|
|
#endif
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(get_tmpfn)
|
|
{
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
|
|
std::unique_ptr<IFsFile> tmpf(Server->openTemporaryFile());
|
|
|
|
if(!tmpf)
|
|
{
|
|
ret.set("err", "Cannot open tmpfile: "+os_last_error_str());
|
|
}
|
|
else
|
|
{
|
|
std::string tmpfn = tmpf->getFilename();
|
|
ret.set("fn", tmpfn);
|
|
tmpf.reset();
|
|
Server->deleteFile(tmpfn);
|
|
}
|
|
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(get_timezone_data)
|
|
{
|
|
std::string data;
|
|
int64 starttime = Server->getTimeMS();
|
|
|
|
while (data.empty() && Server->getTimeMS() - starttime < 1000)
|
|
{
|
|
data = getFile(timezone_data_file);
|
|
|
|
if (data.empty())
|
|
{
|
|
Server->wait(50);
|
|
}
|
|
}
|
|
|
|
if (data.empty())
|
|
{
|
|
JSON::Object ret;
|
|
ret.set("ok", false);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
else
|
|
{
|
|
Server->setContentType(tid, "application/json");
|
|
Server->Write(tid, data);
|
|
}
|
|
}
|
|
|
|
static std::vector<std::pair<std::string, std::string> > getTimezones()
|
|
{
|
|
std::vector<std::pair<std::string, std::string> > ret;
|
|
std::string tzdata;
|
|
int rc = os_popen("timedatectl list-timezones", tzdata);
|
|
if (rc != 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
std::vector<std::string> lines;
|
|
Tokenize(tzdata, lines, "\n");
|
|
|
|
for (auto& line : lines)
|
|
{
|
|
if (line.find("/") != std::string::npos)
|
|
{
|
|
ret.push_back(std::make_pair(getuntil("/", line), getafter("/", line)));
|
|
}
|
|
else
|
|
{
|
|
ret.push_back(std::make_pair(trim(line), ""));
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
ACTION_IMPL(get_timezone_areas)
|
|
{
|
|
auto tzs = getTimezones();
|
|
|
|
std::set<std::string> tzAreas;
|
|
for (auto tz : tzs)
|
|
{
|
|
if (!tz.first.empty())
|
|
tzAreas.insert(tz.first);
|
|
}
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
JSON::Array areas;
|
|
|
|
for (auto tz : tzAreas)
|
|
areas.add(tz);
|
|
|
|
ret.set("areas", areas);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(get_timezone_cities)
|
|
{
|
|
std::string area = POST["area"];
|
|
auto tzs = getTimezones();
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
|
|
JSON::Array cities;
|
|
for (auto tz : tzs)
|
|
{
|
|
if(tz.first == area && !tz.second.empty())
|
|
cities.add(tz.second);
|
|
}
|
|
|
|
ret.set("cities", cities);
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|
|
ACTION_IMPL(set_timezone)
|
|
{
|
|
std::string tz = POST["tz"];
|
|
|
|
JSON::Object ret;
|
|
ret.set("ok", true);
|
|
|
|
if (tz.empty() || tz.find('"')!=std::string::npos)
|
|
{
|
|
ret.set("ok", false);
|
|
restore::writeJsonResponse(tid, ret);
|
|
return;
|
|
}
|
|
|
|
int rc = system(("timedatectl set-timezone \"" + tz + "\"").c_str());
|
|
|
|
if (rc != 0)
|
|
ret.set("ok", false);
|
|
|
|
restore::writeJsonResponse(tid, ret);
|
|
}
|
|
|