urbackup_backend/urbackupclient/client_restore.cpp

2849 lines
69 KiB
C++

/*************************************************************************
* UrBackup - Client/Server backup system
* Copyright (C) 2011-2016 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 <string>
#include "../Interface/Server.h"
#include "../Interface/ThreadPool.h"
#include "../Interface/Thread.h"
#include "../Interface/File.h"
#include "../urbackupcommon/fileclient/tcpstack.h"
#include "../common/data.h"
#include "../stringtools.h"
#include "../urbackupcommon/os_functions.h"
#include "client_restore.h"
#include "client_restore_http.h"
#include <iostream>
#include <stdlib.h>
#include <memory.h>
#include <algorithm>
#include <thread>
#include "../urbackupcommon/fileclient/socket_header.h"
#ifndef _WIN32
#include <net/if.h>
#include <sys/ioctl.h>
#else
#include <ws2tcpip.h>
#endif
#include "../urbackupcommon/mbrdata.h"
#include "../fileservplugin/settings.h"
#include "../fileservplugin/packet_ids.h"
#include <memory>
#include "../cryptoplugin/ICryptoFactory.h"
#include "../fsimageplugin/IFSImageFactory.h"
#ifndef _WIN32
#include "../config.h"
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_LINUX_FS_H
#include <linux/fs.h>
#endif
#ifndef BLKRRPART
#define BLKRRPART _IO(0x12,95)
#endif
#endif
using namespace restore;
#ifdef _WIN32
const std::string pw_file="pw.txt";
#else
const std::string pw_file="urbackup/pw.txt";
#endif
const std::string configure_wlan="wicd-curses";
const std::string configure_networkcard=configure_wlan;
#define UDP_SOURCE_PORT 35623
extern ICryptoFactory *crypto_fak;
extern IFSImageFactory* image_fak;
namespace
{
bool rereadPartitionLayout(const std::string& devfn)
{
bool ret = true;
#ifndef _WIN32
int fd = open(devfn.c_str(), O_NONBLOCK | O_RDWR);
if (fd == -1)
return false;
int rc = ioctl(fd, BLKRRPART, NULL);
if (rc == -1)
{
ret = false;
Server->Log("Error re-reading partition layout. " + os_last_error_str(), LL_WARNING);
}
close(fd);
#endif
return ret;
}
}
namespace restore
{
std::string trim2(const std::string& str)
{
size_t startpos = str.find_first_not_of(" \t\n");
size_t endpos = str.find_last_not_of(" \t\n");
if (std::string::npos == startpos || std::string::npos == endpos)
{
return "";
}
else
{
return str.substr(startpos, endpos - startpos + 1);
}
}
std::string getResponse(IPipe* c)
{
CTCPStack tcpstack;
char* resp = nullptr;
char buffer[1024];
size_t packetsize;
while (resp == nullptr)
{
size_t rc = c->Read(buffer, 1024, 60000);
if (rc == 0)
{
return "";
}
tcpstack.AddData(buffer, rc);
resp = tcpstack.getPacket(&packetsize);
if (resp != nullptr && packetsize == 0)
{
delete[]resp;
return "";
}
}
std::string ret;
ret.resize(packetsize);
memcpy(&ret[0], resp, packetsize);
delete[]resp;
return ret;
}
std::string restorePw()
{
return getFile(pw_file);
}
std::vector<std::string> getBackupclients(int& ec)
{
std::string pw = getFile(pw_file);
CTCPStack tcpstack;
std::vector<std::string> ret;
ec = 0;
IPipe* c = Server->ConnectStream("localhost", 35623, 60000);
if (c == nullptr)
{
Server->Log("Error connecting to client service -1", LL_ERROR);
ec = 10;
return ret;
}
tcpstack.Send(c, "GET BACKUPCLIENTS#pw=" + pw);
std::string r = getResponse(c);
if (r.empty())
{
Server->Log("No response from ClientConnector", LL_ERROR);
ec = 1;
}
else
{
if (r[0] == '0')
{
Server->Log("No backupserver found", LL_ERROR);
ec = 2;
}
else
{
std::vector<std::string> toks;
std::string t = r.substr(1);
Tokenize(t, toks, "\n");
for (size_t i = 0; i < toks.size(); ++i)
{
std::string nam = trim2(getafter("|", toks[i]));
if (!nam.empty())
{
if (std::find(ret.begin(), ret.end(), nam) == ret.end())
{
ret.push_back(nam);
}
}
}
}
}
Server->destroy(c);
return ret;
}
IPipe* connectToService(int& ec)
{
IPipe* c = Server->ConnectStream("localhost", 35623, 60000);
if (c == nullptr)
{
Server->Log("Error connecting to client service -1", LL_ERROR);
ec = 10;
return nullptr;
}
return c;
}
IPipe* connectToService()
{
int ec = 0;
return connectToService(ec);
}
}
struct SPasswordSalt
{
std::string salt;
std::string rnd;
int pbkdf2_rounds;
};
std::vector<SPasswordSalt> getSalt(const std::string& username, int tries, int *ec)
{
std::string pw=getFile(pw_file);
CTCPStack tcpstack;
*ec=0;
std::vector<SPasswordSalt> ret;
IPipe *c=connectToService(*ec);
if(c==nullptr)
{
return ret;
}
bool has_ok = false;
tcpstack.Send(c, "GET SALT#pw="+pw+"&username="+username);
std::string r=getResponse(c);
if(r.empty() )
{
Server->Log("No response from ClientConnector", LL_ERROR);
*ec=1;
}
else
{
Server->Log("Salt Response: "+r, LL_INFO);
std::vector<std::string> toks;
Tokenize(r, toks, "/");
for(size_t i=0;i<toks.size();++i)
{
if(toks[i].find("ok;")==0)
{
has_ok = true;
std::vector<std::string> salt_toks;
Tokenize(toks[i], salt_toks, ";");
SPasswordSalt new_salts;
new_salts.pbkdf2_rounds=0;
if(salt_toks.size()==3)
{
new_salts.salt = salt_toks[1];
new_salts.rnd = salt_toks[2];
}
else if(salt_toks.size()==4)
{
new_salts.salt = salt_toks[1];
new_salts.rnd = salt_toks[2];
new_salts.pbkdf2_rounds = atoi(salt_toks[3].c_str());
}
ret.push_back(new_salts);
}
else
{
Server->Log("SALT error: "+toks[i], LL_ERROR);
Server->Log("Username not found on server", LL_ERROR);
SPasswordSalt new_salts = {};
ret.push_back(new_salts);
tries = 0;
}
}
}
Server->destroy(c);
if (!has_ok && tries>0)
{
Server->wait(10000);
return getSalt(username, tries - 1, ec);
}
return ret;
}
bool tryLogin(const std::string& username, const std::string& password, std::vector<SPasswordSalt> salts, int tries, int *ec)
{
std::string pw=getFile(pw_file);
CTCPStack tcpstack;
*ec=0;
IPipe *c=connectToService(*ec);
if(c==nullptr)
{
return false;
}
std::string auth_str;
if(!username.empty())
{
auth_str="&username="+username;
for(size_t i=0;i<salts.size();++i)
{
if(!salts[i].salt.empty()
&& !salts[i].rnd.empty())
{
std::string pw_md5 = Server->GenerateHexMD5(salts[i].salt+password);
if(salts[i].pbkdf2_rounds>0)
{
pw_md5 = strlower(crypto_fak->generatePasswordHash(hexToBytes(pw_md5), salts[i].salt, salts[i].pbkdf2_rounds));
}
auth_str+="&password"+convert(i)+"="+
Server->GenerateHexMD5(salts[i].rnd+pw_md5);
}
}
}
bool do_retry = false;
bool ret=false;
tcpstack.Send(c, "LOGIN FOR DOWNLOAD#pw="+pw+auth_str);
std::string r=getResponse(c);
if(r.empty() )
{
Server->Log("No response from ClientConnector", LL_ERROR);
*ec=1;
}
else
{
Server->Log("Login Response: "+r, LL_INFO);
if(r=="ok")
{
ret=true;
}
else if (r == "no channels available")
{
ret = true;
do_retry = true;
}
else
{
if (r == "err")
{
tries = 0;
}
Server->Log("Error during login: "+r, LL_ERROR);
}
}
Server->destroy(c);
if ((!ret || do_retry) && tries>0)
{
Server->wait(10000);
return tryLogin(username, password, salts, tries - 1, ec);
}
return ret;
}
std::vector<SImage> getBackupimages(std::string clientname, int *ec)
{
std::string pw=getFile(pw_file);
CTCPStack tcpstack;
std::vector<SImage> ret;
*ec=0;
IPipe *c=Server->ConnectStream("localhost", 35623, 60000);
if(c==nullptr)
{
Server->Log("Error connecting to client service -1", LL_ERROR);
*ec=10;
return ret;
}
tcpstack.Send(c, "GET BACKUPIMAGES "+clientname+"#pw="+pw);
std::string r=getResponse(c);
if(r.empty() )
{
Server->Log("No response from ClientConnector", LL_ERROR);
*ec=1;
}
else
{
if(r[0]=='0')
{
Server->Log("No backupserver found", LL_ERROR);
*ec=1;
}
else
{
ret = parse_backup_images_output(r.substr(1));
}
}
Server->destroy(c);
return ret;
}
struct SFileBackup
{
bool operator<(const SFileBackup &other) const
{
return other.time_s<time_s;
}
std::string time_str;
_i64 time_s;
int id;
int tgroup;
};
std::vector<SFileBackup> getFileBackups(std::string clientname, int *ec)
{
std::string pw=getFile(pw_file);
CTCPStack tcpstack;
std::vector<SFileBackup> ret;
*ec=0;
IPipe *c=Server->ConnectStream("localhost", 35623, 60000);
if(c==nullptr)
{
Server->Log("Error connecting to client service -1", LL_ERROR);
*ec=10;
return ret;
}
tcpstack.Send(c, "GET FILE BACKUPS "+clientname+"#pw="+pw);
std::string r=getResponse(c);
if(r.empty() )
{
Server->Log("No response from ClientConnector", LL_ERROR);
*ec=1;
}
else
{
if(r[0]=='0')
{
Server->Log("No backupserver found", LL_ERROR);
*ec=1;
}
else
{
std::vector<std::string> toks;
std::string t=r.substr(1);
Tokenize(t, toks, "\n");
for(size_t i=0;i<toks.size();++i)
{
std::vector<std::string> t2;
Tokenize(toks[i], t2, "|");
if(t2.size()==4 )
{
SFileBackup si;
si.id=atoi(t2[0].c_str());
si.time_s=os_atoi64(t2[1]);
si.time_str=t2[2];
si.tgroup=atoi(t2[3].c_str());
ret.push_back(si);
}
}
}
}
Server->destroy(c);
return ret;
}
volatile bool restore_retry_ok=false;
namespace restore
{
EDownloadResult retryDownload(EDownloadResult errrc, int img_id, std::string img_time, std::string outfile,
bool mbr, LoginData login_data, DownloadStatus& dl_status, int recur_depth, int64* o_imgsize, int64* o_output_file_size)
{
if (recur_depth == 0)
{
Server->Log("Read Timeout: Retrying -1", LL_WARNING);
int total_tries = 20;
int tries = total_tries;
EDownloadResult rc;
do
{
Server->wait(30000);
int64 starttime = Server->getTimeMS();
rc = downloadImage(img_id, img_time, outfile, mbr, login_data, dl_status, recur_depth + 1, o_imgsize, o_output_file_size);
if (rc == EDownloadResult_Ok)
{
return rc;
}
--tries;
if (Server->getTimeMS() - starttime > 180000)
{
tries = total_tries;
}
if (rc == EDownloadResult_SizeReadError)
{
do_login(login_data);
Server->wait(10000);
}
} while (tries > 0);
}
Server->Log("Read Timeout.", LL_ERROR);
return errrc;
}
EDownloadResult downloadImage(int img_id, std::string img_time, std::string outfile, bool mbr, LoginData login_data,
DownloadStatus& dl_status, int recur_depth, int64* o_imgsize, int64* o_output_file_size)
{
std::string pw = getFile(pw_file);
CTCPStack tcpstack;
std::vector<SImage> ret;
std::unique_ptr<IPipe> client_pipe(Server->ConnectStream("localhost", 35623, 60000));
if (client_pipe.get() == nullptr)
{
Server->Log("Error connecting to client service -1", LL_ERROR);
return EDownloadResult_ConnectError;
}
std::string s_offset;
if (dl_status.offset != -1)
{
s_offset = "&offset=" + convert(dl_status.offset) + "&received_bytes=" + convert(dl_status.received);
}
std::string dl_args;
if (img_id != 0 || !img_time.empty())
{
dl_args = "&img_id=" + convert(img_id) + "&time=" + img_time;
}
else if (login_data.has_login_data
&& !login_data.token.empty())
{
dl_args = "&token=" + EscapeParamString(login_data.token);
}
tcpstack.Send(client_pipe.get(), "DOWNLOAD IMAGE#pw=" + pw + dl_args + "&mbr=" + convert(mbr) + s_offset);
std::string restore_out = outfile;
Server->Log("Restoring to " + restore_out);
std::unique_ptr<IFile> out_file(Server->openFile(restore_out, MODE_RW_READNONE));
if (out_file.get() == nullptr)
{
Server->Log("Could not open \"" + restore_out + "\" for writing. "+os_last_error_str(), LL_ERROR);
return EDownloadResult_OpenError;
}
else if (o_output_file_size != nullptr)
{
*o_output_file_size = out_file->Size();
}
_i64 imgsize = -1;
client_pipe->Read((char*)&imgsize, sizeof(_i64), 60000);
if (o_imgsize != nullptr)
{
*o_imgsize = imgsize;
}
if (imgsize == -1)
{
Server->Log("Error reading size", LL_ERROR);
return EDownloadResult_SizeReadError;
}
if (imgsize == -2)
{
Server->Log("Connection timeout", LL_ERROR);
EDownloadResult rc = retryDownload(EDownloadResult_TimeoutError1, img_id, img_time,
outfile, mbr, login_data, dl_status, recur_depth, o_imgsize, o_output_file_size);
return rc;
}
if (!mbr && imgsize > out_file->Size())
{
Server->Log("Output device too small. File size = " + convert(out_file->Size()) + " needed = " + convert(imgsize), LL_ERROR);
return EDownloadResult_DeviceTooSmall;
}
const size_t c_buffer_size = 32768;
const unsigned int c_block_size = 4096;
char buf[c_buffer_size];
if (mbr == true)
{
_i64 read = 0;
while (read < imgsize)
{
size_t c_read = client_pipe->Read(buf, c_buffer_size, 180000);
if (c_read == 0)
{
EDownloadResult rc = retryDownload(EDownloadResult_TimeoutError2, img_id, img_time, outfile,
mbr, login_data, dl_status, recur_depth, o_imgsize, o_output_file_size);
return rc;
}
out_file->Write(buf, (_u32)c_read);
read += c_read;
}
return EDownloadResult_Ok;
}
else
{
_i64 read = 0;
unsigned int blockleft = 0;
unsigned int off = 0;
char blockdata[c_block_size];
bool first = true;
bool has_data = false;
_i64 pos = 0;
while (pos < imgsize)
{
size_t r = client_pipe->Read(&buf[off], c_buffer_size - off, 180000);
if (r != 0)
r += off;
off = 0;
if (r == 0)
{
Server->Log("Read Timeout: Retrying", LL_WARNING);
client_pipe.reset(nullptr);
out_file.reset(nullptr);
if (has_data)
{
return retryDownload(EDownloadResult_TimeoutError2, img_id, img_time,
outfile, mbr, login_data, dl_status, recur_depth, o_imgsize, o_output_file_size);
}
else
{
Server->Log("Read Timeout: No data", LL_ERROR);
return EDownloadResult_TimeoutError2;
}
}
while (true)
{
if (blockleft == 0)
{
if (!first)
{
//Only write one block
_u32 tw = c_block_size;
//Don't write over blockdev boundary
if (imgsize >= pos && imgsize - pos < c_block_size)
tw = (_u32)(imgsize - pos);
//Write to blockdev
_u32 woff = 0;
do
{
bool has_write_error = false;
_u32 w = out_file->Write(&blockdata[woff], tw - woff, &has_write_error);
if (w == 0)
{
Server->Log("Writing to output file failed", LL_ERROR);
return EDownloadResult_WriteFailed;
}
if (has_write_error)
{
Server->Log("Writing to output file failed -2", LL_ERROR);
return EDownloadResult_WriteFailed;
}
woff += w;
} while (tw - woff > 0);
if (pos > dl_status.offset)
{
dl_status.offset = pos;
}
has_data = true;
}
else
{
first = false;
if (pos != -1 && !restore_retry_ok)
{
restore_retry_ok = true;
}
}
if (r - off >= sizeof(_i64))
{
blockleft = c_block_size;
_i64* s = reinterpret_cast<_i64*>(&buf[off]);
if (*s == 0x7fffffffffffffffLL)
{
Server->Log("Restore finished", LL_INFO);
pos = *s;
break;
}
if (*s > imgsize)
{
Server->Log("Invalid seek value: " + convert(*s), LL_ERROR);
//TODO return error code here after deprecation period
pos = *s;
break;
}
else if (*s < pos)
{
Server->Log("Position out of order!", LL_ERROR);
}
else
{
if (!out_file->Seek(*s))
{
Server->Log("Seeking in output file failed (to position " + convert(*s) + ")", LL_ERROR);
return EDownloadResult_WriteFailed;
}
pos = *s;
}
off += sizeof(_i64);
}
else if (r - off > 0)
{
memmove(buf, &buf[off], r - off);
off = (_u32)r - off;
break;
}
else
{
off = 0;
break;
}
}
else
{
unsigned int available = (std::min)((unsigned int)r - off, blockleft);
if (available > 0)
{
memcpy(&blockdata[c_block_size - blockleft], &buf[off], (_u32)available);
}
read += available;
blockleft -= available;
off += available;
dl_status.received += available;
if (off >= r)
{
off = 0;
break;
}
}
}
}
return EDownloadResult_Ok;
}
return EDownloadResult_Ok;
}
int downloadFiles(int backupid, std::string backup_time)
{
std::string pw=getFile(pw_file);
CTCPStack tcpstack;
std::vector<SImage> ret;
std::unique_ptr<IPipe> client_pipe(Server->ConnectStream("localhost", 35623, 60000));
if(client_pipe.get()==nullptr)
{
Server->Log("Error connecting to client service -1", LL_ERROR);
return 10;
}
tcpstack.Send(client_pipe.get(), "DOWNLOAD FILES#pw="+pw+"&backupid="+convert(backupid)+"&time="+backup_time);
int64 starttime = Server->getTimeMS();
while(Server->getTimeMS()-starttime<60000)
{
std::string msg;
if(client_pipe->Read(&msg, 60000)>0)
{
tcpstack.AddData(&msg[0], msg.size());
if(tcpstack.getPacket(msg) )
{
if(msg=="ok")
{
return 0;
}
else
{
Server->Log("Error: "+msg, LL_ERROR);
return 1;
}
break;
}
}
else
{
break;
}
}
Server->Log("Timeout", LL_ERROR);
return 2;
}
} //namespace restore
namespace restore
{
bool LookupBlocking(std::string pServer, in_addr *dest)
{
const char* host=pServer.c_str();
unsigned int addr = inet_addr(host);
if (addr != INADDR_NONE)
{
dest->s_addr = addr;
}
else
{
addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_socktype = SOCK_STREAM;
addrinfo* hp;
if(getaddrinfo(host, nullptr, &hints, &hp)==0 && hp!=nullptr)
{
if(hp->ai_addrlen<=sizeof(sockaddr_in))
{
memcpy(dest, &reinterpret_cast<sockaddr_in*>(hp->ai_addr)->sin_addr, sizeof(in_addr));
freeaddrinfo(hp);
return true;
}
else
{
freeaddrinfo(hp);
return false;
}
}
else
{
return false;
}
}
return true;
}
bool ping_server(void)
{
int type = SOCK_DGRAM;
#if !defined(_WIN32) && defined(SOCK_CLOEXEC)
type |= SOCK_CLOEXEC;
#endif
SOCKET udpsock=socket(AF_INET, type,0);
#if !defined(_WIN32) && !defined(SOCK_CLOEXEC)
fcntl(udpsock, F_SETFD, fcntl(udpsock, F_GETFD, 0) | FD_CLOEXEC);
#endif
std::string server=Server->getServerParameter("ping_server");
if(server.empty())
return false;
sockaddr_in server_addr;
if(!LookupBlocking(server, &server_addr.sin_addr))
return false;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(UDP_SOURCE_PORT);
std::string ping_clientname=getFile("clientname.txt");
std::vector<char> buffer;
buffer.resize(ping_clientname.size()+2);
buffer[0]=ID_PONG;
buffer[1]=FILESERV_VERSION;
memcpy(&buffer[2], ping_clientname.c_str(), ping_clientname.size());
while(true)
{
int rc = sendto(udpsock, &buffer[0], static_cast<int>(buffer.size()), 0, (sockaddr*)&server_addr, sizeof(server_addr));
if(rc == -1)
{
Server->Log("Error sending to UrBackup server on UDP socket", LL_ERROR);
return false;
}
Server->wait(10000);
}
return true;
}
std::vector<SLsblk> lsblk(const std::string& dev)
{
int rc = system(("lsblk -x MAJ:MIN -o MAJ:MIN,MODEL,SIZE,TYPE,PATH -P "+dev+" 1> out").c_str());
std::vector<SLsblk> ret;
if(rc!=0)
{
Server->Log("Error while running 'lsblk'", LL_ERROR);
return ret;
}
std::vector<std::string> lines;
Tokenize(getFile("out"), lines, "\n");
for(size_t i=0;i<lines.size();++i)
{
SLsblk c;
c.maj_min = getbetween("MAJ:MIN=\"", "\"", lines[i]);
c.model = getbetween("MODEL=\"", "\"", lines[i]);
c.size = getbetween("SIZE=\"", "\"", lines[i]);
c.type = getbetween("TYPE=\"", "\"", lines[i]);
c.path = getbetween("PATH=\"", "\"", lines[i]);
if(next(c.path, 0, "/dev/fd"))
continue;
ret.push_back(c);
}
return ret;
}
std::string getPartitionPath(const std::string& dev, int partnum)
{
std::vector<SLsblk> parts = lsblk(dev);
std::map<size_t, SLsblk> parts_maj_min;
for(auto& it: parts)
{
if(it.type=="part")
{
int maj = watoi(getuntil(":", it.maj_min));
int min = watoi(getafter(":", it.maj_min));
parts_maj_min[maj*100000ULL + min] = it;
}
}
int idx=1;
for(auto it: parts_maj_min)
{
if(idx==partnum)
{
return it.second.path;
}
++idx;
}
return std::string();
}
}
namespace restore
{
std::vector<SImage> parse_backup_images_output(const std::string& data)
{
std::vector<SImage> ret;
std::vector<std::string> toks;
Tokenize(data, toks, "\n");
for (size_t i = 0; i < toks.size(); ++i)
{
std::vector<std::string> t2;
Tokenize(toks[i], t2, "|");
if (t2.size() == 3 || (t2.size() == 4 && t2[0] != "#"))
{
SImage si;
si.id = atoi(t2[0].c_str());
si.time_s = os_atoi64(t2[1]);
si.time_str = t2[2];
if (t2.size() == 4)
{
si.letter = t2[3];
}
ret.push_back(si);
}
else if ( (t2.size() == 4 || t2.size() == 5) &&
t2[0] == "#" && !ret.empty())
{
SImage si;
si.id = atoi(t2[1].c_str());
si.time_s = os_atoi64(t2[2]);
si.time_str = t2[3];
if (t2.size() == 5)
{
si.letter = t2[4];
}
ret[ret.size() - 1].assoc.push_back(si);
}
}
return ret;
}
JSON::Array backup_images_output_to_json(const std::string& data)
{
std::vector<SImage> images = parse_backup_images_output(data);
JSON::Array ret;
for (size_t i = 0; i < images.size(); ++i)
{
JSON::Object image;
image.set("id", images[i].id);
image.set("time_s", images[i].time_s);
image.set("time_str", images[i].time_str);
image.set("letter", images[i].letter);
JSON::Array assoc;
for (size_t j = 0; j < images[i].assoc.size(); ++j)
{
JSON::Object image_assoc;
image_assoc.set("id", images[i].assoc[j].id);
image_assoc.set("time_s", images[i].assoc[j].time_s);
image_assoc.set("time_str", images[i].assoc[j].time_str);
image_assoc.set("letter", images[i].assoc[j].letter);
assoc.add(image_assoc);
}
image.set("assoc", assoc);
ret.add(image);
}
return ret;
}
std::string backup_images_output_to_json_str(const std::string& data)
{
return backup_images_output_to_json(data).stringify(false);
}
struct SgdiskPart
{
size_t number;
int64 start;
int64 end;
std::string size;
std::string code;
std::string type_name;
std::string part_guid;
std::string unique_guid;
std::string name;
std::string flags;
};
std::vector<SgdiskPart> getSgdiskParts(const std::string& dev)
{
std::vector<SgdiskPart> ret;
std::string out;
int rc = os_popen("sgdisk -p \""+dev+"\" 2>&1", out);
if(rc!=0)
return ret;
if(out.find("Number")==std::string::npos)
return ret;
std::string table = getafter("\n", getafter("Number", out));
std::vector<std::string> lines;
Tokenize(table, lines, "\n");
for(auto& line: lines)
{
std::vector<std::string> cols;
Tokenize(line, cols, " ");
SgdiskPart new_part;
new_part.number = 0;
new_part.start = -1;
new_part.end = -1;
size_t idx=0;
for(auto& col: cols)
{
std::string cd = trim(col);
if(!cd.empty())
{
if(idx==0)
new_part.number = watoi(cd);
else if(idx==1)
new_part.start = watoi64(cd);
else if(idx==2)
new_part.end = watoi64(cd);
else if(idx==3)
new_part.size = cd;
else if(idx==4)
new_part.size += " "+ cd;
else if(idx==5)
new_part.code = cd;
else
new_part.type_name += cd;
++idx;
}
}
if(new_part.start!=-1 &&
new_part.number!=0 &&
new_part.end!=-1)
{
std::string iout;
rc = os_popen("sgdisk -i "+std::to_string(new_part.number)+" \""+dev+"\" 2>&1", iout);
if(rc==0)
{
new_part.part_guid=trim(getbetween("Partition GUID code: ", " ", iout));
if(new_part.part_guid.empty())
new_part.part_guid=trim(getbetween("Partition GUID code: ", "\n", iout));
new_part.unique_guid = trim(getbetween("Partition unique GUID: ", "\n", iout));
new_part.flags = trim(getbetween("Attribute flags: ", "\n", iout));
new_part.name = trim(getbetween("Partition name: '", "\n", iout));
if(!new_part.name.empty() && new_part.name[new_part.name.size()-1]=='\'')
new_part.name = new_part.name.substr(0, new_part.name.size()-1);
}
else
{
Server->Log("Getting detailed info for partition "+std::to_string(new_part.number)+" failed: "+iout, LL_ERROR);
}
ret.push_back(new_part);
}
}
return ret;
}
std::string createSgdiskPart(const SgdiskPart& lp, const std::string& out_device)
{
std::string out;
int rc = os_popen("sgdisk -n "+std::to_string(lp.number)+":"+std::to_string(lp.start)+":0 "
"-A "+std::to_string(lp.number)+":=:"+lp.flags+" "
"-c "+std::to_string(lp.number)+":\""+lp.name+"\" "
"-t "+std::to_string(lp.number)+":"+lp.part_guid+" "
"-u "+std::to_string(lp.number)+":"+lp.unique_guid+" "
+out_device, out);
if(rc!=0)
{
return "Error creating partition "+std::to_string(lp.number)+": "+trim(out);
}
return std::string();
}
int64 getSgdiskDiskSize(const std::string& dev)
{
std::string out;
int rc = os_popen("sgdisk -p \""+dev+"\" 2>&1", out);
if(rc!=0)
return -1;
std::string s = trim(getbetween("Disk "+dev+": ", " sectors", out));
if(s.empty())
return -1;
return watoi64(s);
}
bool do_restore_write_mbr(const std::string& mbr_filename, const std::string& out_device,
bool fix_gpt, std::string& errmsg)
{
IFile *f=Server->openFile(mbr_filename, MODE_READ);
if(f==nullptr)
{
errmsg = "Could not open MBR file";
return false;
}
size_t fsize=(size_t)f->Size();
char *buf=new char[fsize];
f->Read(buf, (_u32)fsize);
Server->destroy(f);
CRData mbr(buf, fsize);
SMBRData mbrdata(mbr);
if(mbrdata.hasError())
{
errmsg = "Error while parsing MBR data";
delete []buf;
return false;
}
IFile *dev=Server->openFile(out_device, MODE_RW);
if(dev==nullptr)
{
errmsg = "Could not open device file for writing";
delete []buf;
return false;
}
dev->Seek(0);
Server->Log("Writing MBR data...", LL_INFO);
if (dev->Write(mbrdata.mbr_data) != mbrdata.mbr_data.size())
{
errmsg = "Writing MBR data failed. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
}
else
{
Server->Log("done.", LL_INFO);
}
bool curr_fix_gpt=false;
if (mbrdata.gpt_style)
{
Server->Log("Writing GPT header...");
if (dev->Write(mbrdata.gpt_header_pos, mbrdata.gpt_header) != mbrdata.gpt_header.size() )
{
errmsg = "Writing GPT header failed. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
}
Server->Log("Writing GPT table...");
if (dev->Write(mbrdata.gpt_table_pos, mbrdata.gpt_table) != mbrdata.gpt_table.size())
{
errmsg = "Writing GPT table failed. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
}
if (mbrdata.backup_gpt_header_pos != -1)
{
Server->Log("Writing GPT backup header...");
if (dev->Write(mbrdata.backup_gpt_header_pos, mbrdata.backup_gpt_header) != mbrdata.backup_gpt_header.size())
{
errmsg = "Writing GPT backup header failed. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
curr_fix_gpt = true;
}
}
if (mbrdata.backup_gpt_table_pos != -1)
{
Server->Log("Writing GPT backup table...");
if (dev->Write(mbrdata.backup_gpt_table_pos, mbrdata.backup_gpt_table) != mbrdata.backup_gpt_table.size())
{
errmsg = "Writing backup GPT table failed. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
curr_fix_gpt = true;
}
}
}
if (mbrdata.extra_data_pos != -1)
{
Server->Log("Writing extra data at position "+convert(mbrdata.extra_data_pos)+" size "+convert(mbrdata.extra_data.size())+" ...");
if (dev->Write(mbrdata.extra_data_pos, mbrdata.extra_data) != mbrdata.extra_data.size())
{
errmsg = "Writing extra data failed. " + os_last_error_str();
Server->Log(errmsg, LL_ERROR);
}
}
if (curr_fix_gpt && fix_gpt)
{
std::string out;
int rc = os_popen("sgdisk --version", out);
if (rc != 0 || out.find("sgdisk") == std::string::npos)
{
Server->Log("sgdisk not available. Not able to fixup GPT table", LL_WARNING);
fix_gpt = false;
}
}
if(curr_fix_gpt && fix_gpt)
{
std::string fix_output;
int rc = os_popen("sgdisk -e " + out_device + " 2>&1", fix_output);
errmsg="Fixed GPT backup table: "+trim(fix_output);
if(fix_output.find("too big for the disk")!=std::string::npos)
{
std::vector<SgdiskPart> partitions = getSgdiskParts(out_device);
int64 sgdisk_size = getSgdiskDiskSize(out_device);
while(!partitions.empty())
{
if(sgdisk_size<0)
break;
SgdiskPart lp = partitions[partitions.size()-1];
if(lp.start>=sgdisk_size)
{
Server->Log("Partition "+std::to_string(lp.number)+" not on disk. Removing...");
std::string out;
rc = os_popen("sgdisk -d "+std::to_string(lp.number)+" "+out_device+" 2>&1", out);
if(rc!=0)
{
errmsg="Error deleting last partition (1): "+trim(out);
Server->Log(errmsg, LL_ERROR);
}
}
else
{
break;
}
}
if(!partitions.empty())
{
Server->Log("Partition too big. Resizing last partition", LL_WARNING);
SgdiskPart lp = partitions[partitions.size()-1];
Server->Log("Last partition num="+std::to_string(lp.number)+" start="+std::to_string(lp.start)+" end="+std::to_string(lp.end)
+" size="+lp.size, LL_INFO);
std::string out;
rc = os_popen("sgdisk -d "+std::to_string(lp.number)+" "+out_device+" 2>&1", out);
if(rc!=0)
{
errmsg="Error deleting last partition: "+trim(out);
Server->Log(errmsg, LL_ERROR);
}
std::string err = createSgdiskPart(lp, out_device);
if(!err.empty())
{
errmsg="Error re-creating last partition: "+trim(err);
Server->Log(errmsg, LL_ERROR);
}
}
else
{
Server->Log("No partitions found", LL_ERROR);
}
}
}
Server->destroy(dev);
if (out_device.find("/dev/") == 0)
{
rereadPartitionLayout(out_device);
}
delete []buf;
return true;
}
}
void do_restore(void)
{
std::string cmd=Server->getServerParameter("restore_cmd");
if(cmd=="write_mbr")
{
std::string mbr_filename=Server->getServerParameter("mbr_filename");
std::string out_device=Server->getServerParameter("out_device");
if(mbr_filename.empty())
{
Server->Log("MBR filename not specified (mbr_filename parameter)", LL_ERROR);
exit(1);
}
if(out_device.empty())
{
Server->Log("Output device not specified (out_device paramter)", LL_ERROR);
exit(1);
}
std::string errmsg;
if(!do_restore_write_mbr(mbr_filename, out_device, true, errmsg))
{
Server->Log(errmsg, LL_ERROR);
exit(2);
}
exit(0);
}
else if(cmd=="mbrinfo")
{
std::string mbr_filename=Server->getServerParameter("mbr_filename");
if(mbr_filename.empty())
{
Server->Log("MBR filename not specified (mbr_filename parameter)", LL_ERROR);
exit(1);
}
IFile *f=Server->openFile(mbr_filename, MODE_READ);
if(f==nullptr)
{
Server->Log("Could not open MBR file", LL_ERROR);
exit(1);
}
size_t fsize=(size_t)f->Size();
char *buf=new char[fsize];
f->Read(buf, (_u32)fsize);
Server->destroy(f);
CRData mbr(buf, fsize);
SMBRData mbrdata(mbr);
if(mbrdata.hasError())
{
Server->Log("Error while parsing MBR data", LL_ERROR);
delete []buf; exit(1);
}
std::cout << mbrdata.infoString();
delete []buf;
exit(0);
}
else if (cmd == "login")
{
LoginData login_data;
login_data.has_login_data = true;
login_data.username = Server->getServerParameter("username");
login_data.password = Server->getServerParameter("password");
char* pw = getenv("LOGIN_PASSWORD");
if (pw != nullptr)
{
login_data.password = pw;
}
if (do_login(login_data))
{
exit(0);
}
else
{
exit(1);
}
}
else if (cmd == "read_mbr")
{
std::string dev_fn = Server->getServerParameter("device_fn");
std::unique_ptr<IFile> dev(Server->openFile(dev_fn, MODE_READ_DEVICE));
if (dev.get() == nullptr)
{
Server->Log("Error opening device at " + dev_fn+". "+os_last_error_str(), LL_ERROR);
exit(5);
}
bool gpt_style;
std::vector<IFSImageFactory::SPartition> partitions = image_fak->readPartitions(dev.get(), gpt_style);
if (partitions.empty())
{
Server->Log("No partitions found", LL_ERROR);
exit(4);
}
for (size_t i = 0; i < partitions.size(); ++i)
{
std::cout << "partition " << (i+1) << " off " << partitions[i].offset << " size " << partitions[i].length << std::endl;
}
exit(0);
}
else if(cmd=="resize_ntfs")
{
std::string dev_fn = Server->getServerParameter("device_fn");
std::unique_ptr<IFile> dev(Server->openFile(dev_fn, MODE_READ_DEVICE));
if (dev.get() == nullptr)
{
Server->Log("Error opening device at " + dev_fn+". "+os_last_error_str(), LL_ERROR);
exit(5);
}
dev.reset();
std::string s_new_size = Server->getServerParameter("new_size");
if(s_new_size.empty())
{
Server->Log("new_size paramater missing", LL_ERROR);
exit(6);
}
int64 new_size = watoi64(s_new_size);
std::string err;
std::atomic<int> complete_pc;
std::atomic<bool> done(false);
std::thread t( [&err, dev_fn, new_size, &complete_pc, &done]()
{
err = restore::resize_ntfs(new_size, dev_fn, complete_pc);
done=true;
});
t.detach();
int last_pc = complete_pc;
while(!done)
{
Server->wait(1000);
if(complete_pc!=last_pc)
{
last_pc = complete_pc;
Server->Log("Resize "+std::to_string(last_pc)+"% complete", LL_INFO);
}
}
if(!err.empty())
{
Server->Log("Resize error: "+err, LL_ERROR);
exit(1);
}
exit(0);
}
else if(cmd=="get_sgparts")
{
std::string dev_fn = Server->getServerParameter("device_fn");
Server->Log("Device size: "+PrettyPrintBytes(getSgdiskDiskSize(dev_fn)*512), LL_INFO);
std::vector<SgdiskPart> parts = getSgdiskParts(dev_fn);
for(const SgdiskPart& part: parts)
{
Server->Log("part number="+std::to_string(part.number)
+" start="+std::to_string(part.start)+" end="+std::to_string(part.end)
+" size="+part.size+" part_uuid="+part.part_guid+" unique_uuid="+part.unique_guid
+" name=\""+part.name+"\" flags="+part.flags, LL_INFO);
}
exit(0);
}
else if(cmd=="help")
{
Server->Log("restore_cmd commands are...", LL_INFO);
Server->Log("write_mbr(mbr_filename,out_device)", LL_INFO);
Server->Log("get_clientnames", LL_INFO);
Server->Log("get_backupimages(restore_name)", LL_INFO);
Server->Log("get_backupimages_json(restore_name)", LL_INFO);
Server->Log("get_file_backups(restore_name)", LL_INFO);
Server->Log("download_mbr(restore_img_id,restore_time,restore_out)", LL_INFO);
Server->Log("download_image(restore_img_id,restore_time,restore_out)", LL_INFO);
Server->Log("download_files(restore_backupid,restore_time,restore_out)", LL_INFO);
Server->Log("download_progress(mbr_filename,out_device)", LL_INFO);
Server->Log("login(username,password)", LL_INFO);
Server->Log("read_mbr(device_fn)", LL_INFO);
Server->Log("resize_ntfs(device_fn,new_size)", LL_INFO);
exit(0);
}
else if(cmd=="ping_server")
{
bool b=ping_server();
exit(b?0:1);
}
IPipe *c=Server->ConnectStream("localhost", 35623, 60000);
if(c==nullptr)
{
Server->Log("Error connecting to client service -1", LL_ERROR);
exit(1);return;
}
std::string pw=getFile(pw_file);
CTCPStack tcpstack;
if(cmd=="get_clientnames")
{
tcpstack.Send(c, "GET BACKUPCLIENTS#pw="+pw);
std::string r=getResponse(c);
if(r.empty() )
{
Server->Log("No response from ClientConnector", LL_ERROR);
Server->destroy(c);exit(2);return;
}
else
{
if(r[0]=='0')
{
Server->Log("No backupserver found", LL_ERROR);
Server->destroy(c);exit(3);return;
}
else
{
std::cout << r.substr(1) ;
Server->destroy(c);exit(0);return;
}
}
}
else if(cmd=="get_backupimages"
|| cmd == "get_backupimages_json")
{
tcpstack.Send(c, "GET BACKUPIMAGES "+Server->getServerParameter("restore_name")+"#pw="+pw);
std::string r=getResponse(c);
if(r.empty() )
{
Server->Log("No response from ClientConnector", LL_ERROR);
Server->destroy(c);exit(2);return;
}
else
{
if(r[0]=='0')
{
Server->Log("No backupserver found", LL_ERROR);
Server->destroy(c);exit(3);return;
}
else
{
if (cmd == "get_backupimages_json")
{
std::cout << backup_images_output_to_json_str(r.substr(1));
}
else
{
std::cout << r.substr(1);
}
Server->destroy(c);exit(0);return;
}
}
}
else if(cmd=="get_file_backups" )
{
tcpstack.Send(c, "GET FILE BACKUPS "+Server->getServerParameter("restore_name")+"#pw="+pw);
std::string r=getResponse(c);
if(r.empty() )
{
Server->Log("No response from ClientConnector", LL_ERROR);
Server->destroy(c);exit(2);return;
}
else
{
if(r[0]=='0')
{
Server->Log("No backupserver found", LL_ERROR);
Server->destroy(c);exit(3);return;
}
else
{
std::cout << r.substr(1) ;
Server->destroy(c);exit(0);return;
}
}
}
else if(cmd=="download_mbr" || cmd=="download_image" )
{
bool mbr=false;
if(cmd=="download_mbr")
mbr=true;
LoginData login_data;
login_data.has_login_data = false;
std::string restore_token = Server->getServerParameter("restore_token");
if (!restore_token.empty())
{
login_data.has_login_data = true;
login_data.token = restore_token;
}
DownloadStatus dl_status;
int ec=downloadImage(atoi(Server->getServerParameter("restore_img_id").c_str()), Server->getServerParameter("restore_time"), Server->getServerParameter("restore_out"), mbr, login_data, dl_status);
exit(ec);
}
else if(cmd=="download_files")
{
int ec=downloadFiles(atoi(Server->getServerParameter("restore_backupid").c_str()), Server->getServerParameter("restore_time"));
exit(ec);
}
else if(cmd=="download_progress")
{
std::string decorate = Server->getServerParameter("decorate");
tcpstack.Send(c, "GET DOWNLOADPROGRESS#pw="+pw);
int lpc=0;
while(true)
{
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(!trim2(l).empty())
{
int npc=atoi(trim2(l).c_str());
if(npc!=lpc)
{
if(decorate.empty())
std::cout << npc << std::endl;
else
std::cout << "Image restore complete: "<< npc << "%" << std::endl;
lpc=npc;
}
}
}
if(r==0)
break;
}
if(lpc!=100)
{
if (decorate.empty())
std::cout << "100" << std::endl;
else
std::cout << "Image restore complete: 100%" << std::endl;
}
exit(0);
}
}
class RestoreThread : public IThread
{
public:
RestoreThread(int pImg_id, std::string pImg_time, std::string pOutfile, LoginData login_data)
: img_id(pImg_id), img_time(pImg_time), outfile(pOutfile), imgsize(0), output_file_size(0), login_data(login_data)
{
done=false;
}
void operator()(void)
{
DownloadStatus dl_status;
rc=downloadImage(img_id, img_time, outfile, false, login_data, dl_status, 0, &imgsize, &output_file_size);
done=true;
}
bool isDone(void)
{
return done;
}
EDownloadResult getRC(void)
{
return rc;
}
int64 getImgsize()
{
return imgsize;
}
int64 getOutputFileSize()
{
return output_file_size;
}
private:
EDownloadResult rc;
volatile bool done;
int img_id;
std::string img_time;
std::string outfile;
LoginData login_data;
int64 imgsize;
int64 output_file_size;
};
namespace restore
{
bool has_network_device(void)
{
#ifdef _WIN32
return true;
#else
#ifdef sun
return true;
#else
char buf[1024];
struct ifconf ifc;
struct ifreq* ifr;
int sck;
int nInterfaces;
int i;
int type = SOCK_DGRAM;
#if !defined(_WIN32) && defined(SOCK_CLOEXEC)
type |= SOCK_CLOEXEC;
#endif
/* Get a socket handle. */
sck = socket(AF_INET, type, 0);
if (sck < 0)
{
return true;
}
#if !defined(_WIN32) && !defined(SOCK_CLOEXEC)
fcntl(sck, F_SETFD, fcntl(sck, F_GETFD, 0) | FD_CLOEXEC);
#endif
/* Query available interfaces. */
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (ioctl(sck, SIOCGIFCONF, &ifc) < 0)
{
close(sck);
return true;
}
/* Iterate through the list of interfaces. */
ifr = ifc.ifc_req;
nInterfaces = ifc.ifc_len / sizeof(struct ifreq);
for (i = 0; i < nInterfaces; i++)
{
struct ifreq* item = &ifr[i];
if (htonl(INADDR_LOOPBACK) != ((struct sockaddr_in*)&item->ifr_addr)->sin_addr.s_addr)
{
close(sck);
return true;
}
}
close(sck);
return false;
#endif //sun
#endif //_WIN32
}
}
void ping_named_server(void)
{
std::string out;
while(out.empty())
{
int rc=system("dialog --inputbox \"`cat urbackup/restore/enter_server_ip_input`\" 8 30 2> out");
out=getFile("out");
if(rc!=0)
{
return;
}
}
if(!out.empty())
{
system(("./urbackuprestoreclient --ping-server \""+out+"\" &").c_str());
}
}
namespace restore
{
bool has_internet_connection(int& ec, std::string& errstatus)
{
ec = 0;
IPipe* c = connectToService(ec);
if (c == nullptr)
{
return false;
}
std::string pw = getFile(pw_file);
CTCPStack tcpstack;
tcpstack.Send(c, "STATUS DETAIL#pw=" + pw);
std::string r = getResponse(c);
if (r.empty())
{
Server->Log("No response from ClientConnector", LL_ERROR);
ec = 1;
return false;
}
std::string internet_connected = getbetween("\"internet_connected\": ", ",", r);
std::string internet_status = getbetween("\"internet_status\": \"", "\",", r);
if (internet_connected == "true"
&& r.find("\"servers\": []") == std::string::npos)
{
return true;
}
errstatus = internet_status;
ec = 2;
return false;
}
}
namespace
{
bool internet_server_configured = false;
}
namespace restore
{
void configure_internet_server()
{
std::string internet_server;
while (internet_server.empty())
{
int rc = system("dialog --inputbox \"`cat urbackup/restore/enter_internet_server_name`\" 8 30 2> out");
internet_server = trim(getFile("out"));
if (rc != 0)
{
system("dialog --msgbox \"`cat urbackup/restore/no_internet_server_configured`\" 10 70");
return;
}
}
if (internet_server.empty())
{
system("dialog --msgbox \"`cat urbackup/restore/no_internet_server_configured`\" 10 70");
return;
}
std::string internet_authkey;
while (internet_authkey.empty())
{
int rc = system("dialog --inputbox \"`cat urbackup/restore/enter_internet_server_authkey`\" 8 30 2> out");
internet_authkey = getFile("out");
if (rc != 0)
{
system("dialog --msgbox \"`cat urbackup/restore/no_internet_server_configured`\" 10 70");
return;
}
}
if (internet_authkey.empty())
{
system("dialog --msgbox \"`cat urbackup/restore/no_internet_server_configured`\" 10 70");
return;
}
system("clear");
std::cout << "Configuring UrBackup restore client as Internet client..." << std::endl;
configure_internet_server(internet_server, internet_authkey, "", true);
}
void configure_internet_server(std::string server_url, const std::string& server_authkey, const std::string& server_proxy,
bool with_cli)
{
system("systemctl stop restore-client");
int internet_server_port = 55415;
if (next(server_url, 0, "urbackup://"))
{
server_url.erase(0, 11);
if (server_url.find(":") != std::string::npos)
{
internet_server_port = watoi(getafter(":", server_url));
server_url = getuntil(":", server_url);
}
}
std::string rnd;
rnd.resize(16);
Server->secureRandomFill(&rnd[0], 16);
std::string clientname = "##restore##" + bytesToHex(rnd);
os_create_dir_recursive("urbackup/data_1");
writestring("internet_mode_enabled=true\n"
"internet_server=" + server_url + "\n"
"internet_server_port=" + std::to_string(internet_server_port) + "\n"
"internet_authkey=" + server_authkey + "\n"
"computername=" + clientname + "\n", "urbackup/data_1/settings.cfg");
if (with_cli)
std::cout << "Starting UrBackup Internet restore client..." << std::endl;
if (system("systemctl restart restore-client-internet") != 0)
{
std::cout << "Error starting Internet restore client." << std::endl;
char ch;
std::cin >> ch;
return;
}
if (!with_cli)
return;
std::cout << "Waiting for Internet restore client to connect..." << std::endl;
Server->wait(1000);
int64 starttime = Server->getTimeMS();
int ec;
std::string errstatus;
while (Server->getTimeMS() - starttime < 60000)
{
if (has_internet_connection(ec, errstatus))
{
internet_server_configured = true;
return;
}
if (ec == 2 && next(errstatus, 0, "error:"))
break;
Server->wait(1000);
}
std::cout << "Connecting to Internet server failed: " << errstatus << std::endl;
std::cout << "Restarting local UrBackup client... " << errstatus << std::endl;
system("systemctl stop restore-client-internet");
Server->deleteFile("urbackup/data_1/settings.cfg");
system("systemctl start restore-client");
std::string errmsg;
switch (ec)
{
case 10:
case 1:
errmsg = "`cat urbackup/restore/internal_error`";
break;
case 2:
errmsg = getFile("urbackup/restore/internet_authentification_failed");
errmsg = greplace("_STATUS_", errstatus, errmsg);
break;
}
int r = system(("dialog --menu \"`cat urbackup/restore/error_happend` " + errmsg + ". `cat urbackup/restore/how_to_continue`\" 15 50 10 "
"\"r\" \"`cat urbackup/restore/search_again`\" "
"\"i\" \"`cat urbackup/restore/connect_to_internet_server`\" "
"\"s\" \"`cat urbackup/restore/start_shell`\" \"s\" "
"\"`cat urbackup/restore/stop_restore`\" 2> out").c_str());
if (r != 0)
{
return;
}
std::string out = getFile("out");
if (out == "s")
{
system("bash");
}
else if (out == "i")
{
configure_internet_server();
}
}
void configure_local_server()
{
system("systemctl stop restore-client-internet");
Server->deleteFile("urbackup/data_1/settings.cfg");
system("systemctl restart restore-client");
}
}
namespace restore
{
bool do_login(LoginData& login_data, bool with_cli_output)
{
if (login_data.has_login_data)
{
if (login_data.username.empty())
{
int ec;
return tryLogin("", "", std::vector<SPasswordSalt>(), 10, &ec);
}
int ec;
std::vector<SPasswordSalt> salts = getSalt(login_data.username, 10, &ec);
bool found_salt = false;
for (size_t i = 0; i < salts.size(); ++i)
{
if (!salts[i].salt.empty() &&
!salts[i].rnd.empty())
{
found_salt = true;
break;
}
}
if (!found_salt)
{
return false;
}
return tryLogin(login_data.username, login_data.password, salts, 10, &ec);
}
if (with_cli_output)
{
system("clear");
system("cat urbackup/restore/trying_to_login");
}
int ec;
if (!tryLogin("", "", std::vector<SPasswordSalt>(), 0, &ec))
{
if (ec == 1)
{
//Server probably does not support logging in
std::vector<std::string> clients = getBackupclients(ec);
if (ec == 0 && !clients.empty())
{
return true;
}
}
if (!with_cli_output)
{
return false;
}
while (true)
{
int rc = system("dialog --inputbox \"`cat urbackup/restore/enter_username`\" 8 30 2> out");
std::string username = getFile("out");
if (rc != 0)
{
return false;
}
std::vector<SPasswordSalt> salts = getSalt(username, 5, &ec);
bool found_salt = false;
for (size_t i = 0; i < salts.size(); ++i)
{
if (!salts[i].salt.empty() &&
!salts[i].rnd.empty())
{
found_salt = true;
break;
}
}
if (!found_salt)
{
rc = system("dialog --yesno \"`cat urbackup/restore/user_not_found`\" 7 50");
if (rc != 0)
{
return false;
}
}
else
{
rc = system("dialog --insecure --passwordbox \"`cat urbackup/restore/enter_password`\" 8 30 2> out");
std::string password = getFile("out");
if (rc != 0)
{
return false;
}
if (!tryLogin(username, password, salts, 5, &ec))
{
rc = system("dialog --yesno \"`cat urbackup/restore/login_failed`\" 7 50");
if (rc != 0)
{
return false;
}
}
else
{
login_data.has_login_data = true;
login_data.username = username;
login_data.password = password;
return true;
}
}
}
}
else
{
login_data.has_login_data = true;
return true;
}
}
}
const int start_state=-2;
void restore_wizard(void)
{
int state=start_state;
std::vector<std::string> clients;
std::string clientname;
std::vector<SImage> images;
SImage selimage;
SImage r_selimage;
std::string seldrive;
std::string windows_partition;
std::string selpart;
std::string err;
bool res_sysvol=false;
LoginData login_data;
while(true)
{
switch(state)
{
case -2:
{
system("dialog --msgbox \"`cat urbackup/restore/welcome`\" 10 70");
++state;
}break;
case -1:
{
if(!has_network_device())
{
system("clear");
system("cat urbackup/restore/search_network");
int tries=20;
bool has_interface=false;
do
{
bool b=has_network_device();
if(b)
{
has_interface=true;
break;
}
--tries;
Server->wait(1000);
} while(tries>=0);
if(has_interface==false)
{
int r=system("dialog --menu \"`cat urbackup/restore/no_network_device`\" 15 50 10 \"n\" \"`cat urbackup/restore/configure_networkcard`\" \"w\" \"`cat urbackup/restore/configure_wlan`\" \"e\" \"`cat urbackup/restore/start_shell`\" \"c\" \"`cat urbackup/restore/continue_restore`\" 2> out");
if(r!=0)
{
state=start_state;
break;
}
std::string out=getFile("out");
if(out=="n")
{
system(configure_networkcard.c_str());
state=0;
}
else if(out=="w")
{
system(configure_wlan.c_str());
state=0;
}
else if(out=="e")
{
system("bash");
state=0;
}
else if(out=="c")
state=0;
else
state=99;
}
else
{
state=0;
}
}
else
{
state=0;
}
}break;
case 0:
{
if (!internet_server_configured)
{
system("urbackup/restore/progress-start.sh | dialog --backtitle \"`cat urbackup/restore/search`\" --gauge \"`cat urbackup/restore/t_progress`\" 6 60 0");
}
++state;
}break;
case 1:
{
std::string errmsg;
int ec;
login_data = LoginData();
if(do_login(login_data))
{
clients=getBackupclients(ec);
switch(ec)
{
case 10:
case 1:
errmsg="`cat urbackup/restore/internal_error`";
break;
case 2:
errmsg="`cat urbackup/restore/no_server_found`";
break;
}
if(errmsg.empty() && clients.empty())
{
ec=3;
errmsg="`cat urbackup/restore/no_clients_found`";
}
}
else
{
errmsg="login failed";
ec=1;
}
if(ec!=0)
{
int r=system(("dialog --menu \"`cat urbackup/restore/error_happend` "+errmsg+". `cat urbackup/restore/how_to_continue`\" 15 50 10 "
"\"r\" \"`cat urbackup/restore/search_again`\" "
"\"n\" \"`cat urbackup/restore/configure_networkcard`\" "
"\"w\" \"`cat urbackup/restore/configure_wlan`\" "
"\"e\" \"`cat urbackup/restore/enter_server_ip`\" "
"\"i\" \"`cat urbackup/restore/connect_to_internet_server`\" "
"\"s\" \"`cat urbackup/restore/start_shell`\" \"s\" "
"\"`cat urbackup/restore/stop_restore`\" 2> out").c_str());
if(r!=0)
{
state=start_state;
break;
}
std::string out=getFile("out");
if(out=="r")
state=0;
else if(out=="n")
{
system(configure_networkcard.c_str());
state=0;
}
else if(out=="w")
{
system(configure_wlan.c_str());
state=0;
}
else if(out=="e")
{
ping_named_server();
state=0;
}
else if (out == "i")
{
configure_internet_server();
state = 0;
}
else if(out=="s")
{
system("bash");
state=0;
}
else
state=99;
}
else
{
std::string mi;
for(size_t i=0;i<clients.size();++i)
{
mi+="\""+convert((int)i+1)+"\" \""+clients[i]+"\" ";
}
int r=system(("dialog --menu \"`cat urbackup/restore/select`\" 15 50 10 "+mi+"2> out").c_str());
if(r!=0)
{
state=start_state;
break;
}
std::string out=getFile("out");
clientname=clients[atoi(out.c_str())-1];
++state;
}
}break;
case 2:
{
int ec;
images=getBackupimages(clientname, &ec);
std::string errmsg;
switch(ec)
{
case 10:
case 1:
errmsg="`cat urbackup/restore/internal_error`";
break;
case 2:
errmsg="`cat urbackup/restore/no_server_found`";
break;
}
if(images.empty())
{
ec=3;
errmsg="`cat urbackup/restore/no_images_for_client1` '"+clientname+"' `cat urbackup/restore/no_images_for_client2`";
}
std::sort(images.begin(), images.end());
if(ec!=0)
{
int r=system(("dialog --menu \"`cat urbackup/restore/error_happend` "+errmsg+". `cat urbackup/restore/how_to_continue`\" 15 50 10 \"a\" \"`cat urbackup/restore/error_j_select`\" \"r\" \"`cat urbackup/restore/search_again`\" \"s\" \"`cat urbackup/restore/stop_restore`\" 2> out").c_str());
if(r!=0)
{
state=start_state;
break;
}
std::string out=getFile("out");
if(out=="r")
state=0;
else if(out=="a")
state=1;
else
state=99;
}
else
{
std::string mi;
for(size_t i=0;i<images.size();++i)
{
if(!images[i].letter.empty())
images[i].letter=" "+images[i].letter;
mi+="\""+convert((int)i+1)+"\" \""+images[i].time_str+images[i].letter+"\" ";
}
int r=system(("dialog --menu \"`cat urbackup/restore/select_date`\" 15 50 10 "+mi+"2> out").c_str());
if(r!=0)
{
state=start_state;
break;
}
std::string out=getFile("out");
selimage=images[atoi(out.c_str())-1];
r_selimage=selimage;
++state;
}
}break;
case 3:
{
std::vector<SLsblk> drives = lsblk("");
bool has_disk=false;
for(size_t i=0;i<drives.size();++i)
{
if(drives[i].type=="disk" && !drives[i].path.empty())
{
has_disk=true;
break;
}
}
if(!has_disk)
{
int r=system("dialog --menu \"`cat urbackup/restore/no_disks_found`. `cat urbackup/restore/how_to_continue`\" 15 50 10 \"r\" \"`cat urbackup/restore/search_disk_again`\" \"s\" \"`cat urbackup/restore/stop_restore`\" 2> out");
if(r!=0)
{
state=start_state;
break;
}
std::string out=getFile("out");
if(out=="r")
state=3;
else
state=99;
break;
}
std::string mi;
for(size_t i=0;i<drives.size();++i)
{
if(drives[i].type=="disk")
{
mi+="\""+convert((int)i+1)+"\" \""+drives[i].model+" `cat urbackup/restore/size`: "+drives[i].size+"\" ";
}
}
std::string scmd="dialog --menu \"`cat urbackup/restore/select_drive`\" 15 50 10 "+mi+"2> out";
writestring(scmd, "scmd.sh");
int r=system(scmd.c_str());
if(r!=0)
{
state=start_state;
break;
}
std::string out=getFile("out");
int driveidx=atoi(out.c_str())-1;
seldrive=drives[driveidx].path;
r=system(("dialog --yesno \"`cat urbackup/restore/select_certain`\\n"+drives[driveidx].model+" `cat urbackup/restore/size`: "+drives[driveidx].size+"\" 10 50").c_str());
if(r!=0)
{
break;
}
++state;
}break;
case 4:
{
system("clear");
system("cat urbackup/restore/loading_mbr");
system("echo");
if(FileExists("mbr.dat"))
{
Server->deleteFile("mbr.dat");
}
system("touch mbr.dat");
DownloadStatus dl_status;
EDownloadResult rc = downloadImage(selimage.id, convert(selimage.time_s), "mbr.dat", true, login_data, dl_status);
if (rc != EDownloadResult_Ok )
{
Server->Log("Error downloading MBR", LL_ERROR);
err="cannot_read_mbr";
state=101;
break;
}
system("cat urbackup/restore/reading_mbr");
system("echo");
if (is_disk_mbr("mbr.dat"))
{
std::cout << "Restoring whole disk..." << std::endl;
++state;
selpart = seldrive;
break;
}
IFile *f=Server->openFile("mbr.dat", MODE_READ);
if(f==nullptr)
{
err="cannot_read_mbr";
state=101;
break;
}
size_t fsize=(size_t)f->Size();
char *buf=new char[fsize];
f->Read(buf, (_u32)fsize);
Server->destroy(f);
CRData mbr(buf, fsize);
SMBRData mbrdata(mbr);
if(mbrdata.hasError())
{
err="error_while_reading_mbr";
exit(3);
state=101;
break;
}
system("cat urbackup/restore/writing_mbr");
system("echo");
IFile *dev=Server->openFile(seldrive, MODE_RW);
if(dev==nullptr)
{
err="cannot_open_disk";
state=101;
break;
}
dev->Seek(0);
if(dev->Write(mbrdata.mbr_data)!=mbrdata.mbr_data.size())
{
err="error_writing_mbr";
state=101;
break;
}
bool fix_gpt = false;
std::vector<IFSImageFactory::SPartition> partitions;
if(mbrdata.gpt_style)
{
std::string onlypart = ExtractFileName(seldrive);
std::string logical_block_size_str = getFile("/sys/block/"+onlypart+"/queue/logical_block_size");
if(!logical_block_size_str.empty())
{
unsigned int logical_block_size = atoi(logical_block_size_str.c_str());
if(logical_block_size!=mbrdata.gpt_header_pos)
{
err="gpt_logical_blocksize_change";
state=101;
break;
}
Server->Log("Logical block size: "+convert(logical_block_size));
}
else
{
Server->Log("Could not get logical block size");
}
system("cat urbackup/restore/writing_gpt_header");
system("echo");
if(!dev->Seek(mbrdata.gpt_header_pos) || dev->Write(mbrdata.gpt_header)!=mbrdata.gpt_header.size())
{
err="error_writing_gpt";
state=101;
break;
}
system("cat urbackup/restore/writing_gpt_table");
system("echo");
if(!dev->Seek(mbrdata.gpt_table_pos) || dev->Write(mbrdata.gpt_table)!=mbrdata.gpt_table.size())
{
err="error_writing_gpt";
state=101;
break;
}
system("cat urbackup/restore/writing_backup_gpt_header");
system("echo");
if(!dev->Seek(mbrdata.backup_gpt_header_pos) || dev->Write(mbrdata.backup_gpt_header)!=mbrdata.backup_gpt_header.size())
{
std::cout << "Error writing backup GPT header. " << os_last_error_str() << ". Fixing with GNU parted..." << std::endl;
fix_gpt = true;
}
system("cat urbackup/restore/writing_backup_gpt_table");
system("echo");
if(!dev->Seek(mbrdata.backup_gpt_table_pos) || dev->Write(mbrdata.backup_gpt_table)!=mbrdata.backup_gpt_table.size())
{
std::cout << "Error writing backup GPT table. " << os_last_error_str() << ". Fixing with GNU parted..." << std::endl;
fix_gpt = true;
}
if (fix_gpt)
{
system(("echo -e \"w\\nY\\nY\" | gdisk " + seldrive).c_str());
bool t_gpt_style;
partitions = image_fak->readPartitions(mbrdata.mbr_data, mbrdata.gpt_header, mbrdata.gpt_table, t_gpt_style);
}
else
{
system(("echo -e \"w\\nY\\nY\" | gdisk " + seldrive + " > /dev/null 2>&1").c_str());
}
}
if (mbrdata.extra_data_pos != -1)
{
std::cout << "Writing extra data..." << std::endl;
if (dev->Write(mbrdata.extra_data_pos,
mbrdata.extra_data) != mbrdata.extra_data.size())
{
std::cout << "Error writing extra data. Ignoring...";
}
}
Server->destroy(dev);
system("cat urbackup/restore/reading_partition_table");
system("echo");
Server->Log("Selected device: "+seldrive+" Partition: "+convert(mbrdata.partition_number));
system(("partprobe "+seldrive+" > /dev/null 2>&1").c_str());
Server->wait(10000);
system("cat urbackup/restore/testing_partition");
system("echo");
dev=nullptr;
std::string partpath = getPartitionPath(seldrive, mbrdata.partition_number);
if(!partpath.empty())
{
dev=Server->openFile(partpath, MODE_RW);
}
int try_c=0;
int delete_parts = 0;
while(dev==nullptr && try_c<10)
{
system(("partprobe "+seldrive+" > /dev/null 2>&1").c_str());
Server->wait(10000);
system("cat urbackup/restore/testing_partition");
system("echo");
partpath = getPartitionPath(seldrive, mbrdata.partition_number);
if(!partpath.empty())
{
dev=Server->openFile(partpath, MODE_RW);
}
if(dev==nullptr)
{
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);
system("read -n1 -r -p \"Press any key to continue...\" key");
fix_gpt = false;
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)
{
err="no_restore_partition";
state=101;
break;
}
selpart=partpath;
Server->destroy(dev);
delete []buf;
++state;
}break;
case 5:
{
if(windows_partition.empty())
{
windows_partition=selpart;
}
RestoreThread rt(selimage.id, convert(selimage.time_s), selpart, login_data);
THREADPOOL_TICKET rt_ticket=Server->getThreadPool()->execute(&rt, "image restore");
while(true)
{
system(("./urbackuprestoreclient --image-download-progress | dialog --backtitle \"`cat urbackup/restore/restoration"+(std::string)(res_sysvol?"_sysvol":"")+"`\" --gauge \"`cat urbackup/restore/t_progress`\" 6 60 0").c_str());
while(!rt.isDone() && !restore_retry_ok)
{
Server->wait(1000);
}
if(rt.isDone())
break;
if(restore_retry_ok)
restore_retry_ok=false;
}
Server->getThreadPool()->waitFor(rt_ticket);
EDownloadResult rc=rt.getRC();
std::string errmsg;
switch(rc)
{
case EDownloadResult_ConnectError: errmsg="`cat urbackup/restore/no_connection`"; break;
case EDownloadResult_OpenError: errmsg="`cat urbackup/restore/cannot_write_on_partition`"; break;
case EDownloadResult_SizeReadError: errmsg="`cat urbackup/restore/wrong_size`"; break;
case EDownloadResult_TimeoutError2: errmsg="`cat urbackup/restore/server_doesnot_respond`"; break;
case EDownloadResult_TimeoutError1: errmsg="`cat urbackup/restore/server_connection_timeout`"; break;
case EDownloadResult_WriteFailed: errmsg="`cat urbackup/restore/writing_failed`"; break;
case EDownloadResult_DeviceTooSmall: errmsg = greplace("_1_",
PrettyPrintBytes(rt.getImgsize()), greplace("_2_", PrettyPrintBytes(rt.getOutputFileSize()), getFile("urbackup/restore/device_too_small"))); break;
};
if(rc!=0)
{
int r=system(("dialog --menu \"`cat urbackup/restore/error_happend` "+errmsg+". `cat urbackup/restore/how_to_continue`\" 15 50 10 \"r\" \"`cat urbackup/restore/restart_restore`\" \"s\" \"`cat urbackup/restore/start_shell`\" \"o\" \"`cat urbackup/restore/restore_other`\" \"s\" \"`cat urbackup/restore/stop_restore`\" 2> out").c_str());
if(r!=0)
{
state=start_state;
break;
}
std::string out=getFile("out");
if(out=="r")
state=5;
else if(out=="o")
state=2;
else if(out=="s")
{
system("bash");
state=0;
}
else
state=99;
}
else
{
if(r_selimage.assoc.size()>0)
{
selimage=r_selimage.assoc[0];
r_selimage.assoc.erase(r_selimage.assoc.begin());
res_sysvol=true;
state=4;
}
else
{
++state;
}
}
}break;
case 6:
{
int r=system("dialog --menu \"`cat urbackup/restore/restore_success`\" 15 70 10 \"r\" \"`cat urbackup/restore/restart_computer`\" \"b\" \"`cat urbackup/restore/bootable_on_different_hardware`\" \"o\" \"`cat urbackup/restore/restore_other`\" \"s\" \"`cat urbackup/restore/stop_restore`\" 2> out");
if(r!=0)
{
state=start_state;
break;
}
std::string out=getFile("out");
if(out=="r")
system("init 6");
else if(out=="o")
state=2;
else if(out=="s")
system("bash");
else if(out=="b")
system(("python3 driver_edit.py "+windows_partition).c_str());
else
system("init 6");
}break;
case 99:
{
system("dialog --msgbox \"`cat urbackup/restore/computer_halt`\" 7 50");
system("init 0");
exit(1);
}break;
case 101:
{
int r=system(("dialog --menu \"`cat urbackup/restore/error_happend` `cat urbackup/restore/"+err+"`. `cat urbackup/restore/how_to_continue`\" 15 50 10 \"r\" \"`cat urbackup/restore/restart_restore`\" \"s\" \"`cat urbackup/restore/stop_restore`\" 2> out").c_str());
if(r!=0)
{
state=start_state;
break;
}
std::string out=getFile("out");
if(out=="r")
state=0;
else
state=99;
}break;
default:
{
system("dialog --msgbox \"`cat urbackup/restore/internal_error`!!!!\" 7 50");
exit(99);
}break;
}
}
}